#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: ldap.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
#endif

/*
 * ========================================================================
 * Copyright 2013-2020 Eduardo Chappa
 * Copyright 2006-2008 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

#include "../pith/headers.h"
#include "../pith/ldap.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/status.h"
#include "../pith/util.h"
#include "../pith/imap.h"
#include "../pith/busy.h"
#include "../pith/signal.h"
#include "../pith/ablookup.h"
#include "../pith/readfile.h"

/*
 * Until we can think of a better way to do this. If user exits from an
 * ldap address selection screen we want to return -1 so that call_builder
 * will stay on the same line. Might want to use another return value
 * for the builder so that call_builder more fully understands what we're
 * up to.
 */
int wp_exit;
int wp_nobail;


#ifdef	ENABLE_LDAP
/*
 * Hook to allow user input on whether or not to save chosen LDAP result
 */
void (*pith_opt_save_ldap_entry)(struct pine *, LDAP_CHOOSE_S *, int);


/*
 * Internal prototypes
 */
LDAP_SERV_RES_S *ldap_lookup(LDAP_SERV_S *, char *, CUSTOM_FILT_S *, WP_ERR_S *, int);
LDAP_SERV_S     *copy_ldap_serv_info(LDAP_SERV_S *);
int              our_ldap_get_lderrno(LDAP *, char **, char **);
int              our_ldap_set_lderrno(LDAP *, int, char *, char *);
#endif	/* ENABLE_LDAP */


/*
 * This function does white pages lookups.
 *
 * Args  string -- the string to use in the lookup
 *
 * Returns     NULL -- lookup failed
 *          Address -- A single address is returned if lookup was successful.
 */
ADDRESS *
wp_lookups(char *string, WP_ERR_S *wp_err, int recursing)
{
    ADDRESS *ret_a = NULL;
    char ebuf[200];
#ifdef	ENABLE_LDAP
    LDAP_SERV_RES_S *free_when_done = NULL;
    LDAPLookupStyle style;
    LDAP_CHOOSE_S *winning_e = NULL;
    LDAP_SERV_S *info = NULL;
    static char *fakedomain = "@";
    char *tmp_a_string;
    int   auwe_rv = 0;

    /*
     * Runtime ldap lookup of addrbook entry.
     */
    if(!strncmp(string, RUN_LDAP, LEN_RL)){
	LDAP_SERV_RES_S *head_of_result_list;

	info = break_up_ldap_server(string+LEN_RL);
        head_of_result_list = ldap_lookup(info, "", NULL, wp_err, 1);

	if(head_of_result_list){
	    if(!wp_exit)
	      auwe_rv = ask_user_which_entry(head_of_result_list, string,
					     &winning_e,
					     wp_err,
					     wp_err->wp_err_occurred
					     ? DisplayIfOne : DisplayIfTwo);

	    if(auwe_rv != -5)
	      free_when_done = head_of_result_list;
	}
	else{
	    wp_err->wp_err_occurred = 1;
	    if(wp_err->error){
		q_status_message(SM_ORDER, 3, 5, wp_err->error);
		display_message('x');
		fs_give((void **)&wp_err->error);
	    }

	    /* try using backup email address */
	    if(info && info->mail && *info->mail){
		tmp_a_string = cpystr(info->mail);
		rfc822_parse_adrlist(&ret_a, tmp_a_string, fakedomain);
		fs_give((void **)&tmp_a_string);

		wp_err->error =
		  cpystr(_("Directory lookup failed, using backup email address"));
	    }
	    else{
		/*
		 * Do this so the awful LDAP: ... string won't show up
		 * in the composer. This shouldn't actually happen in
		 * real life, so we're not too concerned about it. If we
		 * were we'd want to recover the nickname we started with
		 * somehow, or something like that.
		 */
		ret_a = mail_newaddr();
		ret_a->mailbox = cpystr("missing-username");
		wp_err->error = cpystr(_("Directory lookup failed, no backup email address available"));
	    }

	    q_status_message(SM_ORDER, 3, 5, wp_err->error);
	    display_message('x');
	}
    }
    else{
	style = F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
			? DisplayIfOne : DisplayIfTwo;
	auwe_rv = ldap_lookup_all(string, as.n_serv, recursing, style, NULL,
				  &winning_e, wp_err, &free_when_done);
    }

    if(winning_e && auwe_rv != -5){
	ret_a = address_from_ldap(winning_e);

	if(pith_opt_save_ldap_entry && ret_a && F_ON(F_ADD_LDAP_TO_ABOOK, ps_global) && !info)
	  (*pith_opt_save_ldap_entry)(ps_global, winning_e, 1);


	fs_give((void **)&winning_e);
    }

    /* Info's only set in the RUN_LDAP case */
    if(info){
	if(ret_a && ret_a->host){
	    ADDRESS *backup = NULL;

	    if(info->mail && *info->mail)
	      rfc822_parse_adrlist(&backup, info->mail, fakedomain);

	    if(!backup || !address_is_same(ret_a, backup)){
		if(wp_err->error){
		    q_status_message(SM_ORDER, 3, 5, wp_err->error);
		    display_message('x');
		    fs_give((void **)&wp_err->error);
		}

		snprintf(ebuf, sizeof(ebuf),
	       _("Warning: current address different from saved address (%s)"),
		   info->mail);
		wp_err->error = cpystr(ebuf);
		q_status_message(SM_ORDER, 3, 5, wp_err->error);
		display_message('x');
	    }

	    if(backup)
	      mail_free_address(&backup);
	}

	free_ldap_server_info(&info);
    }

    if(free_when_done)
      free_ldap_result_list(&free_when_done);
#endif	/* ENABLE_LDAP */

    if(ret_a){
	if(ret_a->mailbox){  /* indicates there was a MAIL attribute */
	    if(!ret_a->host || !ret_a->host[0]){
		if(ret_a->host)
		  fs_give((void **)&ret_a->host);

		ret_a->host = cpystr("missing-hostname");
		wp_err->wp_err_occurred = 1;
		if(wp_err->error)
		  fs_give((void **)&wp_err->error);

		wp_err->error = cpystr(_("Missing hostname in LDAP address"));
		q_status_message(SM_ORDER, 3, 5, wp_err->error);
		display_message('x');
	    }

	    if(!ret_a->mailbox[0]){
		if(ret_a->mailbox)
		  fs_give((void **)&ret_a->mailbox);

		ret_a->mailbox = cpystr("missing-username");
		wp_err->wp_err_occurred = 1;
		if(wp_err->error)
		  fs_give((void **)&wp_err->error);

		wp_err->error = cpystr(_("Missing username in LDAP address"));
		q_status_message(SM_ORDER, 3, 5, wp_err->error);
		display_message('x');
	    }
	}
	else{
	    wp_err->wp_err_occurred = 1;

	    if(wp_err->error)
	      fs_give((void **)&wp_err->error);

	    snprintf(ebuf, sizeof(ebuf), _("No email address available for \"%s\""),
		    (ret_a->personal && *ret_a->personal)
			    ? ret_a->personal
			    : "selected entry");
	    wp_err->error = cpystr(ebuf);
	    q_status_message(SM_ORDER, 3, 5, wp_err->error);
	    display_message('x');
	    mail_free_address(&ret_a);
	    ret_a = NULL;
	}
    }

    return(ret_a);
}


#ifdef	ENABLE_LDAP

/*
 * Goes through all servers looking up string.
 *
 * Args  string -- String to search for
 *          who -- Which servers to look on
 *        style -- Sometimes we want to display no matter what, sometimes
 *                  only if more than one entry, sometimes only if email
 *                  addresses, ...
 *
 *     The possible styles are:
 *      AlwaysDisplayAndMailRequired: This happens when user ^T's from
 *                                    composer to address book screen, and
 *                                    then queries directory server.
 *                     AlwaysDisplay: This happens when user is browsing
 *                                    in address book maintenance screen and
 *                                    then queries directory server.
 *                      DisplayIfOne: These two come from implicit lookups
 *                      DisplayIfTwo: from build_address. If the compose rejects
 *                                    unqualified feature is on we get the
 *                                    DisplayIfOne, otherwise IfTwo.
 *
 *         cust -- Use this custom filter instead of configured filters
 *    winning_e -- Return value
 *       wp_err -- Error handling
 * free_when_done -- Caller needs to free this
 *
 * Returns  -- value returned by ask_user_which_entry
 */
int
ldap_lookup_all(char *string, int who, int recursing, LDAPLookupStyle style,
		CUSTOM_FILT_S *cust, LDAP_CHOOSE_S **winning_e,
		WP_ERR_S *wp_err, LDAP_SERV_RES_S **free_when_done)
{
    int              retval = -1;
    LDAP_SERV_RES_S *head_of_result_list = NULL;

    wp_exit = wp_nobail = 0;
    if(free_when_done)
      *free_when_done = NULL;

    head_of_result_list = ldap_lookup_all_work(string, who, recursing, 
					       cust, wp_err);

    if(!wp_exit)
      retval = ask_user_which_entry(head_of_result_list, string, winning_e,
				    wp_err,
				    (wp_err->wp_err_occurred &&
				     style == DisplayIfTwo) ? DisplayIfOne
							    : style);

    /*
     * Because winning_e probably points into the result list
     * we need to leave the result list alone and have the caller
     * free it after they are done with winning_e.
     */
    if(retval != -5 && free_when_done)
      *free_when_done = head_of_result_list;

    return(retval);
}


/*
 * Goes through all servers looking up string.
 *
 * Args  string -- String to search for
 *          who -- Which servers to look on
 *         cust -- Use this custom filter instead of configured filters
 *       wp_err -- Error handling
 *
 * Returns  -- list of results that needs to be freed by caller
 */
LDAP_SERV_RES_S *
ldap_lookup_all_work(char *string, int who, int recursing, 
		     CUSTOM_FILT_S *cust, WP_ERR_S *wp_err)
{
    int              i;
    LDAP_SERV_RES_S *serv_res;
    LDAP_SERV_RES_S *rr, *head_of_result_list = NULL;

    /* If there is at least one server */
    if(ps_global->VAR_LDAP_SERVERS && ps_global->VAR_LDAP_SERVERS[0] &&
       ps_global->VAR_LDAP_SERVERS[0][0]){
	int how_many_servers;

	for(i = 0; ps_global->VAR_LDAP_SERVERS[i] &&
		   ps_global->VAR_LDAP_SERVERS[i][0]; i++)
	  ;

	how_many_servers = i;

	/* For each server in list */
	for(i = 0; !wp_exit && ps_global->VAR_LDAP_SERVERS[i] &&
			       ps_global->VAR_LDAP_SERVERS[i][0]; i++){
	    LDAP_SERV_S *info;

	    dprint((6, "ldap_lookup_all_work: lookup on server (%.256s)\n",
		    ps_global->VAR_LDAP_SERVERS[i]));
	    info = NULL;
	    if(who == -1 || who == i || who == as.n_serv)
	      info = break_up_ldap_server(ps_global->VAR_LDAP_SERVERS[i]);

	    /*
	     * Who tells us which servers to look on.
	     * Who == -1 means all servers.
	     * Who ==  0 means server[0].
	     * Who ==  1 means server[1].
	     * Who == as.n_serv means query on those with impl set.
	     */
	    if(!(who == -1 || who == i ||
		 (who == as.n_serv && !recursing && info && info->impl) ||
		 (who == as.n_serv && recursing && info && info->rhs))){

		if(info)
		  free_ldap_server_info(&info);
		
		continue;
	    }

	    dprint((6, "ldap_lookup_all_work: ldap_lookup (server: %.20s...)(string: %s)\n",
		    ps_global->VAR_LDAP_SERVERS[i], string));
	    serv_res = ldap_lookup(info, string, cust,
				   wp_err, how_many_servers > 1);
	    if(serv_res){
		/* Add new one to end of list so they come in the right order */
		for(rr = head_of_result_list; rr && rr->next; rr = rr->next)
		  ;
		
		if(rr)
		  rr->next = serv_res;
		else
		  head_of_result_list = serv_res;
	    }

	    if(info)
	      free_ldap_server_info(&info);
	}
    }

    return(head_of_result_list);
}


/*
 * Do an LDAP lookup to the server described in the info argument.
 *
 * Args      info -- LDAP info for server.
 *         string -- String to lookup.
 *           cust -- Possible custom filter description.
 *         wp_err -- We set this is we get a white pages error.
 *  name_in_error -- Caller sets this if they want us to include the server
 *                   name in error messages.
 *
 * Returns  Results of lookup, NULL if lookup failed.
 */
LDAP_SERV_RES_S *
ldap_lookup(LDAP_SERV_S *info, char *string, CUSTOM_FILT_S *cust,
	    WP_ERR_S *wp_err, int name_in_error)
{
    char     ebuf[900];
    char     buf[900];
    char    *serv, *base, *serv_errstr, *s, *t;
    char    *mailattr, *snattr, *gnattr, *cnattr;
    int      we_cancel = 0, we_turned_on = 0;
    LDAP_SERV_RES_S *serv_res = NULL;
    LDAP *ld = NULL;
    long  pwdtrial = 0L;
    int   ld_errnum;
    char *ld_errstr;


    if(!info)
      return(serv_res);

    serv = cpystr((info->serv && *info->serv) ? info->serv : "?");

    if(name_in_error)
      snprintf(ebuf, sizeof(ebuf), " (%s)",
	      (info->nick && *info->nick) ? info->nick : serv);
    else
      ebuf[0] = '\0';

    serv_errstr = cpystr(ebuf);
    base = cpystr(info->base ? info->base : "");

    if(info->port < 0)
      info->port = LDAP_PORT;

    if(info->type < 0)
      info->type = DEF_LDAP_TYPE;

    if(info->srch < 0)
      info->srch = DEF_LDAP_SRCH;
	
    if(info->time < 0)
      info->time = DEF_LDAP_TIME;

    if(info->size < 0)
      info->size = DEF_LDAP_SIZE;

    if(info->scope < 0)
      info->scope = DEF_LDAP_SCOPE;

    mailattr = (info->mailattr && info->mailattr[0]) ? info->mailattr
						     : DEF_LDAP_MAILATTR;
    snattr = (info->snattr && info->snattr[0]) ? info->snattr
						     : DEF_LDAP_SNATTR;
    gnattr = (info->gnattr && info->gnattr[0]) ? info->gnattr
						     : DEF_LDAP_GNATTR;
    cnattr = (info->cnattr && info->cnattr[0]) ? info->cnattr
						     : DEF_LDAP_CNATTR;

    /*
     * We may want to keep ldap handles open, but at least for
     * now, re-open them every time.
     */

    dprint((3, "ldap_lookup(%s,%d)\n", serv ? serv : "?", info->port));

    snprintf(ebuf, sizeof(ebuf), "Searching%s%s%s on %s",
	    (string && *string) ? " for \"" : "",
	    (string && *string) ? string : "",
	    (string && *string) ? "\"" : "",
	    serv);
    we_turned_on = intr_handling_on();		/* this erases keymenu */
    we_cancel = busy_cue(ebuf, NULL, 0);
    if(wp_err->mangled)
      *(wp_err->mangled) = 1;

#ifdef _SOLARIS_SDK
    if(info->tls || info->tlsmust)
      ldapssl_client_init(NULL, NULL);
    if((ld = ldap_init(serv, info->port)) == NULL)
#else
#if (LDAPAPI >= 11)
#ifdef _WINDOWS
    if((ld = ldap_init(serv, info->port)) == NULL)
#else
#ifdef SMIME_SSLCERTS
    /* If we are attempting a ldaps secure connection, we need to tell 
     * ldap that we have certificates. There are many ways to do so. 
     * OpenLDAP has many ways to configure this through configuration
     * files. For example the global (to the system) ldap.conf file, or the
     * personal ldaprc or .ldaprc files. Setting the location of the 
     * certificates must happen at the time we call ldap_initialize, we 
     * cannot set it up before that call, nor after, so what we are going 
     * to do is to test for a .ldaprc file in the home directory. If such 
     * file exists we read it, if not we create it and if it does not have
     * a line for the location of the certificates in the system, we add one.
     * (so we ignore all other configuration files)
     */
    if(info->ldaps && ps_global->home_dir){
	int done = 0;
	char *text, *tls_conf;
	char filename[MAXPATH+1];

        build_path(filename, ps_global->home_dir, ".ldaprc", sizeof(filename));

	if((text = read_file(filename, 0)) != NULL)
	   while(done == 0 
		&& (tls_conf = strstr(text, "TLS_CACERTDIR")) != NULL
		&& (tls_conf == text || *(tls_conf - 1) == '\n')){
		   tls_conf += 13;	/* 13 = strlen("TLS_CACERTDIR") */
		   while (isspace(*tls_conf))
		     tls_conf++;
		   if(!strncmp(tls_conf, SMIME_SSLCERTS, strlen(SMIME_SSLCERTS)))
		      done++;
	    }

	if(!done){
	   STORE_S *so;

	   if((so = so_get(FileStar, filename, WRITE_ACCESS)) != NULL){
	      if(text != NULL){
	        so_puts(so, text);
	        so_puts(so, NEWLINE);
	      }
	      so_puts(so, "TLS_CACERTDIR");
	      so_puts(so, " ");
	      so_puts(so, SMIME_SSLCERTS); 
	      so_puts(so, NEWLINE);
	      so_give(&so);
	   }
	}
	if(text != NULL)
	  fs_give((void **)&text);
    }
#endif /* SMIME_SSLCERTS */

    tmp_20k_buf[0] = '\0';
    s = serv;
    do {
       if ((t = strchr(s, ' ')) != NULL) *t = '\0';
       snprintf(tmp_20k_buf + strlen(tmp_20k_buf),
		SIZEOF_20KBUF - strlen(tmp_20k_buf), "%s://%s",
		info->ldaps ? "ldaps" : "ldap", s);
       if (strchr(s, ':') == NULL){
          snprintf(tmp_20k_buf + strlen(tmp_20k_buf),
		SIZEOF_20KBUF - strlen(tmp_20k_buf),
	        "%s%d", ":", info->port);
       }
       if(t != NULL){
	  *t = ' ';
	  for( ; *t == ' '; t++);
          snprintf(tmp_20k_buf + strlen(tmp_20k_buf),
		SIZEOF_20KBUF - strlen(tmp_20k_buf), "%s", " ");
       }
       s = t;
    } while (s != NULL);

    tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';

    if(ldap_initialize(&ld, tmp_20k_buf) != LDAP_SUCCESS) 
#endif
#else
    if((ld = ldap_open(serv, info->port)) == NULL)
#endif
#endif
    {
      /* TRANSLATORS: All of the three args together are an error message */
      snprintf(ebuf, sizeof(ebuf), _("Access to LDAP server failed: %s%s(%s)"),
	      errno ? error_description(errno) : "",
	      errno ? " " : "",
	      serv);
      wp_err->wp_err_occurred = 1;
      if(wp_err->error)
	fs_give((void **)&wp_err->error);

      wp_err->error = cpystr(ebuf);
      if(we_cancel)
        cancel_busy_cue(-1);

      q_status_message(SM_ORDER, 3, 5, wp_err->error);
      display_message('x');
      dprint((2, "%s\n", ebuf));
    }
    else if(!ps_global->intr_pending){
      int proto = 3, tlsmustbail = 0;
      char *pwd = NULL, user[NETMAXUSER];
#ifdef _WINDOWS
      char *passwd = NULL;
#else
      struct berval passwd = { 0 };
#endif
      char hostbuf[1024];
      NETMBX mb;
#ifndef _WINDOWS
      int rc;
#endif

      memset(&mb, 0, sizeof(mb));

#ifdef _SOLARIS_SDK
      if(info->tls || info->tlsmust)
	rc = ldapssl_install_routines(ld);
#endif

      if(ldap_v3_is_supported(ld) &&
	 our_ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &proto) == 0){
	dprint((5, "ldap: using version 3 protocol\n"));
      }

      /*
       * If we don't set RESTART then the select() waiting for the answer
       * in libldap will be interrupted and stopped by our busy_cue.
       */
      our_ldap_set_option(ld, LDAP_OPT_RESTART, LDAP_OPT_ON);

      /*
       * If we need to authenticate, get the password
       */
      if(info->binddn && info->binddn[0]){
	  char pmt[500];
	  char *space;

	  snprintf(hostbuf, sizeof(hostbuf), "{%s}dummy", info->serv ? info->serv : "?");

	  /*
	   * We don't handle multiple space-delimited hosts well.
	   * We don't know which we're asking for a password for.
	   * We're not connected yet so we can't know.
	   */
	  if((space=strindex(hostbuf, ' ')) != NULL)
	    *space = '\0';

	  mail_valid_net_parse_work(hostbuf, &mb, info->ldaps ? "ldaps" : "ldap");
	  mb.port = info->port;
	  mb.tlsflag = (info->tls || info->tlsmust) ? 1 : 0;
	  mb.sslflag = info->ldaps ? 1 : 0;

try_password_again:

	  if(mb.tlsflag
	     && (pwdtrial > 0 || 
#ifndef _WINDOWS
#ifdef _SOLARIS_SDK
		 (rc == LDAP_SUCCESS)
#else /* !_SOLARIS_SDK */
		 ((rc=ldap_start_tls_s(ld, NULL, NULL)) == LDAP_SUCCESS)
#endif /* !_SOLARIS_SDK */
#else /* _WINDOWS */
		 0  /* TODO: find a way to do this in Windows */
#endif /* _WINDOWS */
		 ))
	    mb.tlsflag = 1;
	  else
	    mb.tlsflag = 0;

	  if((info->tls || info->tlsmust) && !mb.tlsflag){
	    q_status_message(SM_ORDER, 3, 5, "Not able to start TLS encryption for LDAP server");
	    if(info->tlsmust)
	      tlsmustbail++;
	  }

	  if(!tlsmustbail){
	      snprintf(pmt, sizeof(pmt), "  %s", (info->nick && *info->nick) ? info->nick : serv);
	      mm_login_work(&mb, user, &pwd, pwdtrial, pmt, info->binddn);
	      if(pwd && pwd[0])
#ifdef _WINDOWS
		passwd = pwd;
#else
		passwd.bv_len = strlen(pwd);
		passwd.bv_val = pwd;		
#endif
	  }
      }


      /*
       * LDAPv2 requires the bind. v3 doesn't require it but we want
       * to tell the server we're v3 if the server supports v3, and if the
       * server doesn't support v3 the bind is required.
       */
      if(tlsmustbail 
#ifdef _WINDOWS
	|| ldap_simple_bind_s(ld, info->binddn, passwd) != LDAP_SUCCESS){
#else
	|| ldap_sasl_bind_s(ld, info->binddn, LDAP_SASL_SIMPLE, &passwd, NULL, NULL, NULL) != LDAP_SUCCESS){
#endif 
	wp_err->wp_err_occurred = 1;

	ld_errnum = our_ldap_get_lderrno(ld, NULL, &ld_errstr);

        if(!tlsmustbail && info->binddn && info->binddn[0] && pwdtrial < 2L
	   && ld_errnum == LDAP_INVALID_CREDENTIALS){
	  pwdtrial++;
          q_status_message(SM_ORDER, 3, 5, _("Invalid password"));
	  goto try_password_again;
	}

	snprintf(ebuf, sizeof(ebuf), _("LDAP server failed: %s%s%s%s"),
		ldap_err2string(ld_errnum),
		serv_errstr,
		(ld_errstr && *ld_errstr) ? ": " : "",
		(ld_errstr && *ld_errstr) ? ld_errstr : "");

        if(wp_err->error)
	  fs_give((void **)&wp_err->error);

        if(we_cancel)
          cancel_busy_cue(-1);
#ifdef _WINDOWS
	ldap_unbind(ld);
#else
	ldap_unbind_ext(ld, NULL, NULL);
#endif
        wp_err->error = cpystr(ebuf);
        q_status_message(SM_ORDER, 3, 5, wp_err->error);
        display_message('x');
	dprint((2, "%s\n", ebuf));
      }
      else if(!ps_global->intr_pending){
	int          srch_res, args, slen, flen;
#define TEMPLATELEN 512
	char         filt_template[TEMPLATELEN + 1];
	char         filt_format[2*TEMPLATELEN + 1];
	char         filter[2*TEMPLATELEN + 1];
	char         scp[2*TEMPLATELEN + 1];
	char        *p, *q;
	LDAPMessage *res = NULL;
	int intr_happened = 0;
	int tl;

	tl = (info->time == 0) ? info->time : info->time + 10;

	our_ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &tl);
	our_ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &info->size);

	/*
	 * If a custom filter has been passed in and it doesn't include a
	 * request to combine it with the configured filter, then replace
	 * any configured filter with the passed in filter.
	 */
	if(cust && cust->filt && !cust->combine){
	    if(info->cust)
	      fs_give((void **)&info->cust);
	    
	    info->cust = cpystr(cust->filt);
	}

	if(info->cust && *info->cust){	/* use custom filter if present */
	    strncpy(filt_template, info->cust, sizeof(filt_template));
	    filt_template[sizeof(filt_template)-1] = '\0';
	}
	else{				/* else use configured filter */
	    switch(info->type){
	      case LDAP_TYPE_SUR:
		snprintf(filt_template, sizeof(filt_template), "(%s=%%s)", snattr);
		break;
	      case LDAP_TYPE_GIVEN:
		snprintf(filt_template, sizeof(filt_template), "(%s=%%s)", gnattr);
		break;
	      case LDAP_TYPE_EMAIL:
		snprintf(filt_template, sizeof(filt_template), "(%s=%%s)", mailattr);
		break;
	      case LDAP_TYPE_CN_EMAIL:
		snprintf(filt_template, sizeof(filt_template), "(|(%s=%%s)(%s=%%s))", cnattr,
			mailattr);
		break;
	      case LDAP_TYPE_SUR_GIVEN:
		snprintf(filt_template, sizeof(filt_template), "(|(%s=%%s)(%s=%%s))",
			snattr, gnattr);
		break;
	      case LDAP_TYPE_SEVERAL:
		snprintf(filt_template, sizeof(filt_template),
			"(|(%s=%%s)(%s=%%s)(%s=%%s)(%s=%%s))",
			cnattr, mailattr, snattr, gnattr);
		break;
	      default:
	      case LDAP_TYPE_CN:
		snprintf(filt_template, sizeof(filt_template), "(%s=%%s)", cnattr);
		break;
	    }
	}

	/* just copy if custom */
	if(info->cust && *info->cust)
	  info->srch = LDAP_SRCH_EQUALS;

	p = filt_template;
	q = filt_format;
	memset((void *)filt_format, 0, sizeof(filt_format));
	args = 0;
	while(*p && (q - filt_format) + 4 < sizeof(filt_format)){
	    if(*p == '%' && *(p+1) == 's'){
		args++;
		switch(info->srch){
		  /* Exact match */
		  case LDAP_SRCH_EQUALS:
		    *q++ = *p++;
		    *q++ = *p++;
		    break;

		  /* Append wildcard after %s */
		  case LDAP_SRCH_BEGINS:
		    *q++ = *p++;
		    *q++ = *p++;
		    *q++ = '*';
		    break;

		  /* Insert wildcard before %s */
		  case LDAP_SRCH_ENDS:
		    *q++ = '*';
		    *q++ = *p++;
		    *q++ = *p++;
		    break;

		  /* Put wildcard before and after %s */
		  default:
		  case LDAP_SRCH_CONTAINS:
		    *q++ = '*';
		    *q++ = *p++;
		    *q++ = *p++;
		    *q++ = '*';
		    break;
		}
	    }
	    else
	      *q++ = *p++;
	}

	if(q - filt_format < sizeof(filt_format))
	  *q = '\0';

	filt_format[sizeof(filt_format)-1] = '\0';

	/*
	 * If combine is lit we put the custom filter and the filt_format
	 * filter and combine them with an &.
	 */
	if(cust && cust->filt && cust->combine){
	    char *combined;
	    size_t l;

	    l = strlen(filt_format) + strlen(cust->filt) + 3;
	    combined = (char *) fs_get((l+1) * sizeof(char));
	    snprintf(combined, l+1, "(&%s%s)", cust->filt, filt_format);
	    strncpy(filt_format, combined, sizeof(filt_format));
	    filt_format[sizeof(filt_format)-1] = '\0';
	    fs_give((void **) &combined);
	}

	/*
	 * Ad hoc attempt to make "Steve Hubert" match
	 * Steven Hubert but not Steven Shubert.
	 * We replace a <SPACE> with * <SPACE> (not * <SPACE> *).
	 */
	memset((void *)scp, 0, sizeof(scp));
	if(info->nosub)
	  strncpy(scp, string, sizeof(scp));
	else{
	    p = string;
	    q = scp;
	    while(*p && (q - scp) + 1 < sizeof(scp)){
		if(*p == SPACE && *(p+1) != SPACE){
		    *q++ = '*';
		    *q++ = *p++;
		}
		else
		  *q++ = *p++;
	    }
	}

	scp[sizeof(scp)-1] = '\0';

	slen = strlen(scp);
	flen = strlen(filt_format);
	/* truncate string if it will overflow filter */
	if(args*slen + flen - 2*args > sizeof(filter)-1)
	  scp[(sizeof(filter)-1 - flen)/args] = '\0';

	/*
	 * Replace %s's with scp.
	 */
	switch(args){
	  case 0:
	    snprintf(filter, sizeof(filter), "%s", filt_format);
	    break;
	  case 1:
	    snprintf(filter, sizeof(filter), filt_format, scp);
	    break;
	  case 2:
	    snprintf(filter, sizeof(filter), filt_format, scp, scp);
	    break;
	  case 3:
	    snprintf(filter, sizeof(filter), filt_format, scp, scp, scp);
	    break;
	  case 4:
	    snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp);
	    break;
	  case 5:
	    snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp);
	    break;
	  case 6:
	    snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp, scp);
	    break;
	  case 7:
	    snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp, scp, scp);
	    break;
	  case 8:
	    snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp, scp, scp,
		    scp);
	    break;
	  case 9:
	    snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp, scp, scp,
		    scp, scp);
	    break;
	  case 10:
	  default:
	    snprintf(filter, sizeof(filter), filt_format, scp, scp, scp, scp, scp, scp, scp,
		    scp, scp, scp);
	    break;
	}

	/* replace double *'s with single *'s in filter */
	for(p = q = filter; *p; p++)
	  if(*p != '*' || p == filter || *(p-1) != '*')
	    *q++ = *p;

	*q = '\0';

	(void) removing_double_quotes(base);
	dprint((5, "about to ldap_search(\"%s\", %s)\n",
	       base ? base : "?", filter ? filter : "?"));
        if(ps_global->intr_pending)
	  srch_res = LDAP_PROTOCOL_ERROR;
	else{
	  int msgid;
	  time_t start_time;
	  struct timeval tv = {0}, *tvp = NULL;

	  memset((void *)&tv, 0, sizeof(struct timeval));
          tv.tv_sec = info->time;
	  tvp = &tv;

	  start_time = time((time_t *)0);

	  dprint((6, "ldap_lookup: calling ldap_search\n"));
#ifdef _WINDOWS
	  msgid = ldap_search(ld, base, info->scope, filter, NULL, 0);
#else
	  if(ldap_search_ext(ld, base, info->scope, filter, NULL, 0,
			NULL, NULL, tvp, info->size, &msgid) != LDAP_SUCCESS)
	    msgid = -1;
#endif

	  if(msgid == -1)
	    srch_res = our_ldap_get_lderrno(ld, NULL, NULL);
	  else{
	    int lres;
	    /*
	     * Warning: struct timeval is not portable. However, since it is
	     * part of LDAP api it must be portable to all platforms LDAP
	     * has been ported to.
	     */
	    struct timeval t;

	    t.tv_sec  = 1; t.tv_usec = 0;
	      
	    do {
	      if(ps_global->intr_pending)
		intr_happened = 1;

	      dprint((6, "ldap_result(id=%d): ", msgid));
	      if((lres=ldap_result(ld, msgid, LDAP_MSG_ALL, &t, &res)) == -1){
	        /* error */
		srch_res = our_ldap_get_lderrno(ld, NULL, NULL);
	        dprint((6, "error (-1 returned): ld_errno=%d\n",
			   srch_res));
	      }
	      else if(lres == 0){  /* timeout, no results available */
		if(intr_happened){
#ifdef _WINDOWS
		  ldap_abandon(ld, msgid);
#else
		  ldap_abandon_ext(ld, msgid, NULL, NULL);
#endif
		  srch_res = LDAP_PROTOCOL_ERROR;
		  if(our_ldap_get_lderrno(ld, NULL, NULL) == LDAP_SUCCESS)
		    our_ldap_set_lderrno(ld, LDAP_PROTOCOL_ERROR, NULL, NULL);

	          dprint((6, "timeout, intr: srch_res=%d\n",
			     srch_res));
		}
		else if(info->time > 0 &&
			((long)time((time_t *)0) - start_time) > info->time){
		  /* try for partial results */
		  t.tv_sec  = 0; t.tv_usec = 0;
		  lres = ldap_result(ld, msgid, LDAP_MSG_RECEIVED, &t, &res);
		  if(lres > 0 && lres != LDAP_RES_SEARCH_RESULT){
		    srch_res = LDAP_SUCCESS;
		    dprint((6, "partial result: lres=0x%x\n", lres));
		  }
		  else{
		    if(lres == 0)
#ifdef _WINDOWS
		      ldap_abandon(ld, msgid);
#else
		      ldap_abandon_ext(ld, msgid, NULL, NULL);
#endif

		    srch_res = LDAP_TIMEOUT;
		    if(our_ldap_get_lderrno(ld, NULL, NULL) == LDAP_SUCCESS)
		      our_ldap_set_lderrno(ld, LDAP_TIMEOUT, NULL, NULL);

	            dprint((6,
			       "timeout, total_time (%d), srch_res=%d\n",
			       info->time, srch_res));
		  }
		}
		else{
	          dprint((6, "timeout\n"));
		}
	      }
	      else{
#ifdef _WINDOWS
		srch_res = ldap_result2error(ld, res, 0);
	        dprint((6, "lres=0x%x, srch_res=%d\n", lres,
			   srch_res));
#else
		int err;
		char *dn, *text, **ref;
		LDAPControl **srv;

		dn = text = NULL; ref = NULL; srv = NULL;
		srch_res = ldap_parse_result(ld, res,
			&err, &dn, &text, &ref, &srv, 0);
	        dprint((6, "lres=0x%x, srch_res=%d, dn=%s, text=%s\n", lres,
			   srch_res, dn ? dn : "", text ? text : ""));
		if(dn) ber_memfree(dn);
		if(text) ber_memfree(text);
		if(ref) ber_memvfree((void **) ref);
		if(srv) ldap_controls_free(srv);
#endif
	      }
	    }while(lres == 0 &&
		    !(intr_happened ||
		      (info->time > 0 &&
		       ((long)time((time_t *)0) - start_time) > info->time)));
	  }
	}

	if(intr_happened){
	  wp_exit = 1;
          if(we_cancel)
            cancel_busy_cue(-1);

	  if(wp_err->error)
	    fs_give((void **)&wp_err->error);
	  else{
	    q_status_message(SM_ORDER, 0, 1, "Interrupt");
	    display_message('x');
	    fflush(stdout);
	  }

	  if(res)
	    ldap_msgfree(res);
	  if(ld)
#ifdef _WINDOWS
	    ldap_unbind(ld);
#else
	    ldap_unbind_ext(ld, NULL, NULL);
#endif
	  
	  res = NULL; ld  = NULL;
	}
	else if(srch_res != LDAP_SUCCESS &&
	   srch_res != LDAP_TIMELIMIT_EXCEEDED &&
	   srch_res != LDAP_RESULTS_TOO_LARGE &&
	   srch_res != LDAP_TIMEOUT &&
	   srch_res != LDAP_SIZELIMIT_EXCEEDED){
	  wp_err->wp_err_occurred = 1;

	  ld_errnum = our_ldap_get_lderrno(ld, NULL, &ld_errstr);

	  snprintf(ebuf, sizeof(ebuf), _("LDAP search failed: %s%s%s%s"),
		  ldap_err2string(ld_errnum),
		  serv_errstr,
		  (ld_errstr && *ld_errstr) ? ": " : "",
		  (ld_errstr && *ld_errstr) ? ld_errstr : "");

          if(wp_err->error)
	    fs_give((void **)&wp_err->error);

          wp_err->error = cpystr(ebuf);
          if(we_cancel)
            cancel_busy_cue(-1);

          q_status_message(SM_ORDER, 3, 5, wp_err->error);
          display_message('x');
	  dprint((2, "%s\n", ebuf));
	  if(res)
	    ldap_msgfree(res);
	  if(ld)
#ifdef _WINDOWS
	    ldap_unbind(ld);
#else
	    ldap_unbind_ext(ld, NULL, NULL);
#endif

	  res = NULL; ld  = NULL;
	}
	else{
	  int cnt;

	  cnt = ldap_count_entries(ld, res);

	  if(cnt > 0){

	    if(srch_res == LDAP_TIMELIMIT_EXCEEDED ||
	       srch_res == LDAP_RESULTS_TOO_LARGE ||
	       srch_res == LDAP_TIMEOUT ||
	       srch_res == LDAP_SIZELIMIT_EXCEEDED){
	      wp_err->wp_err_occurred = 1;
	      ld_errnum = our_ldap_get_lderrno(ld, NULL, &ld_errstr);

	      snprintf(ebuf, sizeof(ebuf), _("LDAP partial results: %s%s%s%s"),
		      ldap_err2string(ld_errnum),
		      serv_errstr,
		      (ld_errstr && *ld_errstr) ? ": " : "",
		      (ld_errstr && *ld_errstr) ? ld_errstr : "");
	      dprint((2, "%s\n", ebuf));
	      if(wp_err->error)
		fs_give((void **)&wp_err->error);

	      wp_err->error = cpystr(ebuf);
	      if(we_cancel)
		cancel_busy_cue(-1);

	      q_status_message(SM_ORDER, 3, 5, wp_err->error);
	      display_message('x');
	    }

	    dprint((5, "Matched %d entries on %s\n",
	           cnt, serv ? serv : "?"));

	    serv_res = (LDAP_SERV_RES_S *)fs_get(sizeof(LDAP_SERV_RES_S));
	    memset((void *)serv_res, 0, sizeof(*serv_res));
	    serv_res->ld   = ld;
	    serv_res->res  = res;
	    serv_res->info_used = copy_ldap_serv_info(info);
	    /* Save by reference? */
	    if(info->ref){
		snprintf(buf, sizeof(buf), "%s:%s", serv, comatose(info->port));
		serv_res->serv = cpystr(buf);
	    }
	    else
	      serv_res->serv = NULL;

	    serv_res->next = NULL;
	  }
	  else{
	    if(srch_res == LDAP_TIMELIMIT_EXCEEDED ||
	       srch_res == LDAP_RESULTS_TOO_LARGE ||
	       srch_res == LDAP_TIMEOUT ||
	       srch_res == LDAP_SIZELIMIT_EXCEEDED){
	      wp_err->wp_err_occurred = 1;
	      wp_err->ldap_errno      = srch_res;

	      ld_errnum = our_ldap_get_lderrno(ld, NULL, &ld_errstr);

	      snprintf(ebuf, sizeof(ebuf), _("LDAP search failed: %s%s%s%s"),
		      ldap_err2string(ld_errnum),
		      serv_errstr,
		      (ld_errstr && *ld_errstr) ? ": " : "",
		      (ld_errstr && *ld_errstr) ? ld_errstr : "");

	      if(wp_err->error)
		fs_give((void **)&wp_err->error);

	      wp_err->error = cpystr(ebuf);
	      if(we_cancel)
		cancel_busy_cue(-1);

	      q_status_message(SM_ORDER, 3, 5, wp_err->error);
	      display_message('x');
	      dprint((2, "%s\n", ebuf));
	    }

	    dprint((5, "Matched 0 entries on %s\n",
		   serv ? serv : "?"));
	    if(res)
	      ldap_msgfree(res);
	    if(ld)
#ifdef _WINDOWS
	      ldap_unbind(ld);
#else
	      ldap_unbind_ext(ld, NULL, NULL);
#endif

	    res = NULL; ld  = NULL;
	  }
	}
      }
      if(pwd)
	fs_give((void **) &pwd);
    }

    if(we_cancel)
      cancel_busy_cue(-1);

    if(we_turned_on)
      intr_handling_off();

    if(serv)
      fs_give((void **)&serv);
    if(base)
      fs_give((void **)&base);
    if(serv_errstr)
      fs_give((void **)&serv_errstr);

    return(serv_res);
}


/*
 * Given a list of entries, present them to user so user may
 * select one.
 *
 * Args  head -- The head of the list of results
 *       orig -- The string the user was searching for
 *     result -- Returned pointer to chosen LDAP_SEARCH_WINNER or NULL
 *     wp_err -- Error handling
 *      style -- 
 *
 * Returns  0   ok
 *         -1   Exit chosen by user
 *         -2   None of matched entries had an email address
 *         -3   No matched entries
 *         -4   Goback to Abook List chosen by user
 *         -5   caller shouldn't free head
 */
int
ask_user_which_entry(LDAP_SERV_RES_S *head, char *orig, LDAP_CHOOSE_S **result,
		     WP_ERR_S *wp_err, LDAPLookupStyle style)
{
    ADDR_CHOOSE_S ac;
    char          t[200];
    int           retval;

    dprint((3, "ask_user_which(style=%s)\n",
	style == AlwaysDisplayAndMailRequired ? "AlwaysDisplayAndMailRequired" :
	  style == AlwaysDisplay ? "AlwaysDisplay" :
	    style == DisplayIfTwo ? "DisplayIfTwo" :
	      style == DisplayForURL ? "DisplayForURL" :
	        style == DisplayIfOne ? "DisplayIfOne" : "?"));

    /*
     * Set up a screen for user to choose one entry.
     */

    if(style == AlwaysDisplay || style == DisplayForURL)
      snprintf(t, sizeof(t), "SEARCH RESULTS INDEX");
    else{
	int len;

	len = strlen(orig);
	snprintf(t, sizeof(t), _("SELECT ONE ADDRESS%s%s%s"),
		(orig && *orig && len < 40) ? " FOR \"" : "",
		(orig && *orig && len < 40) ? orig : "",
		(orig && *orig && len < 40) ? "\"" : "");
    }

    memset(&ac, 0, sizeof(ADDR_CHOOSE_S));
    ac.title = cpystr(t);
    ac.res_head = head;

    retval = ldap_addr_select(ps_global, &ac, result, style, wp_err, orig);

    switch(retval){
      case 0:		/* Ok */
	break;

      case -1:		/* Exit chosen by user */
	wp_exit = 1;
	break;

      case -4:		/* GoBack to AbookList chosen by user */
	break;

      case -5:
	wp_nobail = 1;
	break;

      case -2:
	if(style != AlwaysDisplay){
	    if(wp_err->error)
	      fs_give((void **)&wp_err->error);

	    wp_err->error =
   cpystr(_("None of the names matched on directory server has an email address"));
	    q_status_message(SM_ORDER, 3, 5, wp_err->error);
	    display_message('x');
	}

	break;

      case -3:
	if(style == AlwaysDisplayAndMailRequired){
	    if(wp_err->error)
	      fs_give((void **)&wp_err->error);

	    wp_err->error = cpystr(_("No matches on directory server"));
	    q_status_message(SM_ORDER, 3, 5, wp_err->error);
	    display_message('x');
	}

	break;
    }

    fs_give((void **)&ac.title);

    return(retval);
}


ADDRESS *
address_from_ldap(LDAP_CHOOSE_S *winning_e)
{
    ADDRESS *ret_a = NULL;

    if(winning_e){
	char       *a;
	BerElement *ber;

	ret_a = mail_newaddr();
	for(a = ldap_first_attribute(winning_e->ld, winning_e->selected_entry, &ber);
	    a != NULL;
	    a = ldap_next_attribute(winning_e->ld, winning_e->selected_entry, ber)){
	    int i;
	    char  *p;
	    struct berval **vals;

	    dprint((9, "attribute: %s\n", a ? a : "?"));
	    if(!ret_a->personal &&
	       strcmp(a, winning_e->info_used->cnattr) == 0){
		dprint((9, "Got cnattr:"));
		vals = ldap_get_values_len(winning_e->ld, winning_e->selected_entry, a);
		for(i = 0; i < ldap_count_values_len(vals); i++)
		  dprint((9, "       %s\n",
		         vals[i] ? vals[i]->bv_val : "?"));
	    
		if(ALPINE_LDAP_can_use(vals))
		  ret_a->personal = cpystr(vals[0]->bv_val);

		ldap_value_free_len(vals);
	    }
	    else if(!ret_a->mailbox &&
		    strcmp(a, winning_e->info_used->mailattr) == 0){
		dprint((9, "Got mailattr:"));
		vals = ldap_get_values_len(winning_e->ld, winning_e->selected_entry, a);
		for(i = 0; i < ldap_count_values_len(vals); i++)
		  dprint((9, "         %s\n",
		         vals[i] ? vals[i]->bv_val : "?"));
		    
		/* use first one */
		if(ALPINE_LDAP_can_use(vals)){
		    if((p = strindex(vals[0]->bv_val, '@')) != NULL){
			ret_a->host = cpystr(p+1);
			*p = '\0';
		    }

		    ret_a->mailbox = cpystr(vals[0]->bv_val);
		}

		ldap_value_free_len(vals);
	    }

	    our_ldap_memfree(a);
	}
    }

    return(ret_a);
}


/*
 * Break up the ldap-server string stored in the pinerc into its
 * parts. The structure is allocated here and should be freed by the caller.
 *
 * The original string looks like
 *     <servername>[:port] <SPACE> "/base=<base>/impl=1/..."
 *
 * Args  serv_str -- The original string from the pinerc to parse.
 *
 * Returns A pointer to a structure with filled in answers.
 *
 *  Some of the members have defaults. If port is -1, that means to use
 *  the default LDAP_PORT. If base is NULL, use "". Type and srch have
 *  defaults defined in alpine.h. If cust is non-NULL, it overrides type and
 *  srch.
 */
LDAP_SERV_S *
break_up_ldap_server(char *serv_str)
{
    char *lserv;
    char *q, *p, *tail;
    int   i, only_one = 1;
    LDAP_SERV_S *info = NULL;

    if(!serv_str)
      return(info);

    info = (LDAP_SERV_S *)fs_get(sizeof(LDAP_SERV_S));

    /*
     * Initialize to defaults.
     */
    memset((void *)info, 0, sizeof(*info));
    info->port  = -1;
    info->srch  = -1;
    info->type  = -1;
    info->time  = -1;
    info->size  = -1;
    info->scope = -1;

    /* copy the whole string to work on */
    lserv = cpystr(serv_str);
    if(lserv)
      removing_trailing_white_space(lserv);

    if(!lserv || !*lserv || *lserv == '"'){
	if(lserv)
	  fs_give((void **)&lserv);

	if(info)
	  free_ldap_server_info(&info);

	return(NULL);
    }

    tail = lserv;
    while((tail = strindex(tail, SPACE)) != NULL){
	tail++;
	if(*tail == '"' || *tail == '/'){
	    *(tail-1) = '\0';
	    break;
	}
	else
	  only_one = 0;
    }

    /* tail is the part after server[:port] <SPACE> */
    if(tail && *tail){
	removing_leading_white_space(tail);
	(void)removing_double_quotes(tail);
    }

    /* get the optional port number */
    if(only_one && (q = strindex(lserv, ':')) != NULL){
	int ldapport = -1;

	*q = '\0';
	if((ldapport = atoi(q+1)) >= 0)
	  info->port = ldapport;
    }

    /* use lserv for serv even though it has a few extra bytes alloced */
    info->serv = lserv;
    
    if(tail && *tail){
	/* get the search base */
	if((q = srchstr(tail, "/base=")) != NULL)
	  info->base = remove_backslash_escapes(q+6);

	if((q = srchstr(tail, "/binddn=")) != NULL)
	  info->binddn = remove_backslash_escapes(q+8);

	/* get the implicit parameter */
	if((q = srchstr(tail, "/impl=1")) != NULL)
	  info->impl = 1;

	/* get the rhs parameter */
	if((q = srchstr(tail, "/rhs=1")) != NULL)
	  info->rhs = 1;

	/* get the ref parameter */
	if((q = srchstr(tail, "/ref=1")) != NULL)
	  info->ref = 1;

	/* get the nosub parameter */
	if((q = srchstr(tail, "/nosub=1")) != NULL)
	  info->nosub = 1;

	/* get the tls parameter */
	if((q = srchstr(tail, "/tls=1")) != NULL)
	  info->tls = 1;

	/* get the tlsmust parameter */
	if((q = srchstr(tail, "/tlsm=1")) != NULL)
	  info->tlsmust = 1;

	/* get the ldaps parameter */
	if((q = srchstr(tail, "/ldaps=1")) != NULL)
	  info->ldaps = 1;

	/* get the search type value */
	if((q = srchstr(tail, "/type=")) != NULL){
	    NAMEVAL_S *v;

	    q += 6;
	    if((p = strindex(q, '/')) != NULL)
	      *p = '\0';
	    
	    for(i = 0; (v = ldap_search_types(i)); i++)
	      if(!strucmp(q, v->name)){
		  info->type = v->value;
		  break;
	      }
	    
	    if(p)
	      *p = '/';
	}

	/* get the search rule value */
	if((q = srchstr(tail, "/srch=")) != NULL){
	    NAMEVAL_S *v;

	    q += 6;
	    if((p = strindex(q, '/')) != NULL)
	      *p = '\0';
	    
	    for(i = 0; (v = ldap_search_rules(i)); i++)
	      if(!strucmp(q, v->name)){
		  info->srch = v->value;
		  break;
	      }
	    
	    if(p)
	      *p = '/';
	}

	/* get the scope */
	if((q = srchstr(tail, "/scope=")) != NULL){
	    NAMEVAL_S *v;

	    q += 7;
	    if((p = strindex(q, '/')) != NULL)
	      *p = '\0';
	    
	    for(i = 0; (v = ldap_search_scope(i)); i++)
	      if(!strucmp(q, v->name)){
		  info->scope = v->value;
		  break;
	      }
	    
	    if(p)
	      *p = '/';
	}

	/* get the time limit */
	if((q = srchstr(tail, "/time=")) != NULL){
	    q += 6;
	    if((p = strindex(q, '/')) != NULL)
	      *p = '\0';
	    
	    /* This one's a number */
	    if(*q){
		char *err;

		err = strtoval(q, &i, 0, 500, 0, tmp_20k_buf, SIZEOF_20KBUF, "ldap timelimit");
		if(err){
		  dprint((1, "%s\n", err ? err : "?"));
		}
		else
		  info->time = i;
	    }

	    if(p)
	      *p = '/';
	}

	/* get the size limit */
	if((q = srchstr(tail, "/size=")) != NULL){
	    q += 6;
	    if((p = strindex(q, '/')) != NULL)
	      *p = '\0';
	    
	    /* This one's a number */
	    if(*q){
		char *err;

		err = strtoval(q, &i, 0, 500, 0, tmp_20k_buf, SIZEOF_20KBUF, "ldap sizelimit");
		if(err){
		  dprint((1, "%s\n", err ? err : "?"));
		}
		else
		  info->size = i;
	    }

	    if(p)
	      *p = '/';
	}

	/* get the custom search filter */
	if((q = srchstr(tail, "/cust=")) != NULL)
	  info->cust = remove_backslash_escapes(q+6);

	/* get the nickname */
	if((q = srchstr(tail, "/nick=")) != NULL)
	  info->nick = remove_backslash_escapes(q+6);

	/* get the mail attribute name */
	if((q = srchstr(tail, "/matr=")) != NULL)
	  info->mailattr = remove_backslash_escapes(q+6);

	/* get the sn attribute name */
	if((q = srchstr(tail, "/satr=")) != NULL)
	  info->snattr = remove_backslash_escapes(q+6);

	/* get the gn attribute name */
	if((q = srchstr(tail, "/gatr=")) != NULL)
	  info->gnattr = remove_backslash_escapes(q+6);

	/* get the cn attribute name */
	if((q = srchstr(tail, "/catr=")) != NULL)
	  info->cnattr = remove_backslash_escapes(q+6);

	/* get the backup mail address */
	if((q = srchstr(tail, "/mail=")) != NULL)
	  info->mail = remove_backslash_escapes(q+6);
    }

    return(info);
}


void
free_ldap_server_info(LDAP_SERV_S **info)
{
    if(info && *info){
	if((*info)->serv)
	  fs_give((void **)&(*info)->serv);

	if((*info)->base)
	  fs_give((void **)&(*info)->base);

	if((*info)->cust)
	  fs_give((void **)&(*info)->cust);

	if((*info)->binddn)
	  fs_give((void **)&(*info)->binddn);

	if((*info)->nick)
	  fs_give((void **)&(*info)->nick);

	if((*info)->mail)
	  fs_give((void **)&(*info)->mail);

	if((*info)->mailattr)
	  fs_give((void **)&(*info)->mailattr);

	if((*info)->snattr)
	  fs_give((void **)&(*info)->snattr);

	if((*info)->gnattr)
	  fs_give((void **)&(*info)->gnattr);

	if((*info)->cnattr)
	  fs_give((void **)&(*info)->cnattr);

	fs_give((void **)info);
	*info = NULL;
    }
}


LDAP_SERV_S *
copy_ldap_serv_info(LDAP_SERV_S *src)
{
    LDAP_SERV_S *info = NULL;

    if(src){
	info = (LDAP_SERV_S *) fs_get(sizeof(*info));

	/*
	 * Initialize to defaults.
	 */
	memset((void *)info, 0, sizeof(*info));

	info->serv     = src->serv ? cpystr(src->serv) : NULL;
	info->base     = src->base ? cpystr(src->base) : NULL;
	info->cust     = src->cust ? cpystr(src->cust) : NULL;
	info->binddn   = src->binddn ? cpystr(src->binddn) : NULL;
	info->nick     = src->nick ? cpystr(src->nick) : NULL;
	info->mail     = src->mail ? cpystr(src->mail) : NULL;
	info->mailattr = cpystr((src->mailattr && src->mailattr[0])
			    ? src->mailattr : DEF_LDAP_MAILATTR);
	info->snattr = cpystr((src->snattr && src->snattr[0])
			    ? src->snattr : DEF_LDAP_SNATTR);
	info->gnattr = cpystr((src->gnattr && src->gnattr[0])
			    ? src->gnattr : DEF_LDAP_GNATTR);
	info->cnattr = cpystr((src->cnattr && src->cnattr[0])
			    ? src->cnattr : DEF_LDAP_CNATTR);

	info->port  = (src->port < 0)  ? LDAP_PORT      : src->port;
	info->time  = (src->time < 0)  ? DEF_LDAP_TIME  : src->time;
	info->size  = (src->size < 0)  ? DEF_LDAP_SIZE  : src->size;
	info->type  = (src->type < 0)  ? DEF_LDAP_TYPE  : src->type;
	info->srch  = (src->srch < 0)  ? DEF_LDAP_SRCH  : src->srch;
	info->scope = (src->scope < 0) ? DEF_LDAP_SCOPE : src->scope;
	info->impl  = src->impl;
	info->rhs   = src->rhs;
	info->ref   = src->ref;
	info->nosub = src->nosub;
	info->tls   = src->tls;
    }

    return(info);
}


void
free_ldap_result_list(LDAP_SERV_RES_S **r)
{
    if(r && *r){
	free_ldap_result_list(&(*r)->next);
	if((*r)->res)
	  ldap_msgfree((*r)->res);
	if((*r)->ld)
#ifdef _WINDOWS
	  ldap_unbind((*r)->ld);
#else
	  ldap_unbind_ext((*r)->ld, NULL, NULL);
#endif
	if((*r)->info_used)
	  free_ldap_server_info(&(*r)->info_used);
	if((*r)->serv)
	  fs_give((void **) &(*r)->serv);

	fs_give((void **) r);
    }
}


/*
 * Mask API differences. 
 */
void
our_ldap_memfree(void *a)
{
#if (LDAPAPI >= 15)
    if(a)
      ldap_memfree(a);
#endif
}


/*
 * Mask API differences.
 */
void
our_ldap_dn_memfree(void *a)
{
#if defined(_WINDOWS)
    if(a)
      ldap_memfree(a);
#else
#if (LDAPAPI >= 15)
    if(a)
      ldap_memfree(a);
#else
    if(a)
      free(a);
#endif
#endif
}


/*
 * More API masking.
 */
int
our_ldap_get_lderrno(LDAP *ld, char **m, char **s)
{
    int ret = 0;

#if (LDAPAPI >= 2000)
    if(ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, (void *)&ret) == 0){
	if(s)
	  ldap_get_option(ld, LDAP_OPT_ERROR_STRING, (void *)s);
    }
#elif (LDAPAPI >= 15)
    ret = ldap_get_lderrno(ld, m, s);
#else
    ret = ld->ld_errno;
    if(s)
      *s = ld->ld_error;
#endif

    return(ret);
}


/*
 * More API masking.
 */
int
our_ldap_set_lderrno(LDAP *ld, int e, char *m, char *s)
{
    int ret;

#if (LDAPAPI >= 2000)
    if(ldap_set_option(ld, LDAP_OPT_ERROR_NUMBER, (void *)&e) == 0)
      ret = LDAP_SUCCESS;
    else
      (void)ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, (void *)&ret);
#elif (LDAPAPI >= 15)
    ret = ldap_set_lderrno(ld, e, m, s);
#else
    /* this is all we care about */
    ld->ld_errno = e;
    ret = LDAP_SUCCESS;
#endif

    return(ret);
}


/*
 * More API masking.
 */
int
our_ldap_set_option(LDAP *ld, int option, void *optdata)
{
    int ret;

#if (LDAPAPI >= 15)
    ret = ldap_set_option(ld, option, optdata);
#else
    switch(option){
      case LDAP_OPT_TIMELIMIT:
	ld->ld_timelimit = *(int *)optdata;
	break;
	
      case LDAP_OPT_SIZELIMIT:
	ld->ld_sizelimit = *(int *)optdata;
	break;
    
      case LDAP_OPT_RESTART:
	if((int)optdata)
	  ld->ld_options |= LDAP_OPT_RESTART;
	else
	  ld->ld_options &= ~LDAP_OPT_RESTART;

	break;
    
      /*
       * Does nothing here. There is only one protocol version supported.
       */
      case LDAP_OPT_PROTOCOL_VERSION:
	ret = -1;
	break;
    
      default:
	alpine_panic("LDAP function not implemented");
    }
#endif

    return(ret);
}


/*
 * Returns 1 if we can use LDAP version 3 protocol.
 */
int
ldap_v3_is_supported(LDAP *ld)
{
    return(1);
}

struct tl_table {
    char *ldap_ese;
    char *translated;
};

static struct tl_table ldap_trans_table[]={
    /*
     * TRANSLATORS: This is a list of LDAP attributes with translations to present
     * to the user. For example the attribute mail is Email Address and the attribute
     * cn is Name.
     */
    {"mail",				N_("Email Address")},
#define LDAP_MAIL_ATTR 0
    {"sn",				N_("Surname")},
#define LDAP_SN_ATTR 1
    {"givenName",			N_("Given Name")},
#define LDAP_GN_ATTR 2
    {"cn",				N_("Name")},
#define LDAP_CN_ATTR 3
    {"electronicmail",			N_("Email Address")},
#define LDAP_EMAIL_ATTR 4
    {"o",				N_("Organization")},
    {"ou",				N_("Unit")},
    {"c",				N_("Country")},
    {"st",				N_("State or Province")},
    {"l",				N_("Locality")},
    {"objectClass",			N_("Object Class")},
    {"title",				N_("Title")},
    {"departmentNumber",		N_("Department")},
    {"postalAddress",			N_("Postal Address")},
    {"homePostalAddress",		N_("Home Address")},
    {"mailStop",			N_("Mail Stop")},
    {"telephoneNumber",			N_("Voice Telephone")},
    {"homePhone",			N_("Home Telephone")},
    {"officePhone",			N_("Office Telephone")},
    {"facsimileTelephoneNumber",	N_("FAX Telephone")},
    {"mobile",				N_("Mobile Telephone")},
    {"pager",				N_("Pager")},
    {"roomNumber",			N_("Room Number")},
    {"uid",				N_("User ID")},
    {NULL,				NULL}
};

char *
ldap_translate(char *a, LDAP_SERV_S *info_used)
{
    int i;

    if(info_used){
	if(info_used->mailattr && strucmp(info_used->mailattr, a) == 0)
	  return(_(ldap_trans_table[LDAP_MAIL_ATTR].translated));
	else if(info_used->snattr && strucmp(info_used->snattr, a) == 0)
	  return(_(ldap_trans_table[LDAP_SN_ATTR].translated));
	else if(info_used->gnattr && strucmp(info_used->gnattr, a) == 0)
	  return(_(ldap_trans_table[LDAP_GN_ATTR].translated));
	else if(info_used->cnattr && strucmp(info_used->cnattr, a) == 0)
	  return(_(ldap_trans_table[LDAP_CN_ATTR].translated));
    }

    for(i = 0; ldap_trans_table[i].ldap_ese; i++){
	if(info_used)
	  switch(i){
	    case LDAP_MAIL_ATTR:
	    case LDAP_SN_ATTR:
	    case LDAP_GN_ATTR:
	    case LDAP_CN_ATTR:
	    case LDAP_EMAIL_ATTR:
	      continue;
	  }

	if(strucmp(ldap_trans_table[i].ldap_ese, a) == 0)
	  return(_(ldap_trans_table[i].translated));
    }
    
    return(a);
}

char **
berval_to_array(struct berval **v)
{
  char **rv = NULL;
  int i, len;

  if(v == NULL) return rv; 
  len = ldap_count_values_len(v);

  rv = fs_get((len+1)*sizeof(char *));
  for(i = 0; i < len; i++)
    if(ALPINE_LDAP_can_use_num(v, i))
	rv[i] = cpystr(v[i]->bv_val);
    else
	rv[i] = NULL;
  rv[len] = NULL;

  return rv;
}

#endif	/* ENABLE_LDAP */
