Logo Search packages:      
Sourcecode: openldap version File versions

neoXldap.c

/*
 * NeoSoft Tcl client extensions to Lightweight Directory Access Protocol.
 * 
 * Copyright (c) 1998-1999 NeoSoft, Inc.  
 * All Rights Reserved.
 * 
 * This software may be used, modified, copied, distributed, and sold,
 * in both source and binary form provided that these copyrights are
 * retained and their terms are followed.
 * 
 * Under no circumstances are the authors or NeoSoft Inc. responsible
 * for the proper functioning of this software, nor do the authors
 * assume any liability for damages incurred with its use.
 * 
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to NeoSoft, Inc.
 * 
 * NeoSoft, Inc. may not be used to endorse or promote products derived
 * from this software without specific prior written permission. This
 * software is provided ``as is'' without express or implied warranty.
 * 
 * Requests for permission may be sent to NeoSoft Inc, 1770 St. James Place,
 * Suite 500, Houston, TX, 77056.
 *
 * $OpenLDAP: pkg/ldap/contrib/ldaptcl/neoXldap.c,v 1.17.10.1 2007/08/31 23:13:51 quanah Exp $
 *
 */

/*
 * This code was originally developed by Karl Lehenbauer to work with
 * Umich-3.3 LDAP.  It was debugged against the Netscape LDAP server
 * and their much more reliable SDK, and again backported to the
 * Umich-3.3 client code.  The UMICH_LDAP define is used to include
 * code that will work with the Umich-3.3 LDAP, but not with Netscape's
 * SDK.  OpenLDAP may support some of these, but they have not been tested.
 * Currently supported by Randy Kunkee (kunkee@OpenLDAP.org).
 */

/*
 * Add timeout to controlArray to set timeout for ldap_result.
 * 4/14/99 - Randy
 */

#include "tclExtend.h"

#include <lber.h>
#include <ldap.h>
#include <string.h>
#include <sys/time.h>
#include <math.h>

/*
 * Macros to do string compares.  They pre-check the first character before
 * checking of the strings are equal.
 */

#define STREQU(str1, str2) \
      (((str1) [0] == (str2) [0]) && (strcmp (str1, str2) == 0))
#define STRNEQU(str1, str2, n) \
      (((str1) [0] == (str2) [0]) && (strncmp (str1, str2, n) == 0))

/*
 * The following section defines some common macros used by the rest
 * of the code.  It's ugly, and can use some work.  This code was
 * originally developed to work with Umich-3.3 LDAP.  It was debugged
 * against the Netscape LDAP server and the much more reliable SDK,
 * and then again backported to the Umich-3.3 client code.
 */
#define OPEN_LDAP 1
#if defined(OPEN_LDAP)
       /* LDAP_API_VERSION must be defined per the current draft spec
       ** it's value will be assigned RFC number.  However, as
       ** no RFC is defined, it's value is currently implementation
       ** specific (though I would hope it's value is greater than 1823).
       ** In OpenLDAP 2.x-devel, its 2000 + the draft number, ie 2002.
       ** This section is for OPENLDAP.
       */
#ifndef LDAP_API_FEATURE_X_OPENLDAP
#define ldap_memfree(p) free(p)
#endif
#ifdef LDAP_OPT_ERROR_NUMBER
#define ldap_get_lderrno(ld)  (ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &lderrno), lderrno)
#else
#define ldap_get_lderrno(ld) (ld->ld_errno)
#endif
#define LDAP_ERR_STRING(ld)  \
      ldap_err2string(ldap_get_lderrno(ld))
#elif defined( LDAP_OPT_SIZELIMIT )
       /*
       ** Netscape SDK w/ ldap_set_option, ldap_get_option
       */
#define LDAP_ERR_STRING(ld)  \
      ldap_err2string(ldap_get_lderrno(ldap))
#else
       /* U-Mich/OpenLDAP 1.x API */
       /* RFC-1823 w/ changes */
#define UMICH_LDAP 1
#define ldap_memfree(p) free(p)
#define ldap_ber_free(p, n) ber_free(p, n)
#define ldap_value_free_len(bvals) ber_bvecfree(bvals)
#define ldap_get_lderrno(ld) (ld->ld_errno)
#define LDAP_ERR_STRING(ld)  \
      ldap_err2string(ld->ld_errno)
#endif

typedef struct ldaptclobj {
    LDAP    *ldap;
    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;


#define LDAPTCL_INTERRCODES   0x001

#include "ldaptclerr.h"

static
LDAP_SetErrorCode(LDAPTCL *ldaptcl, int code, Tcl_Interp *interp)
{
    char shortbuf[16];
    char *errp;
    int   lderrno;

    if (code == -1)
      code = ldap_get_lderrno(ldaptcl->ldap);
    if ((ldaptcl->flags & LDAPTCL_INTERRCODES) || code > LDAPTCL_MAXERR ||
      ldaptclerrorcode[code] == NULL) {
      sprintf(shortbuf, "0x%03x", code);
      errp = shortbuf;
    } else
      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;
}

/*-----------------------------------------------------------------------------
 * LDAP_ProcessOneSearchResult --
 * 
 *   Process one result return from an LDAP search.
 *
 * Paramaters:
 *   o interp -            Tcl interpreter; Errors are returned in result.
 *   o ldap -              LDAP structure pointer.
 *   o entry -             LDAP message pointer.
 *   o destArrayNameObj -  Name of Tcl array in which to store attributes.
 *   o evalCodeObj -       Tcl_Obj pointer to code to eval against this result.
 * Returns:
 *   o TCL_OK if processing succeeded..
 *   o TCL_ERROR if an error occured, with error message in interp.
 *-----------------------------------------------------------------------------
 */
int
LDAP_ProcessOneSearchResult (interp, ldap, entry, destArrayNameObj, evalCodeObj)
    Tcl_Interp     *interp;
    LDAP           *ldap;
    LDAPMessage    *entry;
    Tcl_Obj        *destArrayNameObj;
    Tcl_Obj        *evalCodeObj;
{
    char           *attributeName;
    Tcl_Obj        *attributeNameObj;
    Tcl_Obj        *attributeDataObj;
    int             i; 
    BerElement     *ber; 
    struct berval **bvals;
    char       *dn;
    int               lderrno;

    Tcl_UnsetVar (interp, Tcl_GetStringFromObj (destArrayNameObj, NULL), 0);

    dn = ldap_get_dn(ldap, entry);
    if (dn != NULL) {
      if (Tcl_SetVar2(interp,       /* set dn */
                   Tcl_GetStringFromObj(destArrayNameObj, NULL),
                   "dn",
                   dn,
                   TCL_LEAVE_ERR_MSG) == NULL)
          return TCL_ERROR;
      ldap_memfree(dn);
    }
    attributeNameObj = Tcl_NewObj();
    Tcl_IncrRefCount (attributeNameObj);

    /* Note that attributeName below is allocated for OL2+ libldap, so it
       must be freed with ldap_memfree().  Test below is admittedly a hack.
    */

    for (attributeName = ldap_first_attribute (ldap, entry, &ber); 
      attributeName != NULL;
      attributeName = ldap_next_attribute(ldap, entry, ber)) {

      bvals = ldap_get_values_len(ldap, entry, attributeName);

      if (bvals != NULL) {
          /* Note here that the U.of.M. ldap will return a null bvals
             when the last attribute value has been deleted, but still
             retains the attributeName.  Even though this is documented
             as an error, we ignore it to present a consistent interface
             with Netscape's server
          */
          attributeDataObj = Tcl_NewObj();
          Tcl_SetStringObj(attributeNameObj, attributeName, -1);
#if LDAP_API_VERSION >= 2004
          ldap_memfree(attributeName);    /* free if newer API */
#endif
          for (i = 0; bvals[i] != NULL; i++) {
            Tcl_Obj *singleAttributeValueObj;

            singleAttributeValueObj = Tcl_NewStringObj(bvals[i]->bv_val, bvals[i]->bv_len);
            if (Tcl_ListObjAppendElement (interp, 
                                    attributeDataObj, 
                                    singleAttributeValueObj) 
              == TCL_ERROR) {
                ber_free(ber, 0);
                return TCL_ERROR;
            }
          }

          ldap_value_free_len(bvals);

          if (Tcl_ObjSetVar2 (interp, 
                        destArrayNameObj,
                        attributeNameObj,
                        attributeDataObj,
                        TCL_LEAVE_ERR_MSG) == NULL) {
            return TCL_ERROR;
          }
      }
    }
    Tcl_DecrRefCount (attributeNameObj);
    return Tcl_EvalObj (interp, evalCodeObj);
}

/*-----------------------------------------------------------------------------
 * LDAP_PerformSearch --
 * 
 *   Perform an LDAP search.
 *
 * Paramaters:
 *   o interp -            Tcl interpreter; Errors are returned in result.
 *   o ldap -              LDAP structure pointer.
 *   o base -              Base DN from which to perform search.
 *   o scope -             LDAP search scope, must be one of LDAP_SCOPE_BASE,
 *                         LDAP_SCOPE_ONELEVEL, or LDAP_SCOPE_SUBTREE.
 *   o attrs -             Pointer to array of char * pointers of desired
 *                         attribute names, or NULL for all attributes.
 *   o filtpatt            LDAP filter pattern.
 *   o value               Value to get sprintf'ed into filter pattern.
 *   o destArrayNameObj -  Name of Tcl array in which to store attributes.
 *   o evalCodeObj -       Tcl_Obj pointer to code to eval against this result.
 * Returns:
 *   o TCL_OK if processing succeeded..
 *   o TCL_ERROR if an error occured, with error message in interp.
 *-----------------------------------------------------------------------------
 */
int 
LDAP_PerformSearch (interp, ldaptcl, base, scope, attrs, filtpatt, value,
      destArrayNameObj, evalCodeObj, timeout_p, all, sortattr)
    Tcl_Interp     *interp;
    LDAPTCL        *ldaptcl;
    char           *base;
    int             scope;
    char          **attrs;
    char           *filtpatt;
    char           *value;
    Tcl_Obj        *destArrayNameObj;
    Tcl_Obj        *evalCodeObj;
    struct timeval *timeout_p;
    int               all;
    char       *sortattr;
{
    LDAP     *ldap = ldaptcl->ldap;
    char          filter[BUFSIZ];
    int           resultCode;
    int           errorCode;
    int             abandon;
    int             tclResult = TCL_OK;
    int             msgid;
    LDAPMessage  *resultMessage = 0;
    LDAPMessage  *entryMessage = 0;
    char      *sortKey;

    int             lderrno;

    sprintf(filter, filtpatt, value);

    fflush(stderr);
    if ((msgid = ldap_search (ldap, base, scope, filter, attrs, 0)) == -1) {
      Tcl_AppendResult (interp,
                          "LDAP start search error: ",
                              LDAP_ERR_STRING(ldap),
                          (char *)NULL);
      LDAP_SetErrorCode(ldaptcl, -1, interp);
      return TCL_ERROR;
    }

    abandon = 0;
    if (sortattr)
      all = 1;
    tclResult = TCL_OK;
    while (!abandon) {
      resultCode = ldap_result (ldap, msgid, all, timeout_p, &resultMessage);
      if (resultCode != LDAP_RES_SEARCH_RESULT &&
          resultCode != LDAP_RES_SEARCH_ENTRY)
            break;

      if (sortattr) {
          sortKey = (strcasecmp(sortattr, "dn") == 0) ? NULL : sortattr;
          ldap_sort_entries(ldap, &resultMessage, sortKey, strcasecmp);
      }
      entryMessage = ldap_first_entry(ldap, resultMessage);

      while (entryMessage) {
          tclResult = LDAP_ProcessOneSearchResult  (interp, 
                            ldap, 
                            entryMessage,
                            destArrayNameObj,
                            evalCodeObj);
          if (tclResult != TCL_OK) {
            if (tclResult == TCL_CONTINUE) {
                tclResult = TCL_OK;
            } else if (tclResult == TCL_BREAK) {
                tclResult = TCL_OK;
                abandon = 1;
                break;
            } else if (tclResult == TCL_ERROR) {
                char msg[100];
                sprintf(msg, "\n    (\"search\" body line %d)",
                      interp->errorLine);
                Tcl_AddObjErrorInfo(interp, msg, -1);
                abandon = 1;
                break;
            } else {
                abandon = 1;
                break;
            }
          }
          entryMessage = ldap_next_entry(ldap, entryMessage);
      }
      if (resultCode == LDAP_RES_SEARCH_RESULT || all)
          break;
      if (resultMessage)
      ldap_msgfree(resultMessage);
      resultMessage = NULL;
    }
    if (abandon) {
      if (resultMessage)
          ldap_msgfree(resultMessage);
      if (resultCode == LDAP_RES_SEARCH_ENTRY)
          ldap_abandon(ldap, msgid);
      return tclResult;
    }
    if (resultCode == -1) {
      Tcl_ResetResult (interp);
      Tcl_AppendResult (interp,
                        "LDAP result search error: ",
                        LDAP_ERR_STRING(ldap),
                        (char *)NULL);
      LDAP_SetErrorCode(ldaptcl, -1, interp);
      return TCL_ERROR;
    }

    if ((errorCode = ldap_result2error (ldap, resultMessage, 0))
      != LDAP_SUCCESS) {
      Tcl_ResetResult (interp);
      Tcl_AppendResult (interp,
                        "LDAP search error: ",
                        ldap_err2string(errorCode),
                        (char *)NULL);
      if (resultMessage)
        ldap_msgfree(resultMessage);
      LDAP_SetErrorCode(ldaptcl, errorCode, interp);
      return TCL_ERROR;
    }
    if (resultMessage)
      ldap_msgfree(resultMessage);
    return tclResult;
}

/*-----------------------------------------------------------------------------
 * NeoX_LdapTargetObjCmd --
 *  
 * Implements the body of commands created by Neo_LdapObjCmd.
 *  
 * Results:
 *      A standard Tcl result.
 *      
 * Side effects:
 *      See the user documentation.
 *-----------------------------------------------------------------------------
 */     
int
NeoX_LdapTargetObjCmd (clientData, interp, objc, objv)
    ClientData    clientData;
    Tcl_Interp   *interp;
    int           objc;
    Tcl_Obj      *CONST objv[];
{
    char         *command;
    char         *subCommand;
    LDAPTCL      *ldaptcl = (LDAPTCL *)clientData;
    LDAP         *ldap = ldaptcl->ldap;
    char         *dn;
    int           is_add = 0;
    int           is_add_or_modify = 0;
    int           mod_op = 0;
    char     *m, *s, *errmsg;
    int            errcode;
    int            tclResult;
    int            lderrno;   /* might be used by LDAP_ERR_STRING macro */

    Tcl_Obj      *resultObj = Tcl_GetObjResult (interp);

    if (objc < 2) {
      Tcl_WrongNumArgs (interp, 1, objv, "subcommand [args...]");
      return TCL_ERROR;
    }

    command = Tcl_GetStringFromObj (objv[0], NULL);
    subCommand = Tcl_GetStringFromObj (objv[1], NULL);

    /* object bind authtype name password */
    if (STREQU (subCommand, "bind")) {
      char     *binddn;
      char     *passwd;
      int       stringLength;
      char     *ldap_authString;
      int       ldap_authInt;

      if (objc != 5) {
          Tcl_WrongNumArgs (interp, 2, objv, "authtype dn passwd");
          return TCL_ERROR;
      }

      ldap_authString = Tcl_GetStringFromObj (objv[2], NULL);

      if (STREQU (ldap_authString, "simple")) {
          ldap_authInt = LDAP_AUTH_SIMPLE;
      }
#ifdef UMICH_LDAP
      else if (STREQU (ldap_authString, "kerberos_ldap")) {
          ldap_authInt = LDAP_AUTH_KRBV41;
      } else if (STREQU (ldap_authString, "kerberos_dsa")) {
          ldap_authInt = LDAP_AUTH_KRBV42;
      } else if (STREQU (ldap_authString, "kerberos_both")) {
          ldap_authInt = LDAP_AUTH_KRBV4;
      }
#endif
      else {
          Tcl_AppendStringsToObj (resultObj,
                            "\"",
                            command,
                            " ",
                            subCommand, 
#ifdef UMICH_LDAP
                            "\" authtype must be one of \"simple\", ",
                            "\"kerberos_ldap\", \"kerberos_dsa\" ",
                            "or \"kerberos_both\"",
#else
                            "\" authtype must be \"simple\", ",
#endif
                            (char *)NULL);
          return TCL_ERROR;
      }

      binddn = Tcl_GetStringFromObj (objv[3], &stringLength);
      if (stringLength == 0)
          binddn = NULL;

      passwd = Tcl_GetStringFromObj (objv[4], &stringLength);
      if (stringLength == 0)
          passwd = NULL;

/*  ldap_bind_s(ldap, dn, pw, method) */

#ifdef UMICH_LDAP
#define LDAP_BIND(ldap, dn, pw, method) \
  ldap_bind_s(ldap, dn, pw, method)
#else
#define LDAP_BIND(ldap, dn, pw, method) \
  ldap_simple_bind_s(ldap, dn, pw)
#endif
      if ((errcode = LDAP_BIND (ldap, 
                   binddn, 
                   passwd, 
                   ldap_authInt)) != LDAP_SUCCESS) {

          Tcl_AppendStringsToObj (resultObj,
                              "LDAP bind error: ",
                            ldap_err2string(errcode),
                            (char *)NULL);
          LDAP_SetErrorCode(ldaptcl, errcode, interp);
          return TCL_ERROR;
      }
      return TCL_OK;
    }

    if (STREQU (subCommand, "unbind")) {
      if (objc != 2) {
          Tcl_WrongNumArgs (interp, 2, objv, "");
          return TCL_ERROR;
      }

       return Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], NULL));
    }

    /* object delete dn */
    if (STREQU (subCommand, "delete")) {
      if (objc != 3) {
          Tcl_WrongNumArgs (interp, 2, objv, "dn");
          return TCL_ERROR;
      }

       dn = Tcl_GetStringFromObj (objv [2], NULL);
       if ((errcode = ldap_delete_s(ldap, dn)) != LDAP_SUCCESS) {
         Tcl_AppendStringsToObj (resultObj,
                             "LDAP delete error: ",
                           ldap_err2string(errcode),
                           (char *)NULL);
         LDAP_SetErrorCode(ldaptcl, errcode, interp);
         return TCL_ERROR;
       }
       return TCL_OK;
    }

    /* object rename_rdn dn rdn */
    /* object modify_rdn dn rdn */
    if (STREQU (subCommand, "rename_rdn") || STREQU (subCommand, "modify_rdn")) {
      char    *rdn;
      int      deleteOldRdn;

      if (objc != 4) {
          Tcl_WrongNumArgs (interp, 2, objv, "dn rdn");
          return TCL_ERROR;
      }

      dn = Tcl_GetStringFromObj (objv [2], NULL);
      rdn = Tcl_GetStringFromObj (objv [3], NULL);

      deleteOldRdn = (*subCommand == 'r');

      if ((errcode = ldap_modrdn2_s (ldap, dn, rdn, deleteOldRdn)) != LDAP_SUCCESS) {
          Tcl_AppendStringsToObj (resultObj,
                            "LDAP ",
                            subCommand,
                            " error: ",
                            ldap_err2string(errcode),
                            (char *)NULL);
          LDAP_SetErrorCode(ldaptcl, errcode, interp);
          return TCL_ERROR;
      }
      return TCL_OK;
    }

    /* object add dn attributePairList */
    /* object add_attributes dn attributePairList */
    /* object replace_attributes dn attributePairList */
    /* object delete_attributes dn attributePairList */

    if (STREQU (subCommand, "add")) {
      is_add = 1;
      is_add_or_modify = 1;
    } else {
      is_add = 0;
      if (STREQU (subCommand, "add_attributes")) {
          is_add_or_modify = 1;
          mod_op = LDAP_MOD_ADD;
      } else if (STREQU (subCommand, "replace_attributes")) {
          is_add_or_modify = 1;
          mod_op = LDAP_MOD_REPLACE;
      } else if (STREQU (subCommand, "delete_attributes")) {
          is_add_or_modify = 1;
          mod_op = LDAP_MOD_DELETE;
      }
    }

    if (is_add_or_modify) {
      int          result;
      LDAPMod    **modArray;
      LDAPMod     *mod;
      char       **valPtrs = NULL;
      int          attribObjc;
      Tcl_Obj    **attribObjv;
      int          valuesObjc;
      Tcl_Obj    **valuesObjv;
      int          nPairs, allPairs;
      int          i;
      int          j;
      int        pairIndex;
      int        modIndex;

      Tcl_Obj      *resultObj = Tcl_GetObjResult (interp);

      if (objc < 4 || objc > 4 && is_add || is_add == 0 && objc&1) {
          Tcl_AppendStringsToObj (resultObj,
                            "wrong # args: ",
                            Tcl_GetStringFromObj (objv [0], NULL),
                            " ",
                            subCommand,
                            " dn attributePairList",
                            (char *)NULL);
          if (!is_add)
            Tcl_AppendStringsToObj (resultObj,
                " ?[add|delete|replace] attributePairList ...?", (char *)NULL);
          return TCL_ERROR;
      }

      dn = Tcl_GetStringFromObj (objv [2], NULL);

      allPairs = 0;
      for (i = 3; i < objc; i += 2) {
          if (Tcl_ListObjLength (interp, objv[i], &j) == TCL_ERROR)
            return TCL_ERROR;
          if (j & 1) {
            Tcl_AppendStringsToObj (resultObj,
                              "attribute list does not contain an ",
                              "even number of key-value elements",
                              (char *)NULL);
            return TCL_ERROR;
          }
          allPairs += j / 2;
      }

      modArray = (LDAPMod **)malloc (sizeof(LDAPMod *) * (allPairs + 1));

      pairIndex = 3;
      modIndex = 0;

      do {

      if (Tcl_ListObjGetElements (interp, objv [pairIndex], &attribObjc, &attribObjv)
        == TCL_ERROR) {
         mod_op = -1;
         goto badop;
      }

      nPairs = attribObjc / 2;

      for (i = 0; i < nPairs; i++) {
          mod = modArray[modIndex++] = (LDAPMod *) malloc (sizeof(LDAPMod));
          mod->mod_op = mod_op;
          mod->mod_type = Tcl_GetStringFromObj (attribObjv [i * 2], NULL);

          if (Tcl_ListObjGetElements (interp, attribObjv [i * 2 + 1], &valuesObjc, &valuesObjv) == TCL_ERROR) {
            /* FIX: cleanup memory here */
            mod_op = -1;
            goto badop;
          }

          valPtrs = mod->mod_vals.modv_strvals = \
              (char **)malloc (sizeof (char *) * (valuesObjc + 1));
          valPtrs[valuesObjc] = (char *)NULL;

          for (j = 0; j < valuesObjc; j++) {
            valPtrs [j] = Tcl_GetStringFromObj (valuesObjv[j], NULL);

            /* If it's "delete" and value is an empty string, make
             * 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_REPLACE)) {
                  valPtrs [j] = NULL;
            }
          }
      }

      pairIndex += 2;
      if (mod_op != -1 && pairIndex < objc) {
          subCommand = Tcl_GetStringFromObj (objv[pairIndex - 1], NULL);
          mod_op = -1;
          if (STREQU (subCommand, "add")) {
            mod_op = LDAP_MOD_ADD;
          } else if (STREQU (subCommand, "replace")) {
            mod_op = LDAP_MOD_REPLACE;
          } else if (STREQU (subCommand, "delete")) {
            mod_op = LDAP_MOD_DELETE;
          }
          if (mod_op == -1) {
            Tcl_SetStringObj (resultObj,
                  "Additional operators must be one of"
                  " add, replace, or delete", -1);
            mod_op = -1;
            goto badop;
          }
      }

      } while (mod_op != -1 && pairIndex < objc);
      modArray[modIndex] = (LDAPMod *) NULL;

      if (is_add) {
          result = ldap_add_s (ldap, dn, modArray);
      } else {
          result = ldap_modify_s (ldap, dn, modArray);
          if (ldaptcl->caching)
            ldap_uncache_entry (ldap, dn);
      }

        /* free the modArray elements, then the modArray itself. */
badop:
      for (i = 0; i < modIndex; i++) {
          free ((char *) modArray[i]->mod_vals.modv_strvals);
          free ((char *) modArray[i]);
      }
      free ((char *) modArray);

      /* after modArray is allocated, mod_op = -1 upon error for cleanup */
      if (mod_op == -1)
          return TCL_ERROR;

      /* FIX: memory cleanup required all over the place here */
        if (result != LDAP_SUCCESS) {
          Tcl_AppendStringsToObj (resultObj,
                            "LDAP ",
                            subCommand,
                            " error: ",
                            ldap_err2string(result),
                            (char *)NULL);
          LDAP_SetErrorCode(ldaptcl, result, interp);
          return TCL_ERROR;
      }
      return TCL_OK;
    }

    /* object search controlArray dn pattern */
    if (STREQU (subCommand, "search")) {
      char        *controlArrayName;
      Tcl_Obj     *controlArrayNameObj;

      char        *scopeString;
      int          scope;

      char        *derefString;
      int          deref;

      char        *baseString;

      char       **attributesArray;
      char        *attributesString;
      int          attributesArgc;

      char        *filterPatternString;

      char      *timeoutString;
      double           timeoutTime;
      struct timeval timeout, *timeout_p;

      char      *paramString;
      int        cacheThis = -1;
      int        all = 0;

      char      *sortattr;

      Tcl_Obj     *destArrayNameObj;
      Tcl_Obj     *evalCodeObj;

      if (objc != 5) {
          Tcl_WrongNumArgs (interp, 2, objv,
                           "controlArray destArray code");
          return TCL_ERROR;
      }

        controlArrayNameObj = objv [2];
      controlArrayName = Tcl_GetStringFromObj (controlArrayNameObj, NULL);

      destArrayNameObj = objv [3];

      evalCodeObj = objv [4];

      baseString = Tcl_GetVar2 (interp, 
                          controlArrayName, 
                          "base",
                          0);

      if (baseString == (char *)NULL) {
          Tcl_AppendStringsToObj (resultObj,
                            "required element \"base\" ",
                            "is missing from ldap control array \"",
                            controlArrayName,
                            "\"",
                            (char *)NULL);
          return TCL_ERROR;
      }

      filterPatternString = Tcl_GetVar2 (interp,
                                   controlArrayName,
                                   "filter",
                                   0);
      if (filterPatternString == (char *)NULL) {
          filterPatternString = "(objectclass=*)";
      }

      /* Fetch scope setting from control array.
       * If it doesn't exist, default to subtree scoping.
       */
      scopeString = Tcl_GetVar2 (interp, controlArrayName, "scope", 0);
      if (scopeString == NULL) {
          scope = LDAP_SCOPE_SUBTREE;
      } else {
          if (STREQU(scopeString, "base")) 
            scope = LDAP_SCOPE_BASE;
          else if (STRNEQU(scopeString, "one", 3))
            scope = LDAP_SCOPE_ONELEVEL;
          else if (STRNEQU(scopeString, "sub", 3))
            scope = LDAP_SCOPE_SUBTREE;
          else {
            Tcl_AppendStringsToObj (resultObj,
                                "\"scope\" element of \"",
                                controlArrayName,
                                "\" array is not one of ",
                                "\"base\", \"onelevel\", ",
                              "or \"subtree\"",
                              (char *) NULL);
            return TCL_ERROR;
          }
      }

#ifdef LDAP_OPT_DEREF                           
      /* Fetch dereference control setting from control array.
       * If it doesn't exist, default to never dereference. */
      derefString = Tcl_GetVar2 (interp,
                           controlArrayName,
                           "deref",
                           0);
      if (derefString == (char *)NULL) {
          deref = LDAP_DEREF_NEVER;
      } else {
          if (STREQU(derefString, "never"))
            deref = LDAP_DEREF_NEVER;
          else if (STREQU(derefString, "search"))
            deref = LDAP_DEREF_SEARCHING;
          else if (STREQU(derefString, "find"))
            deref = LDAP_DEREF_FINDING;
          else if (STREQU(derefString, "always"))
            deref = LDAP_DEREF_ALWAYS;
          else {
            Tcl_AppendStringsToObj (resultObj,
                                "\"deref\" element of \"",
                                controlArrayName,
                                "\" array is not one of ",
                                "\"never\", \"search\", \"find\", ",
                                "or \"always\"",
                                (char *) NULL);
            return TCL_ERROR;
          }
      }
#endif

      /* Fetch list of attribute names from control array.
       * If entry doesn't exist, default to NULL (all).
       */
      attributesString = Tcl_GetVar2 (interp,
                                controlArrayName,
                                "attributes", 
                                0);
      if (attributesString == (char *)NULL) {
          attributesArray = NULL;
      } else {
          if ((Tcl_SplitList (interp, 
                        attributesString,
                        &attributesArgc, 
                        &attributesArray)) != TCL_OK) {
            return TCL_ERROR;
          }
      }

      /* Fetch timeout value if there is one
       */
      timeoutString = Tcl_GetVar2 (interp,
                                controlArrayName,
                                "timeout", 
                                0);
      timeout.tv_usec = 0;
      if (timeoutString == (char *)NULL) {
          timeout_p = NULL;
          timeout.tv_sec = 0;
      } else {
          if (Tcl_GetDouble(interp, timeoutString, &timeoutTime) != TCL_OK)
            return TCL_ERROR;
          timeout.tv_sec = floor(timeoutTime);
          timeout.tv_usec = (timeoutTime-timeout.tv_sec) * 1000000;
          timeout_p = &timeout;
      }

      paramString = Tcl_GetVar2 (interp, controlArrayName, "cache", 0);
      if (paramString) {
          if (Tcl_GetInt(interp, paramString, &cacheThis) == TCL_ERROR)
            return TCL_ERROR;
      }

      paramString = Tcl_GetVar2 (interp, controlArrayName, "all", 0);
      if (paramString) {
          if (Tcl_GetInt(interp, paramString, &all) == TCL_ERROR)
            return TCL_ERROR;
      }

      sortattr = Tcl_GetVar2 (interp, controlArrayName, "sort", 0);

#ifdef UMICH_LDAP
      ldap->ld_deref = deref; 
      ldap->ld_timelimit = 0;
      ldap->ld_sizelimit = 0; 
      ldap->ld_options = 0;
#endif

      /* 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) {
                Tcl_SetStringObj(resultObj, "Caching never before enabled, I have no timeout value to use", -1);
                return TCL_ERROR;
            }
            ldap_enable_cache(ldap, ldaptcl->timeout, ldaptcl->maxmem);
          }
          else
            ldap_disable_cache(ldap);
      }
#endif

#ifdef LDAP_OPT_DEREF
      ldap_set_option(ldap, LDAP_OPT_DEREF, &deref);
#endif

      tclResult = LDAP_PerformSearch (interp, 
                              ldaptcl, 
                              baseString, 
                              scope, 
                              attributesArray, 
                              filterPatternString, 
                              "",
                              destArrayNameObj,
                              evalCodeObj,
                            timeout_p,
                            all,
                            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);
      }
#ifdef LDAP_OPT_DEREF
      deref = LDAP_DEREF_NEVER;
      ldap_set_option(ldap, LDAP_OPT_DEREF, &deref);
#endif
#endif
      return tclResult;
    }

    /* object compare dn attr value */
    if (STREQU (subCommand, "compare")) {
      char        *dn;
      char      *attr;
      char      *value;
      int        result;
      int        lderrno;

      if (objc != 5) {
          Tcl_WrongNumArgs (interp, 
                           2, objv,
                           "dn attribute value");
          return TCL_ERROR;
      }

      dn = Tcl_GetStringFromObj (objv[2], NULL);
      attr = Tcl_GetStringFromObj (objv[3], NULL);
      value = Tcl_GetStringFromObj (objv[4], NULL);
      
      result = ldap_compare_s (ldap, dn, attr, value);
      if (result == LDAP_COMPARE_TRUE || result == LDAP_COMPARE_FALSE) {
          Tcl_SetBooleanObj(resultObj, result == LDAP_COMPARE_TRUE);
          return TCL_OK;
      }
      LDAP_SetErrorCode(ldaptcl, result, interp);
      Tcl_AppendStringsToObj (resultObj,
                        "LDAP compare error: ",
                        LDAP_ERR_STRING(ldap),
                        (char *)NULL);
      return TCL_ERROR;
    }

    if (STREQU (subCommand, "cache")) {
#if defined(UMICH_LDAP) || (defined(OPEN_LDAP) && !defined(LDAP_API_VERSION))
      char *cacheCommand;

      if (objc < 3) {
        badargs:
          Tcl_WrongNumArgs (interp, 2, objv [0], "command [args...]");
          return TCL_ERROR;
      }

      cacheCommand = Tcl_GetStringFromObj (objv [2], NULL);

      if (STREQU (cacheCommand, "uncache")) {
          char *dn;

          if (objc != 4) {
            Tcl_WrongNumArgs (interp, 
                               3, objv,
                               "dn");
            return TCL_ERROR;
          }

            dn = Tcl_GetStringFromObj (objv [3], NULL);
          ldap_uncache_entry (ldap, dn);
          return TCL_OK;
      }

      if (STREQU (cacheCommand, "enable")) {
          long   timeout = ldaptcl->timeout;
          long   maxmem = ldaptcl->maxmem;

          if (objc > 5) {
            Tcl_WrongNumArgs (interp, 3, objv, "?timeout? ?maxmem?");
            return TCL_ERROR;
          }

          if (objc > 3) {
            if (Tcl_GetLongFromObj (interp, objv [3], &timeout) == TCL_ERROR)
                return TCL_ERROR;
          }
          if (timeout == 0) {
            Tcl_SetStringObj(resultObj,
                objc > 3 ? "timeouts must be greater than 0" : 
                "no previous timeout to reference", -1);
            return TCL_ERROR;
          }

          if (objc > 4)
            if (Tcl_GetLongFromObj (interp, objv [4], &maxmem) == TCL_ERROR)
                return TCL_ERROR;

          if (ldap_enable_cache (ldap, timeout, maxmem) == -1) {
            Tcl_AppendStringsToObj (resultObj,
                              "LDAP cache enable error: ",
                              LDAP_ERR_STRING(ldap),
                              (char *)NULL);
            LDAP_SetErrorCode(ldaptcl, -1, interp);
            return TCL_ERROR;
          }
          ldaptcl->caching = 1;
          ldaptcl->timeout = timeout;
          ldaptcl->maxmem = maxmem;
          return TCL_OK;
      }

      if (objc != 3) goto badargs;

      if (STREQU (cacheCommand, "disable")) {
          ldap_disable_cache (ldap);
          ldaptcl->caching = 0;
          return TCL_OK;
      }

      if (STREQU (cacheCommand, "destroy")) {
          ldap_destroy_cache (ldap);
          ldaptcl->caching = 0;
          return TCL_OK;
      }

      if (STREQU (cacheCommand, "flush")) {
          ldap_flush_cache (ldap);
          return TCL_OK;
      }

      if (STREQU (cacheCommand, "no_errors")) {
          ldap_set_cache_options (ldap, LDAP_CACHE_OPT_CACHENOERRS);
          return TCL_OK;
      }

      if (STREQU (cacheCommand, "all_errors")) {
          ldap_set_cache_options (ldap, LDAP_CACHE_OPT_CACHEALLERRS);
          return TCL_OK;
      }

      if (STREQU (cacheCommand, "size_errors")) {
          ldap_set_cache_options (ldap, 0);
          return TCL_OK;
      }
      Tcl_AppendStringsToObj (resultObj,
                        "\"",
                        command,
                        " ",
                        subCommand, 
                        "\" subcommand", 
                        " must be one of \"enable\", ",
                        "\"disable\", ",
                        "\"destroy\", \"flush\", \"uncache\", ",
                        "\"no_errors\", \"size_errors\",",
                        " or \"all_errors\"",
                        (char *)NULL);
      return TCL_ERROR;
#else
      return TCL_OK;
#endif
    }
    if (STREQU (subCommand, "trap")) {
      Tcl_Obj *listObj, *resultObj;
      int *p, l, i, code;

      if (objc > 4) {
          Tcl_WrongNumArgs (interp, 2, objv,
                           "command ?errorCode-list?");
          return TCL_ERROR;
      }
      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 = (int*)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, NULL));
          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) {
          Tcl_AppendStringsToObj(resultObj, "Wrong # of arguments",
            (char*)NULL);
          return TCL_ERROR;
      }
      return Tcl_GetIntFromObj(interp, objv[2], &ldap_debug);
    }
#endif

    /* FIX: this needs to enumerate all the possibilities */
    Tcl_AppendStringsToObj (resultObj,
                          "subcommand \"", 
                      subCommand, 
                      "\" must be one of \"add\", ",
                      "\"add_attributes\", ",
                      "\"bind\", \"cache\", \"delete\", ",
                      "\"delete_attributes\", \"modify\", ",
                      "\"modify_rdn\", \"rename_rdn\", ",
                      "\"replace_attributes\", ",
                      "\"search\" or \"unbind\".",
                          (char *)NULL);
    return TCL_ERROR;
}

/* 
 * Delete and LDAP command object
 *
 */
static void
NeoX_LdapObjDeleteCmd(clientData)
    ClientData    clientData;
{
    LDAPTCL      *ldaptcl = (LDAPTCL *)clientData;
    LDAP         *ldap = ldaptcl->ldap;

    if (ldaptcl->trapCmdObj)
      Tcl_DecrRefCount (ldaptcl->trapCmdObj);
    if (ldaptcl->traplist)
      free(ldaptcl->traplist);
    ldap_unbind(ldap);
    free((char*) ldaptcl);
}

/*-----------------------------------------------------------------------------
 * NeoX_LdapObjCmd --
 *  
 * Implements the `ldap' command:
 *    ldap open newObjName host [port]
 *    ldap init newObjName host [port]
 *  
 * Results:
 *      A standard Tcl result.
 *      
 * Side effects:
 *      See the user documentation.
 *-----------------------------------------------------------------------------
 */     
static int
NeoX_LdapObjCmd (clientData, interp, objc, objv)
    ClientData    clientData;
    Tcl_Interp   *interp;
    int           objc;
    Tcl_Obj      *CONST objv[];
{
    extern int    errno;
    char         *subCommand;
    char         *newCommand;
    char         *ldapHost;
    int           ldapPort = LDAP_PORT;
    LDAP         *ldap;
    LDAPTCL  *ldaptcl;

    Tcl_Obj      *resultObj = Tcl_GetObjResult (interp);

    if (objc < 3) {
      Tcl_WrongNumArgs (interp, 1, objv,
                         "(open|init) new_command host [port]|explode dn");
      return TCL_ERROR;
    }

    subCommand = Tcl_GetStringFromObj (objv[1], NULL);

    if (STREQU(subCommand, "explode")) {
      char *param;
      int nonames = 0;
      int list = 0;
      char **exploded, **p;

      param = Tcl_GetStringFromObj (objv[2], NULL);
      if (param[0] == '-') {
          if (STREQU(param, "-nonames")) {
            nonames = 1;
          } else if (STREQU(param, "-list")) {
            list = 1;
          } else {
            Tcl_WrongNumArgs (interp, 1, objv, "explode ?-nonames|-list? dn");
            return TCL_ERROR;
          }
      }
      if (nonames || list)
          param = Tcl_GetStringFromObj (objv[3], NULL);
      exploded = ldap_explode_dn(param, nonames);
      for (p = exploded; *p; p++) {
          if (list) {
            char *q = strchr(*p, '=');
            if (!q) {
                Tcl_SetObjLength(resultObj, 0);
                Tcl_AppendStringsToObj(resultObj, "rdn ", *p,
                  " missing '='", NULL);
                ldap_value_free(exploded);
                return TCL_ERROR;
            }
            *q = '\0';
            if (Tcl_ListObjAppendElement(interp, resultObj,
                  Tcl_NewStringObj(*p, -1)) != TCL_OK ||
                  Tcl_ListObjAppendElement(interp, resultObj,
                  Tcl_NewStringObj(q+1, -1)) != TCL_OK) {
                ldap_value_free(exploded);
                return TCL_ERROR;
            }
          } else {
            if (Tcl_ListObjAppendElement(interp, resultObj,
                  Tcl_NewStringObj(*p, -1))) {
                ldap_value_free(exploded);
                return TCL_ERROR;
            }
          }
      }
      ldap_value_free(exploded);
      return TCL_OK;
    }

#ifdef UMICH_LDAP
    if (STREQU(subCommand, "friendly")) {
      char *friendly = ldap_dn2ufn(Tcl_GetStringFromObj(objv[2], NULL));
      Tcl_SetStringObj(resultObj, friendly, -1);
      free(friendly);
      return TCL_OK;
    }
#endif

    newCommand = Tcl_GetStringFromObj (objv[2], NULL);
    ldapHost = Tcl_GetStringFromObj (objv[3], NULL);

    if (objc == 5) {
      if (Tcl_GetIntFromObj (interp, objv [4], &ldapPort) == TCL_ERROR) {
          Tcl_AppendStringsToObj (resultObj,
                            "LDAP port number is non-numeric",
                            (char *)NULL);
            return TCL_ERROR;
      }
    }

    if (STREQU (subCommand, "open")) {
      ldap = ldap_open (ldapHost, ldapPort);
    } else if (STREQU (subCommand, "init")) {
      int version = -1;
      int i;
      int value;
      char *subOption;
      char *subValue;

#if LDAPTCL_PROTOCOL_VERSION_DEFAULT
      version = LDAPTCL_PROTOCOL_VERSION_DEFAULT;
#endif

      for (i = 6; i < objc; i += 2)  {
          subOption =  Tcl_GetStringFromObj(objv[i-1], NULL);
          if (STREQU (subOption, "protocol_version")) {
#ifdef LDAP_OPT_PROTOCOL_VERSION
            subValue = Tcl_GetStringFromObj(objv[i], NULL);
            if (STREQU (subValue, "2")) {
                version = LDAP_VERSION2;
            }
            else if (STREQU (subValue, "3")) {
#ifdef LDAP_VERSION3
                version = LDAP_VERSION3;
#else
                Tcl_SetStringObj (resultObj, "protocol_version 3 not supported", -1);
                return TCL_ERROR;
#endif
            }
            else {
                Tcl_SetStringObj (resultObj, "protocol_version must be '2' or '3'", -1);
                return TCL_ERROR;
            }
#else
            Tcl_SetStringObj (resultObj, "protocol_version not supported", -1);
            return TCL_ERROR;
#endif
          } else if (STREQU (subOption, "port")) {
            if (Tcl_GetIntFromObj (interp, objv [i], &ldapPort) == TCL_ERROR) {
                Tcl_AppendStringsToObj (resultObj,
                                  "LDAP port number is non-numeric",
                                  (char *)NULL);
                return TCL_ERROR;
            }
          } else {
            Tcl_SetStringObj (resultObj, "valid options: protocol_version, port", -1);
            return TCL_ERROR;
          }
      }
      ldap = ldap_init (ldapHost, ldapPort);

#ifdef LDAP_OPT_PROTOCOL_VERSION
      if (version != -1)
          ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
#endif
    } else {
      Tcl_AppendStringsToObj (resultObj, 
                        "option was not \"open\" or \"init\"");
      return TCL_ERROR;
    }

    if (ldap == (LDAP *)NULL) {
      Tcl_SetErrno(errno);
      Tcl_AppendStringsToObj (resultObj, 
                        Tcl_PosixError (interp), 
                        (char *)NULL);
      return TCL_ERROR;
    }

#ifdef UMICH_LDAP
    ldap->ld_deref = LDAP_DEREF_NEVER;  /* Turn off alias dereferencing */
#endif

    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,
                    newCommand,
                          NeoX_LdapTargetObjCmd,
                          (ClientData) ldaptcl,
                          NeoX_LdapObjDeleteCmd);
    return TCL_OK;
}

/*-----------------------------------------------------------------------------
 * Neo_initLDAP --
 *     Initialize the LDAP interface.
 *-----------------------------------------------------------------------------
 */     
int
Ldaptcl_Init (interp)
Tcl_Interp   *interp;
{
    Tcl_CreateObjCommand (interp,
                    "ldap",
                          NeoX_LdapObjCmd,
                          (ClientData) NULL,
                          (Tcl_CmdDeleteProc*) NULL);
    /*
    if (Neo_initLDAPX(interp) != TCL_OK)
      return TCL_ERROR;
    */
    Tcl_PkgProvide(interp, "Ldaptcl", VERSION);
    return TCL_OK;
}

Generated by  Doxygen 1.6.0   Back to index