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 from ldaptor import interfaces, delta 20 from ldaptor.protocols import pureldap, pureber 21 from ldaptor.protocols.ldap import distinguishedname, ldaperrors 22 23 from twisted.python import log 24 from twisted.internet import protocol, defer 25 2830 debug = False 31 35 36 berdecoder = pureldap.LDAPBERDecoderContext_TopLevel( 37 inherit=pureldap.LDAPBERDecoderContext_LDAPMessage( 38 fallback=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()), 39 inherit=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()))) 40128 12942 self.buffer += recd 43 while 1: 44 try: 45 o, bytes=pureber.berDecodeObject(self.berdecoder, self.buffer) 46 except pureber.BERExceptionInsufficientData: #TODO 47 o, bytes=None, 0 48 self.buffer = self.buffer[bytes:] 49 if o is None: 50 break 51 self.handle(o)52 56 6062 if not self.connected: 63 raise LDAPServerConnectionLostException() 64 msg=pureldap.LDAPMessage(op, id=id) 65 if self.debug: 66 log.msg('S->C %s' % repr(msg), debug=True) 67 self.transport.write(str(msg))68 7173 if controls is not None: 74 for controlType, criticality, controlValue in controls: 75 if criticality: 76 raise ldaperrors.LDAPUnavailableCriticalExtension, \ 77 'Unknown control %s' % controlType7880 log.msg('Unknown request: %r' % request) 81 msg = pureldap.LDAPExtendedResponse(resultCode=ldaperrors.LDAPProtocolError.resultCode, 82 responseName='1.3.6.1.4.1.1466.20036', 83 errorMessage='Unknown request') 84 return msg8587 reason.trap(ldaperrors.LDAPException) 88 return self._callErrorHandler(name=name, 89 resultCode=reason.value.resultCode, 90 errorMessage=reason.value.message)91 9597 return pureldap.LDAPExtendedResponse(resultCode=resultCode, 98 responseName='1.3.6.1.4.1.1466.20036', 99 errorMessage=errorMessage)100102 errh = getattr(self, 'fail_'+name, self.failDefault) 103 return errh(resultCode=resultCode, errorMessage=errorMessage)104106 return self._callErrorHandler(name=name, 107 resultCode=ldaperrors.LDAPProtocolError.resultCode, 108 errorMessage=reason.getErrorMessage())109111 assert isinstance(msg.value, pureldap.LDAPProtocolRequest) 112 if self.debug: 113 log.msg('S<-C %s' % repr(msg), debug=True) 114 115 if msg.id==0: 116 self.unsolicitedNotification(msg.value) 117 else: 118 name = msg.value.__class__.__name__ 119 handler = getattr(self, 'handle_'+name, self.handleUnknown) 120 d = defer.maybeDeferred(handler, 121 msg.value, 122 msg.controls, 123 lambda response: self._cbHandle(response, msg.id)) 124 d.addErrback(self._cbLDAPError, name) 125 d.addErrback(defer.logError) 126 d.addErrback(self._cbOtherError, name) 127 d.addCallback(self._cbHandle, msg.id)131 """An LDAP server""" 132 boundUser = None 133 134 fail_LDAPBindRequest = pureldap.LDAPBindResponse 135168 d.addCallback(_cb) 169 return d 170 d.addCallback(_gotEntry, request.auth) 171 172 return d 173137 if request.version != 3: 138 raise ldaperrors.LDAPProtocolError, \ 139 'Version %u not supported' % request.version 140 141 self.checkControls(controls) 142 143 if request.dn == '': 144 # anonymous bind 145 self.boundUser=None 146 return pureldap.LDAPBindResponse(resultCode=0) 147 else: 148 dn = distinguishedname.DistinguishedName(request.dn) 149 root = interfaces.IConnectedLDAPEntry(self.factory) 150 d = root.lookup(dn) 151 152 def _noEntry(fail): 153 fail.trap(ldaperrors.LDAPNoSuchObject) 154 return None155 d.addErrback(_noEntry) 156 157 def _gotEntry(entry, auth): 158 if entry is None: 159 raise ldaperrors.LDAPInvalidCredentials 160 161 d = entry.bind(auth) 162 def _cb(entry): 163 self.boundUser=entry 164 msg = pureldap.LDAPBindResponse( 165 resultCode=ldaperrors.Success.resultCode, 166 matchedDN=str(entry.dn)) 167 return msg175 # explicitly do not check unsupported critical controls -- we 176 # have no way to return an error, anyway. 177 self.transport.loseConnection()178180 root = interfaces.IConnectedLDAPEntry(self.factory) 181 reply(pureldap.LDAPSearchResultEntry( 182 objectName='', 183 attributes=[ ('supportedLDAPVersion', ['3']), 184 ('namingContexts', [str(root.dn)]), 185 ('supportedExtension', [ 186 pureldap.LDAPPasswordModifyRequest.oid, 187 ]), 188 ], 189 )) 190 return pureldap.LDAPSearchResultDone(resultCode=ldaperrors.Success.resultCode)191193 def _sendEntryToClient(entry): 194 reply(pureldap.LDAPSearchResultEntry( 195 objectName=str(entry.dn), 196 attributes=entry.items(), 197 ))198 d = base.search(filterObject=request.filter, 199 attributes=request.attributes, 200 scope=request.scope, 201 derefAliases=request.derefAliases, 202 sizeLimit=request.sizeLimit, 203 timeLimit=request.timeLimit, 204 typesOnly=request.typesOnly, 205 callback=_sendEntryToClient) 206 207 def _done(_): 208 return pureldap.LDAPSearchResultDone(resultCode=ldaperrors.Success.resultCode) 209 d.addCallback(_done) 210 return d 211213 reason.trap(ldaperrors.LDAPException) 214 return pureldap.LDAPSearchResultDone(resultCode=reason.value.resultCode)215217 return pureldap.LDAPSearchResultDone(resultCode=ldaperrors.other, 218 errorMessage=reason.getErrorMessage())219 220 fail_LDAPSearchRequest = pureldap.LDAPSearchResultDone 221223 self.checkControls(controls) 224 225 if (request.baseObject == '' 226 and request.scope == pureldap.LDAP_SCOPE_baseObject 227 and request.filter == pureldap.LDAPFilter_present('objectClass')): 228 return self.getRootDSE(request, reply) 229 dn = distinguishedname.DistinguishedName(request.baseObject) 230 root = interfaces.IConnectedLDAPEntry(self.factory) 231 d = root.lookup(dn) 232 d.addCallback(self._cbSearchGotBase, dn, request, reply) 233 d.addErrback(self._cbSearchLDAPError) 234 d.addErrback(defer.logError) 235 d.addErrback(self._cbSearchOtherError) 236 return d237 238 fail_LDAPDelRequest = pureldap.LDAPDelResponse 239241 self.checkControls(controls) 242 243 dn = distinguishedname.DistinguishedName(request.value) 244 root = interfaces.IConnectedLDAPEntry(self.factory) 245 d = root.lookup(dn) 246 def _gotEntry(entry): 247 d = entry.delete() 248 return d249 d.addCallback(_gotEntry) 250 def _report(entry): 251 return pureldap.LDAPDelResponse(resultCode=0) 252 d.addCallback(_report) 253 return d 254 255 fail_LDAPAddRequest = pureldap.LDAPAddResponse 256258 self.checkControls(controls) 259 260 attributes = {} 261 for name, vals in request.attributes: 262 attributes.setdefault(name.value, set()) 263 attributes[name.value].update([x.value for x in vals]) 264 dn = distinguishedname.DistinguishedName(request.entry) 265 rdn = str(dn.split()[0]) 266 parent = dn.up() 267 root = interfaces.IConnectedLDAPEntry(self.factory) 268 d = root.lookup(parent) 269 def _gotEntry(parent): 270 d = parent.addChild(rdn, attributes) 271 return d272 d.addCallback(_gotEntry) 273 def _report(entry): 274 return pureldap.LDAPAddResponse(resultCode=0) 275 d.addCallback(_report) 276 return d 277 278 fail_LDAPModifyDNRequest = pureldap.LDAPModifyDNResponse 279281 self.checkControls(controls) 282 283 dn = distinguishedname.DistinguishedName(request.entry) 284 newrdn = distinguishedname.RelativeDistinguishedName(request.newrdn) 285 deleteoldrdn = bool(request.deleteoldrdn) 286 if not deleteoldrdn: 287 #TODO support this 288 raise ldaperrors.LDAPUnwillingToPerform("Cannot handle preserving old RDN yet.") 289 newSuperior = request.newSuperior 290 if newSuperior is None: 291 newSuperior = dn.up() 292 else: 293 newSuperior = distinguishedname.DistinguishedName(newSuperior) 294 newdn = distinguishedname.DistinguishedName( 295 listOfRDNs=(newrdn,)+newSuperior.split()) 296 297 #TODO make this more atomic 298 root = interfaces.IConnectedLDAPEntry(self.factory) 299 d = root.lookup(dn) 300 def _gotEntry(entry): 301 d = entry.move(newdn) 302 return d303 d.addCallback(_gotEntry) 304 def _report(entry): 305 return pureldap.LDAPModifyDNResponse(resultCode=0) 306 d.addCallback(_report) 307 return d 308 309 fail_LDAPModifyRequest = pureldap.LDAPModifyResponse 310312 self.checkControls(controls) 313 314 root = interfaces.IConnectedLDAPEntry(self.factory) 315 mod = delta.ModifyOp.fromLDAP(request) 316 d = mod.patch(root) 317 def _patched(entry): 318 return entry.commit()319 d.addCallback(_patched) 320 def _report(entry): 321 return pureldap.LDAPModifyResponse(resultCode=0) 322 d.addCallback(_report) 323 return d 324 325 fail_LDAPExtendedRequest = pureldap.LDAPExtendedResponse 326328 self.checkControls(controls) 329 330 for handler in [getattr(self, attr) 331 for attr in dir(self) 332 if attr.startswith('extendedRequest_')]: 333 if getattr(handler, 'oid', None) == request.requestName: 334 berdecoder = getattr(handler, 'berdecoder', None) 335 336 if berdecoder is None: 337 values = [request.requestValue] 338 else: 339 values = pureber.berDecodeMultiple(request.requestValue, berdecoder) 340 341 d = defer.maybeDeferred(handler, *values, **{'reply': reply}) 342 def eb(fail, oid): 343 fail.trap(ldaperrors.LDAPException) 344 return pureldap.LDAPExtendedResponse( 345 resultCode=fail.value.resultCode, 346 errorMessage=fail.value.message, 347 responseName=oid, 348 )349 d.addErrback(eb, request.requestName) 350 return d 351 352 raise ldaperrors.LDAPProtocolError('Unknown extended request: %s' % request.requestName) 353355 if not isinstance(data, pureber.BERSequence): 356 raise ldaperrors.LDAPProtocolError('Extended request PasswordModify expected a BERSequence.') 357 358 userIdentity = None 359 oldPasswd = None 360 newPasswd = None 361 362 for value in data: 363 if isinstance(value, pureldap.LDAPPasswordModifyRequest_userIdentity): 364 if userIdentity is not None: 365 raise ldaperrors.LDAPProtocolError( 366 'Extended request PasswordModify received userIdentity twice.') 367 userIdentity = value.value 368 elif isinstance(value, pureldap.LDAPPasswordModifyRequest_oldPasswd): 369 if oldPasswd is not None: 370 raise ldaperrors.LDAPProtocolError('Extended request PasswordModify received oldPasswd twice.') 371 oldPasswd = value.value 372 elif isinstance(value, pureldap.LDAPPasswordModifyRequest_newPasswd): 373 if newPasswd is not None: 374 raise ldaperrors.LDAPProtocolError('Extended request PasswordModify received newPasswd twice.') 375 newPasswd = value.value 376 else: 377 raise ldaperrors.LDAPProtocolError('Extended request PasswordModify received unexpected item.') 378 379 if self.boundUser is None: 380 raise ldaperrors.LDAPStrongAuthRequired() 381 382 if (userIdentity is not None 383 and userIdentity != self.boundUser.dn): 384 #TODO this hardcodes ACL 385 log.msg('User %(actor)s tried to change password of %(target)s' % { 386 'actor': str(self.boundUser.dn), 387 'target': str(userIdentity), 388 }) 389 raise ldaperrors.LDAPInsufficientAccessRights() 390 391 if (oldPasswd is not None 392 or newPasswd is None): 393 raise ldaperrors.LDAPOperationsError('Password does not support this case.') 394 395 self.boundUser.setPassword(newPasswd) 396 return pureldap.LDAPExtendedResponse(resultCode=ldaperrors.Success.resultCode, 397 responseName=self.extendedRequest_LDAPPasswordModifyRequest.oid) 398 399 # TODO 400 if userIdentity is None: 401 userIdentity = str(self.boundUser.dn) 402 403 raise NotImplementedError('VALUE %r' % value)404 extendedRequest_LDAPPasswordModifyRequest.oid = pureldap.LDAPPasswordModifyRequest.oid 405 extendedRequest_LDAPPasswordModifyRequest.berdecoder = ( 406 pureber.BERDecoderContext( 407 inherit=pureldap.LDAPBERDecoderContext_LDAPPasswordModifyRequest(inherit=pureber.BERDecoderContext()))) 408 409 if __name__ == '__main__': 410 """ 411 Demonstration LDAP server; reads LDIF from stdin and 412 serves that over LDAP on port 10389. 413 """ 414 from twisted.internet import reactor 415 import sys 416 log.startLogging(sys.stderr) 417 418 from twisted.python import components 419 from ldaptor import inmemory 420 424 components.registerAdapter(lambda x: x.root, 425 LDAPServerFactory, 426 interfaces.IConnectedLDAPEntry) 427429 factory = LDAPServerFactory(db) 430 factory.protocol = LDAPServer 431 reactor.listenTCP(10389, factory)432 433 d = inmemory.fromLDIFFile(sys.stdin) 434 d.addCallback(start) 435 d.addErrback(log.err) 436 reactor.run() 437
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Wed Jan 8 20:56:56 2014 | http://epydoc.sourceforge.net |