the LDAP database at \fIhostlist\fR. \fIhostlist\fR may contain elements
of the format \fBhost:port\fR if a port other than the default LDAP port
of 389 is required. The LDAP library will attempt to connect to each
-host in turn until it succeeds.
+host in turn until it succeeds or exhausts the list.
.PP
The \fBexplode\fR form provides a means (via ldap_explode(3)) to explode a DN
into its component parts. \fB-nonames\fR strips off the attribute names,
For the purposes of this example, we're going to assume that "foo" is the
command created by opening a connection using "ldap open".
-Note: Karl is particularly dissatisfied with the syntax of this option,
-so it is one of the most likely things to change in a subsequent release.
-
.SH BINDING
After a connection is made to an LDAP server, an LDAP bind operation must
Both simple authentication and kerberos authentication are available.
LDAP version 3 supports many new "SSL"-style authentication and encryption
-systems, which are not currently supported by the UMich server, and hence
-by this interface package.
+systems, which are not currently supported by the OpenLDAP v1.2 server, and
+hence by this interface package.
-Currently simple authentication, and kerberos-based authentication, are
-supported.
+Currently simple and kerberos-based authentication, are supported.
To use LDAP and still have reasonable security in a networked,
Internet/Intranet environment, secure shell can be used to setup
"attributePairList" is a list of key-value pairs, the same as would
be returned by "array get" if an array had been set up containing the
-key-value pairs. Note that, right now, the sort of lowest-level pair
-of the DN must also appear in the attributePairList, as in:
+key-value pairs.
foo add "cn=karl, ou=People, o=NeoSoft Inc, c=US" {cn karl ...}
+Some directory servers and/or their client SDKs will automatically
+add the leaf attribute value for you.
+
Here is a more precise description of how an attributePairList looks:
{cn {karl {Karl Lehenbauer}} telephone 713-968-5800}
Note here that two cn values, "karl" and "Karl Lehenbauer", are added.
-A command error is to write
+Is it an error to write:
{cn {Karl Lehenbauer}}
We have noticed that the Netscape server will automatically add the
left-most rdn portion of the DN (ie. cn=karl), whereas the University
-of Michigan version does not.
+of Michigan and OpenLDAP 1.2 versions do not.
.SH ADDING, DELETING, AND REPLACING OBJECT ATTRIBUTES
-You can have multiple occurrences of the same attribute in a record.
+You can have multiple values for a given attribute in an LDAP object.
These are represented in search results, through the Tcl interface,
as a list.
This adds key-value pairs to an existing DN. If an attribute being
added already exists, the new value will be appended to the list.
+If a particular value being added to an attribute already exists in
+the object a Tcl error is raised.
foo replace_attributes dn attributePairList
-This replaces specified key-value pairs in an existing DN, leaving
-unnamed ones untouched.
+This replaces the specified attributes in an existing DN, leaving
+unnamed ones untouched. Any previous values for the supplied attributes
+(if any) are discarded.
foo delete_attributes dn attributePairList
-This deletes attributes in the list. If a pair is "foo {bar snap}" and
-you delete "foo bar", "foo" will still have "snap".
+This deletes attributes in the list. If an attribute "foo" has the
+value list {bar snap}, and you delete using the attributePairList "foo bar",
+"foo" will still have "snap".
-If you provide an empty string ("") for the value part of the key-value
-pair, the entire attribute will be deleted. To reiterate, if you provide
-a non-empty string for the value part, only that value will be removed
-from the value list.
+If you provide an empty string ("") for the value list,
+the entire attribute will be deleted.
.SH SEARCHING
.SH CACHING (Note: Netscape clients do not have caching interfaces).
-The UMich LDAP library offers the client application fairly fine-
-grained control of caching of results retrieved from searches,
+The UMich and OpenLDAP client libraries offers the client application fairly
+fine-grained control of caching of results retrieved from searches,
offering significant performance improvement and reduced
network traffic.
This should be used, for example, after doing an add_attributes,
delete_attributes, or replace_attributes (ldap_modify(3))
- involving the requested DN.
+ involving the requested DN. Generally this should not be needed,
+ as the Tcl interface automatically performs this operation on
+ any dn that is modified (add,replace,delete) while caching is
+ enabled.
foo cache no_errors
with Tcl 8.0 or above.
This package interfaces with the University of Michigan LDAP protocol
-package, version 3.3, an implementation of version 2 of the LDAP protocol.
+package, version 3.3, and OpenLDAP version 1.2, both of which are
+implementations of version 2 of the LDAP protocol.
Although an LDAP client (or server) could be written in native Tcl 8.0,
as Tcl 8.0 and above can do binary I/O, and Tcl 8 and above have strings
.SH AUTHORS
It was written by Karl Lehenbauer, of NeoSoft, Inc., in August and
-September of 1997. Ldap explode, and numerous bug fixes by Randy
-Kunkee, also of NeoSoft, Inc., in 1998.
+September of 1997. Ldap explode, and numerous bug fixes and extensions
+by Randy Kunkee, also of NeoSoft, Inc., in 1998-1999.
.SH KEYWORDS
element, join, list, separator
int caching; /* flag 1/0 if caching is enabled */
long timeout; /* timeout from last cache enable */
long maxmem; /* maxmem from last cache enable */
+ Tcl_Obj *trapCmdObj; /* error handler */
+ int *traplist; /* list of errorCodes to trap */
int flags;
} LDAPTCL;
static
LDAP_SetErrorCode(LDAPTCL *ldaptcl, int code, Tcl_Interp *interp)
{
- char shortbuf[6];
+ char shortbuf[16];
char *errp;
int lderrno;
errp = ldaptclerrorcode[code];
Tcl_SetErrorCode(interp, errp, NULL);
+ if (ldaptcl->trapCmdObj) {
+ int *i;
+ Tcl_Obj *cmdObj;
+ if (ldaptcl->traplist != NULL) {
+ for (i = ldaptcl->traplist; *i && *i != code; i++)
+ ;
+ if (*i == 0) return;
+ }
+ (void) Tcl_EvalObj(interp, ldaptcl->trapCmdObj);
+ }
+}
+
+static
+LDAP_ErrorStringToCode(Tcl_Interp *interp, char *s)
+{
+ int offset;
+ int code;
+
+ offset = (strncasecmp(s, "LDAP_", 5) == 0) ? 0 : 5;
+ for (code = 0; code < LDAPTCL_MAXERR; code++) {
+ if (!ldaptclerrorcode[code]) continue;
+ if (strcasecmp(s, ldaptclerrorcode[code]+offset) == 0)
+ return code;
+ }
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, s, " is an invalid code", (char *) NULL);
+ return -1;
}
/*-----------------------------------------------------------------------------
return TCL_ERROR;
ldap_memfree(dn);
}
+ attributeNameObj = Tcl_NewObj();
+ Tcl_IncrRefCount (attributeNameObj);
for (attributeName = ldap_first_attribute (ldap, entry, &ber);
attributeName != NULL;
attributeName = ldap_next_attribute(ldap, entry, ber)) {
as an error, we ignore it to present a consistent interface
with Netscape's server
*/
- attributeNameObj = Tcl_NewStringObj (attributeName, -1);
- Tcl_IncrRefCount (attributeNameObj);
attributeDataObj = Tcl_NewObj();
+ Tcl_SetStringObj(attributeNameObj, attributeName, -1);
for (i = 0; bvals[i] != NULL; i++) {
Tcl_Obj *singleAttributeValueObj;
- singleAttributeValueObj = Tcl_NewStringObj (bvals[i]->bv_val, -1);
+ singleAttributeValueObj = Tcl_NewStringObj(bvals[i]->bv_val, bvals[i]->bv_len);
if (Tcl_ListObjAppendElement (interp,
attributeDataObj,
singleAttributeValueObj)
TCL_LEAVE_ERR_MSG) == NULL) {
return TCL_ERROR;
}
- Tcl_DecrRefCount (attributeNameObj);
}
}
+ Tcl_DecrRefCount (attributeNameObj);
return Tcl_EvalObj (interp, evalCodeObj);
}
int abandon;
int tclResult = TCL_OK;
int msgid;
- LDAPMessage *resultMessage;
- LDAPMessage *entryMessage;
+ LDAPMessage *resultMessage = 0;
+ LDAPMessage *entryMessage = 0;
char *sortKey;
Tcl_Obj *resultObj;
}
if (resultCode == LDAP_RES_SEARCH_RESULT || all)
break;
+ if (resultMessage)
ldap_msgfree(resultMessage);
+ resultMessage = NULL;
}
if (abandon) {
- ldap_msgfree(resultMessage);
+ if (resultMessage)
+ ldap_msgfree(resultMessage);
if (resultCode == LDAP_RES_SEARCH_ENTRY)
ldap_abandon(ldap, msgid);
return tclResult;
"LDAP search error: ",
ldap_err2string(errorCode),
(char *)NULL);
- ldap_msgfree(resultMessage);
+ if (resultMessage)
+ ldap_msgfree(resultMessage);
LDAP_SetErrorCode(ldaptcl, errorCode, interp);
return TCL_ERROR;
}
+ if (resultMessage)
+ ldap_msgfree(resultMessage);
return tclResult;
}
nPairs = attribObjc / 2;
- modArray = (LDAPMod **)ckalloc (sizeof(LDAPMod *) * (nPairs + 1));
+ modArray = (LDAPMod **)malloc (sizeof(LDAPMod *) * (nPairs + 1));
modArray[nPairs] = (LDAPMod *) NULL;
for (i = 0; i < nPairs; i++) {
- mod = modArray[i] = (LDAPMod *) ckalloc (sizeof(LDAPMod));
+ mod = modArray[i] = (LDAPMod *) malloc (sizeof(LDAPMod));
mod->mod_op = mod_op;
mod->mod_type = Tcl_GetStringFromObj (attribObjv [i * 2], NULL);
}
valPtrs = mod->mod_vals.modv_strvals = \
- (char **)ckalloc (sizeof (char *) * (valuesObjc + 1));
+ (char **)malloc (sizeof (char *) * (valuesObjc + 1));
valPtrs[valuesObjc] = (char *)NULL;
for (j = 0; j < valuesObjc; j++) {
* value be NULL to indicate entire attribute is to be
* deleted */
if ((*valPtrs [j] == '\0')
- && (mod->mod_op == LDAP_MOD_DELETE)) {
+ && (mod->mod_op == LDAP_MOD_DELETE || mod->mod_op == LDAP_MOD_REPLACE)) {
valPtrs [j] = NULL;
}
}
/* free the modArray elements, then the modArray itself. */
for (i = 0; i < nPairs; i++) {
- ckfree ((char *) modArray[i]->mod_vals.modv_strvals);
- ckfree ((char *) modArray[i]);
+ free ((char *) modArray[i]->mod_vals.modv_strvals);
+ free ((char *) modArray[i]);
}
- ckfree ((char *) modArray);
+ free ((char *) modArray);
/* FIX: memory cleanup required all over the place here */
if (result != LDAP_SUCCESS) {
/* Caching control within the search: if the "cache" control array */
/* value is set, disable/enable caching accordingly */
+#if 0
if (cacheThis >= 0 && ldaptcl->caching != cacheThis) {
if (cacheThis) {
if (ldaptcl->timeout == 0) {
else
ldap_disable_cache(ldap);
}
+#endif
tclResult = LDAP_PerformSearch (interp,
ldaptcl,
baseString,
sortattr);
/* Following the search, if we changed the caching behavior, change */
/* it back. */
+#if 0
if (cacheThis >= 0 && ldaptcl->caching != cacheThis) {
if (cacheThis)
ldap_disable_cache(ldap);
else
ldap_enable_cache(ldap, ldaptcl->timeout, ldaptcl->maxmem);
}
+#endif
return tclResult;
}
return TCL_ERROR;
}
#endif
+ if (STREQU (subCommand, "trap")) {
+ Tcl_Obj *listObj, *resultObj;
+ int *p, l, i, code;
+
+ if (objc > 4)
+ return TclX_WrongArgs (interp, objv [0],
+ "trap command ?errorCode-list?");
+ if (objc == 2) {
+ if (!ldaptcl->trapCmdObj)
+ return TCL_OK;
+ resultObj = Tcl_NewListObj(0, NULL);
+ Tcl_ListObjAppendElement(interp, resultObj, ldaptcl->trapCmdObj);
+ if (ldaptcl->traplist) {
+ listObj = Tcl_NewObj();
+ for (p = ldaptcl->traplist; *p; p++) {
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewStringObj(ldaptclerrorcode[*p], -1));
+ }
+ Tcl_ListObjAppendElement(interp, resultObj, listObj);
+ }
+ Tcl_SetObjResult(interp, resultObj);
+ return TCL_OK;
+ }
+ if (ldaptcl->trapCmdObj) {
+ Tcl_DecrRefCount (ldaptcl->trapCmdObj);
+ ldaptcl->trapCmdObj = NULL;
+ }
+ if (ldaptcl->traplist) {
+ free(ldaptcl->traplist);
+ ldaptcl->traplist = NULL;
+ }
+ Tcl_GetStringFromObj(objv[2], &l);
+ if (l == 0)
+ return TCL_OK; /* just turn off trap */
+ ldaptcl->trapCmdObj = objv[2];
+ Tcl_IncrRefCount (ldaptcl->trapCmdObj);
+ if (objc < 4)
+ return TCL_OK; /* no code list */
+ if (Tcl_ListObjLength(interp, objv[3], &l) != TCL_OK)
+ return TCL_ERROR;
+ if (l == 0)
+ return TCL_OK; /* empty code list */
+ ldaptcl->traplist = malloc(sizeof(int) * (l + 1));
+ ldaptcl->traplist[l] = 0;
+ for (i = 0; i < l; i++) {
+ Tcl_ListObjIndex(interp, objv[3], i, &resultObj);
+ code = LDAP_ErrorStringToCode(interp, Tcl_GetStringFromObj(resultObj));
+ if (code == -1) {
+ free(ldaptcl->traplist);
+ ldaptcl->traplist = NULL;
+ return TCL_ERROR;
+ }
+ ldaptcl->traplist[i] = code;
+ }
+ return TCL_OK;
+ }
+ if (STREQU (subCommand, "trapcodes")) {
+ int code;
+ Tcl_Obj *resultObj;
+ Tcl_Obj *stringObj;
+ resultObj = Tcl_GetObjResult(interp);
+
+ for (code = 0; code < LDAPTCL_MAXERR; code++) {
+ if (!ldaptclerrorcode[code]) continue;
+ Tcl_ListObjAppendElement(interp, resultObj,
+ Tcl_NewStringObj(ldaptclerrorcode[code], -1));
+ }
+ return TCL_OK;
+ }
#ifdef LDAP_DEBUG
if (STREQU (subCommand, "debug")) {
if (objc != 3) {
LDAPTCL *ldaptcl = (LDAPTCL *)clientData;
LDAP *ldap = ldaptcl->ldap;
+ if (ldaptcl->trapCmdObj)
+ Tcl_DecrRefCount (ldaptcl->trapCmdObj);
+ if (ldaptcl->traplist)
+ free(ldaptcl->traplist);
ldap_unbind(ldap);
- ckfree((char*) ldaptcl);
+ free((char*) ldaptcl);
}
/*-----------------------------------------------------------------------------
ldap->ld_deref = LDAP_DEREF_NEVER; /* Turn off alias dereferencing */
#endif
- ldaptcl = (LDAPTCL *) ckalloc(sizeof(LDAPTCL));
+ ldaptcl = (LDAPTCL *) malloc(sizeof(LDAPTCL));
ldaptcl->ldap = ldap;
ldaptcl->caching = 0;
ldaptcl->timeout = 0;
ldaptcl->maxmem = 0;
+ ldaptcl->trapCmdObj = NULL;
+ ldaptcl->traplist = NULL;
ldaptcl->flags = 0;
Tcl_CreateObjCommand (interp,