2 # -*- coding: utf-8 -*-
3 # slapd-backend-shell - backend shell pour slapd (OpenLDAP)
4 # Copyright ©2010 Agence universitaire de la Francophonie
6 # Licence : GNU General Public License, version 3
7 # Auteur : Progfou <jean-christophe.andre@auf.org>
8 # Création : 2010-03-23
10 # Documentation de base : voir "man slapd-shell"
13 # - placer ce script dans /usr/local/sbin/slapd-backend-shell
14 # - le rendre exécutable
15 # - ajouter les lignes suivantes dans /etc/ldap/slapd.conf
16 # moduleload back_shell
19 # bind /usr/local/sbin/slapd-shell-backend
20 # unbind /usr/local/sbin/slapd-shell-backend
21 # search /usr/local/sbin/slapd-shell-backend
22 # - ajouter éventuellement "loglevel stats shell" et surveiller /var/log/debug
23 # - créer un dossier /var/log/ldap et l'attribuer à openldap:openldap en 0700
24 # - relancer le service slapd
25 # - tester avec (le mot de passe est test1) :
26 # ldapsearch -x -b o=shell -D mail=test1@example.com,o=shell -W
32 from crypt import crypt
35 LOGFILE = '/var/log/ldap/backend-shell.log'
37 #=============================================================================
39 #=============================================================================
41 # BIND OPERATION, voir http://tools.ietf.org/html/rfc2251#section-4.2
42 LDAP_AUTH_SIMPLE = 128
44 # BIND RESPONSE, voir http://tools.ietf.org/html/rfc2251#section-4.2.3
45 # et http://tools.ietf.org/html/rfc2251#section-4.1.10
46 # quand l'opération se termine avec succès
48 # quand ce serveur n'est pas approprié
50 # quand c'est un méchanisme SASL inconnu
51 LDAP_AUTH_METHOD_NOT_SUPPORTED = 7
52 # quand on veut forcer une authentification (interdir l'accès anonyme) :
53 LDAP_INAPPROPRIATE_AUHTENTICATION = 48
54 # quand le mot de passe est incorrect :
55 LDAP_INVALID_CREDENTIALS = 49
57 #=============================================================================
59 #=============================================================================
62 'test1@example.com': '$1$2JF5hLaZ$tZz4RXau.GCTqUsAmCeXM/',
63 'test2@example.com': '$1$o2YURjVS$Qd02DXFpGqPtfy1yGH1CM0',
67 """Recherche du mot de passe crypté en fonction de l'utilisateur.
68 On cherche d'abord dans le cache... et un jour dans la vraie base. ;-)
74 def search_object(filter=None):
75 """On recherche les objects correspondant aux filtre.
78 # FIXME: tout pour le moment...
79 # TODO: tenir compte de filter
81 dn = 'mail=%s,%s' % (key, LDAP_BASE)
82 obj = { 'dn': [ dn ] }
83 for k,v in (x.split('=') for x in dn.split(',')):
88 obj.update({'mail': [ key ] })
89 obj.update({'userPassword': [ '{CRYPT}' + _cache[key] ] })
95 line = sys.stdin.readline().rstrip('\n').rstrip('\r')
97 key,value = line.split(': ')
99 params[key] = [ value ]
101 params[key].append(value)
102 line = sys.stdin.readline().rstrip('\n').rstrip('\r')
105 def ldap_result(code=None, matched=None, info=None):
108 lines.append('code: %s' % code)
110 lines.append('matched: %s' % matched)
112 lines.append('info: %s' % info)
113 return '\n'.join(lines) + '\n'
115 #=============================================================================
116 # PROGRAMME PRINCIPAL
117 #=============================================================================
119 logging.basicConfig(level=logging.DEBUG, filename=LOGFILE,
120 format="%(asctime)s %(levelname)s %(message)s")
121 logging.info('Starting.')
123 command = sys.stdin.readline().rstrip('\n').rstrip('\r')
124 logging.debug("Got command '%s'." % command)
126 if command == 'UNBIND':
127 # msgid: <message id>
128 # <repeat { "suffix:" <database suffix DN> }>
130 params = get_params()
131 logging.debug("Got params '%s'." % params)
132 # FIXME: on devrait vérifier les suffixes, le dn, noter le unbind, ...
133 print ldap_result(code=LDAP_SUCCESS)
136 if command == 'BIND':
137 # msgid: <message id>
138 # <repeat { "suffix:" <database suffix DN> }>
140 # method: <method number>
141 # credlen: <length of <credentials>>
142 # cred: <credentials>
143 params = get_params()
144 logging.debug("Got params '%s'." % params)
145 # on vérifie qu'on supporte bien la méthode demandée
146 method = int(params['method'][0])
147 if method != LDAP_AUTH_SIMPLE:
148 logging.info("Unsupported auth method '%s'." % method)
149 print ldap_result(code=LDAP_AUTH_METHOD_NOT_SUPPORTED)
151 # on vérifie que la demande concerne bien notre base
152 if LDAP_BASE not in params['suffix']:
153 logging.warning("'%s' not in requested suffixes." % LDAP_BASE)
154 print ldap_result(code=LDAP_REFERRAL)
156 # on vérifie que le 'dn' n'est pas vide (pas de connexion anonyme)
159 logging.info("Rejecting anonymous connection.")
160 print ldap_result(code=LDAP_INAPPROPRIATE_AUTHENTICATION)
162 # on vérifie que le 'dn' demandé est valide (contient 'mail')
163 dn_dict = dict([x.split('=') for x in dn.split(',')])
164 if 'mail' not in dn_dict:
165 logging.info("Invalid dn '%s' (missing 'mail' component)." % dn)
166 print ldap_result(code=LDAP_INVALID_CREDENTIALS)
168 # on vérifie que l'objet demandé existe bien dans la base
169 mail = dn_dict['mail']
170 cred = find_cred(mail)
172 logging.info("Can't find credential for '%s'." % mail)
173 print ldap_result(code=LDAP_INVALID_CREDENTIALS)
175 # on vérifie que le mot de passe correspond bien
176 if crypt(params['cred'][0], cred) != cred:
177 logging.info("Credential mismatch for '%s'." % mail)
178 print ldap_result(code=LDAP_INVALID_CREDENTIALS)
180 # si on a passé tout ça, c'est tout bon ! :-)
181 logging.debug("Successful bind for '%s'." % mail)
182 print ldap_result(code=LDAP_SUCCESS)
185 if command == 'SEARCH':
186 # msgid: <message id>
187 # <repeat { "suffix:" <database suffix DN> }>
189 # scope: <0-2, see ldap.h>
190 # deref: <0-3, see ldap.h>
191 # sizelimit: <size limit>
192 # timelimit: <time limit>
194 # attrsonly: <0 or 1>
195 # attrs: <"all" or space-separated attribute list>
196 params = get_params()
197 logging.debug("Got params '%s'." % params)
198 # on vérifie que la demande concerne bien notre base
199 if LDAP_BASE not in params['suffix']:
200 logging.warning("'%s' not in requested suffixes." % LDAP_BASE)
201 print ldap_result(code=LDAP_REFERRAL)
203 base = params['base'][0]
204 if base != LDAP_BASE:
205 logging.warning("Invalid base '%s'." % base)
206 print ldap_result(code=LDAP_REFERRAL)
208 # on affiche le résultat
209 sizelimit = int(params['sizelimit'][0])
210 filter = params['filter'][0]
211 attrs = params['attrs'][0].split(' ')
212 logging.debug("Start of search for '%s' (sizelimit=%s, attrs='%s')." \
213 % (filter, sizelimit, attrs))
214 for obj in search_object(filter)[0:sizelimit]:
215 if attrs[0] == 'all':
216 attr_list = obj.keys()
219 print 'dn: %s' % obj['dn'][0]
220 attr_list = set(attr_list) - set(['dn','userPassword'])
221 for attr in attr_list:
223 for value in obj[attr]:
224 print '%s: %s' % (attr, value)
228 # si on a passé tout ça, c'est tout bon ! :-)
229 logging.debug("End of search.")
230 print ldap_result(code=LDAP_SUCCESS)