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