script-publish.py: Fix double-spacing in <pre> by removing \n
[blog.git] / posts / LDAP / mutt-ldap.py
1 #!/usr/bin/env python2
2 #
3 # Copyright (C) 2008-2012  W. Trevor King
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 """LDAP address searches for Mutt.
19
20 Add :file:`mutt-ldap.py` to your ``PATH`` and add the following line
21 to your :file:`.muttrc`::
22
23   set query_command = "mutt-ldap.py '%s'"
24
25 Search for addresses with `^t`, optionally after typing part of the
26 name.  Configure your connection by creating :file:`~/.mutt-ldap.py`
27 contaning something like::
28
29   [connection]
30   server = myserver.example.net
31   basedn = ou=people,dc=example,dc=net
32
33 See the `CONFIG` options for other available settings.
34 """
35
36 import email.utils
37 import itertools
38 import os.path
39 import ConfigParser
40
41 import ldap
42 import ldap.sasl
43
44
45 CONFIG = ConfigParser.SafeConfigParser()
46 CONFIG.add_section('connection')
47 CONFIG.set('connection', 'server', 'domaincontroller.yourdomain.com')
48 CONFIG.set('connection', 'port', '389')  # set to 636 for default over SSL
49 CONFIG.set('connection', 'ssl', 'no')
50 CONFIG.set('connection', 'starttls', 'no')
51 CONFIG.set('connection', 'basedn', 'ou=x co.,dc=example,dc=net')
52 CONFIG.add_section('auth')
53 CONFIG.set('auth', 'user', '')
54 CONFIG.set('auth', 'password', '')
55 CONFIG.set('auth', 'gssapi', 'no')
56 CONFIG.read(os.path.expanduser('~/.mutt-ldap.rc'))
57
58 def connect():
59     protocol = 'ldap'
60     if CONFIG.getboolean('connection', 'ssl'):
61         protocol = 'ldaps'
62     url = '{}://{}:{}'.format(
63         protocol,
64         CONFIG.get('connection', 'server'),
65         CONFIG.get('connection', 'port'))
66     connection = ldap.initialize(url)
67     if CONFIG.getboolean('connection', 'starttls') and protocol == 'ldap':
68         connection.start_tls_s()
69     if CONFIG.getboolean('auth', 'gssapi'):
70         sasl = ldap.sasl.gssapi()
71         connection.sasl_interactive_bind_s('', sasl)
72     else:
73         connection.bind(
74             CONFIG.get('auth', 'user'),
75             CONFIG.get('auth', 'password'),
76             ldap.AUTH_SIMPLE)
77     return connection
78
79 def search(query, connection=None):
80     local_connection = False
81     try:
82         if not connection:
83             local_connection = True
84             connection = connect()
85         post = ''
86         if query:
87             post = '*'
88         filterstr = u'(|{})'.format(
89             u' '.join([u'({}=*{}{})'.format(field, query, post)
90                        for field in ['cn', 'displayName', 'uid', 'mail']]))
91         r = connection.search_s(
92             CONFIG.get('connection', 'basedn'),
93             ldap.SCOPE_SUBTREE,
94             filterstr.encode('utf-8'))
95     finally:
96         if local_connection and connection:
97             connection.unbind()
98     return r
99
100 def format_entry(entry):
101     cn,data = entry
102     if 'mail' in data:
103         name = data.get('displayName', data['cn'])[-1]
104         for m in data['mail']:
105             yield email.utils.formataddr((name, m))
106
107
108 if __name__ == '__main__':
109     import sys
110
111     query = unicode(' '.join(sys.argv[1:]), 'utf-8')
112     entries = search(query)
113     addresses = list(itertools.chain(
114             *[format_entry(e) for e in sorted(entries)]))
115     print('{} addresses found:'.format(len(addresses)))
116     print('\n'.join(addresses))