Home | Trees | Indices | Help |
|
---|
|
1 # Ldaptor, a Pure-Python library for LDAP 2 # Copyright (C) 2003 Tommi Virtanen 3 # 4 # This library is free software; you can redistribute it and/or 5 # modify it under the terms of version 2.1 of the GNU Lesser General Public 6 # License as published by the Free Software Foundation. 7 # 8 # This library is distributed in the hope that it will be useful, 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 # Lesser General Public License for more details. 12 # 13 # You should have received a copy of the GNU Lesser General Public 14 # License along with this library; if not, write to the Free Software 15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 16 17 """LDAP protocol server""" 18 19 import sets 20 from ldaptor import interfaces, delta 21 from ldaptor.protocols import pureldap, pureber 22 from ldaptor.protocols.ldap import distinguishedname, ldaperrors 23 24 from twisted.python import log 25 from twisted.internet import protocol, defer 26 2931 debug = False 32 36 37 berdecoder = pureldap.LDAPBERDecoderContext_TopLevel( 38 inherit=pureldap.LDAPBERDecoderContext_LDAPMessage( 39 fallback=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()), 40 inherit=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()))) 41129 13043 self.buffer += recd 44 while 1: 45 try: 46 o, bytes=pureber.berDecodeObject(self.berdecoder, self.buffer) 47 except pureldap.BERExceptionInsufficientData: 48 o, bytes=None, 0 49 self.buffer = self.buffer[bytes:] 50 if o is None: 51 break 52 self.handle(o)53 57 6163 if not self.connected: 64 raise LDAPServerConnectionLostException() 65 msg=pureldap.LDAPMessage(op, id=id) 66 if self.debug: 67 log.debug('S->C %s' % repr(msg)) 68 self.transport.write(str(msg))69 7274 if controls is not None: 75 for controlType, criticality, controlValue in controls: 76 if criticality: 77 raise ldaperrors.LDAPUnavailableCriticalExtension, \ 78 'Unknown control %s' % controlType7981 log.msg('Unknown request: %r' % request) 82 msg = pureldap.LDAPExtendedResponse(resultCode=ldaperrors.LDAPProtocolError.resultCode, 83 responseName='1.3.6.1.4.1.1466.20036', 84 errorMessage='Unknown request') 85 return msg8688 reason.trap(ldaperrors.LDAPException) 89 return self._callErrorHandler(name=name, 90 resultCode=reason.value.resultCode, 91 errorMessage=reason.value.message)92 9698 return pureldap.LDAPExtendedResponse(resultCode=resultCode, 99 responseName='1.3.6.1.4.1.1466.20036', 100 errorMessage=errorMessage)101103 errh = getattr(self, 'fail_'+name, self.failDefault) 104 return errh(resultCode=resultCode, errorMessage=errorMessage)105107 return self._callErrorHandler(name=name, 108 resultCode=ldaperrors.LDAPProtocolError.resultCode, 109 errorMessage=reason.getErrorMessage())110112 assert isinstance(msg.value, pureldap.LDAPProtocolRequest) 113 if self.debug: 114 log.debug('S<-C %s' % repr(msg)) 115 116 if msg.id==0: 117 self.unsolicitedNotification(msg.value) 118 else: 119 name = msg.value.__class__.__name__ 120 handler = getattr(self, 'handle_'+name, self.handleUnknown) 121 d = defer.maybeDeferred(handler, 122 msg.value, 123 msg.controls, 124 lambda response: self._cbHandle(response, msg.id)) 125 d.addErrback(self._cbLDAPError, name) 126 d.addErrback(defer.logError) 127 d.addErrback(self._cbOtherError, name) 128 d.addCallback(self._cbHandle, msg.id)132 """An LDAP server""" 133 boundUser = None 134 135 fail_LDAPBindRequest = pureldap.LDAPBindResponse 136169 d.addCallback(_cb) 170 return d 171 d.addCallback(_gotEntry, request.auth) 172 173 return d 174138 if request.version != 3: 139 raise ldaperrors.LDAPProtocolError, \ 140 'Version %u not supported' % request.version 141 142 self.checkControls(controls) 143 144 if request.dn == '': 145 # anonymous bind 146 self.boundUser=None 147 return pureldap.LDAPBindResponse(resultCode=0) 148 else: 149 dn = distinguishedname.DistinguishedName(request.dn) 150 root = interfaces.IConnectedLDAPEntry(self.factory) 151 d = root.lookup(dn) 152 153 def _noEntry(fail): 154 fail.trap(ldaperrors.LDAPNoSuchObject) 155 return None156 d.addErrback(_noEntry) 157 158 def _gotEntry(entry, auth): 159 if entry is None: 160 raise ldaperrors.LDAPInvalidCredentials 161 162 d = entry.bind(auth) 163 def _cb(entry): 164 self.boundUser=entry 165 msg = pureldap.LDAPBindResponse( 166 resultCode=ldaperrors.Success.resultCode, 167 matchedDN=str(entry.dn)) 168 return msg176 # explicitly do not check unsupported critical controls -- we 177 # have no way to return an error, anyway. 178 self.transport.loseConnection()179181 root = interfaces.IConnectedLDAPEntry(self.factory) 182 reply(pureldap.LDAPSearchResultEntry( 183 objectName='', 184 attributes=[ ('supportedLDAPVersion', ['3']), 185 ('namingContexts', [str(root.dn)]), 186 ('supportedExtension', [ 187 pureldap.LDAPPasswordModifyRequest.oid, 188 ]), 189 ], 190 )) 191 return pureldap.LDAPSearchResultDone(resultCode=ldaperrors.Success.resultCode)192194 def _sendEntryToClient(entry): 195 reply(pureldap.LDAPSearchResultEntry( 196 objectName=str(entry.dn), 197 attributes=entry.items(), 198 ))199 d = base.search(filterObject=request.filter, 200 attributes=request.attributes, 201 scope=request.scope, 202 derefAliases=request.derefAliases, 203 sizeLimit=request.sizeLimit, 204 timeLimit=request.timeLimit, 205 typesOnly=request.typesOnly, 206 callback=_sendEntryToClient) 207 208 def _done(_): 209 return pureldap.LDAPSearchResultDone(resultCode=ldaperrors.Success.resultCode) 210 d.addCallback(_done) 211 return d 212214 reason.trap(ldaperrors.LDAPException) 215 return pureldap.LDAPSearchResultDone(resultCode=reason.value.resultCode)216218 return pureldap.LDAPSearchResultDone(resultCode=ldaperrors.other, 219 errorMessage=reason.getErrorMessage())220 221 fail_LDAPSearchRequest = pureldap.LDAPSearchResultDone 222224 self.checkControls(controls) 225 226 if (request.baseObject == '' 227 and request.scope == pureldap.LDAP_SCOPE_baseObject 228 and request.filter == pureldap.LDAPFilter_present('objectClass')): 229 return self.getRootDSE(request, reply) 230 dn = distinguishedname.DistinguishedName(request.baseObject) 231 root = interfaces.IConnectedLDAPEntry(self.factory) 232 d = root.lookup(dn) 233 d.addCallback(self._cbSearchGotBase, dn, request, reply) 234 d.addErrback(self._cbSearchLDAPError) 235 d.addErrback(defer.logError) 236 d.addErrback(self._cbSearchOtherError) 237 return d238 239 fail_LDAPDelRequest = pureldap.LDAPDelResponse 240242 self.checkControls(controls) 243 244 dn = distinguishedname.DistinguishedName(request.value) 245 root = interfaces.IConnectedLDAPEntry(self.factory) 246 d = root.lookup(dn) 247 def _gotEntry(entry): 248 d = entry.delete() 249 return d250 d.addCallback(_gotEntry) 251 def _report(entry): 252 return pureldap.LDAPDelResponse(resultCode=0) 253 d.addCallback(_report) 254 return d 255 256 fail_LDAPAddRequest = pureldap.LDAPAddResponse 257259 self.checkControls(controls) 260 261 attributes = {} 262 for name, vals in request.attributes: 263 attributes.setdefault(name.value, sets.Set()) 264 attributes[name.value].update([x.value for x in vals]) 265 dn = distinguishedname.DistinguishedName(request.entry) 266 rdn = str(dn.split()[0]) 267 parent = dn.up() 268 root = interfaces.IConnectedLDAPEntry(self.factory) 269 d = root.lookup(parent) 270 def _gotEntry(parent): 271 d = parent.addChild(rdn, attributes) 272 return d273 d.addCallback(_gotEntry) 274 def _report(entry): 275 return pureldap.LDAPAddResponse(resultCode=0) 276 d.addCallback(_report) 277 return d 278 279 fail_LDAPModifyDNRequest = pureldap.LDAPModifyDNResponse 280282 self.checkControls(controls) 283 284 dn = distinguishedname.DistinguishedName(request.entry) 285 newrdn = distinguishedname.RelativeDistinguishedName(request.newrdn) 286 deleteoldrdn = bool(request.deleteoldrdn) 287 if not deleteoldrdn: 288 #TODO support this 289 raise ldaperrors.LDAPUnwillingToPerform("Cannot handle preserving old RDN yet.") 290 newSuperior = request.newSuperior 291 if newSuperior is None: 292 newSuperior = dn.up() 293 else: 294 newSuperior = distinguishedname.DistinguishedName(newSuperior) 295 newdn = distinguishedname.DistinguishedName( 296 listOfRDNs=(newrdn,)+newSuperior.split()) 297 298 #TODO make this more atomic 299 root = interfaces.IConnectedLDAPEntry(self.factory) 300 d = root.lookup(dn) 301 def _gotEntry(entry): 302 d = entry.move(newdn) 303 return d304 d.addCallback(_gotEntry) 305 def _report(entry): 306 return pureldap.LDAPModifyDNResponse(resultCode=0) 307 d.addCallback(_report) 308 return d 309 310 fail_LDAPModifyRequest = pureldap.LDAPModifyResponse 311313 self.checkControls(controls) 314 315 root = interfaces.IConnectedLDAPEntry(self.factory) 316 mod = delta.ModifyOp.fromLDAP(request) 317 d = mod.patch(root) 318 def _patched(entry): 319 return entry.commit()320 d.addCallback(_patched) 321 def _report(entry): 322 return pureldap.LDAPModifyResponse(resultCode=0) 323 d.addCallback(_report) 324 return d 325 326 fail_LDAPExtendedRequest = pureldap.LDAPExtendedResponse 327329 self.checkControls(controls) 330 331 for handler in [getattr(self, attr) 332 for attr in dir(self) 333 if attr.startswith('extendedRequest_')]: 334 if getattr(handler, 'oid', None) == request.requestName: 335 berdecoder = getattr(handler, 'berdecoder', None) 336 337 if berdecoder is None: 338 values = [request.requestValue] 339 else: 340 values = pureber.berDecodeMultiple(request.requestValue, berdecoder) 341 342 d = defer.maybeDeferred(handler, *values, **{'reply': reply}) 343 def eb(fail, oid): 344 fail.trap(ldaperrors.LDAPException) 345 return pureldap.LDAPExtendedResponse( 346 resultCode=fail.value.resultCode, 347 errorMessage=fail.value.message, 348 responseName=oid, 349 )350 d.addErrback(eb, request.requestName) 351 return d 352 353 raise ldaperrors.LDAPProtocolError('Unknown extended request: %s' % request.requestName) 354356 if not isinstance(data, pureber.BERSequence): 357 raise ldaperrors.LDAPProtocolError('Extended request PasswordModify expected a BERSequence.') 358 359 userIdentity = None 360 oldPasswd = None 361 newPasswd = None 362 363 for value in data: 364 if isinstance(value, pureldap.LDAPPasswordModifyRequest_userIdentity): 365 if userIdentity is not None: 366 raise ldaperrors.LDAPProtocolError( 367 'Extended request PasswordModify received userIdentity twice.') 368 userIdentity = value.value 369 elif isinstance(value, pureldap.LDAPPasswordModifyRequest_oldPasswd): 370 if oldPasswd is not None: 371 raise ldaperrors.LDAPProtocolError('Extended request PasswordModify received oldPasswd twice.') 372 oldPasswd = value.value 373 elif isinstance(value, pureldap.LDAPPasswordModifyRequest_newPasswd): 374 if newPasswd is not None: 375 raise ldaperrors.LDAPProtocolError('Extended request PasswordModify received newPasswd twice.') 376 newPasswd = value.value 377 else: 378 raise ldaperrors.LDAPProtocolError('Extended request PasswordModify received unexpected item.') 379 380 if self.boundUser is None: 381 raise ldaperrors.LDAPStrongAuthRequired() 382 383 if (userIdentity is not None 384 and userIdentity != self.boundUser.dn): 385 #TODO this hardcodes ACL 386 log.msg('User %(actor)s tried to change password of %(target)s' % { 387 'actor': str(self.boundUser.dn), 388 'target': str(userIdentity), 389 }) 390 raise ldaperrors.LDAPInsufficientAccessRights() 391 392 if (oldPasswd is not None 393 or newPasswd is None): 394 raise ldaperrors.LDAPOperationsError('Password does not support this case.') 395 396 self.boundUser.setPassword(newPasswd) 397 return pureldap.LDAPExtendedResponse(resultCode=ldaperrors.Success.resultCode, 398 responseName=self.extendedRequest_LDAPPasswordModifyRequest.oid) 399 400 # TODO 401 if userIdentity is None: 402 userIdentity = str(self.boundUser.dn) 403 404 raise NotImplementedError('VALUE %r' % value)405 extendedRequest_LDAPPasswordModifyRequest.oid = pureldap.LDAPPasswordModifyRequest.oid 406 extendedRequest_LDAPPasswordModifyRequest.berdecoder = ( 407 pureber.BERDecoderContext( 408 inherit=pureldap.LDAPBERDecoderContext_LDAPPasswordModifyRequest(inherit=pureber.BERDecoderContext()))) 409 410 if __name__ == '__main__': 411 """ 412 Demonstration LDAP server; reads LDIF from stdin and 413 serves that over LDAP on port 10389. 414 """ 415 from twisted.internet import reactor 416 import sys 417 log.startLogging(sys.stderr) 418 419 from twisted.python import components 420 from ldaptor import inmemory 421 425 components.registerAdapter(lambda x: x.root, 426 LDAPServerFactory, 427 interfaces.IConnectedLDAPEntry) 428430 factory = LDAPServerFactory(db) 431 factory.protocol = LDAPServer 432 reactor.listenTCP(10389, factory)433 434 d = inmemory.fromLDIFFile(sys.stdin) 435 d.addCallback(start) 436 d.addErrback(log.err) 437 reactor.run() 438
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Tue May 15 09:31:51 2012 | http://epydoc.sourceforge.net |