Package ldaptor :: Package protocols :: Package ldap :: Module ldapserver
[hide private]
[frames] | no frames]

Source Code for Module ldaptor.protocols.ldap.ldapserver

  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   
27 -class LDAPServerConnectionLostException(ldaperrors.LDAPException):
28 pass
29
30 -class BaseLDAPServer(protocol.Protocol):
31 debug = False 32
33 - def __init__(self):
34 self.buffer = '' 35 self.connected = None
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()))) 41
42 - def dataReceived(self, recd):
43 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
54 - def connectionMade(self):
55 """TCP connection has opened""" 56 self.connected = 1
57
58 - def connectionLost(self, reason=protocol.connectionDone):
59 """Called when TCP connection has been lost""" 60 self.connected = 0
61
62 - def queue(self, id, op):
63 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
70 - def unsolicitedNotification(self, msg):
71 log.msg("Got unsolicited notification: %s" % repr(msg))
72
73 - def checkControls(self, controls):
74 if controls is not None: 75 for controlType, criticality, controlValue in controls: 76 if criticality: 77 raise ldaperrors.LDAPUnavailableCriticalExtension, \ 78 'Unknown control %s' % controlType
79
80 - def handleUnknown(self, request, controls, callback):
81 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 msg
86
87 - def _cbLDAPError(self, reason, name):
88 reason.trap(ldaperrors.LDAPException) 89 return self._callErrorHandler(name=name, 90 resultCode=reason.value.resultCode, 91 errorMessage=reason.value.message)
92
93 - def _cbHandle(self, response, id):
94 if response is not None: 95 self.queue(id, response)
96
97 - def failDefault(self, resultCode, errorMessage):
101
102 - def _callErrorHandler(self, name, resultCode, errorMessage):
103 errh = getattr(self, 'fail_'+name, self.failDefault) 104 return errh(resultCode=resultCode, errorMessage=errorMessage)
105
106 - def _cbOtherError(self, reason, name):
107 return self._callErrorHandler(name=name, 108 resultCode=ldaperrors.LDAPProtocolError.resultCode, 109 errorMessage=reason.getErrorMessage())
110
111 - def handle(self, msg):
112 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)
129 130
131 -class LDAPServer(BaseLDAPServer):
132 """An LDAP server""" 133 boundUser = None 134 135 fail_LDAPBindRequest = pureldap.LDAPBindResponse 136
137 - def handle_LDAPBindRequest(self, request, controls, reply):
138 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 None
156 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 msg
169 d.addCallback(_cb) 170 return d 171 d.addCallback(_gotEntry, request.auth) 172 173 return d 174
175 - def handle_LDAPUnbindRequest(self, request, controls, reply):
176 # explicitly do not check unsupported critical controls -- we 177 # have no way to return an error, anyway. 178 self.transport.loseConnection()
179
180 - def getRootDSE(self, request, reply):
181 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)
192
193 - def _cbSearchGotBase(self, base, dn, request, reply):
194 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 212
213 - def _cbSearchLDAPError(self, reason):
214 reason.trap(ldaperrors.LDAPException) 215 return pureldap.LDAPSearchResultDone(resultCode=reason.value.resultCode)
216
217 - def _cbSearchOtherError(self, reason):
218 return pureldap.LDAPSearchResultDone(resultCode=ldaperrors.other, 219 errorMessage=reason.getErrorMessage())
220 221 fail_LDAPSearchRequest = pureldap.LDAPSearchResultDone 222
223 - def handle_LDAPSearchRequest(self, request, controls, reply):
224 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 d
238 239 fail_LDAPDelRequest = pureldap.LDAPDelResponse 240
241 - def handle_LDAPDelRequest(self, request, controls, reply):
242 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 d
250 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 257
258 - def handle_LDAPAddRequest(self, request, controls, reply):
259 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 d
273 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 280
281 - def handle_LDAPModifyDNRequest(self, request, controls, reply):
282 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 d
304 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 311
312 - def handle_LDAPModifyRequest(self, request, controls, reply):
313 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 327
328 - def handle_LDAPExtendedRequest(self, request, controls, reply):
329 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) 354
355 - def extendedRequest_LDAPPasswordModifyRequest(self, data, reply):
356 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
422 - class LDAPServerFactory(protocol.ServerFactory):
423 - def __init__(self, root):
424 self.root = root
425 components.registerAdapter(lambda x: x.root, 426 LDAPServerFactory, 427 interfaces.IConnectedLDAPEntry) 428
429 - def start(db):
430 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