Commits

leonard  committed 028d1f0

Initial revision

  • Participants

Comments (0)

Files changed (41)

File ldapmodule/CHANGES

+Change history for ldapmodule
+
+1.9
+	- removed mod_next for RedHat 5.2 <esj%harvee.billerica.ma.us>
+	- fixed bug where error objects would sometimes be wrong
+	- Works with Solaris 2.7's ldap. (Probably with Netscape's ldap too)
+	  but errors are not available. Sun's man pages suck.
+	- Use correct form of --without-threads option in openldap.sh 
+	  <a.d.stribblehill%durham.ac.uk>
+	- correctly use --enable-cidict fix
+1.8
+	- Exceptions are now classes with a common base class (ldap.LDAPError)
+	  <davidma%premier1.net>
+	- /etc/services is consulted first for the 'ldap' service port number
+	- catch two possible null pointer dereferences
+1.7
+	(No code changes, just install fixes)
+	- install notes cleanup
+	- public domain
+	- fix bug exposed by RedHat Linux 5.2: get extra libs from
+	  openldap's config.status <uche.ogbuji%fourthought.com>
+	- support for OpenLDAP 1.2.7
+	- openldap.sh does MD5 checksum (for corrupted downloads mainly)
+1.6
+	- added experimental 'case insensitive' dictionary, meaning that
+	  attributes records returned can be indexed by case-insensitive
+	  attribute names
+	- added a shell script called openldap.sh which downloads, configures,
+	  and builds the essentials from an OpenLDAP distribution
+1.5
+	- better type checking
+	- documentation in __doc__ strings
+	- support for WIN32 compilation
+	- README tells how to do a minimum-build of the OpenLDAP distribution
+1.4
+	- python 1.5 savvy
+	- uses a configure script
+	- README tells how to do a minimum-build of UM's ldap distribution
+1.3
+	- errors are module-specific now (Neale Pickett <neale%lanl.gov>)
+1.2
+	- patches from Donn Cave <donn%u.washington.edu> (time.h and cpp fixes)
+1.1
+	- fixed potential bug where attributes could get lost
+	- added 'unbind()' method and 'valid' attribute
+1.0
+	- no changes
+0.4
+	- fixed bug where ldap_explode_dn would fail and exception wasn't
+	  indicative of real error (bad DN)
+	- fixed bug where ldap_get_dn would fail and exception wasn't raised
+	- fixed bug where None passed as attribute list to modify would
+	  fail to initialise a pointer and seg fault later on
+0.3
+	- execptions contain more error information
+0.2
+	- thread support
+0.1
+	- documentation fixes, code cleanup
+0.0
+	- basically working 
+
+$Id$

File ldapmodule/CIDict.c

+/* David Leonard <david.leonard@csee.uq.edu.au>, 1999. Public domain. */
+
+/*	$Id$	*/
+
+#include "common.h"
+#ifdef USE_CIDICT
+
+/*
+ * Case Insensitive dictionary
+ *
+ * e.g:
+ *	>>> foo['Bar'] = 123
+ *	>>> print foo['baR']
+ *	123
+ *
+ * This dictionary can be used to hold the replies returned by
+ * case-insensitive X.500 directory services, without the
+ * caller having to know what case it has converted everything to.
+ *
+ * XXX I forget whose idea this originally was, but its a good one. - d
+ */
+
+#include "Python.h"
+
+/*
+ * Return a new object representing a lowercased version of the argument.
+ * Typically this is a string -> string conversion.
+ */
+static PyObject *
+case_insensitive(PyObject *o)
+{
+	char *str, *cp;
+	int len, i;
+	PyObject *s;
+
+	if (o == NULL)
+		return NULL;
+
+	if (!PyString_Check(o)) {
+		Py_INCREF(o);
+		return o;
+	}
+
+	str = PyString_AS_STRING(o);
+	len = PyString_GET_SIZE(o);
+	cp = malloc(len);
+	for (i = 0; i < len; i++)
+		cp[i] = tolower(str[i]);
+	s = PyString_FromString(cp);
+	free(cp);
+	return s;
+}
+
+/* read-access the dictionary after lowecasing the subscript */
+
+static PyObject *
+cid_subscript(PyObject *d, PyObject *k)
+{
+	PyObject *ret;
+	PyObject *cik;
+
+	cik = case_insensitive(k);
+	ret = (*PyDict_Type.tp_as_mapping->mp_subscript)(d, cik);
+	Py_XDECREF(cik);
+	return ret;
+}
+
+/* write-access the dictionary after lowecasing the subscript */
+
+static int
+cid_ass_subscript(PyObject *d, PyObject *k, PyObject *v)
+{
+	int ret;
+	PyObject *cik;
+
+	cik = case_insensitive(k);
+	ret = (*PyDict_Type.tp_as_mapping->mp_ass_subscript)(d, cik, v);
+	Py_XDECREF(cik);
+	return ret;
+}
+
+/* This type and mapping structure gets filled in from the PyDict structs */
+ 
+static PyMappingMethods CIDict_mapping;
+PyTypeObject CIDict_Type;
+
+/* Initialise the case-insensitive dictionary type */
+
+static void
+CIDict_init()
+{
+	/*
+	 * Duplicate the standard python dictionary type, 
+	 * but override the subscript accessor methods
+	 */
+	memcpy(&CIDict_Type, &PyDict_Type, sizeof CIDict_Type);
+	CIDict_Type.tp_name = "cidictionary";
+	CIDict_Type.tp_as_mapping = &CIDict_mapping;
+
+	memcpy(&CIDict_mapping, PyDict_Type.tp_as_mapping, 
+		sizeof CIDict_mapping);
+	CIDict_mapping.mp_subscript = cid_subscript;
+	CIDict_mapping.mp_ass_subscript = cid_ass_subscript;
+}
+
+/* Create a new case-insensitive dictionary, based on PyDict */
+
+PyObject *
+CIDict_New()
+{
+	PyObject *mp;
+	static int initialised = 0;
+
+	if (!initialised) {
+		CIDict_init();
+		initialised = 1;
+	}
+		
+	mp = PyDict_New();
+	mp->ob_type = &CIDict_Type;
+	return (mp);
+}
+
+#endif USE_CIDICT

File ldapmodule/CIDict.h

+/* David Leonard <david.leonard@csee.uq.edu.au>, 1999. Public domain. */
+/*	$Id$	*/
+
+#ifdef USE_CIDICT
+
+extern PyTypeObject CIDict_Type;
+PyObject * CIDict_New();
+
+#endif

File ldapmodule/Demo/test.py

+
+import sys
+sys.path.append("/homes/leonard/src/ldapmodule")
+import getpass
+import ldap
+
+#l = ldap.open("localhost", 31001)
+l = ldap.open("marta.it.uq.edu.au")
+
+login_dn = "cn=root,ou=CSEE,o=UQ,c=AU"
+login_pw = getpass.getpass("Password for %s: " % login_dn)
+l.simple_bind_s(login_dn, login_pw)
+
+#
+# create a new sub organisation
+#
+
+try:
+    dn = "ou=CSEE,o=UQ,c=AU"
+    print "Adding", repr(dn)
+    l.add_s(dn,
+	 [
+	    ("objectclass",["organizationalUnit"]),
+	    ("ou", ["CSEE"]),
+	    ("description", [
+		    "Department of Computer Science and Electrical Engineering"]),
+	 ]
+       )
+
+except ldap.LDAPError:
+    pass
+
+#
+# create an entry for me
+#
+
+dn = "cn=David Leonard,ou=CSEE,o=UQ,c=AU"
+print "Updating", repr(dn)
+
+try:
+	l.delete_s(dn)
+except:
+	pass
+
+l.add_s(dn,
+     [
+	("objectclass",			["organizationalPerson"]),
+	("sn",				["Leonard"]),
+	("cn",				["David Leonard"]),
+	("description",			["Ph.D. student"]),
+	("display-name",		["David Leonard"]),
+	#("commonname",			["David Leonard"]),
+	("mail",			["david.leonard@csee.uq.edu.au"]),
+	("othermailbox",		["d@openbsd.org"]),
+	("givenname",			["David"]),
+	("surname",			["Leonard"]),
+	("seeAlso",			["http://www.csee.uq.edu.au/~leonard/"]),
+	("url",				["http://www.csee.uq.edu.au/~leonard/"]),
+	#("homephone",			[]),
+	#("fax",			[]),
+	#("otherfacsimiletelephonenumber",[]),
+	#("officefax",			[]),
+	#("mobile",			[]),
+	#("otherpager",			[]),
+	#("officepager",		[]),
+	#("pager",			[]),
+	("info",			["info"]),
+	("title",			["Mr"]),
+	#("telephonenumber",		[]),
+	("l",				["Brisbane"]),
+	("st",				["Queensland"]),
+	("c",				["AU"]),
+	("co",				["co"]),
+	("o",				["UQ"]),
+	("ou",				["CSEE"]),
+	#("homepostaladdress",		[]),
+	#("postaladdress",		[]),
+	#("streetaddress",		[]),
+	#("street",			[]),
+	("department",			["CSEE"]),
+	("comment",			["comment"]),
+	#("postalcode",			[]),
+	("physicaldeliveryofficename",  ["Bldg 78, UQ, St Lucia"]),
+	("preferredDeliveryMethod",	["email"]),
+	("initials",			["DRL"]),
+	("conferenceinformation",	["MS-conferenceinformation"]),
+	#("usercertificate",		[]),
+	("labeleduri",			["labeleduri"]),
+	("manager",			["cn=Jaga Indulska"]),
+	("reports",			["reports"]),
+	("jpegPhoto",			[open("/www/leonard/leonard.jpg","r").read()]),
+	("uid",				["leonard"]),
+	("userPassword",		[""])
+
+    ])
+
+#
+# search beneath the CSEE/UQ/AU tree
+#
+
+res = l.search_s(
+	"ou=CSEE, o=UQ, c=AU", 
+	ldap.SCOPE_SUBTREE, 
+	"objectclass=*",
+      )
+print res
+
+l.unbind()
+

File ldapmodule/Doc/Doc.tex

+\documentclass[11pt]{report}
+\usepackage{myformat}
+\usepackage{postscriptfonts}
+
+% if the above doesn't work, just use this:
+%\documentstyle[twoside,11pt,myformat]{report}
+
+% $Id$
+
+\title{Python LDAP module reference}
+\author{David Leonard}
+
+\makeindex
+\begin{document}
+
+\maketitle
+
+\input{libldap}
+
+\end{document}

File ldapmodule/Doc/LDAPObject.tex

+
+% $Id$
+
+\subsection{LDAP Objects}
+
+\noindent
+LDAP objects are created by the \code{open()} function defined at the top 
+level of the module. The connection is automatically unbound and closed 
+during garbage collection.
+
+Most methods initiate an asynchronous request to the LDAP server 
+and return a message id that can be used later to retrieve the result. 
+The methods ending with \code{_s} are the synchronous form and wait for and
+return with
+the server's result, \textit{\'{a} la} \code{result()}, or with
+\code{None} if no data is expected. See the \code{result()} method
+for a description of the data structure returned from the server.
+
+\subsubsection{Exceptions from methods}
+
+Unlike the \C\ library, errors are not returned as result codes, but
+are instead turned into exceptions, raised as soon an the error condition 
+is detected. The exceptions are accompanied by a dictionary containing
+extra information. 
+
+This dictionary contains an entry for the key \code{'desc'} 
+for an English description of the error class and \code{'info'} which
+contains a string containing more information the server may have sent.
+
+If the exception was one of 
+\code{NO_SUCH_OBJECT}, 
+\code{ALIAS_PROBLEM}, 
+\code{INVALID_DNS_SYNTAX}, 
+\code{IS_LEAF}, or 
+\code{ALIAS_DEREFERENCING_PROBLEM}, 
+then \code{'matched'} will be a key for
+the name of the lowest entry (object or alias) that was matched and is a 
+truncated form of the name provided or aliased dereferenced.
+
+%%============================================================
+%% __methods__
+
+\subsubsection{Methods on LDAP Objects}
+
+\renewcommand{\indexsubitem}{(LDAP method)}
+
+%%------------------------------------------------------------
+%% abandon
+
+\begin{funcdesc}{abandon}{msgid}
+Abandons or cancels an LDAP operation in progress. The \var{msgid}
+should be the message id of an outstanding LDAP operation as returned by
+the asynchronous methods \code{search()}, \code{modify()} etc. 
+The caller can expect that the result
+of an abandoned operation will not be returned from a future call to 
+\code{result()}.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% add
+
+\begin{funcdesc}{add}{dn\, modlist}
+     \funcline{add_s}{dn\, modlist}
+This function is similar to \code{modify()}, except that no operation
+integer need be included in the tuples.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% bind
+
+\begin{funcdesc}{bind}{who\, cred\, method}
+     \funcline{bind_s}{who\, cred\, method}
+     \funcline{simple_bind}{who\, passwd}
+     \funcline{simple_bind_s}{who\, passwd}
+     \funcline{kerberos_bind_s}{who}
+     \funcline{kerberos_bind1}{who}
+     \funcline{kerberos_bind1_s}{who}
+     \funcline{kerberos_bind2}{who}
+     \funcline{kerberos_bind2_s}{who}
+After an LDAP object is created, and before any other operations can be
+attempted over the connection, a bind operation must be performed.
+
+This method attempts to bind with the LDAP server using 
+either simple authentication, or kerberos. The general method \code{bind()}
+takes a third parameter, \var{method} which can be one of
+\code{AUTH_SIMPLE}, \code{AUTH_KRBV41} or \code{AUTH_KRBV42}.
+The \var{cred} parameter is ignored for Kerberos authentication.
+
+Kerberos authentication is only available if the LDAP library and 
+the ldap module were compiled with \code{-DWITH_KERBEROS}.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% compare
+
+\begin{funcdesc}{compare}{dn\, attr\, value}
+     \funcline{compare_s}{dn\, attr\, value}
+Perform an LDAP comparison between the attribute named \var{attr} of 
+entry \var{dn}, and the value \var{value}. The synchronous form
+returns \code{0} for false, or \code{1} for true.
+The asynchronous form returns the message id of the initiates request, 
+and the result of the asynchronous compare can be obtained using 
+\code{result()}.  
+
+Note that this latter technique yields the answer
+by raising the exception objects \code{COMPARE_TRUE} or \code{COMPARE_FALSE}.
+
+A design bug in the library prevents \var{value} from containing nul characters.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% delete
+
+\begin{funcdesc}{delete}{dn}
+     \funcline{delete_s}{dn}
+Performs an LDAP delete operation on \var{dn}. The asynchronous form
+returns the message id of the initiated request, and the result can be obtained
+from a subsequent call to \code{result()}.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% destroy_cache
+
+\begin{funcdesc}{destroy_cache}{}
+Turns off caching and removed it from memory.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% disable_cache
+
+\begin{funcdesc}{disable_cache}{}
+Temporarily disables use of the cache. New requests are not cached, and
+the cache is not checked when returning results. Cache contents are not
+deleted.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% enable_cache
+
+\begin{funcdesc}{enable_cache}{\optional{timeout=\code{NO_LIMIT}\, 
+                               \optional{maxmem=\code{NO_LIMIT}}}}
+Using a cache often greatly improves performance. By default the cache
+is disabled. Specifying \var{timeout} in seconds is used to decide how long
+to keep cached requests. The \var{maxmem} value is in bytes, and is used
+to set an upper bound on how much memory the cache will use. A value of
+\code{NO_LIMIT} for either indicates unlimited. 
+Subsequent calls to
+\code{enable_cache} can be used to adjust these parameters.
+
+This and other caching methods are not available if the library and the 
+ldap module were compiled with \code{-DNO_CACHE}.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% flush_cache
+
+\begin{funcdesc}{flush_cache}{}
+Deletes the cache's contents, but does not affect it in any other way.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% modify
+
+\begin{funcdesc}{modify}{ dn\, modlist }
+     \funcline{modify_s}{ dn\, modlist }
+Performs an LDAP modify operation on an entry's attributes. 
+\var{dn} is the DN of the entry to modify,
+and \var{modlist} is the list of modifications to make to the entry.
+
+Each element of the list \var{modlist} should be a tuple of the form 
+\code{(mod_op,mod_type,mod_vals)},
+where \var{mod_op} is the operation (one of \code{MOD_ADD}, \code{MOD_DELETE},
+or \code{MOD_REPLACE}),
+\var{mod_type} is a string indicating the attribute type name, and 
+\var{mod_vals} is either a string value or a list of string values to add, 
+delete or replace respectively.  For the delete operation, \var{mod_vals}
+may be \code{None} indicating that all attributes are to be deleted.
+
+The asynchronous \code{modify()} returns the message id of the 
+initiated request.
+
+See the \code{ber_*} methods for decoding Basic Encoded ASN.1 values.
+(To be implemented.)
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% modrdn
+
+\begin{funcdesc}{modrdn}{dn\, newrdn \optional{\, delold=\code{1}}}
+     \funcline{modrdn_s}{dn\, newrdn \optional{\, delold=\code{1}}}
+Perform a modify RDN operation. These routines take \var{dn}, the DN
+of the entry whose RDN is to be changed, and \var{newrdn}, the new RDN to
+give to the entry. The optional parameter \var{delold} is used to specify
+whether the old RDN should be kept as an attribute of the entry or not.
+The asynchronous version returns the initiated message id.
+
+This actually corresponds to the \code{modrdn2*} routines in the \C\ library.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% result
+
+\begin{funcdesc}{result}{\optional{ msgid=\code{RES_ANY} \optional{\, all=\code{1} \optional{\, timeout=\code{-1}}}}}
+This method is used to wait for and return the result of an operation
+previously initiated by one of the LDAP asynchronous operation routines
+(eg \code{search()}, \code{modify()}, etc.) They all returned
+an invocation identifier (a message id) upon successful initiation of
+their operation. This id is guaranteed to be unique across an LDAP session,
+and can be used to request the result of a specific operation via the
+\var{msgid} parameter of the \code{result()} method.
+
+If the result of a specific operation is required, \var{msgid} should
+be set to the invocation message id returned when the operation was
+initiated; otherwise \code{RES_ANY} should be supplied. 
+
+The \var{all}
+parameter only has meaning for \code{search()} responses and is used to select 
+whether a single entry of the search response should be returned, or to
+wait for \emph{all} the results of the search before returning. 
+%See \code{search()} for more details.
+
+A search response is made up of zero or more search entries followed by
+a search result. If \var{all} is \code{0}, search entries will be returned one
+at a time as they come in, via separate calls to \code{result()}. If
+\var{all} is \code{1}, the search response will be returned in its 
+entirety, ie after all entries and the final search result have been
+received.
+
+The method returns a tuple of the form 
+\code{(\var{result_type}, \var{result_data})}.
+The \var{result_type} is a string, being one of:
+\code{'RES_BIND'}, \code{'RES_SEARCH_ENTRY'}, \code{'RES_SEARCH_RESULT'}, 
+\code{'RES_MODIFY'}, \code{'RES_ADD'}, \code{'RES_DELETE'}, 
+\code{'RES_MODRDN'}, or \code{'RES_COMPARE'}.
+
+The constants \code{RES_*} are set to these strings, for convenience.
+
+See \code{search()} for a description of the search result's \var{result_data},
+otherwise the \var{result_data} is normally meaningless.
+
+The \code{result()} method will block for \var{timeout} seconds, or 
+indefinitely if \var{timeout} is negative. 
+A timeout of \code{0} will effect a poll. 
+The timeout can be expressed as a floating-point value.
+
+If a timeout occurs, the tuple \code{(None,None)} is returned.
+% XXX - should this raise a TIMEOUT exception?
+
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% search
+
+\begin{funcdesc}{search}{base\, scope\, filter\optional{\, 
+	attrlist=\code{None}\optional{\, attrsonly=\code{0}}}}
+     \funcline{search_s}{base\, scope\, filter\optional{\, 
+     	attrlist=\code{None}\optional{\, attrsonly=\code{0}}}}
+    \funcline{search_st}{base\, scope\, filter\optional{\,
+    	attrlist=\code{None}\optional{\, attrsonly=\code{0} \optional{\, 
+		timeout=\code{-1}}}}}
+Perform an LDAP search operation, with \var{base} as the DN of the entry
+at which to start the search, \var{scope} being one of 
+\code{SCOPE_BASE} (to search the object itself), 
+\code{SCOPE_ONELEVEL} (to search the object's immediate children), or
+\code{SCOPE_SUBTREE} (to search the object and all its descendants).
+
+\var{filter} is a string representation of the filter to apply in
+the search. Simple filters can be specified as
+\code{"\var{attribute_type}=\var{attribute_value}"}. 
+More complex filters are specified using a prefix notation according to 
+the following BNF:
+\begin{eqnarray*}
+	\var{filter}	&::=& \code{"("} \ \var{filtercomp} \ \code{")"}
+\\	\var{filtercomp}&::=& \var{and} \ |\ \var{or}\ |\ \var{not} 
+			      \ |\ \var{simple}
+\\	\var{and}	&::=& \code{"\&"} \ \var{filterlist}
+\\	\var{or}	&::=& \code{"|"} \ \var{filterlist}
+\\	\var{not}	&::=& \code{"!"} \ \var{filter}
+\\	\var{filterlist}&::=& \var{filter} \ |\ \var{filter} \ \var{filterlist}
+\\	\var{simple}	&::=& \var{attributetype}\ \var{filtertype} 
+			      \ \var{attributevalue}
+\\	\var{filtertype}&::=& \code{"="} \ |\ \code{"\~="}\ |\ \code{"<="}
+		              \ |\ \code{">="}
+\end{eqnarray*}
+
+When using the asynchronous form and \code{result()}, the \var{all}
+parameter affects how results come in.
+For \var{all} set to \code{0}, 
+result tuples trickle in (with the same message id), and with the result
+type \code{RES_SEARCH_ENTRY}, until the final result which has 
+a result type of \code{RES_SEARCH_RESULT} and a (usually) empty data field.
+When \var{all} is set to \code{1}, only one result is returned, with a
+result type of \code{RES_SEARCH_RESULT}, and all the result tuples listed 
+in the data field.
+
+Each result tuple is of the form \code{(\var{dn},\var{attrs})}, 
+where \var{dn} is a string containing
+the DN (distinguished name) of the entry, and 
+\var{attrs} is a dictionary
+containing the attributes associated with the entry. 
+The keys of \var{attrs} are strings, 
+and the associated values are lists of strings.
+
+The DN in \var{dn} is extracted using the underlying \code{ldap_get_dn()},
+which may raise an exception if the DN is malformed.
+
+If \var{attrsonly} is non-zero, the values of \var{attrs} will be meaningless
+(they are not transmitted in the result).
+
+The retrieved attributes can be limited with the \var{attrlist} parameter.
+If \var{attrlist} is \code{None}, all the attributes of each entry are returned.
+
+The synchronous form with timeout, \code{search_st()}, will block for at most
+\var{timeout} seconds (or indefinitely if \var{timeout} is negative). A
+\code{TIMEOUT} exception is raised if no result is received within the
+time.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% set_cache_options
+
+\begin{funcdesc}{set_cache_options}{option}
+Changes the caching behaviour. Currently supported options are
+    \code{CACHE_OPT_CACHENOERRS}, which suppresses caching of requests
+    	that resulted in an error, and
+    \code{CACHE_OPT_CACHEALLERRS}, which enables caching of all requests.
+The default behaviour is not to cache requests that result in errors, except 
+those that result in a \code{SIZELIMIT_EXCEEDED} exception.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% set_rebind_proc
+
+\begin{funcdesc}{set_rebind_proc}{func}
+If a referral is returned from the server, automatic
+re-binding can be achieved by providing a function that accepts as an argument
+the newly opened LDAP object and returns the tuple \code{(who, cred, method)}.
+
+Passing a value of \code{None} for \var{func} will disable
+this facility. 
+
+Because of restrictions in the implementation, only one
+rebinding function is supported at any one time. This method is only
+available if the module and library were compiled with \code{-DLDAP_REFERRALS}.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% ufn_setfilter
+
+\begin{funcdesc}{ufn_setfilter}{filtername}
+       \funcline{ufn_setprefix}{prefix}
+       \funcline{ufn_search_s}{url\optional{\, attrsonly=\code{0}}}
+       \funcline{ufn_search_st}{url\optional{\, attrsonly=\code{0} \optional{\, timeout=\code{-1}}}}
+See the LDAP library manual pages for more information on these
+`user-friendly name' functions.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% unbind
+
+\begin{funcdesc}{unbind_s}{}
+       \funcline{unbind}{}
+This call is used to unbind from the directory, terminate the current
+association, and free resources. Once called, the connection to the
+LDAP server is closed and the LDAP object is invalid. Further invocation
+of methods on the object will yield an exception.
+
+The \code{unbind} and \code{unbind_s} methods are identical, and are 
+synchronous in nature
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% uncache_entry
+
+\begin{funcdesc}{uncache_entry}{dn}
+Removes all cached entries that make reference to \var{dn}. This should be
+used, for example, after doing a \code{modify()} involving \var{dn}.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% uncache_request
+
+\begin{funcdesc}{uncache_request}{msgid}
+Remove the request indicated by \var{msgid} from the cache.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% url_search
+
+\begin{funcdesc}{url_search_s}{url\, \optional{attrsonly=\code{0}}}
+       \funcline{url_search_s}{url\, \optional{attrsonly=\code{0} \optional{\, timeout=\code{-1}}}}
+These routine works much like \code{search_s*}, except that many search
+parameters are pulled out of the URL \var{url}. 
+
+LDAP URLs look like this: 
+
+\code{"ldap://\var{host}\optional{:\var{port}}/\var{dn}\optional{?\var{attributes}\optional{?\var{scope}\optional{?\var{filter}}}}"}
+
+where \var{scope} is one of \code{base} (default), \code{one} or \code{sub},
+and \var{attributes} is a comma-separated list of attributes to be retrieved.
+
+URLs wrapped in angle-brackets and/or preceded by \code{"URL:"} are 
+tolerated.
+\end{funcdesc}
+
+%%============================================================
+%% attributes
+
+\subsubsection{Attributes on LDAP Objects}
+
+Each LDAP object also sports the following attributes.
+
+%%------------------------------------------------------------
+%% deref
+
+\begin{datadesc}{deref}
+    Controls for when an automatic dereference of a referral occurs.
+    This must be one of
+    \code{DEREF_NEVER}, \code{DEREF_SEARCHING}, \code{DEREF_FINDING},
+    or \code{DEREF_ALWAYS}.
+\end{datadesc}
+
+%%------------------------------------------------------------
+%% errno
+
+\begin{datadesc}{errno}
+   \dataline{error}
+   \dataline{matched}
+    These read-only attributes are set after an exception has been raised, and
+    are also included with the value raised. See the section
+    `Exceptions from methods', above.
+\end{datadesc}
+
+%%------------------------------------------------------------
+%% lberoptions
+
+\begin{datadesc}{lberoptions}
+    Options for the BER library.
+\end{datadesc}
+
+%%------------------------------------------------------------
+%% options
+
+\begin{datadesc}{options}
+    General options. This field is the bitiwse OR of the flags
+	\code{OPT_REFERRALS} (follow referrals), and
+	\code{OPT_RESTART}   (restart the \var{select} system call
+			      when interrupted).
+\end{datadesc}
+
+%%------------------------------------------------------------
+%% refhoplimit
+
+\begin{datadesc}{refhoplimit}
+    Maximum number of referrals to follow before raising an exception.
+    Defaults to 5.
+\end{datadesc}
+
+%%------------------------------------------------------------
+%% sizelimit
+
+\begin{datadesc}{sizelimit}
+    Limit on size of message to receive from server. 
+    Defaults to \code{NO_LIMIT}.
+\end{datadesc}
+
+%%------------------------------------------------------------
+%% timelimit
+
+\begin{datadesc}{timelimit}
+    Limit on waiting for any response. 
+    Defaults to \code{NO_LIMIT}.
+\end{datadesc}
+
+%%------------------------------------------------------------
+%% valid
+
+\begin{datadesc}{valid}
+    If zero, the connection has been unbound. See \code{unbind()} for
+    more information.
+\end{datadesc}
+

File ldapmodule/Doc/Makefile

+
+# $Id$
+
+TARGET = Doc
+SRCS=	$(TARGET).tex constants.tex functions.tex LDAPObject.tex libldap.tex
+
+build:	Doc.ps Doc.pdf
+
+$(TARGET).ps: $(TARGET).dvi
+	dvips -o $@ $(TARGET).dvi
+
+$(TARGET).pdf: $(TARGET).ps
+	ps2pdf $(TARGET).ps $@
+
+$(TARGET).dvi:	$(SRCS)
+	latex $(TARGET)
+	latex $(TARGET)
+
+$(TARGET).html: $(SRCS) tex2html
+	perl tex2html $(SRCS) >$@
+
+clean:
+	rm -f *.aux *.dvi *.idx *.log 
+
+distclean: clean
+	rm $(TARGET).ps

File ldapmodule/Doc/constants.tex

+
+% $Id$
+
+\subsection{Constants and Exceptions}
+
+The following exceptions and constants are exported from the module:
+
+\renewcommand{\indexsubitem}{(in module ldap)}
+
+%%------------------------------------------------------------
+%% error
+
+\begin{excdesc}{LDAPError}
+This is the base exeception class raised when some error arises within
+the glue code between the \C\ interface and the Python API.
+\end{excdesc}
+
+%%------------------------------------------------------------
+%% ALIAS_DEREF_PROBLEM ... USER_CANCELLED
+
+\begin{excdesc}{ALIAS_DEREF_PROBLEM}
+       \excline{ALIAS_PROBLEM}
+       \excline{ALREADY_EXISTS}
+       \excline{AUTH_UNKNOWN}
+       \excline{BUSY}
+       \excline{COMPARE_FALSE}
+       \excline{COMPARE_TRUE}
+       \excline{CONSTRAINT_VIOLATION}
+       \excline{DECODING_ERROR}
+       \excline{ENCODING_ERROR}
+       \excline{FILTER_ERROR}
+       \excline{INAPPROPRIATE_AUTH}
+       \excline{INAPPROPRIATE_MATCHING}
+       \excline{INSUFFICIENT_ACCESS}
+       \excline{INVALID_CREDENTIALS}
+       \excline{INVALID_DN_SYNTAX}
+       \excline{INVALID_SYNTAX}
+       \excline{IS_LEAF}
+       \excline{LOCAL_ERROR}
+       \excline{LOOP_DETECT}
+       \excline{NAMING_VIOLATION}
+       \excline{NOT_ALLOWED_ON_NONLEAF}
+       \excline{NOT_ALLOWED_ON_RDN}
+       \excline{NO_OBJECT_CLASS_MODS}
+       \excline{NO_SUCH_ATTRIBUTE}
+       \excline{NO_SUCH_OBJECT}
+       \excline{OBJECT_CLASS_VIOLATION}
+       \excline{OPERATIONS_ERROR}
+       \excline{OTHER}
+       \excline{PARAM_ERROR}
+       \excline{PARTIAL_RESULTS}
+       \excline{PROTOCOL_ERROR}
+       \excline{RESULTS_TOO_LARGE}
+       \excline{SERVER_DOWN}
+       \excline{SIZELIMIT_EXCEEDED}
+       \excline{STRONG_AUTH_NOT_SUPPORTED}
+       \excline{STRONG_AUTH_REQUIRED}
+       \excline{TIMELIMIT_EXCEEDED}
+       \excline{TIMEOUT}
+       \excline{TYPE_OR_VALUE_EXISTS}
+       \excline{UNAVAILABLE}
+       \excline{UNDEFINED_TYPE}
+       \excline{UNWILLING_TO_PERFORM}
+       \excline{USER_CANCELLED}
+These exceptions are raised when a result code from an underlying API
+call does not indicate success.
+\end{excdesc}
+
+%%------------------------------------------------------------
+%% PORT
+
+\begin{datadesc}{PORT}
+The standard TCP port that LDAP servers listen on.
+\end{datadesc}
+
+%%============================================================
+%% others
+
+Many other (undocumented) constants available in the module correspond 
+to those found in the LDAP header file, \code{<ldap.h>}.

File ldapmodule/Doc/functions.tex

+
+% $Id$
+
+\subsection{Functions}
+
+The following functions are available at the module level:
+
+%%------------------------------------------------------------
+%% dn2ufn
+
+\begin{funcdesc}{dn2ufn}{dn}
+Turns the DN \var{dn} into a more user-friendly form, stripping off type names.
+See RFC 1781 ``Using the Directory to Achieve User Friendly Naming''
+for more details on the UFN format.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% explode_dn
+
+\begin{funcdesc}{explode_dn}{dn \optional{\, notypes=\code{0}}}
+This function takes the DN \var{dn} and breaks it up into its component parts. 
+Each part is known as an RDN (Relative Distinguished Name). The
+\var{notypes} parameter is used to specify that only the RDN values be returned
+and not their types. For example, the DN \code{"cn=Bob, c=US"} would be
+returned as either \code{["cn=Bob", "c=US"]} or \code{["Bob","US"]}
+depending on whether \var{notypes} was \code{0} or \code{1}, respectively.
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% is_ldap_url
+
+\begin{funcdesc}{is_ldap_url}{url}
+This function returns true if \var{url} `looks like' an LDAP URL 
+(as opposed to some other kind of URL). 
+\end{funcdesc}
+
+%%------------------------------------------------------------
+%% open
+
+\begin{funcdesc}{open}{host \optional{\, port=\code{PORT}}}
+Opens a new connection with an LDAP server, and returns an LDAP object
+representative of this.
+\end{funcdesc}

File ldapmodule/Doc/libldap.tex

+% $Id$
+
+%%============================================================
+%% LDAP Module
+
+\section{Module \sectcode{ldap}} \label{app:ldapmodule}
+
+\bimodindex{ldap}
+
+\renewcommand{\indexsubitem}{(in module ldap)}
+
+This module provides access to the University of Michigan's LDAP 
+(Lightweight Directory Access Protocol) \C\ interface. 
+It is more-or-less compliant with the interface described in RFC 1823,
+with the notable differences that lists are manipulated via Python
+list operations, and errors appear as exceptions.
+
+For more detailed information on the \C\ interface, 
+please see the documentation that accompanies the package available from
+\code{ftp://terminator.rs.itd.umich.edu/ldap/ldap-3.3.tar.Z}.
+
+This documentation is current for the python ldap module, version
+$1.4$.
+
+\input{constants}
+
+\input{functions}
+
+\input{LDAPObject}
+

File ldapmodule/Doc/myformat.sty

+%
+% myformat.sty for the Python doc  [updated to work with Latex2e]
+%
+
+% Style parameters and macros used by most documents here
+\raggedbottom
+\sloppy
+\parindent =       0mm
+\parskip =         2mm
+
+% Variable used by begin code command
+\newlength{\codewidth}
+
+% Command to start a code block (follow this by \begin{verbatim})
+\newcommand{\bcode}{
+	% Calculate the text width for the minipage:
+	\setlength{\codewidth}{\linewidth}
+	\addtolength{\codewidth}{-\parindent}
+	%
+	\par
+	\vspace{3mm}
+	\indent
+	\begin{minipage}[t]{\codewidth}
+}
+
+% Command to end a code block (precede this by \end{verbatim})
+\newcommand{\ecode}{
+	\end{minipage}
+	\vspace{3mm}
+	\par
+	\noindent
+}
+
+% Underscore hack (only act like subscript operator if in math mode)
+%
+% The following is due to Mark Wooding (the old version didn't work with
+% Latex 2e.
+
+\DeclareRobustCommand\hackscore{%
+  \ifmmode_\else\textunderscore\fi%
+}
+\begingroup
+\catcode`\_\active
+\def\next{%
+  \AtBeginDocument{\catcode`\_\active\def_{\hackscore{}}}%
+}
+\expandafter\endgroup\next
+
+%
+% This is the old hack, which didn't work with 2e.  
+% If you're still using Latex 2.09, you can give it a try if the above fails.
+%
+%\def\_{\ifnum\fam=\ttfamily \char'137\else{\tt\char'137}\fi}
+%\catcode`\_=12
+%\catcode`\_=\active\def_{\ifnum\fam=\ttfamily \char'137 \else{\tt\char'137}\fi}
+
+% Define \itembreak: force the text after an item to start on a new line
+\newcommand{\itembreak}{
+\mbox{}
+\\*[0mm]
+}
+
+% Command to generate two index entries (using subentries)
+\newcommand{\indexii}[2]{\index{#1!#2}\index{#2!#1}}
+
+% And three entries (using only one level of subentries)
+\newcommand{\indexiii}[3]{\index{#1!#2 #3}\index{#2!#3, #1}\index{#3!#1 #2}}
+
+% And four (again, using only one level of subentries)
+\newcommand{\indexiv}[4]{
+\index{#1!#2 #3 #4}
+\index{#2!#3 #4, #1}
+\index{#3!#4, #1 #2}
+\index{#4!#1 #2 #3}
+}
+
+% Command to generate a reference to a function, statement, keyword, operator
+\newcommand{\stindex}[1]{\indexii{statement}{#1@{\tt#1}}}
+\newcommand{\kwindex}[1]{\indexii{keyword}{#1@{\tt#1}}}
+\newcommand{\opindex}[1]{\indexii{operator}{#1@{\tt#1}}}
+\newcommand{\exindex}[1]{\indexii{exception}{#1@{\tt#1}}}
+\newcommand{\obindex}[1]{\indexii{object}{#1}}
+\newcommand{\bifuncindex}[1]{\index{#1@{\tt#1} (built-in function)}}
+
+% Add an index entry for a module
+\newcommand{\modindex}[2]{\index{#1@{\tt#1} (#2module)}}
+\newcommand{\bimodindex}[1]{\modindex{#1}{built-in }}
+\newcommand{\stmodindex}[1]{\modindex{#1}{standard }}
+
+% Additional string for an index entry
+\newcommand{\indexsubitem}{}
+\newcommand{\ttindex}[1]{\index{#1@{\tt#1} \indexsubitem}}
+
+% Define \itemjoin: some negative vspace to join two items together
+\newcommand{\itemjoin}{
+\mbox{}
+\vspace{-\itemsep}
+\vspace{-\parsep}
+}
+
+% Define \funcitem{func}{args}: define a function item
+\newcommand{\funcitem}[2]{%
+\ttindex{#1}%
+\item[\code{#1(\varvars{#2})}]
+\ 
+}
+
+
+% from user-level, fulllineitems should be called as an environment
+\def\fulllineitems{\list{}{\labelwidth \leftmargin \labelsep 0pt
+\rightmargin 0pt \topsep -\parskip \partopsep \parskip
+\itemsep -\parsep
+\let\makelabel\itemnewline}}
+\let\endfulllineitems\endlist
+
+
+% funcdesc should be called as an \begin{funcdesc} ... \end{funcdesc}
+\newcommand{\funcline}[2]{\item[\code{#1(\varvars{#2})}]\ttindex{#1}}
+\newcommand{\funcdesc}[2]{\fulllineitems\funcline{#1}{#2}}
+\let\endfuncdesc\endfulllineitems
+\newcommand{\optional}[1]{{\ \Large[}{#1}\hspace{0.5mm}{\Large]}\ }
+
+
+% same for excdesc
+\newcommand{\excline}[1]{\item[\code{#1}]\ttindex{#1}}
+\newcommand{\excdesc}[1]{\fulllineitems\excline{#1}}
+\let\endexcdesc\endfulllineitems
+
+% same for datadesc
+\newcommand{\dataline}[1]{\item[\code{#1}]\ttindex{#1}}
+\newcommand{\datadesc}[1]{\fulllineitems\dataline{#1}}
+\let\enddatadesc\endfulllineitems
+
+
+% Define \dataitem{name}: define a data item
+\newcommand{\dataitem}[1]{%
+\ttindex{#1}%
+\item[{\tt #1}]
+\ 
+}
+
+% Define \excitem{name}: define an exception item
+\newcommand{\excitem}[1]{%
+\ttindex{#1}%
+\item[{\tt #1}]
+\itembreak
+}
+
+\let\nodename=\label
+
+\newcommand{\ABC}{{\sc abc}}
+\newcommand{\UNIX}{{\sc Unix}}
+\newcommand{\ASCII}{{\sc ascii}}
+\newcommand{\Cpp}{C\protect\raisebox{.18ex}{++}}
+\newcommand{\C}{C}
+\newcommand{\EOF}{{\sc eof}}
+
+% code is the most difficult one...
+\newcommand{\code}[1]{{\@vobeyspaces\@noligs\def\{{\char`\{}\def\}{\char`\}}\def\~{\char`\~}\def\^{\char`\^}\def\e{\char`\\}\def\${\char`\$}\def\#{\char`\#}\def\&{\char`\&}\def\%{\char`\%}%
+\mbox{\tt #1}}}
+
+\newcommand{\kbd}[1]{\mbox{\tt #1}}
+\newcommand{\key}[1]{\mbox{\tt #1}}
+\newcommand{\samp}[1]{\mbox{`\code{#1}'}}
+\newcommand{\var}[1]{\mbox{\it#1\/}}
+\let\file=\samp
+\newcommand{\dfn}[1]{{\em #1\/}}
+\renewcommand{\emph}[1]{{\em #1\/}}
+\newcommand{\strong}[1]{{\bf #1}}
+
+\newcommand{\varvars}[1]{{\def\,{\/{\tt\char`\,}}\def\({\/{\tt\char`\(}}\def\){\/{\tt\char`\)}}\var{#1}}}
+
+\newif\iftexi\texifalse
+\newif\iflatex\latextrue
+
+\newenvironment{tableii}[4]{\begin{center}\def\lineii##1##2{\csname#2\endcsname{##1}&##2\\}\begin{tabular}{#1}\hline#3&#4\\
+\hline}{\hline\end{tabular}\end{center}}
+
+\newenvironment{tableiii}[5]{\begin{center}\def\lineiii##1##2##3{\csname#2\endcsname{##1}&##2&##3\\}\begin{tabular}{#1}\hline#3&#4&#5\\
+\hline}{\hline\end{tabular}\end{center}}
+
+\newcommand{\itemnewline}[1]{\@tempdima\linewidth
+\advance\@tempdima \leftmargin\makebox[\@tempdima][l]{#1}}
+
+\newcommand{\sectcode}[1]{{\tt #1}}

File ldapmodule/LDAPObject.c

+/* David Leonard <david.leonard@csee.uq.edu.au>, 1999. Public domain. */
+
+/* 
+ * LDAPObject - wrapper around an LDAP* context
+ * $Id$
+ */
+
+#include <math.h>
+#include <limits.h>
+#include "common.h"
+#include "errors.h"
+#include "constants.h"
+#include "LDAPObject.h"
+#include "message.h"
+
+#include "Python.h"
+
+/* constructor */
+
+LDAPObject*
+newLDAPObject( LDAP* l ) 
+{
+    LDAPObject* self = (LDAPObject*) PyObject_NEW(LDAPObject, &LDAP_Type);
+    if (self == NULL) 
+    	return NULL;
+    self->ldap = l;
+    self->_save = NULL;
+    self->valid = 1;
+    return self;
+}
+
+/* destructor */
+
+static void
+dealloc( LDAPObject* self )
+{
+    if (self->ldap) {
+	if (self->valid) {
+	    ldap_unbind( self->ldap );
+	    self->valid = 0;
+	}
+	self->ldap = NULL;
+    }
+    PyMem_DEL(self);
+}
+
+/*------------------------------------------------------------
+ * utility functions
+ */
+
+/* 
+ * check to see if the LDAPObject is valid, 
+ * ie has been opened, and not closed. An exception is set if not valid.
+ */
+
+static int
+not_valid( LDAPObject* l ) {
+    if (l->valid) {
+    	return 0;
+    } else {
+    	PyErr_SetString( LDAPexception_class, "LDAP connection invalid" );
+	return 1;
+    }
+}
+  
+/* free the LDAPMod allocated in Tuple_to_LDAPMod() */
+
+static void
+free_LDAPMod( LDAPMod* lm ) {
+    struct berval **bv;
+
+    for(bv = lm->mod_bvalues; bv && *bv; bv++) {
+      if ((*bv)->bv_val) free( (*bv)->bv_val );
+      free( *bv );
+    }
+    if (lm->mod_bvalues) free(lm->mod_bvalues);
+    lm->mod_bvalues = NULL;	/* paranoia */
+    if (lm->mod_type != NULL) free(lm->mod_type);
+}
+
+/* convert a tuple of the form (int,str,[str,...]) 
+ * or (str, [str,...]) if no_op, into an LDAPMod 
+ */
+
+/* XXX - there is no way to pass complex-structured BER objects in here! */
+
+static LDAPMod*
+Tuple_to_LDAPMod( PyObject* tup, int no_op ) 
+{
+    int op;
+    char *type;
+    struct berval **bervals;
+    PyObject *list;
+    int listlen;
+    LDAPMod *lm;
+
+    if (no_op) {
+        if (!PyArg_ParseTuple( tup, "sO", &type, &list )) return NULL;
+	op = 0;
+    } else {
+	if (!PyArg_ParseTuple( tup, "isO", &op, &type, &list )) return NULL;
+    }
+
+    lm = malloc( sizeof(LDAPMod) );
+    if (lm == NULL) { PyErr_NoMemory(); return NULL; }
+
+    if (PyNone_Check(list)) {
+
+	/* None... used for delete */
+    	bervals = NULL;
+
+    } else if (PyString_Check( list )) {
+        /* a single string on its own... treat as list of 1 */
+	int length;
+
+	bervals = (struct berval**) malloc( 2 * sizeof(struct berval*) );
+	if (bervals==NULL) { free(lm); PyErr_NoMemory(); return NULL; }
+
+	bervals[1] = NULL;
+	bervals[0] = malloc( sizeof( struct berval ) );
+	if (bervals[0] == NULL) { 
+		free_LDAPMod(lm); 
+		PyErr_NoMemory(); 
+		return NULL; 
+	}
+	length = PyString_Size( list );
+	bervals[0]->bv_val = 
+		(char*)malloc( length * sizeof(char) );
+	if (bervals[0]->bv_val == NULL) {
+		free_LDAPMod(lm); 
+		PyErr_NoMemory(); 
+		return NULL; 
+	}
+	bervals[0]->bv_len = length;
+	memcpy( bervals[0]->bv_val, PyString_AsString(list), length );
+    } else if (!PySequence_Check( list )) {
+    	/* not a list */
+    	PyErr_SetObject( PyExc_TypeError, Py_BuildValue( "sO",
+		"expected sequence of strings", list ));
+	free(lm);
+	return NULL;
+    } else {
+	/* a list, possible of strings */
+	int i;
+
+	listlen = PySequence_Length( list );
+	for(i=0; i<listlen; i++) 
+	   if (!PyString_Check( PySequence_GetItem( list, i ) ) ) {
+		PyErr_SetObject( PyExc_TypeError, Py_BuildValue( "sOi",
+		   "expected sequence of strings", list, i ));
+	   	return NULL;
+	   }
+
+	/* a list, definitely of strings */
+
+	bervals = (struct berval**) malloc( 
+			(listlen+1) * sizeof(struct berval*) );
+	if (bervals==NULL) { 
+	    free(lm); 
+	    PyErr_NoMemory(); 
+	    return NULL; 
+	}
+
+	bervals[listlen] = NULL;
+
+	for(i=0; i<listlen; i++) {
+	   struct berval *bv;
+	   char *val;
+	   int len;
+	   PyObject *str;
+
+	   str = PySequence_GetItem( list, i );
+	   len = PyString_Size( str );
+	   val = (char*) malloc( len * sizeof(char) );
+	   if (val != NULL)
+	       bv = (struct berval*) malloc( sizeof(struct berval) );
+	   else
+	       bv = NULL;
+
+	   bervals[i] = bv;
+	   if (bv==NULL) {
+	      if (val!=NULL) free(val);
+	      free_LDAPMod( lm );
+	      PyErr_NoMemory();
+	      return NULL;
+	   }
+	   memcpy( val, PyString_AsString(str), len * sizeof(char) );
+	   bv->bv_val = val;
+	   bv->bv_len = len;
+	}
+    }
+
+
+    /* bervals is now either NULL or pointer to a completely allocated 
+     * list of pointers to duplicated strings  */
+    
+    lm->mod_bvalues = bervals;
+    lm->mod_type = strdup(type);
+
+    if (lm->mod_type == NULL) { 
+	free_LDAPMod(lm); 
+    	PyErr_NoMemory();
+	return NULL; 
+    }
+
+    lm->mod_op = op | LDAP_MOD_BVALUES;
+    /* lm->mod_next = NULL; */		/* only used in server */
+
+    return lm;
+}
+
+/* free the structure allocated in List_to_LDAPMods() */
+
+static void
+free_LDAPMods( LDAPMod** lms ) {
+    LDAPMod** lmp;
+    for ( lmp = lms; *lmp; lmp++ )
+    	free_LDAPMod( *lmp );
+    free( lms );
+}
+
+/* convert a list of tuples into a LDAPMod*[] array structure */
+
+static LDAPMod**
+List_to_LDAPMods( PyObject *list, int no_op ) {
+
+    int i, len;
+    LDAPMod** lms;
+
+    if (!PySequence_Check(list)) {
+	PyErr_SetObject( PyExc_TypeError, Py_BuildValue("sO",
+			"expected list of tuples", list ));
+    	return NULL;
+    }
+
+    len = PySequence_Length(list);
+    lms = (LDAPMod**) malloc( (len+1) * sizeof( LDAPMod* ) );
+    if (lms==NULL) { PyErr_NoMemory(); return NULL; }
+    lms[len] = NULL;
+
+    for( i=0; i<len; i++ ) {
+       lms[i] = Tuple_to_LDAPMod( PySequence_GetItem( list, i ), no_op );
+       if (lms[i] == NULL) {
+	  free_LDAPMods( lms );
+	  return NULL;
+       }
+    }
+    return lms;
+}
+
+/*
+ * convert a python list of strings into an attr list (char*[]).
+ * returns 1 if successful, 0 if not (with exception set)
+ */
+
+int
+attrs_from_List( PyObject *attrlist, char***attrsp ) {
+
+    char **attrs;
+
+    if (PyNone_Check( attrlist )) {
+    	attrs = NULL;
+    } else if (PyString_Check( attrlist )) {
+	/* caught by John Benninghoff <johnb@netscape.com> */
+	PyErr_SetObject( PyExc_TypeError, Py_BuildValue("sO",
+		  "expected *list* of strings, not a string", attrlist ));
+        return 0;
+    } else if (PySequence_Check( attrlist )) {
+	int len = PySequence_Length( attrlist );
+	int i;
+
+        attrs = (char**) malloc( (1+len) * sizeof(char*) );
+	if (attrs == NULL) { 
+	    PyErr_NoMemory();
+	    return 0;
+	}
+
+	attrs[len] = NULL;
+	for(i=0; i<len; i++) {
+	    PyObject *s = PySequence_GetItem( attrlist, i );
+	    if (!PyString_Check(s)) {
+		free(attrs);
+		PyErr_SetObject( PyExc_TypeError, Py_BuildValue("sOi",
+			  "expected list of strings or None", attrlist,i ));
+		return 0;
+	    }
+	    attrs[i] = PyString_AsString(s);
+	}
+
+    } else {
+    	PyErr_SetObject( PyExc_TypeError, Py_BuildValue("sO",
+			  "expected list of strings or None", attrlist ));
+	return 0;
+    }
+
+    *attrsp = attrs;
+    return 1;
+}
+
+/* free memory allocated from above routine */
+
+static void
+free_attrs( char*** attrsp ) {
+    if (*attrsp != NULL) {
+   	free( *attrsp );
+	*attrsp = NULL;
+    }
+}
+
+static void
+set_timeval_from_double( struct timeval *tv, double d ) {
+	tv->tv_usec = (long) ( fmod(d, 1.0) * 1000000.0 );
+	tv->tv_sec = (long) floor(d);
+}
+
+/*------------------------------------------------------------
+ * methods
+ */
+
+/* ldap_unbind */
+
+static PyObject*
+l_ldap_unbind( LDAPObject* self, PyObject* args )
+{
+    if (!PyArg_ParseTuple( args, "")) return NULL;
+    if (not_valid(self)) return NULL;
+    if ( ldap_unbind( self->ldap ) == -1 )
+    	return LDAPerror( self->ldap, "ldap_unbind" );
+    self->valid = 0;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static char doc_unbind[] =
+"unbind_s() -> None\n"
+"unbind() -> int\n\n"
+"\tThis call is used to unbind from the directory, terminate\n"
+"\tthe current association, and free resources. Once called, the\n"
+"\tconnection to the LDAP server is closed and the LDAP object\n"
+"\tis invalid. Further invocation of methods on the object will\n"
+"\tyield an exception.\n"
+"\n"
+"\tThe unbind and unbind_s methods are identical, and are\n"
+"\tsynchronous in nature";
+
+/* ldap_abandon */
+
+static PyObject*
+l_ldap_abandon( LDAPObject* self, PyObject* args )
+{
+    int msgid;
+
+    if (!PyArg_ParseTuple( args, "i", &msgid)) return NULL;
+    if (not_valid(self)) return NULL;
+    if ( ldap_abandon( self->ldap, msgid ) == -1 )
+    	return LDAPerror( self->ldap, "ldap_abandon" );
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static char doc_abandon[] =
+"abandon(msgid) -> None\n\n"
+"\tAbandons or cancels an LDAP operation in progress. The msgid should\n"
+"\tbe the message id of an outstanding LDAP operation as returned\n"
+"\tby the asynchronous methods search(), modify() etc.  The caller\n"
+"\tcan expect that the result of an abandoned operation will not be\n"
+"\treturned from a future call to result().";
+
+/* ldap_add */
+
+static PyObject *
+l_ldap_add( LDAPObject* self, PyObject *args )
+{
+    char *dn;
+    PyObject *modlist;
+    int msgid;
+    LDAPMod **mods;
+
+    if (!PyArg_ParseTuple( args, "sO", &dn, &modlist )) return NULL;
+    if (not_valid(self)) return NULL;
+
+    mods = List_to_LDAPMods( modlist, 1 );
+    if (mods==NULL) return NULL;
+
+    msgid = ldap_add( self->ldap, dn, mods );
+    free_LDAPMods( mods );
+
+    if (msgid == -1)
+    	return LDAPerror( self->ldap, "ldap_add_s" );
+
+    return PyInt_FromLong(msgid);
+}
+
+static char doc_add[] =
+"add(dn, modlist) -> int\n\n"
+"\tThis function is similar to modify(), except that no operation\n"
+"\tinteger need be included in the tuples.";
+
+/* ldap_add_s */
+
+static PyObject *
+l_ldap_add_s( LDAPObject* self, PyObject *args )
+{
+    char *dn;
+    PyObject *modlist;
+    int ret;
+    LDAPMod **mods;
+
+    if (!PyArg_ParseTuple( args, "sO", &dn, &modlist )) return NULL;
+    if (not_valid(self)) return NULL;
+
+    mods = List_to_LDAPMods( modlist, 1 );
+    if (mods==NULL) return NULL;
+
+    LDAP_BEGIN_ALLOW_THREADS( self );
+    ret = ldap_add_s( self->ldap, dn, mods );
+    LDAP_END_ALLOW_THREADS( self );
+
+    free_LDAPMods( mods );
+
+    if (ret != LDAP_SUCCESS) 
+    	return LDAPerror( self->ldap, "ldap_add_s" );
+
+    Py_INCREF( Py_None );
+    return Py_None;
+}
+
+static char doc_bind[] =
+"bind(who, cred, method) -> int\n"
+"bind_s(who, cred, method) -> None\n"
+"simple_bind(who, passwd) -> int\n"
+"simple_bind_s(who, passwd) -> None\n"
+#ifdef WITH_KERBEROS
+"kerberos_bind_s(who) -> None\n"
+"kerberos_bind1(who) -> None\n"
+"kerberos_bind1_s(who) -> None\n"
+"kerberos_bind2(who) -> None\n"
+"kerberos_bind2_s(who) -> None\n\n"
+#endif
+"\tAfter an LDAP object is created, and before any other operations\n"
+"\tcan be attempted over the connection, a bind operation must\n"
+"\tbe performed.\n"
+"\n"
+"\tThis method attempts to bind with the LDAP server using either\n"
+"\tsimple authentication, or kerberos. The general method bind()\n"
+"\ttakes a third parameter, method which can be one of AUTH_SIMPLE,\n"
+"\tAUTH_KRBV41 or AUTH_KRBV42.  The cred parameter is ignored\n"
+"\tfor Kerberos authentication.\n"
+"\n"
+"\tKerberos authentication is only available if the LDAP library\n"
+"\tand the ldap module were both configured with --with-kerberos or\n"
+"\tcompiled with -DWITH_KERBEROS.";
+
+/* ldap_bind */
+
+static PyObject*
+l_ldap_bind( LDAPObject* self, PyObject* args )
+{
+    char *who, *cred;
+    int method;
+    int msgid;
+
+    if (!PyArg_ParseTuple( args, "ssi", &who, &cred, &method)) return NULL;
+    if (not_valid(self)) return NULL;
+    msgid = ldap_bind( self->ldap, who, cred, method );
+    if (msgid == -1)
+    	return LDAPerror( self->ldap, "ldap_bind" );
+    return PyInt_FromLong( msgid );
+}
+
+/* ldap_bind_s */
+
+static PyObject*
+l_ldap_bind_s( LDAPObject* self, PyObject* args )
+{
+    char *who, *cred;
+    int method;
+    int result;
+
+    if (!PyArg_ParseTuple( args, "ssi", &who, &cred, &method)) return NULL;
+    if (not_valid(self)) return NULL;
+
+    LDAP_BEGIN_ALLOW_THREADS( self );
+    result =  ldap_bind_s( self->ldap, who, cred, method );
+    LDAP_END_ALLOW_THREADS( self );
+
+    if ( result != LDAP_SUCCESS )
+    	return LDAPerror( self->ldap, "ldap_bind_s" );
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+#ifdef LDAP_REFERRALS
+
+/* ldap_set_rebind_proc */
+
+/* XXX - this could be called when threads are allowed!!! */
+
+  static PyObject* rebind_callback_func = NULL;
+  static LDAPObject* rebind_callback_ld = NULL;
+
+  static int rebind_callback( LDAP*ld, char**dnp, char**credp, 
+  			      int*methp, int freeit
+#ifdef LDAP_SET_REBIND_PROC_3ARGS
+			      , void *unused			/* Ugh */
+#endif
+			      )
+  {
+      PyObject *result;
+      PyObject *args;
+      int was_saved;
+
+      char *dn, *cred;
+      int  meth;
+
+      if (freeit)  {
+      	if (*dnp) free(*dnp);
+	if (*credp) free(*credp);
+	return LDAP_SUCCESS;
+      }
+
+      /* paranoia? */
+      if (rebind_callback_ld == NULL)
+      	Py_FatalError("rebind_callback: rebind_callback_ld == NULL");
+      if (rebind_callback_ld->ldap != ld) 
+      	Py_FatalError("rebind_callback: rebind_callback_ld->ldap != ld");
+      if (not_valid(rebind_callback_ld)) 
+      	Py_FatalError("rebind_callback: ldap connection closed");
+
+      was_saved = (rebind_callback_ld->_save != NULL);
+
+      if (was_saved)
+	  LDAP_END_ALLOW_THREADS( rebind_callback_ld );
+
+      args = Py_BuildValue("(O)", rebind_callback_ld);
+      result = PyEval_CallObject( rebind_callback_func, args );
+      Py_DECREF(args);
+
+      if (result != NULL && 
+          !PyArg_ParseTuple( result, "ssi", &dn, &cred, &meth ) )
+      {
+	  Py_DECREF( result );
+          result = NULL;
+      }
+
+      if (result == NULL) {
+	PyErr_Print();
+	if (was_saved) 
+	    LDAP_BEGIN_ALLOW_THREADS( rebind_callback_ld );
+      	return !LDAP_SUCCESS;
+      }
+
+      Py_DECREF(result);
+      *dnp = strdup(dn);
+      if (!*dnp) return LDAP_NO_MEMORY;
+      *credp = strdup(cred);
+      if (!*credp) return LDAP_NO_MEMORY;
+      *methp = meth;
+
+      if (was_saved) 
+      	  LDAP_BEGIN_ALLOW_THREADS( rebind_callback_ld );
+
+      return LDAP_SUCCESS;
+  }
+
+static PyObject*
+l_ldap_set_rebind_proc( LDAPObject* self, PyObject* args )
+{
+    PyObject *func;
+
+    if (!PyArg_ParseTuple( args, "O", &func)) return NULL;
+    if (not_valid(self)) return NULL;
+
+    if ( PyNone_Check(func) ) {
+#ifdef LDAP_SET_REBIND_PROC_3ARGS
+    	ldap_set_rebind_proc( self->ldap, NULL, 0);
+#else
+    	ldap_set_rebind_proc( self->ldap, NULL );
+#endif
+	rebind_callback_func = NULL;
+	rebind_callback_ld = NULL;
+	Py_INCREF(Py_None);
+	return Py_None;
+    }
+
+    if ( PyFunction_Check(func) ) {
+	rebind_callback_func = func;
+	rebind_callback_ld = self;
+#ifdef LDAP_SET_REBIND_PROC_3ARGS
+        ldap_set_rebind_proc( self->ldap, rebind_callback, 0);
+#else
+        ldap_set_rebind_proc( self->ldap, rebind_callback);
+#endif
+	Py_INCREF(Py_None);
+	return Py_None;
+    }
+
+    PyErr_SetString( PyExc_TypeError, "expected function or None" );
+    return NULL;
+}
+
+static char doc_set_rebind_proc[] =
+"set_rebind_proc(func) -> None\n\n"
+"\tIf a referral is returned from the server, automatic re-binding\n"
+"\tcan be achieved by providing a function that accepts as an\n"
+"\targument the newly opened LDAP object and returns the tuple\n"
+"\t(who, cred, method).\n"
+"\n"
+"\tPassing a value of None for func will disable this facility.\n"
+"\n"
+"\tBecause of restrictions in the implementation, only one\n"
+"\trebinding function is supported at any one time. This method\n"
+"\tis only available if the module and library were compiled with\n"
+"\tsupport for it.";
+
+
+#endif /* LDAP_REFERRALS */
+
+/* ldap_simple_bind */
+
+static PyObject*
+l_ldap_simple_bind( LDAPObject *self, PyObject *args ) 
+{
+    char *who, *cred;
+    int msgid;
+
+    if (!PyArg_ParseTuple( args, "zz", &who, &cred )) return NULL;
+    if (not_valid(self)) return NULL;
+    msgid = ldap_simple_bind( self->ldap, who, cred );
+    if ( msgid == -1 )
+    	return LDAPerror( self->ldap, "ldap_simple_bind" );
+    return PyInt_FromLong( msgid );
+}
+
+/* ldap_simple_bind_s */
+
+static PyObject*
+l_ldap_simple_bind_s( LDAPObject *self, PyObject *args ) 
+{
+    char *who, *cred;
+    int result;
+
+    if (!PyArg_ParseTuple( args, "zz", &who, &cred )) return NULL;
+    if (not_valid(self)) return NULL;
+    LDAP_BEGIN_ALLOW_THREADS( self );
+    result = ldap_simple_bind_s( self->ldap, who, cred );
+    LDAP_END_ALLOW_THREADS( self );
+    if ( result != LDAP_SUCCESS )
+    	return LDAPerror( self->ldap, "ldap_simple_bind_s" );
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+#ifdef WITH_KERBEROS
+
+/* ldap_kerberos_bind_s */
+
+static PyObject*
+l_ldap_kerberos_bind_s( LDAPObject *self, PyObject *args ) 
+{
+    char *who;
+    int result;
+
+    if (!PyArg_ParseTuple( args, "s", &who )) return NULL;
+    if (not_valid(self)) return NULL;
+    LDAP_BEGIN_ALLOW_THREADS( self );
+    result = ldap_kerberos_bind_s( self->ldap, who );
+    LDAP_END_ALLOW_THREADS( self );
+    if ( result != LDAP_SUCCESS )
+    	return LDAPerror( self->ldap, "ldap_kerberos_bind_s" );
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/* ldap_kerberos_bind1 */
+
+static PyObject*
+l_ldap_kerberos_bind1( LDAPObject *self, PyObject *args ) 
+{
+    char *who;
+
+    if (!PyArg_ParseTuple( args, "s", &who )) return NULL;
+    if (not_valid(self)) return NULL;
+    if ( ldap_kerberos_bind1( self->ldap, who ) == -1 )
+    	return LDAPerror( self->ldap, "ldap_kerberos_bind1" );
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/* ldap_kerberos_bind1_s */
+
+static PyObject*
+l_ldap_kerberos_bind1_s( LDAPObject *self, PyObject *args ) 
+{
+    char *who;
+    int result;
+
+    if (!PyArg_ParseTuple( args, "s", &who )) return NULL;
+    if (not_valid(self)) return NULL;
+    LDAP_BEGIN_ALLOW_THREADS( self );
+    result = ldap_kerberos_bind1_s( self->ldap, who );
+    LDAP_END_ALLOW_THREADS( self );
+    if ( result != LDAP_SUCCESS )
+    	return LDAPerror( self->ldap, "ldap_kerberos_bind1_s" );
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/* ldap_kerberos_bind2 */
+
+static PyObject*
+l_ldap_kerberos_bind2( LDAPObject *self, PyObject *args ) 
+{
+    char *who;
+
+    if (!PyArg_ParseTuple( args, "s", &who )) return NULL;
+    if (not_valid(self)) return NULL;
+    if ( ldap_kerberos_bind2( self->ldap, who ) == -1 )
+    	return LDAPerror( self->ldap, "ldap_kerberos_bind2" );
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/* ldap_kerberos_bind2_s */
+
+static PyObject*
+l_ldap_kerberos_bind2_s( LDAPObject *self, PyObject *args ) 
+{
+    char *who;
+    int result;
+
+    if (!PyArg_ParseTuple( args, "s", &who )) return NULL;
+    if (not_valid(self)) return NULL;
+    LDAP_BEGIN_ALLOW_THREADS( self );
+    result = ldap_kerberos_bind2_s( self->ldap, who );
+    LDAP_END_ALLOW_THREADS( self );
+    if ( result != LDAP_SUCCESS )
+    	return LDAPerror( self->ldap, "ldap_kerberos_bind2_s" );
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+#endif /* WITH_KERBEROS */
+
+#ifndef NO_CACHE
+
+/* ldap_enable_cache */
+
+static PyObject*
+l_ldap_enable_cache( LDAPObject* self, PyObject* args )
+{
+    long timeout = LDAP_NO_LIMIT;
+    long maxmem  = LDAP_NO_LIMIT;
+
+    if (!PyArg_ParseTuple( args, "|ll", &timeout, &maxmem )) return NULL;
+    if (not_valid(self)) return NULL;
+    if ( ldap_enable_cache( self->ldap, timeout, maxmem ) == -1 )
+    	return LDAPerror( self->ldap, "ldap_enable_cache" );
+    Py_INCREF( Py_None );
+    return Py_None;
+}
+
+static char doc_enable_cache[] =
+"enable_cache([timeout=NO_LIMIT, [maxmem=NO_LIMIT]]) -> None\n\n"
+"\tUsing a cache often greatly improves performance. By default\n"
+"\tthe cache is disabled. Specifying timeout in seconds is used\n"
+"\tto decide how long to keep cached requests. The maxmem value\n"
+"\tis in bytes, and is used to set an upper bound on how much\n"
+"\tmemory the cache will use. A value of NO_LIMIT for either\n"
+"\tindicates unlimited.  Subsequent calls to enable_cache()\n"
+"\tcan be used to adjust these parameters.\n"
+"\n"
+"\tThis and other caching methods are not available if the library\n"
+"\tand the ldap module were compiled with support for it.";
+
+/* ldap_disable_cache */
+
+static PyObject *
+l_ldap_disable_cache( LDAPObject* self, PyObject *args )
+{
+    if (!PyArg_ParseTuple( args, "" )) return NULL;
+    if (not_valid(self)) return NULL;
+    ldap_disable_cache( self->ldap );
+    Py_INCREF( Py_None );
+    return Py_None;
+}
+
+static char doc_disable_cache[] =
+"disable_cache() -> None\n\n"
+"\tTemporarily disables use of the cache. New requests are\n"
+"\tnot cached, and the cache is not checked when returning\n"
+"\tresults. Cache contents are not deleted.";
+
+/* ldap_set_cache_options */
+
+static PyObject *
+l_ldap_set_cache_options( LDAPObject* self, PyObject *args )
+{
+    long opts;
+
+    if (!PyArg_ParseTuple( args, "l", &opts )) return NULL;
+    if (not_valid(self)) return NULL;
+    ldap_set_cache_options( self->ldap, opts );
+    Py_INCREF( Py_None );
+    return Py_None;
+}
+
+static char doc_set_cache_options[] =
+"set_cache_options(option) -> None\n\n"
+"\tChanges the caching behaviour. Currently supported options are\n"
+"\t    CACHE_OPT_CACHENOERRS, which suppresses caching of requests\n"
+"\t        that resulted in an error, and\n"
+"\t    CACHE_OPT_CACHEALLERRS, which enables caching of all requests.\n"
+"\tThe default behaviour is not to cache requests that result in\n"
+"\terrors, except those that result in a SIZELIMIT_EXCEEDED exception.";
+
+/* ldap_destroy_cache */
+
+static PyObject *
+l_ldap_destroy_cache( LDAPObject* self, PyObject *args )
+{
+    if (!PyArg_ParseTuple( args, "" )) return NULL;
+    if (not_valid(self)) return NULL;
+    ldap_destroy_cache( self->ldap );
+    Py_INCREF( Py_None );
+    return Py_None;
+}
+
+static char doc_destroy_cache[] =
+"destroy_cache() -> None\n\n"
+"\tTurns off caching and removed it from memory.";
+
+/* ldap_flush_cache */
+
+static PyObject *
+l_ldap_flush_cache( LDAPObject* self, PyObject *args )
+{
+    if (!PyArg_ParseTuple( args, "" )) return NULL;
+    if (not_valid(self)) return NULL;
+    ldap_flush_cache( self->ldap );
+    Py_INCREF( Py_None );
+    return Py_None;
+}
+
+static char doc_flush_cache[] =
+"flush_cache() -> None\n\n"
+"\tDeletes the cache's contents, but does not affect it in any other way.";
+
+/* ldap_uncache_entry */
+
+static PyObject *
+l_ldap_uncache_entry( LDAPObject* self, PyObject *args )
+{
+    char *dn;
+
+    if (!PyArg_ParseTuple( args, "s", &dn )) return NULL;
+    if (not_valid(self)) return NULL;
+    ldap_uncache_entry( self->ldap, dn );
+    Py_INCREF( Py_None );
+    return Py_None;
+}
+
+static char doc_uncache_entry[] =
+"uncache_entry(dn) -> None\n\n"
+"\tRemoves all cached entries that make reference to dn. This should be\n"
+"\tused, for example, after doing a modify() involving dn.";
+
+/* ldap_uncache_request */
+
+static PyObject *
+l_ldap_uncache_request( LDAPObject* self, PyObject *args )
+{
+    int msgid;
+
+    if (!PyArg_ParseTuple( args, "i", &msgid )) return NULL;
+    if (not_valid(self)) return NULL;
+    ldap_uncache_request( self->ldap, msgid );
+    Py_INCREF( Py_None );
+    return Py_None;
+}
+
+static char doc_uncache_request[] =