Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ldapobject.py 48KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267
  1. """
  2. ldapobject.py - wraps class _ldap.LDAPObject
  3. See https://www.python-ldap.org/ for details.
  4. """
  5. from __future__ import unicode_literals
  6. from os import strerror
  7. from ldap.pkginfo import __version__, __author__, __license__
  8. __all__ = [
  9. 'LDAPObject',
  10. 'SimpleLDAPObject',
  11. 'ReconnectLDAPObject',
  12. 'LDAPBytesWarning'
  13. ]
  14. if __debug__:
  15. # Tracing is only supported in debugging mode
  16. import traceback
  17. import sys,time,pprint,_ldap,ldap,ldap.sasl,ldap.functions
  18. import warnings
  19. from ldap.schema import SCHEMA_ATTRS
  20. from ldap.controls import LDAPControl,DecodeControlTuples,RequestControlTuples
  21. from ldap.extop import ExtendedRequest,ExtendedResponse
  22. from ldap.compat import reraise
  23. from ldap import LDAPError
  24. PY2 = sys.version_info[0] <= 2
  25. if PY2:
  26. text_type = unicode
  27. else:
  28. text_type = str
  29. # See SimpleLDAPObject._bytesify_input
  30. _LDAP_WARN_SKIP_FRAME = True
  31. class LDAPBytesWarning(BytesWarning):
  32. """python-ldap bytes mode warning
  33. """
  34. def _raise_byteswarning(message):
  35. """Raise LDAPBytesWarning
  36. """
  37. # Call stacks that raise the warning tend to be complicated, so
  38. # getting a useful stacklevel is tricky.
  39. # We walk stack frames, ignoring functions in uninteresting files,
  40. # based on the _LDAP_WARN_SKIP_FRAME marker in globals().
  41. stacklevel = 2
  42. try:
  43. getframe = sys._getframe
  44. except AttributeError:
  45. pass
  46. else:
  47. frame = sys._getframe(stacklevel)
  48. while frame and frame.f_globals.get('_LDAP_WARN_SKIP_FRAME'):
  49. stacklevel += 1
  50. frame = frame.f_back
  51. warnings.warn(message, LDAPBytesWarning, stacklevel=stacklevel+1)
  52. class NO_UNIQUE_ENTRY(ldap.NO_SUCH_OBJECT):
  53. """
  54. Exception raised if a LDAP search returned more than entry entry
  55. although assumed to return a unique single search result.
  56. """
  57. class SimpleLDAPObject:
  58. """
  59. Drop-in wrapper class around _ldap.LDAPObject
  60. """
  61. CLASSATTR_OPTION_MAPPING = {
  62. "protocol_version": ldap.OPT_PROTOCOL_VERSION,
  63. "deref": ldap.OPT_DEREF,
  64. "referrals": ldap.OPT_REFERRALS,
  65. "timelimit": ldap.OPT_TIMELIMIT,
  66. "sizelimit": ldap.OPT_SIZELIMIT,
  67. "network_timeout": ldap.OPT_NETWORK_TIMEOUT,
  68. "error_number":ldap.OPT_ERROR_NUMBER,
  69. "error_string":ldap.OPT_ERROR_STRING,
  70. "matched_dn":ldap.OPT_MATCHED_DN,
  71. }
  72. def __init__(
  73. self,uri,
  74. trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None,
  75. bytes_strictness=None,
  76. ):
  77. self._trace_level = trace_level or ldap._trace_level
  78. self._trace_file = trace_file or ldap._trace_file
  79. self._trace_stack_limit = trace_stack_limit
  80. self._uri = uri
  81. self._ldap_object_lock = self._ldap_lock('opcall')
  82. self._l = ldap.functions._ldap_function_call(ldap._ldap_module_lock,_ldap.initialize,uri)
  83. self.timeout = -1
  84. self.protocol_version = ldap.VERSION3
  85. # Bytes mode
  86. # ----------
  87. if PY2:
  88. if bytes_mode is None:
  89. bytes_mode = True
  90. if bytes_strictness is None:
  91. _raise_byteswarning(
  92. "Under Python 2, python-ldap uses bytes by default. "
  93. "This will be removed in Python 3 (no bytes for "
  94. "DN/RDN/field names). "
  95. "Please call initialize(..., bytes_mode=False) explicitly.")
  96. bytes_strictness = 'warn'
  97. else:
  98. if bytes_strictness is None:
  99. bytes_strictness = 'error'
  100. else:
  101. if bytes_mode:
  102. raise ValueError("bytes_mode is *not* supported under Python 3.")
  103. bytes_mode = False
  104. bytes_strictness = 'error'
  105. self.bytes_mode = bytes_mode
  106. self.bytes_strictness = bytes_strictness
  107. def _bytesify_input(self, arg_name, value):
  108. """Adapt a value following bytes_mode in Python 2.
  109. In Python 3, returns the original value unmodified.
  110. With bytes_mode ON, takes bytes or None and returns bytes or None.
  111. With bytes_mode OFF, takes unicode or None and returns bytes or None.
  112. For the wrong argument type (unicode or bytes, respectively),
  113. behavior depends on the bytes_strictness setting.
  114. In all cases, bytes or None are returned (or an exception is raised).
  115. """
  116. if not PY2:
  117. return value
  118. if value is None:
  119. return value
  120. elif self.bytes_mode:
  121. if isinstance(value, bytes):
  122. return value
  123. elif self.bytes_strictness == 'silent':
  124. pass
  125. elif self.bytes_strictness == 'warn':
  126. _raise_byteswarning(
  127. "Received non-bytes value for '{}' in bytes mode; "
  128. "please choose an explicit "
  129. "option for bytes_mode on your LDAP connection".format(arg_name))
  130. else:
  131. raise TypeError(
  132. "All provided fields *must* be bytes when bytes mode is on; "
  133. "got type '{}' for '{}'.".format(type(value).__name__, arg_name)
  134. )
  135. return value.encode('utf-8')
  136. else:
  137. if isinstance(value, unicode):
  138. return value.encode('utf-8')
  139. elif self.bytes_strictness == 'silent':
  140. pass
  141. elif self.bytes_strictness == 'warn':
  142. _raise_byteswarning(
  143. "Received non-text value for '{}' with bytes_mode off and "
  144. "bytes_strictness='warn'".format(arg_name))
  145. else:
  146. raise TypeError(
  147. "All provided fields *must* be text when bytes mode is off; "
  148. "got type '{}' for '{}'.".format(type(value).__name__, arg_name)
  149. )
  150. return value
  151. def _bytesify_modlist(self, arg_name, modlist, with_opcode):
  152. """Adapt a modlist according to bytes_mode.
  153. A modlist is a tuple of (op, attr, value), where:
  154. - With bytes_mode ON, attr is checked to be bytes
  155. - With bytes_mode OFF, attr is converted from unicode to bytes
  156. - value is *always* bytes
  157. """
  158. if not PY2:
  159. return modlist
  160. if with_opcode:
  161. return tuple(
  162. (op, self._bytesify_input(arg_name, attr), val)
  163. for op, attr, val in modlist
  164. )
  165. else:
  166. return tuple(
  167. (self._bytesify_input(arg_name, attr), val)
  168. for attr, val in modlist
  169. )
  170. def _unbytesify_text_value(self, value):
  171. """Adapt a 'known text, UTF-8 encoded' returned value following bytes_mode.
  172. With bytes_mode ON, takes bytes or None and returns bytes or None.
  173. With bytes_mode OFF, takes bytes or None and returns unicode or None.
  174. This function should only be applied on field *values*; distinguished names
  175. or field *names* are already natively handled in result4.
  176. """
  177. if value is None:
  178. return value
  179. # Preserve logic of assertions only under Python 2
  180. if PY2:
  181. assert isinstance(value, bytes), "Expected bytes value, got text instead (%r)" % (value,)
  182. if self.bytes_mode:
  183. return value
  184. else:
  185. return value.decode('utf-8')
  186. def _maybe_rebytesify_text(self, value):
  187. """Re-encodes text to bytes if needed by bytes_mode.
  188. Takes unicode (and checks for it), and returns:
  189. - bytes under bytes_mode
  190. - unicode otherwise.
  191. """
  192. if not PY2:
  193. return value
  194. if value is None:
  195. return value
  196. assert isinstance(value, text_type), "Should return text, got bytes instead (%r)" % (value,)
  197. if not self.bytes_mode:
  198. return value
  199. else:
  200. return value.encode('utf-8')
  201. def _bytesify_result_value(self, result_value):
  202. """Applies bytes_mode to a result value.
  203. Such a value can either be:
  204. - a dict mapping an attribute name to its list of values
  205. (where attribute names are unicode and values bytes)
  206. - a list of referals (which are unicode)
  207. """
  208. if not PY2:
  209. return result_value
  210. if hasattr(result_value, 'items'):
  211. # It's a attribute_name: [values] dict
  212. return {
  213. self._maybe_rebytesify_text(key): value
  214. for (key, value) in result_value.items()
  215. }
  216. elif isinstance(result_value, bytes):
  217. return result_value
  218. else:
  219. # It's a list of referals
  220. # Example value:
  221. # [u'ldap://DomainDnsZones.xxxx.root.local/DC=DomainDnsZones,DC=xxxx,DC=root,DC=local']
  222. return [self._maybe_rebytesify_text(referal) for referal in result_value]
  223. def _bytesify_results(self, results, with_ctrls=False):
  224. """Converts a "results" object according to bytes_mode.
  225. Takes:
  226. - a list of (dn, {field: [values]}) if with_ctrls is False
  227. - a list of (dn, {field: [values]}, ctrls) if with_ctrls is True
  228. And, if bytes_mode is on, converts dn and fields to bytes.
  229. """
  230. if not PY2:
  231. return results
  232. if with_ctrls:
  233. return [
  234. (self._maybe_rebytesify_text(dn), self._bytesify_result_value(fields), ctrls)
  235. for (dn, fields, ctrls) in results
  236. ]
  237. else:
  238. return [
  239. (self._maybe_rebytesify_text(dn), self._bytesify_result_value(fields))
  240. for (dn, fields) in results
  241. ]
  242. def _ldap_lock(self,desc=''):
  243. if ldap.LIBLDAP_R:
  244. return ldap.LDAPLock(desc='%s within %s' %(desc,repr(self)))
  245. else:
  246. return ldap._ldap_module_lock
  247. def _ldap_call(self,func,*args,**kwargs):
  248. """
  249. Wrapper method mainly for serializing calls into OpenLDAP libs
  250. and trace logs
  251. """
  252. self._ldap_object_lock.acquire()
  253. if __debug__:
  254. if self._trace_level>=1:
  255. self._trace_file.write('*** %s %s - %s\n%s\n' % (
  256. repr(self),
  257. self._uri,
  258. '.'.join((self.__class__.__name__,func.__name__)),
  259. pprint.pformat((args,kwargs))
  260. ))
  261. if self._trace_level>=9:
  262. traceback.print_stack(limit=self._trace_stack_limit,file=self._trace_file)
  263. diagnostic_message_success = None
  264. try:
  265. try:
  266. result = func(*args,**kwargs)
  267. if __debug__ and self._trace_level>=2:
  268. if func.__name__!="unbind_ext":
  269. diagnostic_message_success = self._l.get_option(ldap.OPT_DIAGNOSTIC_MESSAGE)
  270. finally:
  271. self._ldap_object_lock.release()
  272. except LDAPError as e:
  273. exc_type,exc_value,exc_traceback = sys.exc_info()
  274. try:
  275. if 'info' not in e.args[0] and 'errno' in e.args[0]:
  276. e.args[0]['info'] = strerror(e.args[0]['errno'])
  277. except IndexError:
  278. pass
  279. if __debug__ and self._trace_level>=2:
  280. self._trace_file.write('=> LDAPError - %s: %s\n' % (e.__class__.__name__,str(e)))
  281. try:
  282. reraise(exc_type, exc_value, exc_traceback)
  283. finally:
  284. exc_type = exc_value = exc_traceback = None
  285. else:
  286. if __debug__ and self._trace_level>=2:
  287. if not diagnostic_message_success is None:
  288. self._trace_file.write('=> diagnosticMessage: %s\n' % (repr(diagnostic_message_success)))
  289. self._trace_file.write('=> result:\n%s\n' % (pprint.pformat(result)))
  290. return result
  291. def __setattr__(self,name,value):
  292. if name in self.CLASSATTR_OPTION_MAPPING:
  293. self.set_option(self.CLASSATTR_OPTION_MAPPING[name],value)
  294. else:
  295. self.__dict__[name] = value
  296. def __getattr__(self,name):
  297. if name in self.CLASSATTR_OPTION_MAPPING:
  298. return self.get_option(self.CLASSATTR_OPTION_MAPPING[name])
  299. elif name in self.__dict__:
  300. return self.__dict__[name]
  301. else:
  302. raise AttributeError('%s has no attribute %s' % (
  303. self.__class__.__name__,repr(name)
  304. ))
  305. def fileno(self):
  306. """
  307. Returns file description of LDAP connection.
  308. Just a convenience wrapper for LDAPObject.get_option(ldap.OPT_DESC)
  309. """
  310. return self.get_option(ldap.OPT_DESC)
  311. def abandon_ext(self,msgid,serverctrls=None,clientctrls=None):
  312. """
  313. abandon_ext(msgid[,serverctrls=None[,clientctrls=None]]) -> None
  314. abandon(msgid) -> None
  315. Abandons or cancels an LDAP operation in progress. The msgid should
  316. be the message id of an outstanding LDAP operation as returned
  317. by the asynchronous methods search(), modify() etc. The caller
  318. can expect that the result of an abandoned operation will not be
  319. returned from a future call to result().
  320. """
  321. return self._ldap_call(self._l.abandon_ext,msgid,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  322. def abandon(self,msgid):
  323. return self.abandon_ext(msgid,None,None)
  324. def cancel(self,cancelid,serverctrls=None,clientctrls=None):
  325. """
  326. cancel(cancelid[,serverctrls=None[,clientctrls=None]]) -> int
  327. Send cancels extended operation for an LDAP operation specified by cancelid.
  328. The cancelid should be the message id of an outstanding LDAP operation as returned
  329. by the asynchronous methods search(), modify() etc. The caller
  330. can expect that the result of an abandoned operation will not be
  331. returned from a future call to result().
  332. In opposite to abandon() this extended operation gets an result from
  333. the server and thus should be preferred if the server supports it.
  334. """
  335. return self._ldap_call(self._l.cancel,cancelid,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  336. def cancel_s(self,cancelid,serverctrls=None,clientctrls=None):
  337. msgid = self.cancel(cancelid,serverctrls,clientctrls)
  338. try:
  339. res = self.result(msgid,all=1,timeout=self.timeout)
  340. except (ldap.CANCELLED,ldap.SUCCESS):
  341. res = None
  342. return res
  343. def add_ext(self,dn,modlist,serverctrls=None,clientctrls=None):
  344. """
  345. add_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int
  346. This function adds a new entry with a distinguished name
  347. specified by dn which means it must not already exist.
  348. The parameter modlist is similar to the one passed to modify(),
  349. except that no operation integer need be included in the tuples.
  350. """
  351. if PY2:
  352. dn = self._bytesify_input('dn', dn)
  353. modlist = self._bytesify_modlist('modlist', modlist, with_opcode=False)
  354. return self._ldap_call(self._l.add_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  355. def add_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None):
  356. msgid = self.add_ext(dn,modlist,serverctrls,clientctrls)
  357. resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
  358. return resp_type, resp_data, resp_msgid, resp_ctrls
  359. def add(self,dn,modlist):
  360. """
  361. add(dn, modlist) -> int
  362. This function adds a new entry with a distinguished name
  363. specified by dn which means it must not already exist.
  364. The parameter modlist is similar to the one passed to modify(),
  365. except that no operation integer need be included in the tuples.
  366. """
  367. return self.add_ext(dn,modlist,None,None)
  368. def add_s(self,dn,modlist):
  369. return self.add_ext_s(dn,modlist,None,None)
  370. def simple_bind(self,who=None,cred=None,serverctrls=None,clientctrls=None):
  371. """
  372. simple_bind([who='' [,cred='']]) -> int
  373. """
  374. if PY2:
  375. who = self._bytesify_input('who', who)
  376. cred = self._bytesify_input('cred', cred)
  377. return self._ldap_call(self._l.simple_bind,who,cred,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  378. def simple_bind_s(self,who=None,cred=None,serverctrls=None,clientctrls=None):
  379. """
  380. simple_bind_s([who='' [,cred='']]) -> 4-tuple
  381. """
  382. msgid = self.simple_bind(who,cred,serverctrls,clientctrls)
  383. resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
  384. return resp_type, resp_data, resp_msgid, resp_ctrls
  385. def bind(self,who,cred,method=ldap.AUTH_SIMPLE):
  386. """
  387. bind(who, cred, method) -> int
  388. """
  389. assert method==ldap.AUTH_SIMPLE,'Only simple bind supported in LDAPObject.bind()'
  390. return self.simple_bind(who,cred)
  391. def bind_s(self,who,cred,method=ldap.AUTH_SIMPLE):
  392. """
  393. bind_s(who, cred, method) -> None
  394. """
  395. msgid = self.bind(who,cred,method)
  396. return self.result(msgid,all=1,timeout=self.timeout)
  397. def sasl_interactive_bind_s(self,who,auth,serverctrls=None,clientctrls=None,sasl_flags=ldap.SASL_QUIET):
  398. """
  399. sasl_interactive_bind_s(who, auth [,serverctrls=None[,clientctrls=None[,sasl_flags=ldap.SASL_QUIET]]]) -> None
  400. """
  401. return self._ldap_call(self._l.sasl_interactive_bind_s,who,auth,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls),sasl_flags)
  402. def sasl_non_interactive_bind_s(self,sasl_mech,serverctrls=None,clientctrls=None,sasl_flags=ldap.SASL_QUIET,authz_id=''):
  403. """
  404. Send a SASL bind request using a non-interactive SASL method (e.g. GSSAPI, EXTERNAL)
  405. """
  406. auth = ldap.sasl.sasl(
  407. {ldap.sasl.CB_USER:authz_id},
  408. sasl_mech
  409. )
  410. self.sasl_interactive_bind_s('',auth,serverctrls,clientctrls,sasl_flags)
  411. def sasl_external_bind_s(self,serverctrls=None,clientctrls=None,sasl_flags=ldap.SASL_QUIET,authz_id=''):
  412. """
  413. Send SASL bind request using SASL mech EXTERNAL
  414. """
  415. self.sasl_non_interactive_bind_s('EXTERNAL',serverctrls,clientctrls,sasl_flags,authz_id)
  416. def sasl_gssapi_bind_s(self,serverctrls=None,clientctrls=None,sasl_flags=ldap.SASL_QUIET,authz_id=''):
  417. """
  418. Send SASL bind request using SASL mech GSSAPI
  419. """
  420. self.sasl_non_interactive_bind_s('GSSAPI',serverctrls,clientctrls,sasl_flags,authz_id)
  421. def sasl_bind_s(self,dn,mechanism,cred,serverctrls=None,clientctrls=None):
  422. """
  423. sasl_bind_s(dn, mechanism, cred [,serverctrls=None[,clientctrls=None]]) -> int|str
  424. """
  425. return self._ldap_call(self._l.sasl_bind_s,dn,mechanism,cred,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  426. def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None):
  427. """
  428. compare_ext(dn, attr, value [,serverctrls=None[,clientctrls=None]]) -> int
  429. compare_ext_s(dn, attr, value [,serverctrls=None[,clientctrls=None]]) -> bool
  430. compare(dn, attr, value) -> int
  431. compare_s(dn, attr, value) -> bool
  432. Perform an LDAP comparison between the attribute named attr of entry
  433. dn, and the value value. The synchronous form returns True or False.
  434. The asynchronous form returns the message id of the initiates request,
  435. and the result of the asynchronous compare can be obtained using
  436. result().
  437. Note that this latter technique yields the answer by raising
  438. the exception objects COMPARE_TRUE or COMPARE_FALSE.
  439. A design bug in the library prevents value from containing
  440. nul characters.
  441. """
  442. if PY2:
  443. dn = self._bytesify_input('dn', dn)
  444. attr = self._bytesify_input('attr', attr)
  445. return self._ldap_call(self._l.compare_ext,dn,attr,value,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  446. def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None):
  447. msgid = self.compare_ext(dn,attr,value,serverctrls,clientctrls)
  448. try:
  449. ldap_res = self.result3(msgid,all=1,timeout=self.timeout)
  450. except ldap.COMPARE_TRUE:
  451. return True
  452. except ldap.COMPARE_FALSE:
  453. return False
  454. raise ldap.PROTOCOL_ERROR(
  455. 'Compare operation returned wrong result: %r' % (ldap_res)
  456. )
  457. def compare(self,dn,attr,value):
  458. return self.compare_ext(dn,attr,value,None,None)
  459. def compare_s(self,dn,attr,value):
  460. return self.compare_ext_s(dn,attr,value,None,None)
  461. def delete_ext(self,dn,serverctrls=None,clientctrls=None):
  462. """
  463. delete(dn) -> int
  464. delete_s(dn) -> None
  465. delete_ext(dn[,serverctrls=None[,clientctrls=None]]) -> int
  466. delete_ext_s(dn[,serverctrls=None[,clientctrls=None]]) -> tuple
  467. Performs an LDAP delete operation on dn. The asynchronous
  468. form returns the message id of the initiated request, and the
  469. result can be obtained from a subsequent call to result().
  470. """
  471. dn = self._bytesify_input('dn', dn)
  472. return self._ldap_call(self._l.delete_ext,dn,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  473. def delete_ext_s(self,dn,serverctrls=None,clientctrls=None):
  474. msgid = self.delete_ext(dn,serverctrls,clientctrls)
  475. resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
  476. return resp_type, resp_data, resp_msgid, resp_ctrls
  477. def delete(self,dn):
  478. return self.delete_ext(dn,None,None)
  479. def delete_s(self,dn):
  480. return self.delete_ext_s(dn,None,None)
  481. def extop(self,extreq,serverctrls=None,clientctrls=None):
  482. """
  483. extop(extreq[,serverctrls=None[,clientctrls=None]]]) -> int
  484. extop_s(extreq[,serverctrls=None[,clientctrls=None[,extop_resp_class=None]]]]) ->
  485. (respoid,respvalue)
  486. Performs an LDAP extended operation. The asynchronous
  487. form returns the message id of the initiated request, and the
  488. result can be obtained from a subsequent call to extop_result().
  489. The extreq is an instance of class ldap.extop.ExtendedRequest.
  490. If argument extop_resp_class is set to a sub-class of
  491. ldap.extop.ExtendedResponse this class is used to return an
  492. object of this class instead of a raw BER value in respvalue.
  493. """
  494. return self._ldap_call(self._l.extop,extreq.requestName,extreq.encodedRequestValue(),RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  495. def extop_result(self,msgid=ldap.RES_ANY,all=1,timeout=None):
  496. resulttype,msg,msgid,respctrls,respoid,respvalue = self.result4(msgid,all=1,timeout=self.timeout,add_ctrls=1,add_intermediates=1,add_extop=1)
  497. return (respoid,respvalue)
  498. def extop_s(self,extreq,serverctrls=None,clientctrls=None,extop_resp_class=None):
  499. msgid = self.extop(extreq,serverctrls,clientctrls)
  500. res = self.extop_result(msgid,all=1,timeout=self.timeout)
  501. if extop_resp_class:
  502. respoid,respvalue = res
  503. if extop_resp_class.responseName!=respoid:
  504. raise ldap.PROTOCOL_ERROR("Wrong OID in extended response! Expected %s, got %s" % (extop_resp_class.responseName,respoid))
  505. return extop_resp_class(extop_resp_class.responseName,respvalue)
  506. else:
  507. return res
  508. def modify_ext(self,dn,modlist,serverctrls=None,clientctrls=None):
  509. """
  510. modify_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int
  511. """
  512. if PY2:
  513. dn = self._bytesify_input('dn', dn)
  514. modlist = self._bytesify_modlist('modlist', modlist, with_opcode=True)
  515. return self._ldap_call(self._l.modify_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  516. def modify_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None):
  517. msgid = self.modify_ext(dn,modlist,serverctrls,clientctrls)
  518. resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
  519. return resp_type, resp_data, resp_msgid, resp_ctrls
  520. def modify(self,dn,modlist):
  521. """
  522. modify(dn, modlist) -> int
  523. modify_s(dn, modlist) -> None
  524. modify_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int
  525. modify_ext_s(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> tuple
  526. Performs an LDAP modify operation on an entry's attributes.
  527. dn is the DN of the entry to modify, and modlist is the list
  528. of modifications to make to the entry.
  529. Each element of the list modlist should be a tuple of the form
  530. (mod_op,mod_type,mod_vals), where mod_op is the operation (one of
  531. MOD_ADD, MOD_DELETE, MOD_INCREMENT or MOD_REPLACE), mod_type is a
  532. string indicating the attribute type name, and mod_vals is either a
  533. string value or a list of string values to add, delete, increment by or
  534. replace respectively. For the delete operation, mod_vals may be None
  535. indicating that all attributes are to be deleted.
  536. The asynchronous modify() returns the message id of the
  537. initiated request.
  538. """
  539. return self.modify_ext(dn,modlist,None,None)
  540. def modify_s(self,dn,modlist):
  541. return self.modify_ext_s(dn,modlist,None,None)
  542. def modrdn(self,dn,newrdn,delold=1):
  543. """
  544. modrdn(dn, newrdn [,delold=1]) -> int
  545. modrdn_s(dn, newrdn [,delold=1]) -> None
  546. Perform a modify RDN operation. These routines take dn, the
  547. DN of the entry whose RDN is to be changed, and newrdn, the
  548. new RDN to give to the entry. The optional parameter delold
  549. is used to specify whether the old RDN should be kept as
  550. an attribute of the entry or not. The asynchronous version
  551. returns the initiated message id.
  552. This operation is emulated by rename() and rename_s() methods
  553. since the modrdn2* routines in the C library are deprecated.
  554. """
  555. return self.rename(dn,newrdn,None,delold)
  556. def modrdn_s(self,dn,newrdn,delold=1):
  557. return self.rename_s(dn,newrdn,None,delold)
  558. def passwd(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
  559. if PY2:
  560. user = self._bytesify_input('user', user)
  561. oldpw = self._bytesify_input('oldpw', oldpw)
  562. newpw = self._bytesify_input('newpw', newpw)
  563. return self._ldap_call(self._l.passwd,user,oldpw,newpw,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  564. def passwd_s(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
  565. msgid = self.passwd(user,oldpw,newpw,serverctrls,clientctrls)
  566. return self.extop_result(msgid,all=1,timeout=self.timeout)
  567. def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None):
  568. """
  569. rename(dn, newrdn [, newsuperior=None [,delold=1][,serverctrls=None[,clientctrls=None]]]) -> int
  570. rename_s(dn, newrdn [, newsuperior=None] [,delold=1][,serverctrls=None[,clientctrls=None]]) -> None
  571. Perform a rename entry operation. These routines take dn, the
  572. DN of the entry whose RDN is to be changed, newrdn, the
  573. new RDN, and newsuperior, the new parent DN, to give to the entry.
  574. If newsuperior is None then only the RDN is modified.
  575. The optional parameter delold is used to specify whether the
  576. old RDN should be kept as an attribute of the entry or not.
  577. The asynchronous version returns the initiated message id.
  578. This actually corresponds to the rename* routines in the
  579. LDAP-EXT C API library.
  580. """
  581. if PY2:
  582. dn = self._bytesify_input('dn', dn)
  583. newrdn = self._bytesify_input('newrdn', newrdn)
  584. newsuperior = self._bytesify_input('newsuperior', newsuperior)
  585. return self._ldap_call(self._l.rename,dn,newrdn,newsuperior,delold,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  586. def rename_s(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None):
  587. msgid = self.rename(dn,newrdn,newsuperior,delold,serverctrls,clientctrls)
  588. resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
  589. return resp_type, resp_data, resp_msgid, resp_ctrls
  590. def result(self,msgid=ldap.RES_ANY,all=1,timeout=None):
  591. """
  592. result([msgid=RES_ANY [,all=1 [,timeout=None]]]) -> (result_type, result_data)
  593. This method is used to wait for and return the result of an
  594. operation previously initiated by one of the LDAP asynchronous
  595. operation routines (e.g. search(), modify(), etc.) They all
  596. returned an invocation identifier (a message id) upon successful
  597. initiation of their operation. This id is guaranteed to be
  598. unique across an LDAP session, and can be used to request the
  599. result of a specific operation via the msgid parameter of the
  600. result() method.
  601. If the result of a specific operation is required, msgid should
  602. be set to the invocation message id returned when the operation
  603. was initiated; otherwise RES_ANY should be supplied.
  604. The all parameter only has meaning for search() responses
  605. and is used to select whether a single entry of the search
  606. response should be returned, or to wait for all the results
  607. of the search before returning.
  608. A search response is made up of zero or more search entries
  609. followed by a search result. If all is 0, search entries will
  610. be returned one at a time as they come in, via separate calls
  611. to result(). If all is 1, the search response will be returned
  612. in its entirety, i.e. after all entries and the final search
  613. result have been received.
  614. For all set to 0, result tuples
  615. trickle in (with the same message id), and with the result type
  616. RES_SEARCH_ENTRY, until the final result which has a result
  617. type of RES_SEARCH_RESULT and a (usually) empty data field.
  618. When all is set to 1, only one result is returned, with a
  619. result type of RES_SEARCH_RESULT, and all the result tuples
  620. listed in the data field.
  621. The method returns a tuple of the form (result_type,
  622. result_data). The result_type is one of the constants RES_*.
  623. See search() for a description of the search result's
  624. result_data, otherwise the result_data is normally meaningless.
  625. The result() method will block for timeout seconds, or
  626. indefinitely if timeout is negative. A timeout of 0 will effect
  627. a poll. The timeout can be expressed as a floating-point value.
  628. If timeout is None the default in self.timeout is used.
  629. If a timeout occurs, a TIMEOUT exception is raised, unless
  630. polling (timeout = 0), in which case (None, None) is returned.
  631. """
  632. resp_type, resp_data, resp_msgid = self.result2(msgid,all,timeout)
  633. return resp_type, resp_data
  634. def result2(self,msgid=ldap.RES_ANY,all=1,timeout=None):
  635. resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all,timeout)
  636. return resp_type, resp_data, resp_msgid
  637. def result3(self,msgid=ldap.RES_ANY,all=1,timeout=None,resp_ctrl_classes=None):
  638. resp_type, resp_data, resp_msgid, decoded_resp_ctrls, retoid, retval = self.result4(
  639. msgid,all,timeout,
  640. add_ctrls=0,add_intermediates=0,add_extop=0,
  641. resp_ctrl_classes=resp_ctrl_classes
  642. )
  643. return resp_type, resp_data, resp_msgid, decoded_resp_ctrls
  644. def result4(self,msgid=ldap.RES_ANY,all=1,timeout=None,add_ctrls=0,add_intermediates=0,add_extop=0,resp_ctrl_classes=None):
  645. if timeout is None:
  646. timeout = self.timeout
  647. ldap_result = self._ldap_call(self._l.result4,msgid,all,timeout,add_ctrls,add_intermediates,add_extop)
  648. if ldap_result is None:
  649. resp_type, resp_data, resp_msgid, resp_ctrls, resp_name, resp_value = (None,None,None,None,None,None)
  650. else:
  651. if len(ldap_result)==4:
  652. resp_type, resp_data, resp_msgid, resp_ctrls = ldap_result
  653. resp_name, resp_value = None,None
  654. else:
  655. resp_type, resp_data, resp_msgid, resp_ctrls, resp_name, resp_value = ldap_result
  656. if add_ctrls:
  657. resp_data = [ (t,r,DecodeControlTuples(c,resp_ctrl_classes)) for t,r,c in resp_data ]
  658. decoded_resp_ctrls = DecodeControlTuples(resp_ctrls,resp_ctrl_classes)
  659. if resp_data is not None:
  660. resp_data = self._bytesify_results(resp_data, with_ctrls=add_ctrls)
  661. return resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value
  662. def search_ext(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0):
  663. """
  664. search(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0]]]) -> int
  665. search_s(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0]]])
  666. search_st(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0 [,timeout=-1]]]])
  667. search_ext(base,scope,[,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0 [,serverctrls=None [,clientctrls=None [,timeout=-1 [,sizelimit=0]]]]]]])
  668. search_ext_s(base,scope,[,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0 [,serverctrls=None [,clientctrls=None [,timeout=-1 [,sizelimit=0]]]]]]])
  669. Perform an LDAP search operation, with base as the DN of
  670. the entry at which to start the search, scope being one of
  671. SCOPE_BASE (to search the object itself), SCOPE_ONELEVEL
  672. (to search the object's immediate children), or SCOPE_SUBTREE
  673. (to search the object and all its descendants).
  674. filter is a string representation of the filter to
  675. apply in the search (see RFC 4515).
  676. Each result tuple is of the form (dn,entry), where dn is a
  677. string containing the DN (distinguished name) of the entry, and
  678. entry is a dictionary containing the attributes.
  679. Attributes types are used as string dictionary keys and attribute
  680. values are stored in a list as dictionary value.
  681. The DN in dn is extracted using the underlying ldap_get_dn(),
  682. which may raise an exception of the DN is malformed.
  683. If attrsonly is non-zero, the values of attrs will be
  684. meaningless (they are not transmitted in the result).
  685. The retrieved attributes can be limited with the attrlist
  686. parameter. If attrlist is None, all the attributes of each
  687. entry are returned.
  688. serverctrls=None
  689. clientctrls=None
  690. The synchronous form with timeout, search_st() or search_ext_s(),
  691. will block for at most timeout seconds (or indefinitely if
  692. timeout is negative). A TIMEOUT exception is raised if no result is
  693. received within the time.
  694. The amount of search results retrieved can be limited with the
  695. sizelimit parameter if non-zero.
  696. """
  697. if PY2:
  698. base = self._bytesify_input('base', base)
  699. if filterstr is None:
  700. # workaround for default argument,
  701. # see https://github.com/python-ldap/python-ldap/issues/147
  702. if self.bytes_mode:
  703. filterstr = b'(objectClass=*)'
  704. else:
  705. filterstr = u'(objectClass=*)'
  706. else:
  707. filterstr = self._bytesify_input('filterstr', filterstr)
  708. if attrlist is not None:
  709. attrlist = tuple(self._bytesify_input('attrlist', a)
  710. for a in attrlist)
  711. else:
  712. if filterstr is None:
  713. filterstr = '(objectClass=*)'
  714. return self._ldap_call(
  715. self._l.search_ext,
  716. base,scope,filterstr,
  717. attrlist,attrsonly,
  718. RequestControlTuples(serverctrls),
  719. RequestControlTuples(clientctrls),
  720. timeout,sizelimit,
  721. )
  722. def search_ext_s(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0):
  723. msgid = self.search_ext(base,scope,filterstr,attrlist,attrsonly,serverctrls,clientctrls,timeout,sizelimit)
  724. return self.result(msgid,all=1,timeout=timeout)[1]
  725. def search(self,base,scope,filterstr=None,attrlist=None,attrsonly=0):
  726. return self.search_ext(base,scope,filterstr,attrlist,attrsonly,None,None)
  727. def search_s(self,base,scope,filterstr=None,attrlist=None,attrsonly=0):
  728. return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout=self.timeout)
  729. def search_st(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,timeout=-1):
  730. return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout)
  731. def start_tls_s(self):
  732. """
  733. start_tls_s() -> None
  734. Negotiate TLS with server. The `version' attribute must have been
  735. set to VERSION3 before calling start_tls_s.
  736. If TLS could not be started an exception will be raised.
  737. """
  738. return self._ldap_call(self._l.start_tls_s)
  739. def unbind_ext(self,serverctrls=None,clientctrls=None):
  740. """
  741. unbind() -> int
  742. unbind_s() -> None
  743. unbind_ext() -> int
  744. unbind_ext_s() -> None
  745. This call is used to unbind from the directory, terminate
  746. the current association, and free resources. Once called, the
  747. connection to the LDAP server is closed and the LDAP object
  748. is invalid. Further invocation of methods on the object will
  749. yield an exception.
  750. The unbind and unbind_s methods are identical, and are
  751. synchronous in nature
  752. """
  753. res = self._ldap_call(self._l.unbind_ext,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
  754. try:
  755. del self._l
  756. except AttributeError:
  757. pass
  758. return res
  759. def unbind_ext_s(self,serverctrls=None,clientctrls=None):
  760. msgid = self.unbind_ext(serverctrls,clientctrls)
  761. if msgid!=None:
  762. result = self.result3(msgid,all=1,timeout=self.timeout)
  763. else:
  764. result = None
  765. if __debug__ and self._trace_level>=1:
  766. try:
  767. self._trace_file.flush()
  768. except AttributeError:
  769. pass
  770. return result
  771. def unbind(self):
  772. return self.unbind_ext(None,None)
  773. def unbind_s(self):
  774. return self.unbind_ext_s(None,None)
  775. def whoami_s(self,serverctrls=None,clientctrls=None):
  776. return self._ldap_call(self._l.whoami_s,serverctrls,clientctrls)
  777. def get_option(self,option):
  778. result = self._ldap_call(self._l.get_option,option)
  779. if option==ldap.OPT_SERVER_CONTROLS or option==ldap.OPT_CLIENT_CONTROLS:
  780. result = DecodeControlTuples(result)
  781. return result
  782. def set_option(self,option,invalue):
  783. if option==ldap.OPT_SERVER_CONTROLS or option==ldap.OPT_CLIENT_CONTROLS:
  784. invalue = RequestControlTuples(invalue)
  785. return self._ldap_call(self._l.set_option,option,invalue)
  786. def search_subschemasubentry_s(self,dn=None):
  787. """
  788. Returns the distinguished name of the sub schema sub entry
  789. for a part of a DIT specified by dn.
  790. None as result indicates that the DN of the sub schema sub entry could
  791. not be determined.
  792. Returns: None or text/bytes depending on bytes_mode.
  793. """
  794. if self.bytes_mode:
  795. empty_dn = b''
  796. attrname = b'subschemaSubentry'
  797. else:
  798. empty_dn = u''
  799. attrname = u'subschemaSubentry'
  800. if dn is None:
  801. dn = empty_dn
  802. try:
  803. r = self.search_s(
  804. dn,ldap.SCOPE_BASE,None,[attrname]
  805. )
  806. except (ldap.NO_SUCH_OBJECT,ldap.NO_SUCH_ATTRIBUTE,ldap.INSUFFICIENT_ACCESS):
  807. r = []
  808. except ldap.UNDEFINED_TYPE:
  809. return None
  810. try:
  811. if r:
  812. e = ldap.cidict.cidict(r[0][1])
  813. search_subschemasubentry_dn = e.get(attrname,[None])[0]
  814. if search_subschemasubentry_dn is None:
  815. if dn:
  816. # Try to find sub schema sub entry in root DSE
  817. return self.search_subschemasubentry_s(dn=empty_dn)
  818. else:
  819. # If dn was already root DSE we can return here
  820. return None
  821. else:
  822. # With legacy bytes mode, return bytes; otherwise, since this is a DN,
  823. # RFCs impose that the field value *can* be decoded to UTF-8.
  824. return self._unbytesify_text_value(search_subschemasubentry_dn)
  825. except IndexError:
  826. return None
  827. def read_s(self,dn,filterstr=None,attrlist=None,serverctrls=None,clientctrls=None,timeout=-1):
  828. """
  829. Reads and returns a single entry specified by `dn'.
  830. Other attributes just like those passed to `search_ext_s()'
  831. """
  832. r = self.search_ext_s(
  833. dn,
  834. ldap.SCOPE_BASE,
  835. filterstr,
  836. attrlist=attrlist,
  837. serverctrls=serverctrls,
  838. clientctrls=clientctrls,
  839. timeout=timeout,
  840. )
  841. if r:
  842. return r[0][1]
  843. else:
  844. return None
  845. def read_subschemasubentry_s(self,subschemasubentry_dn,attrs=None):
  846. """
  847. Returns the sub schema sub entry's data
  848. """
  849. if self.bytes_mode:
  850. filterstr = b'(objectClass=subschema)'
  851. if attrs is None:
  852. attrs = [attr.encode('utf-8') for attr in SCHEMA_ATTRS]
  853. else:
  854. filterstr = u'(objectClass=subschema)'
  855. if attrs is None:
  856. attrs = SCHEMA_ATTRS
  857. try:
  858. subschemasubentry = self.read_s(
  859. subschemasubentry_dn,
  860. filterstr=filterstr,
  861. attrlist=attrs
  862. )
  863. except ldap.NO_SUCH_OBJECT:
  864. return None
  865. else:
  866. return subschemasubentry
  867. def find_unique_entry(self,base,scope=ldap.SCOPE_SUBTREE,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1):
  868. """
  869. Returns a unique entry, raises exception if not unique
  870. """
  871. r = self.search_ext_s(
  872. base,
  873. scope,
  874. filterstr,
  875. attrlist=attrlist,
  876. attrsonly=attrsonly,
  877. serverctrls=serverctrls,
  878. clientctrls=clientctrls,
  879. timeout=timeout,
  880. sizelimit=2,
  881. )
  882. if len(r)!=1:
  883. raise NO_UNIQUE_ENTRY('No or non-unique search result for %s' % (repr(filterstr)))
  884. return r[0]
  885. def read_rootdse_s(self, filterstr=None, attrlist=None):
  886. """
  887. convenience wrapper around read_s() for reading rootDSE
  888. """
  889. if self.bytes_mode:
  890. base = b''
  891. attrlist = attrlist or [b'*', b'+']
  892. else:
  893. base = u''
  894. attrlist = attrlist or [u'*', u'+']
  895. ldap_rootdse = self.read_s(
  896. base,
  897. filterstr=filterstr,
  898. attrlist=attrlist,
  899. )
  900. return ldap_rootdse # read_rootdse_s()
  901. def get_naming_contexts(self):
  902. """
  903. returns all attribute values of namingContexts in rootDSE
  904. if namingContexts is not present (not readable) then empty list is returned
  905. """
  906. if self.bytes_mode:
  907. name = b'namingContexts'
  908. else:
  909. name = u'namingContexts'
  910. return self.read_rootdse_s(
  911. attrlist=[name]
  912. ).get(name, [])
  913. class ReconnectLDAPObject(SimpleLDAPObject):
  914. """
  915. In case of server failure (ldap.SERVER_DOWN) the implementations
  916. of all synchronous operation methods (search_s() etc.) are doing
  917. an automatic reconnect and rebind and will retry the very same
  918. operation.
  919. This is very handy for broken LDAP server implementations
  920. (e.g. in Lotus Domino) which drop connections very often making
  921. it impossible to have a long-lasting control flow in the
  922. application.
  923. """
  924. __transient_attrs__ = {
  925. '_l',
  926. '_ldap_object_lock',
  927. '_trace_file',
  928. '_reconnect_lock',
  929. '_last_bind',
  930. }
  931. def __init__(
  932. self,uri,
  933. trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None,
  934. bytes_strictness=None, retry_max=1, retry_delay=60.0
  935. ):
  936. """
  937. Parameters like SimpleLDAPObject.__init__() with these
  938. additional arguments:
  939. retry_max
  940. Maximum count of reconnect trials
  941. retry_delay
  942. Time span to wait between two reconnect trials
  943. """
  944. self._uri = uri
  945. self._options = []
  946. self._last_bind = None
  947. SimpleLDAPObject.__init__(self, uri, trace_level, trace_file,
  948. trace_stack_limit, bytes_mode,
  949. bytes_strictness=bytes_strictness)
  950. self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self)))
  951. self._retry_max = retry_max
  952. self._retry_delay = retry_delay
  953. self._start_tls = 0
  954. self._reconnects_done = 0
  955. def __getstate__(self):
  956. """return data representation for pickled object"""
  957. state = {
  958. k: v
  959. for k,v in self.__dict__.items()
  960. if k not in self.__transient_attrs__
  961. }
  962. state['_last_bind'] = self._last_bind[0].__name__, self._last_bind[1], self._last_bind[2]
  963. return state
  964. def __setstate__(self,d):
  965. """set up the object from pickled data"""
  966. hardfail = d.get('bytes_mode_hardfail')
  967. if hardfail:
  968. d.setdefault('bytes_strictness', 'error')
  969. else:
  970. d.setdefault('bytes_strictness', 'warn')
  971. self.__dict__.update(d)
  972. self._last_bind = getattr(SimpleLDAPObject, self._last_bind[0]), self._last_bind[1], self._last_bind[2]
  973. self._ldap_object_lock = self._ldap_lock()
  974. self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self)))
  975. # XXX cannot pickle file, use default trace file
  976. self._trace_file = ldap._trace_file
  977. self.reconnect(self._uri)
  978. def _store_last_bind(self,method,*args,**kwargs):
  979. self._last_bind = (method,args,kwargs)
  980. def _apply_last_bind(self):
  981. if self._last_bind!=None:
  982. func,args,kwargs = self._last_bind
  983. func(self,*args,**kwargs)
  984. else:
  985. # Send explicit anon simple bind request to provoke ldap.SERVER_DOWN in method reconnect()
  986. SimpleLDAPObject.simple_bind_s(self, None, None)
  987. def _restore_options(self):
  988. """Restore all recorded options"""
  989. for k,v in self._options:
  990. SimpleLDAPObject.set_option(self,k,v)
  991. def passwd_s(self,*args,**kwargs):
  992. return self._apply_method_s(SimpleLDAPObject.passwd_s,*args,**kwargs)
  993. def reconnect(self,uri,retry_max=1,retry_delay=60.0):
  994. # Drop and clean up old connection completely
  995. # Reconnect
  996. self._reconnect_lock.acquire()
  997. try:
  998. reconnect_counter = retry_max
  999. while reconnect_counter:
  1000. counter_text = '%d. (of %d)' % (retry_max-reconnect_counter+1,retry_max)
  1001. if __debug__ and self._trace_level>=1:
  1002. self._trace_file.write('*** Trying %s reconnect to %s...\n' % (
  1003. counter_text,uri
  1004. ))
  1005. try:
  1006. # Do the connect
  1007. self._l = ldap.functions._ldap_function_call(ldap._ldap_module_lock,_ldap.initialize,uri)
  1008. self._restore_options()
  1009. # StartTLS extended operation in case this was called before
  1010. if self._start_tls:
  1011. SimpleLDAPObject.start_tls_s(self)
  1012. # Repeat last simple or SASL bind
  1013. self._apply_last_bind()
  1014. except (ldap.SERVER_DOWN,ldap.TIMEOUT):
  1015. if __debug__ and self._trace_level>=1:
  1016. self._trace_file.write('*** %s reconnect to %s failed\n' % (
  1017. counter_text,uri
  1018. ))
  1019. reconnect_counter = reconnect_counter-1
  1020. if not reconnect_counter:
  1021. raise
  1022. if __debug__ and self._trace_level>=1:
  1023. self._trace_file.write('=> delay %s...\n' % (retry_delay))
  1024. time.sleep(retry_delay)
  1025. SimpleLDAPObject.unbind_s(self)
  1026. else:
  1027. if __debug__ and self._trace_level>=1:
  1028. self._trace_file.write('*** %s reconnect to %s successful => repeat last operation\n' % (
  1029. counter_text,uri
  1030. ))
  1031. self._reconnects_done = self._reconnects_done + 1
  1032. break
  1033. finally:
  1034. self._reconnect_lock.release()
  1035. return # reconnect()
  1036. def _apply_method_s(self,func,*args,**kwargs):
  1037. if not hasattr(self,'_l'):
  1038. self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay)
  1039. try:
  1040. return func(self,*args,**kwargs)
  1041. except ldap.SERVER_DOWN:
  1042. SimpleLDAPObject.unbind_s(self)
  1043. # Try to reconnect
  1044. self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay)
  1045. # Re-try last operation
  1046. return func(self,*args,**kwargs)
  1047. def set_option(self,option,invalue):
  1048. self._options.append((option,invalue))
  1049. return SimpleLDAPObject.set_option(self,option,invalue)
  1050. def bind_s(self,*args,**kwargs):
  1051. res = self._apply_method_s(SimpleLDAPObject.bind_s,*args,**kwargs)
  1052. self._store_last_bind(SimpleLDAPObject.bind_s,*args,**kwargs)
  1053. return res
  1054. def simple_bind_s(self,*args,**kwargs):
  1055. res = self._apply_method_s(SimpleLDAPObject.simple_bind_s,*args,**kwargs)
  1056. self._store_last_bind(SimpleLDAPObject.simple_bind_s,*args,**kwargs)
  1057. return res
  1058. def start_tls_s(self,*args,**kwargs):
  1059. res = self._apply_method_s(SimpleLDAPObject.start_tls_s,*args,**kwargs)
  1060. self._start_tls = 1
  1061. return res
  1062. def sasl_interactive_bind_s(self,*args,**kwargs):
  1063. """
  1064. sasl_interactive_bind_s(who, auth) -> None
  1065. """
  1066. res = self._apply_method_s(SimpleLDAPObject.sasl_interactive_bind_s,*args,**kwargs)
  1067. self._store_last_bind(SimpleLDAPObject.sasl_interactive_bind_s,*args,**kwargs)
  1068. return res
  1069. def sasl_bind_s(self,*args,**kwargs):
  1070. res = self._apply_method_s(SimpleLDAPObject.sasl_bind_s,*args,**kwargs)
  1071. self._store_last_bind(SimpleLDAPObject.sasl_bind_s,*args,**kwargs)
  1072. return res
  1073. def add_ext_s(self,*args,**kwargs):
  1074. return self._apply_method_s(SimpleLDAPObject.add_ext_s,*args,**kwargs)
  1075. def cancel_s(self,*args,**kwargs):
  1076. return self._apply_method_s(SimpleLDAPObject.cancel_s,*args,**kwargs)
  1077. def compare_ext_s(self,*args,**kwargs):
  1078. return self._apply_method_s(SimpleLDAPObject.compare_ext_s,*args,**kwargs)
  1079. def delete_ext_s(self,*args,**kwargs):
  1080. return self._apply_method_s(SimpleLDAPObject.delete_ext_s,*args,**kwargs)
  1081. def extop_s(self,*args,**kwargs):
  1082. return self._apply_method_s(SimpleLDAPObject.extop_s,*args,**kwargs)
  1083. def modify_ext_s(self,*args,**kwargs):
  1084. return self._apply_method_s(SimpleLDAPObject.modify_ext_s,*args,**kwargs)
  1085. def rename_s(self,*args,**kwargs):
  1086. return self._apply_method_s(SimpleLDAPObject.rename_s,*args,**kwargs)
  1087. def search_ext_s(self,*args,**kwargs):
  1088. return self._apply_method_s(SimpleLDAPObject.search_ext_s,*args,**kwargs)
  1089. def whoami_s(self,*args,**kwargs):
  1090. return self._apply_method_s(SimpleLDAPObject.whoami_s,*args,**kwargs)
  1091. # The class called LDAPObject will be used as default for
  1092. # ldap.open() and ldap.initialize()
  1093. LDAPObject = SimpleLDAPObject