/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc.  Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * END COPYRIGHT BLOCK **/
/* 
 * util.c:  Miscellaneous stuffs
 *            
 * All blame to Mike McCool
 */

#include "libadmin/libadmin.h"
#include "base/util.h"
#include "private/pprio.h"
#include "prprf.h"
#include "prlog.h"
#include "prerror.h"

#ifdef XP_UNIX
#include <dirent.h>
#include <sys/types.h>
#include <fcntl.h>
#else
#include <base/file.h>
#include <sys/stat.h>
#endif /* WIN32? */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <ctype.h>     /* isdigit */
#ifndef USE_OPENLDAP
#include <ldappr.h>
#include <ldap_ssl.h>
#include <ssl.h>
#endif

#define NUM_ENTRIES 64

#ifdef MCC_PROXY
char *
XP_GetString()
{
        return "ZAP";
}
#endif

#ifdef XP_WIN32
char *GetQueryNT(void)
{
    char *qs = getenv("QUERY_STRING");
    if(qs && (*qs == '\0'))
        qs = NULL;
    return qs;
}
#endif  /* XP_WIN32 */

void escape_for_shell(char *cmd) {
    register int x,y,l;

    l=strlen(cmd);
    for(x=0;cmd[x];x++) {
        if(strchr(" &;`'\"|*!?~<>^()[]{}$\\\x0A",cmd[x])){
            for(y=l+1;y>x;y--)
                cmd[y] = cmd[y-1];
            l++; /* length has been increased */
            cmd[x] = '\\';
            x++; /* skip the character */
        }
    }
}

char dnspecial[]  = {
	'"', '+', ',', ';', '<', '>', '=', '#', '\0'
};

static void
put_dnescaped_char(char **distp, char **srcp)
{
	if (**srcp == '#') {
		char c1 = *((*srcp)+1);
		char c2 = *((*srcp)+2);
		if (isxdigit(c1) && isxdigit(c2)) {
			char *dn = NULL;
			char c;
			int n1 = 0, n2 = 0;
			if ('0' <= c1 && c1 <= '9') {
				n1 = c1 - '0';
			} else if ('a' <= c1 && c1 <= 'f') {
				n1 = c1 - 'a' + 10;
			} else if ('A' <= c1 && c1 <= 'F') {
				n1 = c1 - 'A' + 10;
			}
			if ('0' <= c2 && c2 <= '9') {
				n2 = c2 - '0';
			} else if ('a' <= c2 && c2 <= 'f') {
				n2 = c2 - 'a' + 10;
			} else if ('A' <= c2 && c2 <= 'F') {
				n2 = c2 - 'A' + 10;
			}
			c = n1 * 16 + n2;
			for (dn = dnspecial; dn && *dn; dn++) {
				if (c == *dn) {
					*((*distp)++) = '\\';	/* escape the char with '\\' */
					*((*distp)++) = c;
					break;
				}
			}
			if (c != *dn) {
				*((*distp)++) = c;
			}
			*srcp += 3;
		} else {
			*((*distp)++) = *((*srcp)++);	/* Just copy '#' */
		}
	} else {
		*((*distp)++) = '\\';				/* escape the char with '\\' */
		*((*distp)++) = *((*srcp)++);
	}
}

/*
 * Escape src DN string (RFC 4514)
 * Result
 * Success:
 * 	return value:  0
 *	src includes dn special characters 
 *		--> *dist returns escaped string; caller needs to free it.
 *	src does not include dn special characters --> *dist == NULL
 * Failure
 * 	return value: -1
 */
int
escape_for_dn(char *src, char **dist)
{
	char *srcp = NULL;
	char *distp = NULL;
	char *dn = NULL;
	char *end = NULL;
	int rc = -1;

	if (NULL == dist) {
		return rc;
	}
	*dist = NULL;
	if (NULL == src) {
		return rc;
	}
	end = src + strlen(src);

	for (srcp = src; srcp && *srcp; srcp++) {
		for (dn = dnspecial; dn && *dn; dn++) {
			if (*srcp == *dn) {
				goto out;
			}
		}
	}
out:
	rc = 0;
	if (*srcp && (*srcp == *dn)) { /* src includes a dn special character */
		char *distend = NULL;
		int maxdistlen = ((srcp - src) + (end - srcp) * 2) + 1;
		/* max necessary length */
		*dist =	(char *)PR_Calloc(sizeof(char), maxdistlen);
		if (NULL == *dist) {
			rc = -1;
			goto bail;
		}
		distend = *dist + maxdistlen;
		memcpy(*dist, src, srcp - src); /* copy non escaped part */
		distp = *dist + (srcp - src);
		put_dnescaped_char(&distp, &srcp);
		while (srcp && *srcp && distp && distp < distend) {
			for (dn = dnspecial; dn && *dn; dn++) {
				if (*srcp == *dn) {
					put_dnescaped_char(&distp, &srcp);
					break;
				}
			}
			if ('\0' == *dn) {
				*distp++ = *srcp++;
			}
		}
		if (distp == distend) {
			PR_Free(*dist);
			*dist = NULL;
			rc = -1;
		}
	}
bail:
	return rc;
}

int _admin_dumbsort(const void *s1, const void *s2)
{
    return strcmp(*((char **)s1), *((char **)s2));
}

#ifdef XP_UNIX /* WIN32 change */
/* Lists all files in a directory. */
char **list_directory(char *path, int dashA)  
{
    char **ar;
    DIR *ds;
    struct dirent *d;
    int n, p;

    n = NUM_ENTRIES;
    p = 0;

    ar = (char **) MALLOC(n * sizeof(char *));

    if(!(ds = opendir(path))) {
        free(ar);
        return NULL;
    }

    while( (d = readdir(ds)) ) {
        if ( ( d->d_name[0] != '.' ) ||
	     ( dashA && d->d_name[1] &&
	       ( d->d_name[1] != '.' || d->d_name[2] ) ) ) {
            if(p == (n-1)) {
                n += NUM_ENTRIES;
                ar = (char **) REALLOC(ar, n*sizeof(char *));
            }
            /* 2: Leave space to add a trailing slash later */
            ar[p] = (char *) MALLOC(strlen(d->d_name) + 2);
            strcpy(ar[p++], d->d_name);
        }
    }
    closedir(ds);

    qsort((void *)ar, p, sizeof(char *), _admin_dumbsort);
    ar[p] = NULL;

    return ar;
}

#else /* WIN32 change */
/* Lists all files in a directory. */
char **list_directory(char *path, int dashA)  
{
    char **ar;
    SYS_DIR ds;
    SYS_DIRENT *d;
    int n, p;

    n = NUM_ENTRIES;
    p = 0;

    ar = (char **) MALLOC(n * sizeof(char *));

    if(!(ds = dir_open(path))) {
        return NULL;
    }

    while( (d = dir_read(ds)) ) {
        if ( ( d->d_name[0] != '.' ) ||
	     ( dashA && d->d_name[1] &&
	       ( d->d_name[1] != '.' || d->d_name[2] ) ) ) {
            if(p == (n-1)) {
                n += NUM_ENTRIES;
                ar = (char **) REALLOC(ar, n*sizeof(char *));
            }
            /* 2: Leave space to add a trailing slash later */
            ar[p] = (char *) MALLOC(strlen(d->d_name) + 2);
            strcpy(ar[p++], d->d_name);
        }
    }
    dir_close(ds);

    qsort((void *)ar, p, sizeof(char *), _admin_dumbsort);
    ar[p] = NULL;

    return ar;
}
#endif /* WIN32 */

int file_exists(char *fn)
{
    struct stat finfo;

    if(!stat(fn, &finfo))
        return 1;
    else
        return 0;
}

int get_file_size(char *fn)
{
    struct stat finfo;
    int ans = -1;
 
    if(!stat(fn, &finfo))  {
        ans = finfo.st_size;
    }  else  {
        report_error(FILE_ERROR, fn, "Could not get size of file.");
    }
    return ans;
}

/* return: mtime(f1) < mtime(f2) ? */
int mtime_is_earlier(char *file1, char *file2)
{
    struct stat fi1, fi2;

    if(stat(file1, &fi1))  {
        return -1;
    }
    if(stat(file2, &fi2))  {
        return -1;
    }
    return( (fi1.st_mtime < fi2.st_mtime) ? 1 : 0);
}

time_t get_mtime(char *fn)
{
    struct stat fi;

    if(stat(fn, &fi))
        return 0;
    return fi.st_mtime;
}

int all_numbers(char *target) 
{
    register int x=0;
  
    while(target[x])
        if(!isdigit(target[x++]))
            return 0;
    return 1;
}


int all_numbers_float(char *target) 
{
    register int x;
    int seenpt;

    for(x = 0, seenpt = 0; target[x]; ++x) {
        if((target[x] == '.') && (!seenpt))
            seenpt = 1;
        else if((!isdigit(target[x])) && seenpt)
            return 0;
    }
    return 1;
}

/* Get the current HTTP server URL. */
char *get_serv_url(void)
{
    return(getenv("SERVER_URL"));
}


/* This is basically copy_file from the install section, with the error 
 * reporting changed to match the admin stuff.  Since some stuff depends
 * on copy_file being the install version, I'll cheat and call this one
 * cp_file. */
#ifdef XP_UNIX
 
#define COPY_BUFFER_SIZE        4096

void cp_file(char *sfile, char *dfile, int mode)
{
    int sfd, dfd, len;
    struct stat fi;
 
    char copy_buffer[COPY_BUFFER_SIZE];
    unsigned long read_len;

/* Make sure we're in the right umask */
    umask(022);

    if( (sfd = open(sfile, O_RDONLY)) == -1)
        report_error(FILE_ERROR, sfile, "Can't open file for reading.");
 
    fstat(sfd, &fi);
    if(!(S_ISREG(fi.st_mode))) {
        close(sfd);
        return;
    }
    len = fi.st_size;
 
    if( (dfd = open(dfile, O_RDWR | O_CREAT | O_TRUNC, mode)) == -1)
        report_error(FILE_ERROR, dfile, "Can't write to file.");
 
    while(len) {
        read_len = len>COPY_BUFFER_SIZE?COPY_BUFFER_SIZE:len;
 
        if ( (read_len = read(sfd, copy_buffer, read_len)) == -1) {
            report_error(FILE_ERROR, sfile, "Error reading file for copy.");
        }
 
        if ( write(dfd, copy_buffer, read_len) != read_len) {
            report_error(FILE_ERROR, dfile, "Error writing file for copy.");
        }
 
        len -= read_len;
    }
    close(sfd);
    close(dfd);
}

#else /* XP_WIN32 */
void cp_file(char *sfile, char *dfile, int mode)
{
    HANDLE sfd, dfd, MapHandle;
    PCHAR fp;
    DWORD BytesWritten = 0;
    DWORD len;

    if( (sfd = CreateFile(sfile, GENERIC_READ,
    		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
    		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL))
    			== INVALID_HANDLE_VALUE) {
        report_error(FILE_ERROR, "Cannot open file for reading", sfile);
    }
    len = GetFileSize(sfd, NULL);
    if( (dfd = CreateFile(dfile, GENERIC_READ | GENERIC_WRITE,
    	FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,CREATE_ALWAYS,
    	FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) {
        report_error(FILE_ERROR, "Cannot open destination file for writing", 
		dfile);
    }
	if (len == 0)
		return;
    if( (MapHandle = CreateFileMapping(sfd, NULL, PAGE_READONLY,
    		0, 0, NULL)) == NULL) {
        report_error(FILE_ERROR, "Cannot create file mapping", sfile);
    }
    if (!(fp = MapViewOfFile(MapHandle, FILE_MAP_READ, 0, 0, 0))) {
        report_error(FILE_ERROR, "Cannot map file %s", sfile);
    }
    while ( len) {
    	if(!WriteFile(dfd, fp, len, &BytesWritten, NULL)) {
            report_error(FILE_ERROR, "Cannot write new file", dfile);
	    }
    	len -= BytesWritten;
    	fp += BytesWritten;
    }

    CloseHandle(sfd);
    UnmapViewOfFile(fp);
    CloseHandle(MapHandle);
    FlushFileBuffers(dfd);
    CloseHandle(dfd);
}
#endif

int delete_file(char *path)
{
#ifdef XP_UNIX
    return unlink(path);
#else
    return !(DeleteFile(path));
#endif
}

void create_dir(char *dir, int mode)
{
    if ((dir == (char *) NULL) || (strlen(dir) == 0)) {
       report_error(FILE_ERROR, "No directory is specified",
                         "Could not create a necessary directory.");
    }

    if(!file_exists(dir)) {
#ifdef XP_UNIX
        if(mkdir(dir, mode) == -1)  {
#else  /* XP_WIN32 */
        if(!CreateDirectory(dir, NULL)) {
            if (GetLastError() != ERROR_ALREADY_EXISTS)
#endif /* XP_WIN32 */
            report_error(FILE_ERROR, dir,
                         "Could not create a necessary directory.");
        }
    }
}

#ifdef XP_UNIX
SYS_FILE lf;
#elif defined(XP_WIN32)
HANDLE lf;
#endif

/* Ripped off from the client.  (Sorry, Lou.) */
/* */
/* The magic set of 64 chars in the uuencoded data */
unsigned char uuset[] = {
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T',
'U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7',
'8','9','+','/' };
 
int do_uuencode(unsigned char *src, unsigned char *dst, int srclen)
{
   int  i, r;
   unsigned char *p;
 
/* To uuencode, we snip 8 bits from 3 bytes and store them as
6 bits in 4 bytes.   6*4 == 8*3 (get it?) and 6 bits per byte
yields nice clean bytes
 
It goes like this:
        AAAAAAAA BBBBBBBB CCCCCCCC
turns into the standard set of uuencode ascii chars indexed by numbers:
        00AAAAAA 00AABBBB 00BBBBCC 00CCCCCC
 
Snip-n-shift, snip-n-shift, etc....
 
*/
 
   for (p=dst,i=0; i < srclen; i += 3) {
                /* Do 3 bytes of src */
                register char b0, b1, b2;
 
                b0 = src[0];
                if (i==srclen-1)
                        b1 = b2 = '\0';
                else if (i==srclen-2) {
                        b1 = src[1];
                        b2 = '\0';
                }
                else {
                        b1 = src[1];
                        b2 = src[2];
                }
 
                *p++ = uuset[b0>>2];
                *p++ = uuset[(((b0 & 0x03) << 4) | ((b1 & 0xf0) >> 4))];
                *p++ = uuset[(((b1 & 0x0f) << 2) | ((b2 & 0xc0) >> 6))];
                *p++ = uuset[b2 & 0x3f];
                src += 3;
   }
   *p = 0;      /* terminate the string */
   r = (unsigned char *)p - (unsigned char *)dst;/* remember how many we did */
 
   /* Always do 4-for-3, but if not round threesome, have to go
          clean up the last extra bytes */
 
   for( ; i != srclen; i--)
                *--p = '=';
 
   return r;
}
 
char *alert_word_wrap(char *str, int width, char *linefeed)
{
    char *ans = NULL;
    int counter=0;
    int lsc=0, lsa=0;
    register int strc=0, ansc=0;
    register int x=0;
 
    /* assume worst case */
    ans = (char *) MALLOC((strlen(str)*strlen(linefeed))+32);
 
    for(strc=0, ansc=0; str[strc]; /*none*/)  {
        if(str[strc]=='\n')  {
            counter=0;
            lsc=0, lsa=0;
            for(x=0; linefeed[x]; x++)  {
                ans[ansc++]=linefeed[x];
            }
            strc++;
        }  else if(str[strc]=='\r')  {
            strc++;
        }  else if(str[strc]=='\\')  {
            ans[ansc++]='\\';
            ans[ansc++]=strc++;
        }  else  {
            if(counter==width)  {
                if(lsc && lsa)  {
                    strc=lsc;
                    ansc=lsa;
 
                    counter=0;
                    lsc=0, lsa=0;
                    for(x=0; linefeed[x]; x++)  {
                        ans[ansc++]=linefeed[x];
                    }
                    strc++;
                }  else  {
                /* else, you're a loser, I'm breaking your big word anyway */
                    counter=0;
                    lsc=0, lsa=0;
                    for(x=0; linefeed[x]; x++)  {
                        ans[ansc++]=linefeed[x];
                    }
                    strc++;
                }
            }  else  {
                if(str[strc] == ' ')  {
                    lsc=strc;
                    lsa=ansc;
                }
                ans[ansc++]=str[strc++];
                counter++;
            }
        }
    }
    ans[ansc]='\0';
    return ans;
}


/* do fgets with a filebuffer *, instead of a File *.  Can't use util_getline
   because it complains if the line is too long.
   It does throw away <CR>s, though.
 */
NSAPI_PUBLIC char *system_gets( char *line, int size, filebuffer *fb )
{
  int	c;
  int	i = 0;

  while ( --size ) {
    switch ( c = filebuf_getc( fb ) ) {
    case IO_EOF:
      line[i] = '\0';
      return i ? line : NULL;
    case LF:
      line[i] = c;
      line[i+1] = '\0';
      return line;	/* got a line, and it fit! */
    case IO_ERROR:
      return NULL;
    case CR:
      ++size;
      break;
    default:
      line[i++] = c;
      break;
    }
  }
  /* if we got here, then we overran the buffer size */
  line[i] = '\0';
  return line;
}

#ifndef WIN32

/* make a zero length file, no matter how long it was before */
NSAPI_PUBLIC int
system_zero( SYS_FILE f )
{
  ftruncate( PR_FileDesc2NativeHandle( f ), 0 );
  return 0;
}

#endif

/***********************************************************************
** FUNCTION:	cookieValue
** DESCRIPTION:
**   Get the current value of the cookie variable
** INPUTS:	var - the name of the cookie variable
**		val - if non-NULL, set the in-memory copy of the var
** OUTPUTS:	None
** RETURN:	NULL if the var doesn't exist, else the value
** SIDE EFFECTS:
**	Eats memory
** RESTRICTIONS:
**	Don't screw around with the returned string, if anything else wants
**	to use it.
** MEMORY:	This is a memory leak, so only use it in CGIs
** ALGORITHM:
**	If it's never been called, build a memory structure of the
**	cookie variables.
**	Look for the passed variable, and return its value, or NULL
***********************************************************************/

NSAPI_PUBLIC char *
cookieValue( char *var, char *val )
{
  static char	**vars = NULL;
  static char	**vals = NULL;
  static int	numVars = -1;
  int		i;

  if ( numVars == -1 ) {	/* first time, init the structure */
    char	*cookie = getenv( "HTTP_COOKIE" );

    if ( cookie && *cookie ) {
      int	len = strlen( cookie );
      int	foundVal = 0;

      cookie = STRDUP( cookie );
      numVars = 0;
      vars = (char **)MALLOC( sizeof( char * ) );
      vals = (char **)MALLOC( sizeof( char * ) );
      vars[0] = cookie;
      for ( i = 0 ; i < len ; ++i ) {
	if ( ( ! foundVal ) && ( cookie[i] == '=' ) ) {	
	  vals[numVars++] = cookie + i + 1;
	  cookie[i] = '\0';
	  foundVal = 1;
	} else if ( ( cookie[i] == ';' ) && ( cookie[i+1] == ' ' ) ) {
	  cookie[i] = '\0';
	  vals = (char **) REALLOC( vals,
				    sizeof( char * ) * ( numVars + 1 ) );
	  vars = (char **) REALLOC( vars,
				    sizeof( char * ) * ( numVars + 1 ) );
	  vars[numVars] = cookie + i + 2;
	  i += 2;
	  foundVal = 0;
	}
      }
    } else {	/* no cookie, no vars */
      numVars = 0;
    }
  }
  for ( i = 0 ; i < numVars ; ++i ) {
    if ( strcmp( vars[i], var ) == 0 ) {
      if ( val ) {
	vals[i] = STRDUP( val );
      } else {
	return vals[i];
      }
    }
  }
  return NULL;
}

/***********************************************************************
** FUNCTION:	jsEscape
** DESCRIPTION:
**   Escape the usual suspects, so the parser javascript parser won't eat them
** INPUTS:	src - the string
** OUTPUTS:	NONE
** RETURN:	A malloced string, containing the escaped src
** SIDE EFFECTS:
**	None, except for more memory being eaten
** RESTRICTIONS:
**	None
** MEMORY:	One Malloc, you should free this if you care
***********************************************************************/

NSAPI_PUBLIC char *
jsEscape( char *src )
{
  int	needsEscaping = 0;
  int	i;
  char	*dest;

  for ( i = 0 ; src[i] ; ++i ) {
    if ( src[i] == '\\' || src[i] == '\'' || src[i] == '"' ) {
      ++needsEscaping;
    }
  }
  dest = (char *)MALLOC( i + needsEscaping + 1 );
  for ( i = 0 ; *src ; ++src ) {
    if ( ( *src == '\\' ) || ( *src == '\'' ) || ( *src == '"' ) ) {
      dest[i++] = '\\';	/* escape */
    }
    dest[i++] = *src;
  }
  dest[i] = '\0';
  return dest;
}

/***********************************************************************
** FUNCTION:	jsPWDialogSrc
** DESCRIPTION:
**   Put the source to the passwordDialog JavaScript function out.
** INPUTS:	inScript - if true, don't put <SCRIPT> stuff out
**		otherJS  - if nonNULL, other javascript to execute
** OUTPUTS:	None
** RETURN:	None
** SIDE EFFECTS:
**	clogs up stdout
** RESTRICTIONS:
**	Don't use this outside of a CGI, or before the Content-type:
** MEMORY:	No memory change
** ALGORITHM:
**	@+@What's really going on?
***********************************************************************/

NSAPI_PUBLIC void
jsPWDialogSrc( int inScript, char *otherJS )
{
  static int	srcSpewed = 0;

  otherJS = otherJS ? otherJS : "";

  if ( ! inScript ) {
    fprintf( stdout, "<SCRIPT LANGUAGE=\""MOCHA_NAME"\">\n" );
  }
  if ( ! srcSpewed ) {
    srcSpewed = 1;
    fprintf( stdout, "function passwordDialog( prompt, element ) {\n"
	     "    var dlg = window.open( '', 'dialog', 'height=60,width=500' );\n"
	     "    dlg.document.write(\n"
	     "        '<form name=f1 onSubmit=\"opener.document.'\n"
	     "        + element + '.value = goo.value; window.close(); "
	     "%s; return false;\">',\n"
	     "        prompt, '<input type=password name=goo></form>' );\n"
	     "    dlg.document.f1.goo.focus();\n"
	     "    dlg.document.close();\n"
	     "}\n", otherJS );
  }
  if ( ! inScript ) {
    fprintf( stdout, "</SCRIPT>\n" );
  }
}

/* this macro was copied from libldap/tmplout.c */

#define HREF_CHAR_ACCEPTABLE( c )	(( c >= '-' && c <= '9' ) ||	\
					 ( c >= '@' && c <= 'Z' ) ||	\
					 ( c == '_' ) ||		\
					 ( c >= 'a' && c <= 'z' ))

/* this function is copied from libldap/tmplout.c:strcat_escaped */
NSAPI_PUBLIC void htmladmin_strcat_escaped( char *s1, char *s2 )
{
    unsigned char	*q;
    char		*p, *hexdig = "0123456789ABCDEF";

    p = s1 + strlen( s1 );
    for ( q = (unsigned char *)s2; *q != '\0'; ++q ) {
	if ( HREF_CHAR_ACCEPTABLE( *q )) {
	    *p++ = *q;
	} else {
	    *p++ = '%';
	    *p++ = hexdig[ *q >> 4 ];
	    *p++ = hexdig[ *q & 0x0F ];
	}
    }

    *p = '\0';
}


NSAPI_PUBLIC char *htmladmin_strdup_escaped( char *s )
{
    char	*p;

    p = (char *)malloc( 3 * strlen( s ) + 1 );
    *p = '\0';
    htmladmin_strcat_escaped( p, s );
    return( p );
}

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

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

    return ret;
}

/* returns true if the given path is a valid file, false otherwise */
NSAPI_PUBLIC int
util_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 true if the file was found somewhere, false otherwise */
NSAPI_PUBLIC int
util_find_file_in_paths(
    char *filebuf, /* this will be filled in with the full path/filename if found, '\0' otherwise */
    size_t bufsize, /* size of filebuf e.g. sizeof(filebuf) */
    const char *filename, /* the base filename to look for */
    const char *path, /* path given by caller */
    const char *arpath, /* path relative to ADMSERV_ROOT */
    const char *nrpath /* path relative to NETSITE_ROOT */
)
{
    int retval = 0;
    const char *admroot = getenv("ADMSERV_CONF_DIR");

    /* try given path */
    PR_snprintf(filebuf, bufsize, "%s/%s", path, filename);
    retval = util_is_file_ok(filebuf);
    if (!retval && admroot) { /* look in ADMSERV_ROOT */
	PR_snprintf(filebuf, bufsize, "%s%s%s/%s", admroot,
		    (arpath && *arpath) ? "/" : "",
		    (arpath && *arpath) ? arpath : "",
		    filename);
	retval = util_is_file_ok(filebuf);
    }

    if (!retval) {
	filebuf[0] = '\0';
    }

    return retval;
}

NSAPI_PUBLIC char*
util_get_conf_dir(void)
{
    const char *admroot = getenv("ADMSERV_CONF_DIR");
    static char confdir[PATH_MAX];

    if (!confdir[0]) {
        if (util_is_dir_ok(admroot)) {
            PR_snprintf(confdir, sizeof(confdir), admroot);
        } else if (util_is_dir_ok(CONFIGDIR)) {
            PR_snprintf(confdir, sizeof(confdir), CONFIGDIR);
        } else {
            return NULL;
        }
    }

    return confdir;
}

NSAPI_PUBLIC char*
util_get_security_dir(void)
{
    const char *admroot = getenv("ADMSERV_CONF_DIR");
    static char secdir[PATH_MAX];

    if (!secdir[0]) {
        if (util_is_dir_ok(admroot)) {
            PR_snprintf(secdir, sizeof(secdir), admroot);
        } else if (util_is_dir_ok(SECURITYDIR)) {
            PR_snprintf(secdir, sizeof(secdir), SECURITYDIR);
        } else {
            return util_get_conf_dir();
        }
    }

    return secdir;
}

NSAPI_PUBLIC char*
util_get_log_dir(void)
{
    const char *admroot = getenv("ADMSERV_LOG_DIR");
    static char logdir[PATH_MAX];

    if (!logdir[0]) {
        if (util_is_dir_ok(admroot)) {
            PR_snprintf(logdir, sizeof(logdir), admroot);
        } else if (util_is_dir_ok(LOGDIR)) {
            PR_snprintf(logdir, sizeof(logdir), LOGDIR);
        } else {
            return NULL;
        }
    }

    return logdir;
}

NSAPI_PUBLIC char*
util_get_pid_dir(void)
{
    const char *admroot = getenv("ADMSERV_PID_DIR");
    static char piddir[PATH_MAX];

    if (!piddir[0]) {
        if (util_is_dir_ok(admroot)) {
            PR_snprintf(piddir, sizeof(piddir), admroot);
        } else if (util_is_dir_ok(PIDDIR)) {
            PR_snprintf(piddir, sizeof(piddir), PIDDIR);
        } else {
            return NULL;
        }
    }

    return piddir;
}

/* old style html dir == progpath/../html */
#define HTML_DIR "../html/"

NSAPI_PUBLIC const char*
util_get_html_dir(void)
{
    const char *admroot = getenv("ADMSERV_ROOT");
    const char *nsroot = getenv("NETSITE_ROOT");
    static char htmldir[PATH_MAX];

    if (!htmldir[0]) {
        if (util_is_dir_ok(HTMLDIR)) {
            PR_snprintf(htmldir, sizeof(htmldir), HTMLDIR);
        } else if (util_is_dir_ok(admroot)) {
            PR_snprintf(htmldir, sizeof(htmldir), "%s/../../bin/admin/admin/html", admroot);
        } else if (util_is_dir_ok(nsroot)) {
            PR_snprintf(htmldir, sizeof(htmldir), "%s/bin/admin/admin/html", nsroot);
        } else {
            PR_snprintf(htmldir, sizeof(htmldir), HTML_DIR);
        }
    }

    return htmldir;
}

NSAPI_PUBLIC const char*
util_get_icon_dir(void)
{
    const char *admroot = getenv("ADMSERV_ROOT");
    const char *nsroot = getenv("NETSITE_ROOT");
    static char icondir[PATH_MAX];

    if (!icondir[0]) {
        if (util_is_dir_ok(ICONDIR)) {
            PR_snprintf(icondir, sizeof(icondir), ICONDIR);
        } else if (util_is_dir_ok(admroot)) {
            PR_snprintf(icondir, sizeof(icondir), "%s/../../bin/admin/admin/icons", admroot);
        } else if (util_is_dir_ok(nsroot)) {
            PR_snprintf(icondir, sizeof(icondir), "%s/bin/admin/admin/icons", nsroot);
        } else {
            return NULL;
        }
    }

    return icondir;
}

/* return true if all of the chars in s are valid chars for use in
   file and directory names, and false otherwise.  This means that
   the string must begin with a letter or number, and must contain
   letters, numbers, '.', '-' and '_'.
   The main purpose of this is to see if a malicious client is sending
   us bogus path names in an attempt to gain access or DoS.
*/
NSAPI_PUBLIC int
util_is_valid_path_string(const char *s)
{
    int ret = 0;
    if (s) {
	if (isalnum(*s)) {
	    ret = 1;
	    for(; ret && *s; ++s) {
		ret = isalnum(*s) || (*s == '-') || (*s == '_') || (*s == '.');
	    }
	}
    }
    return ret;
}

/* try various ways to determine if the given name is a valid
   file or directory - this value is passed in as a form
   parameter, and our motto is "Don't trust the user!"
   If the given filetype is directory, also check to see if the optional
   given filename (may be NULL) is in the given directory
*/
NSAPI_PUBLIC int
util_verify_file_or_dir(
    const char *name, /* name of file or directory to check */
    PRFileType filetype, /* type of name */
    const char *childname, /* optional child file/dir to check inside given parent name */
    size_t childlen, /* only compare first childlen chars of childname - use -1 for entire string */
    PRFileType childtype /* type of child */
)
{
    int ret = 0;
    PRFileInfo fileinfo;
    /* first, just a simple access check */
    PRStatus status = PR_GetFileInfo(name, &fileinfo);
    ret = ((status == PR_SUCCESS) && (fileinfo.type == filetype));
    if (ret) {
        /* checks out ok - let's split it into the base name and the parent dir,
           open the parent dir, and see if the base name exists in the parent dir
        */
        char *copy = PL_strdup(name);
        size_t len = strlen(copy);
        char *ptr = &copy[len-1];
        /* get the basename - a really bad name may look like
           /path/foo/// or even ///////////////// */
        for (; (ptr > copy) && (*ptr == '/'); --ptr) {
            /* do nothing */
        }
        if ((ptr == copy) && (*ptr == '/')) {
            /* bad - string consists of nothing but '/' */
            ptr = NULL;
            ret = 0;
        } else {
            PRDir *pdir;
            PRDirEntry *pent;

            ret = 0;
            if (*ptr == '/') {
                *ptr = 0; /* terminate the string at the first trailing '/' */
            }
            ptr = strrchr(copy, '/');
            if (!ptr) {
                ptr = copy;
                copy = PL_strdup(".");
            } else {
                *ptr = 0;
                ++ptr;
                ptr = PL_strdup(ptr);
            }
            /* copy now points at the parent, ptr at the child */
            if ((pdir = PR_OpenDir(copy))) {
                for(pent = PR_ReadDir(pdir, PR_SKIP_BOTH); pent && !ret;
                    pent = PR_ReadDir(pdir, PR_SKIP_BOTH)) {
                    ret = !strcmp(pent->name, ptr);
                }
                PR_CloseDir(pdir);
            }
            if (ret && childname && (filetype == PR_FILE_DIRECTORY)) {
                ret = 0;
                /* we've verified that name is a valid directory - see if
                   the given filename exists in that directory */
                if ((pdir = PR_OpenDir(name))) {
                    for(pent = PR_ReadDir(pdir, PR_SKIP_BOTH); pent && !ret;
                        pent = PR_ReadDir(pdir, PR_SKIP_BOTH)) {
                        if (childlen > 0) {
                            ret = !strncmp(pent->name, childname, childlen);
                        } else {
                            ret = !strcmp(pent->name, childname);
                        }
                    }
                    PR_CloseDir(pdir);
                    if (ret) {
                        /* child exists - check type */
                        char *fullname = PR_smprintf("%s%c%s", name, FILE_PATHSEP, childname);
                        status = PR_GetFileInfo(fullname, &fileinfo);
                        ret = ((status == PR_SUCCESS) && (fileinfo.type == childtype));
                        PR_smprintf_free(fullname);
                    }
                }
            }
        }
        PL_strfree(copy);
        PL_strfree(ptr);
    }
    return ret;
}

NSAPI_PUBLIC int
util_psetHasObjectClass(PsetHndl pset, const char *ocname)
{
    int rval = 0;
    char *val = NULL;
    ValueType nodeObjectClass = psetGetObjectClass(pset, "" /* use root node */, &rval);
    ValueType iter = nodeObjectClass;

    rval = 0;
    while ((!rval) && ((val = *iter++))) {
        rval = !PL_strcasecmp(ocname, val);
    }

    deleteValue(nodeObjectClass);
    return rval;
}

#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 *
util_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
util_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 *
util_ldap_init(
    const char *certdir,
    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 = util_ldap_url_parse(ldapurl, &ludp, 0, &secureurl)) ||
                    !ludp)) {
#ifdef DEBUG
        fprintf(stderr, "util_ldap_init: Could not parse given LDAP URL [%s] : error [%s]\n",
                ldapurl, /* ldapurl cannot be NULL here */
                util_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, "util_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, "util_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)
            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
                fprintf(stderr, "util_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, "util_ldap_init: "
                        "failed: unable to set CACERTDIR option to %s\n", certdir);
#endif
            }
#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, "util_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, "util_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))) {
#ifdef DEBUG
                int prerr = PR_GetError();
                fprintf(stderr, "util_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, "util_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 );
}

int
util_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
util_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, "util_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, "util_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 = util_ldap_get_lderrno(ld, NULL, NULL);
#ifdef DEBUG
                fprintf(stderr, "util_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, "util_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, "util_ldap_bind: "
                        "Error: could not parse bind result "
                        "[%s] mech [%s]: error %d (%s)\n",
                        bindid ? bindid : "(anon)",
                        mech ? mech : "SIMPLE",
                        rc, ldap_err2string(rc));
#endif
                goto done;
            }

            if(err){
                rc = err;
#ifdef DEBUG
                fprintf(stderr, "util_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 = util_ldap_get_lderrno(ld, NULL, NULL);
#ifdef DEBUG
                fprintf(stderr, "util_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, "util_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;
}

void
util_ldap_perror(LDAP *ld, const char *fmt, ...)
{
    char *matched, *extra;
    int err = util_ldap_get_lderrno(ld, &matched, &extra);
    va_list ap;
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fprintf(stderr, ": error %d (%s)", err, ldap_err2string(err));
    if (matched) {
        fprintf(stderr, ": matched DN (%s)", matched);
    }
    if (extra) {
        fprintf(stderr, ": extra (%s)", extra);
    }
    fprintf(stderr, "\n");
}

char **
util_ldap_get_values(LDAP *ld, LDAPMessage *entry, const char *attrtype)
{
#if defined(USE_OPENLDAP)
    struct berval **bvals = NULL;
    char **vals = NULL;
    int ii;

    bvals = ldap_get_values_len(ld, entry, attrtype);

    if (!bvals) {
        return vals;
    }

    for (ii = 0; bvals[ii]; ++ii);
    vals = (char **)PR_Malloc((ii + 1) * sizeof(char *));
    for (ii = 0; vals && bvals && bvals[ii]; ++ii) {
        vals[ii] = PL_strndup(bvals[ii]->bv_val, bvals[ii]->bv_len);
    }
    ldap_value_free_len(bvals);
    if (vals) {
        vals[ii] = NULL;
    }
    return vals;
#else
    return ldap_get_values(ld, entry, attrtype);
#endif
}

void
util_ldap_value_free(char **vals)
{
	int	ii;

	for (ii = 0; vals && vals[ii]; ++ii) {
		PL_strfree(vals[ii]);
    }
	PR_Free(vals);
}
