/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc.  Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation version
 * 2.1 of the License.
 *                                                                                  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *                                                                                  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * mozldap_ldap_explode, mozldap_ldap_explode_dn, mozldap_ldap_explode_rdn
 * and ldap_utf8 functions
 * are from the file ldap/libraries/libldap/getdn.c in the Mozilla LDAP C SDK
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla Communicator client code, released
 * March 31, 1998.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998-1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 *  Copyright (c) 1994 Regents of the University of Michigan.
 *  All rights reserved.
 *
 * END COPYRIGHT BLOCK **/
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include "version.h"
#include "admutil_pvt.h"
#include "libadminutil/admutil-int.h"
#include "libadminutil/distadm.h"
#include <ssl.h>
#include "sslproto.h"
#include "nss.h"
#if defined(USE_OPENLDAP)
#include <lber.h>
#else
#include <ldappr.h>
#include <ldap_ssl.h>
#endif
#ifdef XP_WIN32
#define strcasecmp stricmp
#define strncasecmp _strnicmp
#endif
 
/* Max size for a pathname */
#ifndef PATH_MAX
#define PATH_MAX 256
#endif

#ifndef FILE_PATHSEP
#define FILE_PATHSEP '/'
#endif

/* returns true if the given path is a valid file, false otherwise */
static int
is_file_ok(const char *path)
{
	PRFileInfo prinfo;
	int ret = 0;

	if (path && *path &&
		(PR_SUCCESS == PR_GetFileInfo(path, &prinfo)) &&
		prinfo.type == PR_FILE_FILE) {
		ret = 1;
	}

	return ret;
}

/* returns full path and file name if the file was found somewhere, false otherwise */
static char *
find_file_in_paths(
	const char *filename, /* the base filename to look for */
	const char *path /* path given by caller */
)
{
    char *retval = NULL;
    char *adminutilConfDir = getenv(ADMINUTIL_CONFDIR_ENV_VAR);

    /* try given path */
    if (path) {
        retval = PR_smprintf("%s/%s", path, filename);
    }
    if (!is_file_ok(retval) && adminutilConfDir) {
        PR_smprintf_free(retval);
        retval = PR_smprintf("%s/%s", adminutilConfDir, filename);
        if (!is_file_ok(retval)) {
            PR_smprintf_free(retval);
            retval = NULL;
        }
    }

    return retval;
}

#if defined(USE_OPENLDAP)
static int
admin_ldap_rebind_proc(
	LDAP *ld, LDAP_CONST char *url,
	ber_tag_t request, ber_int_t msgid,
	void *arg)
{
  AdmldapHdnlPtr ptr = (AdmldapHdnlPtr)arg;

  return admutil_ldap_bind(ld, ptr->sieDN, ptr->passwd, LDAP_SASL_SIMPLE, NULL, NULL, NULL, NULL);
}
#else /* NOT USE_OPENLDAP */
static int
admin_ldap_rebind_proc (LDAP *ld, char **whop, char **passwdp,
                       int *authmethodp, int freeit, void *arg) {
  AdmldapHdnlPtr ptr = (AdmldapHdnlPtr)arg;

  if (freeit == 0) {
    *whop = ptr->sieDN;
    *passwdp = ptr->passwd;
    *authmethodp = LDAP_AUTH_SIMPLE;
  }

  return LDAP_SUCCESS;
}
#endif

PR_IMPLEMENT(char**)
admutil_strsdup(char** orig)
{
  int  cnt = 0, i = 0;
  char **dest, **tmpptr;
  
  if (!orig) return NULL;

  tmpptr = orig;
  while (*tmpptr++ != NULL) cnt++;

  dest = (char**)PR_Malloc((cnt+1)*sizeof(char*));

  for (i=0; i < cnt; i++) dest[i] = PL_strdup(orig[i]);

  dest[cnt] = NULL;

  return dest;
}

PR_IMPLEMENT(char**)
admutil_strsdup_berval(struct berval** orig)
{
  int  cnt = 0, i = 0;
  char **dest;
  struct berval **tmpptr;
  
  if (!orig) return NULL;

  tmpptr = orig;
  while (*tmpptr++ != NULL) cnt++;

  dest = (char**)PR_Malloc((cnt+1)*sizeof(char*));

  for (i=0; i < cnt; i++) dest[i] = PL_strndup(orig[i]->bv_val, orig[i]->bv_len);

  dest[cnt] = NULL;

  return dest;
}

PR_IMPLEMENT(void)
admutil_strsFree(char** target)
{
  int  cnt = 0, i = 0;
  char **tmpptr;
  
  if (!target) return;

  tmpptr = target;
  while (*tmpptr++ != NULL) cnt++;

  for (i=0; i < cnt; i++) PR_Free(target[i]);

  PR_Free(target);

  return;
}


PR_IMPLEMENT(AttrNameList)
createAttrNameList(int entries)
{
  AttrNameList nl = (AttrNameList)PR_Malloc((entries+1)*sizeof(NameType));
  if (nl) memset(nl, '\0', (entries+1)*sizeof(NameType));
  return nl;
}

PR_IMPLEMENT(int)
addName(AttrNameList nl, int index, NameType name)
{
  nl[index] = PL_strdup(name);
  if (nl[index]) return ADMUTIL_OP_OK;
  else return ADMUTIL_OP_FAIL;
}

PR_IMPLEMENT(void)
deleteAttrNameList(AttrNameList nl)
{
  NameType name;
  AttrNameList nlptr = nl;
  if (nl) {
    while ((name = *nlptr++)) PR_Free(name);
    PR_Free(nl);
  }
}

PR_IMPLEMENT(void)
deleteValue(ValueType val)
{
    admutil_strsFree((char **)val);
}

PR_IMPLEMENT(AttributeList)
createAttributeList(int entries)
{
  AttributeList nvl = (AttributeList)PR_Malloc((entries+1)*sizeof(AttributePtr));
  if (nvl) memset(nvl, '\0', (entries+1)*sizeof(AttributePtr));
  return nvl;

}

PR_IMPLEMENT(int)
addAttribute(AttributeList nvl, int index, NameType name, ValueType val)
{
  AttributePtr nv = (AttributePtr)PR_Malloc(sizeof(Attribute));
  if (nv) {
    nv->attrName = PL_strdup(name);
    if (val) nv->attrVal = admutil_strsdup(val);
    else nv->attrVal = NULL;
    nvl[index] = nv;
    return ADMUTIL_OP_OK;
  }
  else return ADMUTIL_OP_FAIL;
}

PR_IMPLEMENT(int)
addSingleValueAttribute(AttributeList nvl, int index, NameType name, char* val) {
  AttributePtr nv = (AttributePtr)PR_Malloc(sizeof(Attribute));

  if (nv) {
    nv->attrName = PL_strdup(name);
    if (val) {
      nv->attrVal = (ValueType)PR_Malloc(2*sizeof(char*));
      nv->attrVal[0] = PL_strdup(val);
      nv->attrVal[1] = NULL;
    }
    else nv->attrVal = NULL;
    nvl[index] = nv;
    return ADMUTIL_OP_OK;
  }
  else return ADMUTIL_OP_FAIL;
}

PR_IMPLEMENT(void)
deleteAttributeList(AttributeList nvl) {
  AttributePtr attr;
  AttributeList nvlptr = nvl;
  if (nvl) {
    while ((attr = *nvlptr++)) {
      if (attr->attrName) PR_Free(attr->attrName);
      if (attr->attrVal) admutil_strsFree(attr->attrVal);
      PR_Free(attr);
    }
    PR_Free(nvl);
  }
}


/* Borrow from base/util.cpp  */
#define LF 10
#define CR 13

PR_IMPLEMENT(int)
admutil_getline(FILE *fstream, int maxlen, int lineno, char* buf)
{
  int i, x;
  memset(buf, '\0', maxlen);

  x = 0;
  while(1) {
    switch  (i = getc(fstream)) {
    case EOF:
      if (ferror(fstream)) {
        PR_snprintf(buf, maxlen, "I/O error reading file at line %d", lineno);
        return ADMUTIL_OP_FAIL;
      }
      buf[x] = '\0';
      return 1;
    case LF:
      if(x && (buf[x-1] == '\\')) {
        --x;
        continue;
      }
      buf[x] = '\0';
      return 0;
    case CR:
      continue;
    default:
      buf[x] = (char) i;
      if(++x == maxlen) {
        PR_snprintf(buf, maxlen, "line %d is too long", lineno);
        return -1;
      }
      break;
    }
  }
}

int
timecmp(char* ldapTime, time_t *cacheTime) {

  /* LDAP's timestamp example: 19970708020159Z  */
  int x, yr, mon, day, hr, min, sec;
  char zone;
  struct tm *cachetm;
  sscanf(ldapTime, "%4d%2d%2d%2d%2d%2d%c", &yr, &mon, &day, &hr, &min,
         &sec, &zone);

  if (zone == 'Z') cachetm = gmtime(cacheTime);
  else cachetm = localtime(cacheTime);
  
  if ( (x = yr - 1900 - cachetm->tm_year) || (x = mon - 1 - cachetm->tm_mon) ||
       (x = day - cachetm->tm_mday) || (x = hr - cachetm->tm_hour) ||
       (x = min - cachetm->tm_min) || (x = sec - cachetm->tm_sec) ) {
    return x;
  }
  else return 0;
}


char*
dn2AttrName(char* dn, char* rootDN)
{
  char**  dnList;
  char**  rootList;
  int    rootLen = 0, dnLen = 0, attrLen;
  char   buf[256];

  memset(buf, '\0', sizeof(buf));

  dnList = admldap_ldap_explode_dn(dn, 1);
  rootList = admldap_ldap_explode_dn(rootDN, 1);

  if (rootList && dnList) {
    while (rootList[rootLen]) rootLen++;
    while (dnList[dnLen]) dnLen++;

    attrLen = dnLen - rootLen;

    while (attrLen > 0) {
      if (attrLen == 1)
        PR_snprintf(buf, sizeof(buf), "%s%s", buf, dnList[0]);
      else
        PR_snprintf(buf, sizeof(buf), "%s%s.", buf, dnList[attrLen-1]);
      attrLen--;
    }
  } else {
    char *envvar = PR_GetEnv("ADMINUTIL_DEBUG");
    if (!dnList) {
      if (envvar && (*envvar == '1')) {
        fprintf(stderr, "dn2attrName: Error: invalid dn [%s]\n", dn);
      }
    }
    if (!rootList) {
      if (envvar && (*envvar == '1')) {
        fprintf(stderr, "dn2attrName: Error: invalid rootDN [%s]\n", rootDN);
      }
    }
    PL_strncpy(buf, "unknown", sizeof(buf));
  }
  admutil_strsFree(dnList);
  admutil_strsFree(rootList);

  return PL_strdup(buf);
}

char*
attrName2dn(char* attrName, char* rootDN)
{
  char    result[512], buf[128];
  char    *rdn, *attrPtr, *resultPtr;

  if (!attrName) return NULL;
  if (*attrName == '\0') return PL_strdup(rootDN);
    
  PL_strncpyz(buf, attrName, sizeof(buf));
  memset(result , '\0', sizeof(result));

  resultPtr = result;
  attrPtr = buf;
  while (attrPtr) {
    attrPtr = strrchr(buf, '.');
    if (attrPtr) {
      *attrPtr = '\0';
      rdn = attrPtr+1;
    }
    else rdn = buf;
    /* NOTE: FIXME https://fedorahosted.org/389/ticket/47546 */
    /* This allows you to create bad DNs
     ** no escaping for the rdn value
     ** spaces after the comma
     */
    if (result[0] == '\0')
      PR_snprintf(resultPtr, sizeof(result), "cn=%s", rdn);
    else
      PR_snprintf(resultPtr, sizeof(result), "%s, cn=%s", resultPtr, rdn);
  }
  PR_snprintf(resultPtr, sizeof(result), "%s, %s", resultPtr, rootDN);
  
  return PL_strdup(result);
}


/*
 * List Related stuff
 */
ListNodePtr
createListNode(char* name, void *val, int dflag) {

  ListNodePtr ptr;

  if (!name) return NULL;
  ptr = (ListNodePtr)PR_Malloc(sizeof(ListNode));
  if (!ptr) return NULL;
  ptr->name = PL_strdup(name);
  ptr->val = val;
  ptr->dflag = dflag;
  ptr->next = NULL;
  return ptr;
}

void
listDestroy(ListNodePtr list) {
  /* Clean up */
  ListNodePtr   node, nextptr;
  node = list;
  while (node) {
    nextptr = node->next;
    if (node->name) PR_Free (node->name);
    if (node->dflag) deleteValue ((ValueType)node->val);
    PR_Free (node);
    node= nextptr;
  }
}



ListNodePtr
listFindNode(ListNodePtr list, char* name) {
  /* Not implemented */
  ListNodePtr   node;

  node = list;
  while (node) {
    if (!strcasecmp(name, node->name)) return node;
    node = node->next;
  }
  return NULL;
}

void*
listGetValue(ListNodePtr list, char* name) {
  /* Not implemented */
  ListNodePtr   node;

  node = listFindNode(list, name);

  if (node) return node->val;
  return NULL;
}

int
listCount(ListNodePtr list) {
  /* Not implemented */
  ListNodePtr   node;
  int           cnt = 0;

  node = list;
  while (node) {
    cnt++;
    node = node->next;
  }
  return cnt;
}

ListNodePtr
listCat(ListNodePtr list1, ListNodePtr list2) {
  /* Not implemented */
  ListNodePtr   node;
  
  if (!list1) return list2;
  if (!list2) return list1;

  node = list1;
  while(node->next) node = node->next;
  node->next = list2;
  
  return list1;
}

/* Append to the end */
ListNodePtr
listAppend(ListNodePtr list, char* name, void* val, int dflag) {
  /* Not implemented */
  ListNodePtr   node, newNode;

  if (!list) return NULL;
  if (listFindNode(list, name)) return NULL;
  node = list;
  newNode = createListNode(name, val, dflag);
  if (!newNode) return NULL;

  while(node->next) node = node->next;
  node->next = newNode;
  
  return list;
}

/* Add to the front */
ListNodePtr
listAdd(ListNodePtr list, char* name, void* val, int dflag) {
  /* Not implemented */
  ListNodePtr   newNode;

  if (!list) return NULL;
  if (listFindNode(list, name)) return NULL;
  newNode = createListNode(name, val, dflag);
  if (!newNode) return NULL;
  newNode->next = list;

  return newNode;
}

ListNodePtr
listDelete(ListNodePtr list, char* name) {
  /* Not implemented */
  ListNodePtr   node, nextptr;

  if (!list || !name) {
    goto done;
  }

  if (list->name && !strcasecmp(list->name, name)) {
    nextptr = list->next;
    if (list->name) PR_Free(list->name);
    if (list->dflag) deleteValue ((ValueType)list->val);
    PR_Free(list);
    return nextptr;
  }

  node = list;
  while (node->next) {
    if (node->next->name && !strcasecmp(node->next->name, name)) {
      nextptr = node->next->next;
      if (node->next->name) PR_Free (node->next->name);
      if (node->next->dflag) deleteValue ((ValueType)node->next->val);
      PR_Free (node->next);
      node->next = nextptr;
      return list;
    }
    node = node->next;
  }

done:
  return list;
}

/* 
 * Some utilities which use list
 */
AttributeList
nvlistConvert(ListNodePtr list) {
  AttributeList  resultList = NULL;
  int i = 0, cnt = listCount(list);
  ListNodePtr node = list;

  if (!list) return NULL;

  if (cnt > 0) {
    resultList = createAttributeList(cnt);
    while (node) {
      addAttribute(resultList, i++, node->name, (ValueType)(node->val));
      node = node->next;
    }
    return resultList;
  }
  return NULL;
}

void
nvlistDestroy(ListNodePtr list)
{
  ListNodePtr node = list;

  if (!list) return;

  while (node) {
    deleteValue(node->val);
    node = node->next;
  }

  listDestroy(list);
}

ValueType
valListConvert(ListNodePtr list)
{
  ValueType  resultList = NULL;
  int i = 0, cnt = listCount(list);
  ListNodePtr node = list;

  if (!list) return NULL;

  if (cnt > 0) {
    resultList = (ValueType)PR_Malloc((cnt+1)*sizeof(char*));
    while (node) {
      resultList[i++] = PL_strdup(node->name);
      node = node->next;
    }
    resultList[i] = NULL;
    return resultList;
  }
  return NULL;
}

ListNodePtr
createUpdateList(AttributeList nvl)
{
  AttributePtr    nv;
  AttributeList   nvlptr = nvl;
  char            namebuf[256];
  char           *attrName, *nodeName;
  ListNodePtr     resultList = NULL, nodePtr, attrPtr;

  while ((nv = *nvlptr++)) {
    PR_snprintf(namebuf, sizeof(namebuf), "%s", nv->attrName);
    attrName = strrchr(namebuf, '.');
    if (!attrName) {
      attrName = namebuf;
      nodeName = "";
    }
    else {
      *attrName = '\0';
      attrName++;
      nodeName = namebuf;
    }

    if (!resultList) {
      attrPtr = createListNode(attrName, admutil_strsdup(nv->attrVal), 1);
      resultList = createListNode(nodeName, (void*)attrPtr, 0);
    }
    else {
      nodePtr = (ListNodePtr)listFindNode(resultList, nodeName);
      if (!nodePtr) {
        attrPtr = createListNode(attrName, admutil_strsdup(nv->attrVal), 1);
        listAppend(resultList, nodeName, (void*)attrPtr, 0);
      }
      else {
        attrPtr = createListNode(attrName, admutil_strsdup(nv->attrVal), 1);
        listCat((ListNodePtr)(nodePtr->val), attrPtr);
      }
    }
  }
        
  return resultList;
}

void
destroyUpdateList(ListNodePtr updateList)
{
  ListNodePtr     nodePtr;

  nodePtr = updateList;

  while (nodePtr) {
    listDestroy((ListNodePtr)nodePtr->val);
    nodePtr = nodePtr->next;
  }

  listDestroy(updateList);
}

/*
 * Tree Related Stuff
 */
TreeNodePtr
createTreeNode(char* name, char* val)
{
  TreeNodePtr newNode;

  if (!name || *name == '\0') return NULL;
  if (!val) return NULL;
  newNode = (TreeNodePtr)PR_Malloc(sizeof(TreeNode));
  if (!newNode) return NULL;

  newNode->name = PL_strdup(name);
  newNode->val = createListNode(val, NULL, 0);
  newNode->left = NULL;
  newNode->right = NULL;

  return newNode;
}

int
treeCount(TreeNodePtr root)
{
  if (root) return (treeCount(root->left) + treeCount(root->right) + 1);
  else return 0;
}

TreeNodePtr
treeFindNode(TreeNodePtr node, char* name)
{
  int result;

  if (node) {
    if (!(result = strcasecmp(name, node->name))) return node;
    else if (result > 0) return treeFindNode(node->left, name);
    else return treeFindNode(node->right, name);
  }
  else return NULL;
}

static int
treeHasNode(AdmldapInfo info, char* name)
{
  int result = 0;
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  if (admInfo && admInfo->configInfo) {
    result = treeFindNode(admInfo->configInfo, name) ? 1 : 0;
  }
  return result;
}

ValueType
treeFindValue(TreeNodePtr root, char* name)
{
  TreeNodePtr  target;

  if ((target = treeFindNode(root, name))) return valListConvert(target->val);
  else return NULL;
}

char*
treeFindValueAt(TreeNodePtr root, char* name, int index)
{

  ValueType  vals;
  char*      val;

  vals = treeFindValue(root, name);

  if (vals) {
    val = PL_strdup(vals[0]);
    deleteValue(vals);
    return val;
  }
  else return NULL;
}

TreeNodePtr
treeAddNode(TreeNodePtr node, TreeNodePtr newNode) {

  int result;

  result = strcasecmp(newNode->name, node->name);
  if (!result) return NULL;
  else if (result > 0) {
    if (node->left) return treeAddNode(node->left, newNode);
    else {
      node->left = newNode;
      return newNode;
    }
  }
  else {  /* result < 0 */
    if (node->right) return treeAddNode(node->right, newNode);
    else {
      node->right = newNode;
      return newNode;
    }
  }
}

TreeNodePtr
treeAddValue(TreeNodePtr node, char* val) {

  if (!node) return NULL;
  if (node->val) listAppend(node->val, val, NULL, 0);
  else node->val = createListNode(val, NULL, 0);

  return node;
}

TreeNodePtr
treeAddNameValue(TreeNodePtr node, char* name, char* val) {

  TreeNodePtr targetNode;

  if (!name || *name == '\0') return NULL;
  targetNode = treeFindNode(node, name);

  if (targetNode) return treeAddValue(targetNode, val);
  else {
    targetNode = createTreeNode(name, val);
    if (targetNode) return treeAddNode(node, targetNode);
    return NULL;
  }
}

int
treeRemoveNode(TreeNodePtr node, char* name, int* removeFlag) {
  int result, flag = ADMUTIL_OP_OK, remove;
  TreeNodePtr tmpNodeLeft, tmpNodeRight;

  *removeFlag = 0;

  if (!node) return ADMUTIL_OP_FAIL;  /* Fail  */
  if (!name || *name == '\0') return ADMUTIL_OP_FAIL;  /* Fail  */

  result = strcasecmp(name, node->name);
  if (!result) { /* It is me to be deleted */
    PR_Free(node->name);
    listDestroy(node->val);
    
    /* Reorganize tree if necessary */
    tmpNodeLeft = node->left;
    tmpNodeRight = node->right;
    if (tmpNodeLeft && tmpNodeRight) {
      node->name = tmpNodeLeft->name;
      node->val = tmpNodeLeft->val;
      node->left = tmpNodeLeft->left;
      node->right = tmpNodeLeft->right;
      treeAddNode(node, tmpNodeRight);
      PR_Free(tmpNodeLeft);
    }
    else if (tmpNodeLeft) { /* Right is NULL */
      node->name = tmpNodeLeft->name;
      node->val = tmpNodeLeft->val;
      node->left = tmpNodeLeft->left;
      node->right = tmpNodeLeft->right;
      PR_Free(tmpNodeLeft);
    }
    else if (tmpNodeRight) { /* Left is NULL */
      node->name = tmpNodeRight->name;
      node->val = tmpNodeRight->val;
      node->left = tmpNodeRight->left;
      node->right = tmpNodeRight->right;
      PR_Free(tmpNodeRight);
    }
    else *removeFlag = 1; /* Both left and right are NULL */
    flag = ADMUTIL_OP_OK;  /* Should be success  */
  }
  else if (result > 0) {
    if (node->left) {
      flag = treeRemoveNode(node->left, name, &remove);
      if (remove) {
	PR_Free(node->left);
	node->left = NULL;
      }
    }
    else flag = ADMUTIL_OP_FAIL; /* Can't  find the node  */
  }
  else {
    if (node->right) {
      flag = treeRemoveNode(node->right, name, &remove);
      if (remove) {
	PR_Free(node->right);
	node->right = NULL;
      }
    }
    else flag = ADMUTIL_OP_FAIL;  /* Can't  find the node  */
  }
  return flag;
}

void
treeRemoveTree(TreeNodePtr tree)
{
  if (tree) {
    if (tree->left) { treeRemoveTree(tree->left); tree->left = NULL; };
    if (tree->right) { treeRemoveTree(tree->right); tree->right = NULL; }
    if (tree->name) { PR_Free(tree->name); tree->name = NULL; }
    if (tree->val) { listDestroy(tree->val); tree->val = NULL; }
    PR_Free(tree);
  }
}

ListNodePtr
treeBuildAttrList(char* nodeName, TreeNodePtr node)
{
  ListNodePtr resultList=NULL, leftPtr = NULL, nodePtr = NULL, rightPtr = NULL;
  char namebuf[256];

  if (!nodeName || nodeName[0] == '\0')
    PR_snprintf(namebuf, sizeof(namebuf), "%s", node->name);
  else
    PR_snprintf(namebuf, sizeof(namebuf), "%s.%s", nodeName, node->name);

  if (node->left) leftPtr = treeBuildAttrList(nodeName, node->left);
  nodePtr = createListNode(namebuf, valListConvert(node->val), 0);
  if (node->right) rightPtr = treeBuildAttrList(nodeName, node->right);

  if (leftPtr) resultList = leftPtr;
  if (nodePtr) resultList = listCat(resultList, nodePtr);
  if (rightPtr) resultList = listCat(resultList, rightPtr);

  return resultList;
}

void
treeExport(FILE *fstream, char* parentString, TreeNodePtr node)
{
  char *cptr, *sptr, valBuf[BUFSIZ];
  ListNodePtr listPtr;

  if (node->left) treeExport(fstream, parentString, node->left);
  if (!parentString || *parentString == '\0') 
    fprintf(fstream, "%s: ", node->name);
  else fprintf(fstream, "%s.%s: ", parentString, node->name);

  listPtr = node->val;
  while (listPtr) {
    PR_snprintf(valBuf, sizeof(valBuf), "%s", listPtr->name);
    sptr = valBuf;
    while ((cptr = strchr(sptr, '\n'))) {
      *cptr++ = '\0';
      fprintf(fstream, "%s\n ", sptr);
      sptr=cptr;
    }
    fprintf(fstream, "%s\n", sptr);
    listPtr = listPtr->next;
  }
  
  if (node->right) treeExport(fstream, parentString, node->right);
}


TreeNodePtr
treeImport(FILE *fstream, int* errorcode)
{
  int         status, lineno=1, valLen=0;
  char        linebuf[MAX_LEN], *name=NULL, *val=NULL;
  char        valBuf[BUFSIZ], *valptr = valBuf;
  int         valBuf_len = sizeof(valBuf);
  TreeNodePtr rootNode = NULL;

  if (!fstream) return NULL;
  if (!errorcode) return NULL;
  
  valBuf[0] = '\0';

  while(1) {
    switch(status = admutil_getline(fstream, sizeof(linebuf), lineno++, linebuf)) {
    case -1:
      /* Error on reading, SET ERRORCODE */
      *errorcode = ADMUTIL_SYSTEM_ERR;
      if (rootNode) treeRemoveTree(rootNode);
      return NULL;
      break;
    case 1:
      /* EOF, out of here */
      if (name) {
        if (rootNode) {
          treeAddNameValue(rootNode, name, valBuf);
        }
        else {
          rootNode = createTreeNode(name, valBuf);
        }
        PR_Free(name);
        name = NULL;
      }
      return rootNode;
      break;
    default:
      if (linebuf[0] == ' ') {
        /* This is continue of value  */
        /* Append value to value buffer  */
        val = &linebuf[1];
        valLen= PL_strlen(val);
        /* put back LF */
        *valptr++ = '\n';
        valBuf_len -= 1;
        PR_snprintf(valptr, valBuf_len, "%s", val);
        valBuf_len -= valLen;
        valptr = valptr + valLen;
      }
      else {
        if (name) {
          if (rootNode) {
            treeAddNameValue(rootNode, name, valBuf);
          }
          else {
            rootNode = createTreeNode(name, valBuf);
          }
          PR_Free(name);
          name = NULL;
          valptr=valBuf;
          valBuf[0] = '\0';
        }
        name = PL_strdup(strtok(linebuf, ":"));
        if (!name) break;
        val = linebuf+PL_strlen(name)+1;

        /* Skip over the whitesapce  */
        while (*val && isspace(*val)) val++;

        if (*val == '\0') {
          /* Empty value, Ingore it */
          break;
        }
        /* Put value to value buffer */
        valLen= PL_strlen(val);
        PR_snprintf(valptr, valBuf_len, "%s", val);
        valBuf_len -= valLen;
        valptr = valptr + valLen;
      }
    }
  }
}

/*
 * LDAPMod related utility functions
 */

LDAPMod*
createSingleValMod( char* namep, char* value, int mode)
{
  char*   valbuf[2];

  if (value) {
    valbuf[0] = value;
    valbuf[1] = NULL;

    return createMod(namep, valbuf, mode);
  }
  else return createMod(namep, NULL, mode);
}

LDAPMod*
createMod( char* namep, ValueType values, int mode)
{
  LDAPMod       *newMod;
  
  newMod = (LDAPMod*)PR_Malloc(sizeof(LDAPMod));
  if (!newMod) {
      return newMod;
  }
  newMod->mod_op = mode;
  newMod->mod_type = PL_strdup(namep);
  if (namep && !newMod->mod_type) {
      deleteMod(newMod);
      return NULL;
  }
  if (values) {
       newMod->mod_values = admutil_strsdup(values);
       if (!newMod->mod_values) {
           deleteMod(newMod);
           return NULL;
       }
  }
  else if (mode != LDAP_MOD_ADD) {
      newMod->mod_values = NULL;
  }
  else {
      /* For LDAP_MOD_ADD attribute value must be specified */
      newMod->mod_values = (char**)PR_Malloc(2*sizeof(char*));
      if (!newMod->mod_values) {
          deleteMod(newMod);
          return NULL;
      }
      newMod->mod_values[0] = PL_strdup("");
      if (!newMod->mod_values[0]) {
          deleteMod(newMod);
          return NULL;
      }
      newMod->mod_values[1] = NULL;
  }

  return newMod;
}

void
deleteMod(LDAPMod* mod)
{
  if (mod) {
    if (mod->mod_type) PR_Free ( mod->mod_type );
    if (mod->mod_values) admutil_strsFree (mod->mod_values);
    PR_Free( mod );
  }
}

void
deleteMods(LDAPMod** mods)
{
  LDAPMod* mod;
  LDAPMod** modsptr = mods;

  if (mods) {
    while ((mod = *modsptr++)) deleteMod(mod);
    PR_Free(mods);
  }

}

/* DT 12/13/97
 * admldapBuildInfo calls this function to unescape the URI and also normalize
 * the uri.  Normalizing the uri converts all "\" characters in the URI
 * and pathinfo portion to "/".  Does not touch "\" in query strings.
 */
PR_IMPLEMENT(void)
admUriUnescape(char *s)
{
    char *t, *u;

    if (!s) {
        return;
    }

    for(t = s, u = s; *t; ++t, ++u) {
        if((*t == '%') && t[1] && t[2]) {
            *u = ((t[1] >= 'A' ? ((t[1] & 0xdf) - 'A')+10 : (t[1] - '0'))*16) +
                  (t[2] >= 'A' ? ((t[2] & 0xdf) - 'A')+10 : (t[2] - '0'));
            t += 2;
        }
        else
            if(u != t)
                *u = *t;
    }
    *u = *t;
}

/*
 * Write the info back to its config file
 */
PR_IMPLEMENT(int)
admldapWriteInfoFile(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  int errorcode = ADMUTIL_OP_OK;
  FILE *fileStream = NULL;

  if (admInfo && admInfo->configInfo && admInfo->configFilePath) {
	if((fileStream = fopen(admInfo->configFilePath, "w")) == NULL) {
	  /* Error open file  */
		errorcode = ADMUTIL_SYSTEM_ERR;
		goto done;
	}
	treeExport(fileStream, NULL, admInfo->configInfo);
  }

done:
  if (fileStream) {
	fclose(fileStream);
  }
  return errorcode;
}

/* This function is useful if you just want to read the adm.conf info
   without opening an ldap connection
*/
PR_IMPLEMENT(AdmldapInfo) 
admldapBuildInfoOnly(char* configRoot, int *errorcode)
{
  FILE           *fileStream;
  AdmldapHdnlPtr admInfo = NULL;
  TreeNodePtr    configInfo = NULL;
  char           *path = NULL;
  char           buf[MAX_LEN],
                 *name= NULL, *password=NULL;
  int            status;
  LDAPURLDesc    *ldapInfo = NULL;

  *errorcode = ADMUTIL_OP_OK;

  path = find_file_in_paths("adm.conf", configRoot);
  if (!path) {
    *errorcode = ADMUTIL_ENV_ERR;
	return NULL;
  }

  if((fileStream = fopen(path, "r")) == NULL) {
    /* Error open file  */
    *errorcode = ADMUTIL_SYSTEM_ERR;
    PR_smprintf_free(path);
    return NULL;
  }

  configInfo = treeImport(fileStream, errorcode);
  fclose(fileStream);

  if (!configInfo) {
    *errorcode = ADMUTIL_SYSTEM_ERR;
    PR_smprintf_free(path);
    return NULL;
  }

  admInfo = (AdmldapHdnlPtr)PR_Malloc(sizeof(AdmldapHdnl));
  if (!admInfo) {
    *errorcode = ADMUTIL_SYSTEM_ERR;
    treeRemoveTree(configInfo);
    PR_smprintf_free(path);
    return NULL;
  }
  memset(admInfo, '\0', sizeof(AdmldapHdnl));

  admInfo->configFilePath = path; /* hand off memory */
  path = NULL;

  admInfo->configInfo = configInfo; /* hand off memory */
  configInfo = NULL;

  if (!(admInfo->serverDirectoryURL = treeFindValueAt(admInfo->configInfo, "ldapurl", 0))) { /* admInfo owns malloced memory now */
    /* Error open file  */
    *errorcode = ADMUTIL_SYSTEM_ERR;
    destroyAdmldap((AdmldapInfo)admInfo);
    return NULL;
  }

  /* Get the console authentication DN and password - if it is set */
  admInfo->authDN = treeFindValueAt(admInfo->configInfo, "authdn", 0);
  admInfo->authPasswd = treeFindValueAt(admInfo->configInfo, "authpw", 0);

  if (admutil_ldap_url_parse(admInfo->serverDirectoryURL, &ldapInfo, 0, &admInfo->secure)) {
    *errorcode = ADMUTIL_SYSTEM_ERR;
    destroyAdmldap((AdmldapInfo)admInfo);
    return NULL;
  }

  admInfo->ldapInfo = ldapInfo;

  /* sieDN owns malloced memory returned by treeFindValueAt */
  admInfo->sieDN = treeFindValueAt(admInfo->configInfo, "sie", 0);

  /* Try to get local admin's name and password  */
  path = find_file_in_paths("admpw", configRoot);
  if (!path) {
    *errorcode = ADMUTIL_ENV_ERR;
    destroyAdmldap((AdmldapInfo)admInfo);
	return NULL;
  }

  if((fileStream = fopen(path, "r")) == NULL) {
    /* Error open file  */
    *errorcode = ADMUTIL_SYSTEM_ERR;
    PR_smprintf_free(path);
    destroyAdmldap((AdmldapInfo)admInfo);
    return NULL;
  }

  switch(status = admutil_getline(fileStream, sizeof(buf), 1, buf)) {
  case -1:
    /* Error on reading, SET ERRORCODE */
    *errorcode = ADMUTIL_SYSTEM_ERR;
    PR_smprintf_free(path);
    destroyAdmldap((AdmldapInfo)admInfo);
	fclose(fileStream);
    return NULL;
    break;
  case 1:
    /* EOF */
  default:
    password = strchr(buf, ':');
    if (password) {
      *password++ = '\0';
      while (*password) {
        if (*password == ' ') password++;
        else break;
      }
    
      name = buf;
      if (*password) {
        *errorcode =  ADMUTIL_OP_OK;
        admInfo->admpwFilePath = path; /* hand off memory */
        path = NULL;
        admInfo->localAdminName = PL_strdup(name);
        admInfo->localAdminPassword = PL_strdup(password);
      } else {
        *errorcode =  ADMUTIL_OP_FAIL;
      }
    } else {
      *errorcode =  ADMUTIL_OP_FAIL;
    }
  }
  fclose(fileStream);

  PR_smprintf_free(path);

  if (!treeHasNode((AdmldapInfo)admInfo, "securitydir")) {
    admldapSetSecurityDir((AdmldapInfo)admInfo, configRoot);
  }

  return (AdmldapInfo)admInfo;
}

PR_IMPLEMENT(AdmldapInfo) 
admldapBuildInfoCbk(char* configRoot, char *(*cbk)(), int *errorcode)
{
  char           *siePasswd = NULL;
  char           *siedn = NULL;
  char           *userdn = NULL;
  AdmldapHdnlPtr admInfo = NULL;
  int            ldapError = LDAP_SUCCESS;
  int            secureLDAP = 0;

  *errorcode = ADMUTIL_OP_OK;
  admInfo = (AdmldapHdnlPtr)admldapBuildInfoOnly(configRoot, errorcode);
  if (*errorcode != ADMUTIL_OP_OK) {
	  return (AdmldapInfo)admInfo;
  }

  /* returned value from ADM_Get... should NOT be freed */
  ADM_GetCurrentPassword(errorcode, &siePasswd); /* via PIPE */

  if (admldapGetSecurity((AdmldapInfo)admInfo)) {
    *errorcode = ADMUTIL_NO_SSL_SUPPORT;
    secureLDAP = 1;
  }

  /*  if userdn is initialized, override the siedn to make bind succeed */
  ADM_GetUserDNString(errorcode, &userdn);
  if (strcasecmp(userdn, ADM_NOT_INITIALIZED)) {
    siedn = admldapGetSIEDN(admInfo);
    admldapSetSIEDN(admInfo, userdn);
    admSetCachedSIEPWD(siePasswd);
  }

  if (!secureLDAP) {
    admInfo->ldapHndl = admutil_ldap_init(admInfo, NULL, admInfo->ldapInfo->lud_host,
                                          admInfo->ldapInfo->lud_port, 0, 0, NULL);
  }

  /* authenticate to LDAP server*/

  /*
   * Attempt to authenticate to the directory.  This code will retry
   * attempts as long as there is a new password available to use.
   */
  {
    int configPassword; /* Indicates password is in config file */
    int retry;          /* Indicates that a previous password failed */

    configPassword = (siePasswd != NULL);
    retry = 0;

    /* Attempt to authenticate */
    for(;;retry = 1)
      {
        /* If the password is in the config file, then quit if it is wrong.
         * Otherwise attempt get a password from the user.
         */
        if (configPassword) {
          if (retry) break;
        } else {
          siePasswd = cbk(retry);
          if (siePasswd == NULL) {
            ldapError = LDAP_INVALID_CREDENTIALS;
            break;
          }
        }

        if (!secureLDAP) {
          ldapError = admutil_ldap_bind(admInfo->ldapHndl, admInfo->sieDN, siePasswd, LDAP_SASL_SIMPLE, NULL, NULL, NULL, NULL);
          if (ldapError == LDAP_SUCCESS) break;

          /* Quit on errors other than password problems */
          if (ldapError != LDAP_INVALID_CREDENTIALS) break;
        }
      }
  }

  if ((ldapError != LDAP_SUCCESS ) && !(secureLDAP)){
#ifdef LDAP_DEBUG
    fprintf(stderr, "admutil_ldap_bind: [%s] error [%d:%s]\n", admInfo->sieDN, ldapError, ldap_err2string(ldapError));
#endif
      switch (ldapError) {
      case LDAP_INAPPROPRIATE_AUTH:
      case LDAP_INVALID_CREDENTIALS:
      case LDAP_INSUFFICIENT_ACCESS:
        /* authenticate failed: Should not continue */
        ldap_unbind_ext(admInfo->ldapHndl, NULL, NULL);
        admInfo->ldapHndl = NULL;
        *errorcode = ADMUTIL_LDAP_ERR;
        break;;
      case LDAP_NO_SUCH_OBJECT:
      case LDAP_ALIAS_PROBLEM:
      case LDAP_INVALID_DN_SYNTAX:
        /* Not a good user DN */
        ldap_unbind_ext(admInfo->ldapHndl, NULL, NULL);
        admInfo->ldapHndl = NULL;
        *errorcode = ADMUTIL_LDAP_ERR;
        break;
      default:
        ldap_unbind_ext(admInfo->ldapHndl, NULL, NULL);
        admInfo->ldapHndl = NULL;
        *errorcode = ADMUTIL_LDAP_ERR;
      }
  }

  /* setup the referral */
  if (admInfo->ldapHndl)
  {
      ldap_set_rebind_proc(admInfo->ldapHndl, admin_ldap_rebind_proc,
                             (void *)admInfo);
  }

  if (siePasswd != NULL) {
      /* returned value from ADM_Get... should NOT be freed */
      admInfo->passwd=PL_strdup(siePasswd);
  }

  /* Reset the siedn if we changed it*/
  if (siedn) {
    admldapSetSIEDN(admInfo, siedn);
    PL_strfree(siedn);
  }

  return (AdmldapInfo)admInfo;
}

static char *
cachedPwdCbk(int retry)
{
  if (retry) return NULL;

  return admGetCachedSIEPWD();
}

PR_IMPLEMENT(AdmldapInfo) 
admldapBuildInfo(char* configRoot, int *errorcode)
{
  return admldapBuildInfoCbk(configRoot, cachedPwdCbk, errorcode);
}

PR_IMPLEMENT(void)
destroyAdmldap(AdmldapInfo info)
{
  AdmldapHdnlPtr   admInfo = (AdmldapHdnlPtr)info;
  if (admInfo) {
    treeRemoveTree(admInfo->configInfo);
    admInfo->configInfo = NULL;
    if (admInfo->configFilePath) { 
      PR_Free(admInfo->configFilePath); 
      admInfo->configFilePath=NULL; 
    }
    if (admInfo->serverDirectoryURL) {
      PR_Free(admInfo->serverDirectoryURL);
      admInfo->serverDirectoryURL = NULL; 
    }
    if (admInfo->admpwFilePath) {
      PR_Free(admInfo->admpwFilePath);
      admInfo->admpwFilePath = NULL;
    }
    if (admInfo->localAdminName) {
      PR_Free(admInfo->localAdminName);
      admInfo->localAdminName = NULL;
    }
    if (admInfo->localAdminPassword) {
      memset(admInfo->localAdminPassword, '\0', strlen(admInfo->localAdminPassword));
      PR_Free(admInfo->localAdminPassword);
      admInfo->localAdminPassword = NULL;
    }
    if (admInfo->sieDN) {
      PR_Free(admInfo->sieDN);
      admInfo->sieDN = NULL;
    }
    if (admInfo->userDN) {
      PR_Free(admInfo->userDN);
      admInfo->userDN = NULL;
    }
    if (admInfo->passwd) {
      memset(admInfo->passwd, '\0', strlen(admInfo->passwd));
      PR_Free(admInfo->passwd);
      admInfo->passwd = NULL;
    }
    if (admInfo->ldapHndl) {
      ldap_unbind_ext(admInfo->ldapHndl, NULL, NULL);
      admInfo->ldapHndl = NULL;
    }
    
    if (admInfo->ldapInfo) {
      ldap_free_urldesc(admInfo->ldapInfo);
      admInfo->ldapInfo = NULL;
    }
    PR_Free(admInfo);
  }
}

/*
 * Take a version string: ssl3, tls1.2, ..., tls2.1, etc,
 * and return the NSS version number.
 */
static int
getSSLVersion(char *version)
{
    if( version == NULL ){
        return 0;
    }
	if( !strcasecmp(version, "ssl3") ){
        return SSL_LIBRARY_VERSION_3_0;
    } else {
        char *copy = strdup(version);
        char *iter = NULL;
        char *comp;
        char *endp = NULL;
        PRUint16 major, minor, ssl_version = 0;

        if( strncasecmp(version,"tls",3) == 0 ){
            char *m = copy  + 3;
            if((comp = strtok_r(m, ".", &iter))) {
                major = strtol(comp, &endp, 10);
                if( major > 0 ){
                    major = (major + 2) << 8;
                    if (( comp = strtok_r(NULL, ".", &iter) )){
                        minor = strtol(comp, &endp, 10);
                        if( minor >= 0 && errno != ERANGE ){
                            minor = (minor & 0xff);
                            ssl_version = major + minor;
                            if( (ssl_version & SSL_LIBRARY_VERSION_3_0) == SSL_LIBRARY_VERSION_3_0 ){
                                ssl_version++;
                            }
                        }
                    }
                }
            }
        }
        PL_strfree(copy);
        return ssl_version;
    }
}

PR_IMPLEMENT(int)
admldapGetSSLMin(AdmldapInfo info)
{
    SSLVersionRange range;
    AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
    int version = getSSLVersion(treeFindValueAt(admInfo->configInfo, "sslVersionMin", 0));

    if(!version){
        return SSL_LIBRARY_VERSION_TLS_1_0;
    } else {
        SSL_VersionRangeGetSupported(ssl_variant_stream, &range);
        if (version < range.min){
            version = range.min;
        }
        return version;
    }
}

PR_IMPLEMENT(int)
admldapGetSSLMax(AdmldapInfo info)
{
    SSLVersionRange range;
    AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
    int version = getSSLVersion(treeFindValueAt(admInfo->configInfo, "sslVersionMax", 0));

    SSL_VersionRangeGetSupported(ssl_variant_stream, &range);
    if(!version){
        return range.max;
    } else {
        SSL_VersionRangeGetSupported(ssl_variant_stream, &range);
        if (version > range.max){
            version = range.max;
        }
        return version;
    }
}

PR_IMPLEMENT(char*)
admldapGetHost(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  return PL_strdup(admInfo->ldapInfo->lud_host);
}

PR_IMPLEMENT(int)
admldapGetPort(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  return admInfo->ldapInfo->lud_port;

}

PR_IMPLEMENT(int)
admldapGetSecurity(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  int            ldapSecurity;

  if (admInfo->secure) ldapSecurity = 1;
  else ldapSecurity = 0;

  return ldapSecurity;

}

PR_IMPLEMENT(char*)
admldapGetBaseDN(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  char          *ldapBaseDN;

  if (admInfo->ldapInfo->lud_dn) ldapBaseDN = PL_strdup(admInfo->ldapInfo->lud_dn);
  else ldapBaseDN = NULL;

  return ldapBaseDN;

}

PR_IMPLEMENT(char*)
admldapGetSecurityDir(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  char          *securitydir;

  securitydir = treeFindValueAt(admInfo->configInfo, "securitydir", 0);
  return securitydir;
}

PR_IMPLEMENT(int)
admldapSetSecurityDir(AdmldapInfo info, const char *securityDir)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  int removeFlag = 0;

  if (securityDir) {
	/* remove old one */
	treeRemoveNode(admInfo->configInfo, "securitydir", &removeFlag);
	treeAddNameValue(admInfo->configInfo, "securitydir", (char *)securityDir);
  }

  return ADMUTIL_OP_OK;
}

PR_IMPLEMENT(char*)
admldapGetSIEDN(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  char          *ldapSIEDN = NULL;

  ldapSIEDN = treeFindValueAt(admInfo->configInfo, "sie", 0);
  return ldapSIEDN;
}

PR_IMPLEMENT(int)
admldapSetSIEDN(AdmldapInfo info, const char *sieDN)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  int removeFlag = 0;

  if (sieDN) {
	/* remove old one */
	PL_strfree(admInfo->sieDN);
	treeRemoveNode(admInfo->configInfo, "sie", &removeFlag);
	/* add new one */
	admInfo->sieDN = PL_strdup(sieDN);
	treeAddNameValue(admInfo->configInfo, "sie", (char *)sieDN);
  }

  return ADMUTIL_OP_OK;
}

PR_IMPLEMENT(char*)
admldapGetSIEPWD(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  if(admInfo->passwd)
    return PL_strdup(admInfo->passwd);
  else {
    admInfo->passwd = admGetCachedSIEPWD();
    if(admInfo->passwd)
      return PL_strdup(admInfo->passwd);
  }
  return NULL;
}

PR_IMPLEMENT(char*)
admldapGetISIEDN(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  char          *ldapISIEDN = NULL;

  ldapISIEDN = treeFindValueAt(admInfo->configInfo, "isie", 0);
  return ldapISIEDN;

}

PR_IMPLEMENT(int)
admldapSetISIEDN(AdmldapInfo info, const char *isieDN)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  int removeFlag = 0;

  if (isieDN) {
	/* remove old one */
	treeRemoveNode(admInfo->configInfo, "isie", &removeFlag);
	treeAddNameValue(admInfo->configInfo, "isie", (char *)isieDN);
  }

  return ADMUTIL_OP_OK;
}

PR_IMPLEMENT(char *)
admldapGetDirectoryURL(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  return PL_strdup(admInfo->serverDirectoryURL);
}

PR_IMPLEMENT(int)
admldapSetDirectoryURL(AdmldapInfo info, const char *ldapurl)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  LDAPURLDesc    *ldapInfo;
  int errorcode = ADMUTIL_OP_OK;
  int removeFlag = 0;
  int secure = 0;

  if (!ldapurl || admutil_ldap_url_parse(ldapurl, &ldapInfo, 0, &secure)) {
	/* if the given url is not valid, don't do anything, just return an error */
    errorcode = ADMUTIL_SYSTEM_ERR;
    goto done;
  }

  /* The url is stored in 3 places in 3 different formats:
	 1 - the serverDirectoryURL string
	 2 - parsed in the ldapInfo structure
	 3 - the "ldapurl" key in the configinfo
  */
  /* first, free the old values */
  if (admInfo->ldapInfo) {
    ldap_free_urldesc(admInfo->ldapInfo);
  }
  PL_strfree(admInfo->serverDirectoryURL);
  treeRemoveNode(admInfo->configInfo, "ldapurl", &removeFlag);

  /* set the new values */
  admInfo->serverDirectoryURL = PL_strdup(ldapurl);
  admInfo->ldapInfo = ldapInfo;
  admInfo->secure = secure;
  treeAddNameValue(admInfo->configInfo, "ldapurl", (char *)ldapurl);

done:
  return errorcode;
}

PR_IMPLEMENT(void)
admldapSetLDAPHndl(AdmldapInfo info, LDAP *ld)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  if (admInfo->ldapHndl) ldap_unbind_ext(admInfo->ldapHndl, NULL, NULL);
  admInfo->ldapHndl = ld;
}

LDAP *
admldapGetLDAPHndl(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  return admInfo->ldapHndl;
}

PR_IMPLEMENT(char*)
admldapGetSysUser(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  char          *sysuser = NULL;

  sysuser = treeFindValueAt(admInfo->configInfo, "sysuser", 0);
  return sysuser;

}

PR_IMPLEMENT(char*)
admldapGetSysGroup(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  char          *sysgroup = NULL;

  sysgroup = treeFindValueAt(admInfo->configInfo, "sysgroup", 0);
  return sysgroup;

}

PR_IMPLEMENT(char*)
admldapGetAdminDomain(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  char          *admindomain = NULL;

  admindomain = treeFindValueAt(admInfo->configInfo, "AdminDomain", 0);
  return admindomain;

}

PR_IMPLEMENT(char*)
admldapGetExpressRefreshRate(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  char          *expressrefreshrate = NULL;

  expressrefreshrate = treeFindValueAt(admInfo->configInfo, "ExpressRefreshRate", 0);
  return expressrefreshrate;

}

PR_IMPLEMENT(char*)
admldapGetExpressCGITimeout(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  char          *expresscgitimeout = NULL;

  expresscgitimeout = treeFindValueAt(admInfo->configInfo, "ExpressCGITimeout", 0);
  return expresscgitimeout;

}

PR_IMPLEMENT(char*)
admldapGetLdapStart(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  char          *ldapstart = NULL;

  ldapstart = treeFindValueAt(admInfo->configInfo, "ldapStart", 0);
  return ldapstart;

}

PR_IMPLEMENT(char*)
admldapGetAuthDN(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  return admInfo->authDN;
}

PR_IMPLEMENT(char*)
admldapGetAuthPasswd(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  return admInfo->authPasswd;
}

PR_IMPLEMENT(char*)
admldapGetConfigFileName(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  return PL_strdup(admInfo->configFilePath);
}

PR_IMPLEMENT(char*)
admldapGetAdmpwFilePath(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  return PL_strdup(admInfo->admpwFilePath);
}

PR_IMPLEMENT(char*)
admldapGetLocalAdminName(AdmldapInfo info)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;

  return PL_strdup(admInfo->localAdminName);
}

PR_IMPLEMENT(char *)
admldapGetUserDN(AdmldapInfo info, char *uid)
{
  AdmldapHdnlPtr admInfo = (AdmldapHdnlPtr)info;
  LDAP *ld = NULL;
  char *userDN = NULL;
  char *baseDN = NULL;
  char *uidFilter = NULL;
  int err;
  LDAPMessage *result = NULL;
		  
  if (NULL != admInfo->userDN) {
    userDN = admInfo->userDN;
    goto done;
  }
  if (NULL != uid && NULL != strchr(uid, '=')) {
    userDN = PL_strdup(uid);
	goto done;
  }
  ld = admldapGetLDAPHndl(info);
  if (NULL == ld) {
	goto done;
  }
  baseDN = admldapGetBaseDN(info);
  if (NULL == baseDN) {
	goto done;
  }
  uidFilter = PR_smprintf("(uid=%s)", uid?uid:admInfo->localAdminName);
  err = ldap_search_ext_s(ld, baseDN, LDAP_SCOPE_SUBTREE, uidFilter,
                          NULL, 0, NULL, NULL, NULL, -1, &result);
  if (err != LDAP_SUCCESS || ldap_count_entries(ld, result) == 0) {
    ldap_msgfree(result);
	goto done;
  } else {
    LDAPMessage *entry = ldap_first_entry(ld, result);
    userDN = ldap_get_dn(ld, entry);
    ldap_msgfree(result);
  }
done:
  PR_smprintf_free(uidFilter);
  if (baseDN) PR_Free(baseDN);
  if (userDN && (userDN != admInfo->userDN)) {
    PL_strfree(admInfo->userDN); /* free old one, if any */
    admInfo->userDN = userDN;
  } else {
    userDN = treeFindValueAt(admInfo->configInfo, "userdn", 0);
	if (userDN) {
      admInfo->userDN = userDN;
    } else {
      admInfo->userDN = NULL;
    }
  }
  return admInfo->userDN ? PL_strdup(admInfo->userDN) : NULL;
}

PR_IMPLEMENT(char*)
admGetLocalAdmin(char* configRoot, int *errorcode)
{
  FILE          *fileStream;
  char          *path = NULL, buf[MAX_LEN], *name = NULL;
  int           status;

  *errorcode = ADMUTIL_OP_OK;

  /* Try to get local admin's name and password  */
  path = find_file_in_paths("admpw", configRoot);
  if (!path) {
      *errorcode = ADMUTIL_ENV_ERR;
      return NULL;
  }

  if((fileStream = fopen(path, "r")) == NULL) {
    /* Error open file  */
    *errorcode = ADMUTIL_SYSTEM_ERR;
    PR_smprintf_free(path);
    return NULL;
  }
  PR_smprintf_free(path);
  path = NULL;

  switch(status = admutil_getline(fileStream, sizeof(buf), 1, buf)) {
  case -1:
    /* Error on reading, SET ERRORCODE */
    *errorcode = ADMUTIL_SYSTEM_ERR;
    fclose(fileStream);
    return NULL;
    break;
  case 1:
    /* EOF */
    /*    
    *errorcode =  ADMUTIL_OP_FAIL;
    return NULL;
    break;
    */
  default:
    fclose(fileStream);
    name = strtok(buf, ":");
    if (!name) {
      *errorcode =  ADMUTIL_OP_FAIL;
      return NULL;
    }
    else {
      *errorcode =  ADMUTIL_OP_OK;
      return PL_strdup(name);
    }
  }
}

static char *cachedSIEPWD = NULL;
 
/*
 * admSetCachedSIEPWD
 */
PR_IMPLEMENT(void)
admSetCachedSIEPWD(const char *pwd)
{
  if (cachedSIEPWD) {
    memset(cachedSIEPWD, '\0', strlen(cachedSIEPWD));
    PR_Free(cachedSIEPWD);
  }

  cachedSIEPWD = PL_strdup(pwd);
}

 
/*
 * admGetCachedSIEPWD 
 */
PR_IMPLEMENT(char *)
admGetCachedSIEPWD()
{
  char *result = NULL;

  if (cachedSIEPWD) result = PL_strdup(cachedSIEPWD);

  return result;
}

#if defined(USE_OPENLDAP)
/* mozldap ldap_init and ldap_url_parse accept a hostname in the form
   host1[:port1]SPACEhost2[:port2]SPACEhostN[:portN]
   where SPACE is a single space (0x20) character
   for openldap, we have to convert this to a string like this:
   PROTO://host1[:port1]/SPACEPROTO://host2[:port2]/SPACEPROTO://hostN[:portN]/
   where PROTO is ldap or ldaps or ldapi
   if proto is NULL, assume hostname_or_uri is really a valid ldap uri
*/
static char *
convert_to_openldap_uri(const char *hostname_or_uri, int port, const char *proto)
{
  char *retstr = NULL;
  char *my_copy = NULL;
  char *start = NULL;
  char *iter = NULL;
  char *s = NULL;
  const char *brkstr = " ";

  if (!hostname_or_uri) {
	return NULL;
  }

  my_copy = PL_strdup(hostname_or_uri);
  /* see if hostname_or_uri is an ldap uri */
  if (!proto && !PL_strncasecmp(my_copy, "ldap", 4)) {
    start = my_copy + 4;
    if ((*start == 's') || (*start == 'i')) {
      start++;
    }
    if (!PL_strncmp(start, "://", 3)) {
      *start = '\0';
      proto = my_copy;
      start += 3;
    } else {
#ifdef DEBUG
      fprintf(stderr, "convert_to_openldap_uri: The given LDAP URI [%s] is not valid\n", hostname_or_uri);
#endif
      goto end;
    }
  } else if (!proto) {
#ifdef DEBUG
    fprintf(stderr, "convert_to_openldap_uri: The given LDAP URI [%s] is not valid\n", hostname_or_uri);
#endif
    goto end;
  } else {
    start = my_copy; /* just assume it's not a uri */
  }
	    
  for (s = strtok_r(my_copy, brkstr, &iter); s != NULL;
       s = strtok_r(NULL, brkstr, &iter)) {
	char *ptr;
	int last = 0;
	/* strtok will grab the '/' at the end of the uri, if any,
	   so terminate parsing there */
	if ((ptr = strchr(s, '/'))) {
      *ptr = '\0';
      last = 1;
	}
	if (retstr) {
      retstr = PR_sprintf_append(retstr, "/ %s://%s", proto, s);
	} else {
      retstr = PR_smprintf("%s://%s", proto, s);
	}
	if (last) {
      break;
	}
  }

  /* add the port on the last one */
  retstr = PR_sprintf_append(retstr, ":%d/", port);
end:
  PL_strfree(my_copy);
  return retstr;    
}
#endif /* USE_OPENLDAP */

const char *
admutil_urlparse_err2string( int err )
{
  const char *s="internal error";

  switch( err ) {
  case 0:
	s = "no error";
	break;
  case LDAP_URL_ERR_BADSCOPE:
	s = "invalid search scope";
	break;
  case LDAP_URL_ERR_MEM:
	s = "unable to allocate memory";
	break;
  case LDAP_URL_ERR_PARAM:
	s = "bad parameter to an LDAP URL function";
	break;
#if defined(USE_OPENLDAP)
  case LDAP_URL_ERR_BADSCHEME:
	s = "does not begin with ldap://, ldaps://, or ldapi://";
	break;
  case LDAP_URL_ERR_BADENCLOSURE:
	s = "missing trailing '>' in enclosure";
	break;
  case LDAP_URL_ERR_BADURL:
	s = "not a valid LDAP URL";
	break;
  case LDAP_URL_ERR_BADHOST:
	s = "hostname part of url is not valid or not given";
	break;
  case LDAP_URL_ERR_BADATTRS:
	s = "attribute list not formatted correctly or missing";
	break;
  case LDAP_URL_ERR_BADFILTER:
	s = "search filter not correct";
	break;
  case LDAP_URL_ERR_BADEXTS:
	s = "extensions not specified correctly";
	break;
#else /* !USE_OPENLDAP */
  case LDAP_URL_ERR_NOTLDAP:
	s = "missing ldap:// or ldaps:// or ldapi://";
	break;
  case LDAP_URL_ERR_NODN:
	s = "missing suffix";
	break;
#endif
  }

  return( s );
}

/* there are various differences among url parsers - directory server
   needs the ability to parse partial URLs - those with no dn - and
   needs to be able to tell if it is a secure url (ldaps) or not */
int
admutil_ldap_url_parse(const char *url, LDAPURLDesc **ludpp, int require_dn, int *secure)
{
  PR_ASSERT(url);
  PR_ASSERT(ludpp);
  int rc;
  const char *url_to_use = url;
#if defined(USE_OPENLDAP)
  char *urlescaped = NULL;
#endif

  if (secure) {
    *secure = 0;
  }
#if defined(USE_OPENLDAP)
  /* openldap does not support the non-standard multi host:port URLs supported
     by mozldap - so we have to fake out openldap - replace all spaces with %20 -
     replace all but the last colon with %3A
     Go to the 3rd '/' or to the end of the string (convert only the host:port part) */
  if (url) {
	char *p = strstr(url, "://");
	if (p) {
      int foundspace = 0;
      int coloncount = 0;
      char *lastcolon = NULL;
      p += 3;
      for (; *p && (*p != '/'); p++) {
		if (*p == ' ') {
          foundspace = 1;
		}
		if (*p == ':') {
          coloncount++;
          lastcolon = p;
		}
      }
      if (foundspace) {
		char *src = NULL, *dest = NULL;
		/* have to convert url */
		/* len * 3 is way too much, but acceptable */
		urlescaped = PR_Calloc(strlen(url) * 3, sizeof(char));
		dest = urlescaped;
		/* copy the scheme */
        src = strstr(url, "://");
		src += 3;
		memcpy(dest, url, src-url);
		dest += (src-url);
		/* we have to convert all spaces to %20 - we have to convert
		   all colons except the last one to %3A */
		for (; *src; ++src) {
          if (src < p) {
			if (*src == ' ') {
              memcpy(dest, "%20", 3);
              dest += 3;
			} else if ((coloncount > 1) && (*src == ':') && (src != lastcolon)) {
              memcpy(dest, "%3A", 3);
              dest += 3;
			} else {
              *dest++ = *src;
			}
          } else {
			*dest++ = *src;
          }
		}
		*dest = '\0';
		url_to_use = urlescaped;
      }
	}
  }
#endif

#if defined(HAVE_LDAP_URL_PARSE_NO_DEFAULTS)
  rc = ldap_url_parse_no_defaults(url_to_use, ludpp, require_dn);
  if (!rc && *ludpp && secure) {
    *secure = (*ludpp)->lud_options & LDAP_URL_OPT_SECURE;
  }
#else /* openldap */
#if defined(HAVE_LDAP_URL_PARSE_EXT) && defined(LDAP_PVT_URL_PARSE_NONE) && defined(LDAP_PVT_URL_PARSE_NOEMPTY_DN)
  rc = ldap_url_parse_ext(url_to_use, ludpp, require_dn ? LDAP_PVT_URL_PARSE_NONE : LDAP_PVT_URL_PARSE_NOEMPTY_DN);
#else
  rc = ldap_url_parse(url_to_use, ludpp);
  if ((rc || !*ludpp) && !require_dn) { /* failed - see if failure was due to missing dn */
    size_t len = strlen(url_to_use);
    /* assume the url is just scheme://host:port[/] - add the empty string
       as the DN (adding a trailing / first if needed) and try to parse
       again
    */
    char *urlcopy = PR_smprintf("%s%s%s", url_to_use, (url_to_use[len-1] == '/' ? "" : "/"), "");
    if (*ludpp) {
      ldap_free_urldesc(*ludpp); /* free the old one, if any */
    }
    rc = ldap_url_parse(urlcopy, ludpp);
    PL_strfree(urlcopy);
    urlcopy = NULL;
    if (0 == rc) { /* only problem was the DN - free it */
      PL_strfree((*ludpp)->lud_dn);
      (*ludpp)->lud_dn = NULL;
    }
  }
#endif
  if (!rc && *ludpp && secure) {
    *secure = (*ludpp)->lud_scheme && !strcmp((*ludpp)->lud_scheme, "ldaps");
  }
#endif /* openldap */

#if defined(USE_OPENLDAP)
  if (urlescaped && (*ludpp) && (*ludpp)->lud_host) {
	/* have to unescape lud_host - can unescape in place */
	char *p = strstr((*ludpp)->lud_host, "://");
	if (p) {
      char *dest = NULL;
      p += 3;
      dest = p;
      /* up to the first '/', unescape the host */
      for (; *p && (*p != '/'); p++) {
		if (!strncmp(p, "%20", 3)) {
          *dest++ = ' ';
          p += 2;
		} else if (!strncmp(p, "%3A", 3)) {
          *dest++ = ':';
          p += 2;
		} else {
          *dest++ = *p;
		}
      }
      /* just copy the remainder of the host, if any */
      while (*p) {
		*dest++ = *p++;
      }
      *dest = '\0';
	}
  }
  PL_strfree(urlescaped);
#endif
  return rc;
}

/*
  Perform LDAP init and return an LDAP* handle.  If ldapurl is given,
  that is used as the basis for the protocol, host, port, and whether
  to use starttls (given on the end as ldap://..../?????starttlsOID
  If hostname is given, LDAP or LDAPS is assumed, and this will override
  the hostname from the ldapurl, if any.  If port is > 0, this is the
  port number to use.  It will override the port in the ldapurl, if any.
  If no port is given in port or ldapurl, the default will be used based
  on the secure setting (389 for ldap, 636 for ldaps)
  secure takes 1 of 2 values - 0 means regular ldap, 1 means ldaps
  filename is the ldapi file name - if this is given, and no other options
  are given, ldapi is assumed.
 */
LDAP *
admutil_ldap_init(
  AdmldapInfo info,
  const char *ldapurl, /* full ldap url */
  const char *hostname, /* can also use this to override
                           host in url */
  int port, /* can also use this to override port in url */
  int secure, /* 0 for ldap, 1 for ldaps */
  int shared, /* if true, LDAP* will be shared among multiple threads */
  const char *filename /* for ldapi */
)
{
  LDAPURLDesc	*ludp = NULL;
  LDAP *ld = NULL;
  int rc = 0;
  int secureurl = 0;
  int ldap_version3 = LDAP_VERSION3;

  /* if ldapurl is given, parse it */
  if (ldapurl && ((rc = admutil_ldap_url_parse(ldapurl, &ludp, 0, &secureurl)) ||
                  !ludp)) {
#ifdef DEBUG
	fprintf(stderr, "admutil_ldap_init: Could not parse given LDAP URL [%s] : error [%s]\n",
                    ldapurl, /* ldapurl cannot be NULL here */
                    admutil_urlparse_err2string(rc));
#endif
	goto done;
  }

  /* use url host if no host given */
  if (!hostname && ludp && ludp->lud_host) {
	hostname = ludp->lud_host;
  }

  /* use url port if no port given */
  if (!port && ludp && ludp->lud_port) {
	port = ludp->lud_port;
  }

  /* use secure setting from url if none given */
  if (!secure && ludp) {
	if (secureurl) {
      secure = 1;
	}
  }

#if defined(USE_OPENLDAP)
  if (ldapurl) {
	rc = ldap_initialize(&ld, ldapurl);
	if (rc) {
#ifdef DEBUG
      fprintf(stderr, "admutil_ldap_init: Could not initialize LDAP connection to [%s]: %d:%s\n",
                      ldapurl, rc, ldap_err2string(rc));
#endif
      goto done;
	}
  } else {
	char *makeurl = NULL;
	if (filename) {
      makeurl = PR_smprintf("ldapi://%s/", filename);
	} else { /* host port */
      makeurl = convert_to_openldap_uri(hostname, port, (secure == 1 ? "ldaps" : "ldap"));
	}
	rc = ldap_initialize(&ld, makeurl);
	if (rc) {
#ifdef DEBUG
      fprintf(stderr, "admutil_ldap_init: Could not initialize LDAP connection to [%s]: %d:%s\n",
                      makeurl, rc, ldap_err2string(rc));
#endif
      PL_strfree(makeurl);
      makeurl = NULL;
      goto done;
	}
    PL_strfree(makeurl);
    makeurl = NULL;
  }
#else /* !USE_OPENLDAP */
  if (filename) {
	/* ldapi in mozldap client is not yet supported */
  } else if (secure == 1) {
	ld = ldapssl_init(hostname, port, secure);
  } else { /* regular ldap and/or starttls */
	/*
	 * Leverage the libprldap layer to take care of all the NSPR
	 * integration.
	 * Note that ldapssl_init() uses libprldap implicitly.
	 */
	ld = prldap_init(hostname, port, shared);
  }
#endif /* !USE_OPENLDAP */

  /* must explicitly set version to 3 */
  ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldap_version3);

  if ((ld != NULL) && !filename) {
	/*
	 * Set SSL strength (server certificate validity checking).
	 */
	if (secure > 0) {
#if defined(USE_OPENLDAP)
      char *certdir = admldapGetSecurityDir(info);
      int optval = 0;
#else
      LDAP *myld = NULL;
#endif /* !USE_OPENLDAP */
      int ssl_strength = 0;

#if !defined(USE_OPENLDAP)
      /* we can only use the set functions below with a real
         LDAP* if it has already gone through ldapssl_init -
         so, use NULL if using starttls */
      if (secure == 1) {
		myld = ld;
      }
#endif
		/* verify certificate only */
#if defined(USE_OPENLDAP)
      ssl_strength = LDAP_OPT_X_TLS_NEVER;
#else /* !USE_OPENLDAP */
      ssl_strength = LDAPSSL_AUTH_CERT;
#endif /* !USE_OPENLDAP */

#if defined(USE_OPENLDAP)
      if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &ssl_strength))) {
#ifdef DEBUG
		fprint(stderr, "admutil_ldap_init: "
                        "failed: unable to set REQUIRE_CERT option to %d\n", ssl_strength);
#endif
      }
      /* tell it where our cert db is */
      if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR, certdir))) {
#ifdef DEBUG
		fprintf(stderr, "admutil_ldap_init: "
                        "failed: unable to set CACERTDIR option to %s\n", certdir);
#endif
      }
      PL_strfree(certdir);
      certdir = NULL;
#if defined(LDAP_OPT_X_TLS_PROTOCOL_MIN)
      optval = LDAP_OPT_X_TLS_PROTOCOL_SSL3;
      if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_PROTOCOL_MIN, &optval))) {
#ifdef DEBUG
		fprintf(stderr, "admutil_ldap_init: "
                        "failed: unable to set minimum TLS protocol level to SSL3\n");
#endif
      }
#endif /* LDAP_OPT_X_TLS_PROTOCOL_MIN */
      if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &optval))) {
#ifdef DEBUG
		fprintf(stderr, "admutil_ldap_init: "
                        "failed: unable to create new TLS context\n");
#endif
      }
#else  /* !USE_OPENLDAP */
      if ((rc = ldapssl_set_strength(myld, ssl_strength)) ||
          (rc = ldapssl_set_option(myld, SSL_ENABLE_SSL2, PR_FALSE)) ||
          (rc = ldapssl_set_option(myld, SSL_ENABLE_SSL3, PR_TRUE)) ||
          (rc = ldapssl_set_option(myld, SSL_ENABLE_TLS, PR_TRUE))) {
		int prerr = PR_GetError();

#ifdef DEBUG
		fprintf(stderr, "admutil_ldap_init: "
                        "failed: unable to set SSL options ("
                        "error %d - %s)\n",
                         prerr, PR_ErrorToString(prerr, PR_LANGUAGE_I_DEFAULT));
#endif
      }
      if (secure == 1) {
		/* tell bind code we are using SSL */
		ldap_set_option(ld, LDAP_OPT_SSL, LDAP_OPT_ON);
      }
#endif /* !USE_OPENLDAP */
	}
  }

#ifdef DEBUG
  fprintf(stderr, "admutil_ldap_init: "
                  "Success: set up conn to [%s:%d]%s\n",
                  hostname, port,
                  secure ? " using TLS/SSL" : "");
#endif
done:
  ldap_free_urldesc(ludp);

  return( ld );
}

static int
admutil_ldap_get_lderrno(LDAP *ld, char **m, char **s)
{
  int rc = LDAP_SUCCESS;

#if defined(USE_OPENLDAP)
  ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &rc);
  if (m) {
    ldap_get_option(ld, LDAP_OPT_MATCHED_DN, m);
  }
  if (s) {
#ifdef LDAP_OPT_DIAGNOSTIC_MESSAGE
    ldap_get_option(ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, s);
#else
    ldap_get_option(ld, LDAP_OPT_ERROR_STRING, s);
#endif
  }
#else /* !USE_OPENLDAP */
  rc = ldap_get_lderrno( ld, m, s );
#endif
  return rc;
}

#ifndef LDAP_SASL_EXTERNAL
#define LDAP_SASL_EXTERNAL      "EXTERNAL"      /* TLS/SSL extension */
#endif

/*
 * Does the correct bind operation simple/sasl/cert depending
 * on the arguments passed in.
 */
int
admutil_ldap_bind(
    LDAP *ld, /* ldap connection */
    const char *bindid, /* usually a bind DN for simple bind */
    const char *creds, /* usually a password for simple bind */
    const char *mech, /* name of mechanism */
    LDAPControl **serverctrls, /* additional controls to send */
    LDAPControl ***returnedctrls, /* returned controls */
    struct timeval *timeout, /* timeout */
    int *msgidp /* pass in non-NULL for async handling */
)
{
  int rc = LDAP_SUCCESS;
  int err = LDAP_SUCCESS;
  struct berval bvcreds = {0, NULL};
  LDAPMessage *result = NULL;
  struct berval *servercredp = NULL;

  bvcreds.bv_val = (char *)creds;
  bvcreds.bv_len = creds ? strlen(creds) : 0;

  /* The connection has been set up - now do the actual bind, depending on
     the mechanism and arguments */
  if (!mech || (mech == LDAP_SASL_SIMPLE) ||
      !strcmp(mech, LDAP_SASL_EXTERNAL)) {
	int mymsgid = 0;
#ifdef DEBUG
	fprintf(stderr, "admutil_ldap_bind: "
                    "attempting %s bind with id [%s] creds [%s]\n",
                    mech ? mech : "SIMPLE",
                    bindid, creds);
#endif
	if ((rc = ldap_sasl_bind(ld, bindid, mech, &bvcreds, serverctrls,
                             NULL /* clientctrls */, &mymsgid))) {
#ifdef DEBUG
      fprintf(stderr, "admutil_ldap_bind: "
                      "Error: could not send bind request for id "
                      "[%s] mech [%s]: error %d (%s) %d (%s) %d (%s)\n",
                      bindid ? bindid : "(anon)",
                      mech ? mech : "SIMPLE",
                      rc, ldap_err2string(rc),
                      PR_GetError(), PR_ErrorToString(PR_GetError(), PR_LANGUAGE_I_DEFAULT),
                      errno, strerror(errno));
#endif
      goto done;
	}

	if (msgidp) { /* let caller process result */
      *msgidp = mymsgid;
	} else { /* process results */
      rc = ldap_result(ld, mymsgid, LDAP_MSG_ALL, timeout, &result);
      if (-1 == rc) { /* error */
		rc = admutil_ldap_get_lderrno(ld, NULL, NULL);
#ifdef DEBUG
		fprintf(stderr, "admutil_ldap_bind: "
                        "Error reading bind response for id "
                        "[%s] mech [%s]: error %d (%s)\n",
                        bindid ? bindid : "(anon)",
                        mech ? mech : "SIMPLE",
                        rc, ldap_err2string(rc));
#endif
		goto done;
      } else if (rc == 0) { /* timeout */
		rc = LDAP_TIMEOUT;
#ifdef DEBUG
		fprintf(stderr, "admutil_ldap_bind: "
                        "Error: timeout after [%ld.%ld] seconds reading "
                        "bind response for [%s] mech [%s]\n",
                        timeout ? timeout->tv_sec : 0,
                        timeout ? timeout->tv_usec : 0,
                        bindid ? bindid : "(anon)",
                        mech ? mech : "SIMPLE");
#endif
		goto done;
      }
      /* if we got here, we were able to read success result */
      /* Get the controls sent by the server if requested */
      if ((rc = ldap_parse_result(ld, result, &err, NULL, NULL,
                                  NULL, returnedctrls,
                                  0)) != LDAP_SUCCESS) {
#ifdef DEBUG
          fprintf(stderr, "admutil_ldap_bind: "
                          "Error: could not parse bind result:"
                          " mech [%s]: error %d (%s)\n",
                          mech ? mech : "SIMPLE",
                          rc, ldap_err2string(rc));
#endif
          goto done;
      }

      if(err){
          rc = err;
#ifdef DEBUG
          fprintf(stderr, "admutil_ldap_bind: "
                          "Error: could not bind id "
                          "[%s] mech [%s]: error %d (%s)\n",
                          bindid ? bindid : "(anon)",
                          mech ? mech : "SIMPLE",
                          rc, ldap_err2string(rc));
#endif
          goto done;
      } 

      /* parse the bind result and get the ldap error code */
      if ((rc = ldap_parse_sasl_bind_result(ld, result, &servercredp,
                                            0))) {
		rc = admutil_ldap_get_lderrno(ld, NULL, NULL);
#ifdef DEBUG
        fprintf(stderr, "admutil_ldap_bind: "
                        "Error: could not read bind results for id "
                        "[%s] mech [%s]: error %d (%s)\n",
                        bindid ? bindid : "(anon)",
                        mech ? mech : "SIMPLE",
                        rc, ldap_err2string(rc));
#endif
		goto done;
      }
	}
  } else {
    rc = -1;
#ifdef SASL_AUTH_SUPPORTED
	/* a SASL mech */
	rc = slapd_ldap_sasl_interactive_bind(ld, bindid, creds, mech,
                                          serverctrls, returnedctrls,
                                          msgidp);
	if (LDAP_SUCCESS != rc) {
#ifdef DEBUG
      fprintf(stderr, "admutil_ldap_bind: "
                      "Error: could not perform interactive bind for id "
                      "[%s] mech [%s]: error %d (%s)\n",
                      bindid ? bindid : "(anon)",
                      mech, /* mech cannot be SIMPLE here */
                      rc, ldap_err2string(rc));
#endif
	}
#endif /* SASL_AUTH_SUPPORTED */
  }

done:
  ber_bvfree(servercredp);
  ldap_msgfree(result);

  return rc;
}

#if defined(USE_OPENLDAP)
#define LDAP_DN         1
#define LDAP_RDN        2

#define INQUOTE         1
#define OUTQUOTE        2

#define LDAP_UTF8LEN(s)  ((0x80 & *(unsigned char*)(s)) ?   ldap_utf8len (s) : 1)

static char UTF8len[64]
= {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
   2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 6};

static char*
ldap_utf8next (char* s)
     /* Return a pointer to the character immediately following *s.
        Handle any valid UTF-8 character, including '\0' and ASCII.
        Try to handle a misaligned pointer or a malformed character.
     */
{
    register unsigned char* next = (unsigned char*)s;
    switch (UTF8len [(*next >> 2) & 0x3F]) {
      case 0: /* erroneous: s points to the middle of a character. */
      case 6: if ((*++next & 0xC0) != 0x80) break;
      case 5: if ((*++next & 0xC0) != 0x80) break;
      case 4: if ((*++next & 0xC0) != 0x80) break;
      case 3: if ((*++next & 0xC0) != 0x80) break;
      case 2: if ((*++next & 0xC0) != 0x80) break;
      case 1: ++next;
    }
    return (char*) next;
}

static int
ldap_utf8len (const char* s)
     /* Return the number of char's in the character at *s. */
{
    return ldap_utf8next((char*)s) - s;
}

static int
ldap_utf8isspace( char* s )
{
    register unsigned char *c = (unsigned char*)s;
    int len = ldap_utf8len(s);

    if (len == 0) {
        return 0;
    } else if (len == 1) {
        switch (*c) {
            case 0x09:
            case 0x0A:
            case 0x0B:
            case 0x0C:
            case 0x0D:
            case 0x20:
                return 1;
            default:
                return 0;
        }
    } else if (len == 2) {
        if (*c == 0xc2) {
                return *(c+1) == 0x80;
        }
    } else if (len == 3) {
        if (*c == 0xE2) {
            c++;
            if (*c == 0x80) {
                c++;
                return (*c>=0x80 && *c<=0x8a);
            }
        } else if (*c == 0xE3) {
            return (*(c+1)==0x80) && (*(c+2)==0x80);
        } else if (*c==0xEF) {
            return (*(c+1)==0xBB) && (*(c+2)==0xBF);
        }
        return 0;
    }

    /* should never reach here */
    return 0;
}

static char **
mozldap_ldap_explode( const char *dn, const int notypes, const int nametype )
{
        char    *p, *q, *rdnstart, **rdns = NULL;
        size_t  plen = 0;
        int             state = 0;
        int             count = 0;
        int             startquote = 0;
        int             endquote = 0;
        int             len = 0;
        int             goteq = 0;

        if ( dn == NULL ) {
                dn = "";
        }

        while ( ldap_utf8isspace( (char *)dn )) { /* ignore leading spaces */
                ++dn;
        }

        p = rdnstart = (char *) dn;
        state = OUTQUOTE;

        do {
                p += plen;
                plen = 1;
                switch ( *p ) {
                case '\\':
                        if ( *++p == '\0' )
                                p--;
                        else
                                plen = LDAP_UTF8LEN(p);
                        break;
                case '"':
                        if ( state == INQUOTE )
                                state = OUTQUOTE;
                        else
                                state = INQUOTE;
                        break;
                case '+': if ( nametype != LDAP_RDN ) break;
                case ';':
                case ',':
                case '\0':
                        if ( state == OUTQUOTE ) {
                                /*
                                 * semicolon and comma are not valid RDN
                                 * separators.
                                 */
                                if ( nametype == LDAP_RDN &&
                                        ( *p == ';' || *p == ',' || !goteq)) {
                                        admutil_strsFree( rdns );
                                        return NULL;
                                }
                                if ( (*p == ',' || *p == ';') && !goteq ) {
                                   /* If we get here, we have a case similar
                                    * to <attr>=<value>,<string>,<attr>=<value>
                                    * This is not a valid dn */
                                    admutil_strsFree( rdns );
                                    return NULL;
                                }
                                goteq = 0;
                                ++count;
                                if ( rdns == NULL ) {
                                        if (( rdns = (char **)PR_Malloc( 8
                                                 * sizeof( char *))) == NULL )
                                                return( NULL );
                                } else if ( count >= 8 ) {
                                        if (( rdns = (char **)PR_Realloc(
                                            (char *)rdns, (count+1) *
                                            sizeof( char *))) == NULL )
                                                return( NULL );
                                }
                                rdns[ count ] = NULL;
                                endquote = 0;
                                if ( notypes ) {
                                        for ( q = rdnstart;
                                            q < p && *q != '='; ++q ) {
                                                ;
                                        }
                                        if ( q < p ) { /* *q == '=' */
                                                rdnstart = ++q;
                                        }
                                        if ( *rdnstart == '"' ) {
                                                startquote = 1;
                                                ++rdnstart;
                                        }

                                        if ( (*(p-1) == '"') && startquote ) {
                                                endquote = 1;
                                                --p;
                                        }
                                }

                                len = p - rdnstart;
                                if (( rdns[ count-1 ] = (char *)PR_Calloc(
                                    1, len + 1 )) != NULL ) {
                                        memcpy( rdns[ count-1 ], rdnstart,
                                            len );
                                        if ( !endquote ) {
                                                /* trim trailing spaces */
                                                while ( len > 0 &&
                                                    ldap_utf8isspace(
                                                    &rdns[count-1][len-1] )) {
                                                        --len;
                                                }
                                        }
                                        rdns[ count-1 ][ len ] = '\0';
                                }

                                /*
                                 *  Don't forget to increment 'p' back to where
                                 *  it should be.  If we don't, then we will
                                 *  never get past an "end quote."
                                 */
                                if ( endquote == 1 )
                                        p++;

                                rdnstart = *p ? p + 1 : p;
                                while ( ldap_utf8isspace( rdnstart ))
                                        ++rdnstart;
                        }
                        break;
                case '=':
                        if ( state == OUTQUOTE ) {
                                goteq = 1;
                        }
                        /* FALL */
                default:
                        plen = LDAP_UTF8LEN(p);
                        break;
                }
        } while ( *p );

        return( rdns );
}

static char **
mozldap_ldap_explode_dn( const char *dn, const int notypes )
{
        return( mozldap_ldap_explode( dn, notypes, LDAP_DN ) );
}

static char **
mozldap_ldap_explode_rdn( const char *rdn, const int notypes )
{
        return( mozldap_ldap_explode( rdn, notypes, LDAP_RDN ) );
}
#endif /* USE_OPENLDAP */

char **
admldap_ldap_explode_dn( const char *dn, const int notypes )
{
#if defined(USE_OPENLDAP)
  return mozldap_ldap_explode_dn(dn, notypes);
#else
  return ldap_explode_dn(dn, notypes);
#endif
}

char **
admldap_ldap_explode_rdn( const char *rdn, const int notypes )
{
#if defined(USE_OPENLDAP)
  return mozldap_ldap_explode_rdn(rdn, notypes);
#else
  return ldap_explode_rdn(rdn, notypes);
#endif
}

/*
  emacs settings
  Local Variables:
  c-basic-offset: 2
  End:
*/
