/* ========================================================================
 * Copyright 2013-2026 Eduardo Chappa
 * Copyright 2006-2009 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/adrbklib.h"
#include "../pith/abdlc.h"
#include "../pith/addrbook.h"
#include "../pith/addrstring.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/status.h"
#include "../pith/remote.h"
#include "../pith/tempfile.h"
#include "../pith/bldaddr.h"
#include "../pith/signal.h"
#include "../pith/busy.h"
#include "../pith/util.h"


AddrScrState as;

void (*pith_opt_save_and_restore)(int, SAVE_STATE_S *);


/*
 * We don't want any end of line fixups to occur, so include "b" in DOS modes.
 */
#if defined(DOS) || defined(OS2)
#define	ADRBK_NAME		"addrbook"
#else
#define	ADRBK_NAME		".addressbook"
#endif

#define	OPEN_WRITE_MODE		(O_TRUNC|O_WRONLY|O_CREAT|O_BINARY)


#ifndef MAXPATH
#define MAXPATH 1000    /* Longest file path we can deal with */
#endif

#define TABWIDTH 8
#define INDENTSTR "   "
#define INDENTXTRA " : "
#define INDENT 3  /* length of INDENTSTR */


#define SLOP 3

static int writing; /* so we can give understandable error message */

AdrBk               *edited_abook;

static char empty[]       = "";

jmp_buf jump_over_qsort;


/* internal prototypes */
int            copy_abook_to_tempfile(AdrBk *, char *, size_t);
char          *dir_containing(char *);
char          *get_next_abook_entry(FILE *, int);
void           strip_addr_string(char *, char **, char **);
void           adrbk_check_local_validity(AdrBk *, long);
int            write_single_abook_entry(AdrBk_Entry *, FILE *, int *, int *, int *, int *);
char	      *backcompat_encoding_for_abook(char *, size_t, char *, size_t, char *);
int            percent_abook_saved(void);
void           exp_del_nth(EXPANDED_S *, a_c_arg_t);
void           exp_add_nth(EXPANDED_S *, a_c_arg_t);
int            cmp_ae_by_full_lists_last(const qsort_t *,const qsort_t *);
int            cmp_cntr_by_full_lists_last(const qsort_t *, const qsort_t *);
int            cmp_ae_by_full(const qsort_t *, const qsort_t *);
int            cmp_cntr_by_full(const qsort_t *, const qsort_t *);
int            cmp_ae_by_nick_lists_last(const qsort_t *,const qsort_t *);
int            cmp_cntr_by_nick_lists_last(const qsort_t *, const qsort_t *);
int            cmp_ae_by_nick(const qsort_t *, const qsort_t *);
int            cmp_cntr_by_nick(const qsort_t *, const qsort_t *);
int            cmp_addr(const qsort_t *, const qsort_t *);
void           sort_addr_list(char **);
int            build_abook_datastruct(AdrBk *, char *, size_t);
AdrBk_Entry   *init_ae(AdrBk *, AdrBk_Entry *, char *);
adrbk_cntr_t   count_abook_entries_on_disk(AdrBk *, a_c_arg_t *);
AdrBk_Entry   *adrbk_get_delae(AdrBk *, a_c_arg_t);
void           free_ae_parts(AdrBk_Entry *);
void           add_entry_to_trie(AdrBk_Trie **, char *, a_c_arg_t);
int            build_abook_tries(AdrBk *, char *);
void           repair_abook_tries(AdrBk *);
adrbk_cntr_t   lookup_nickname_in_trie(AdrBk *, char *);
adrbk_cntr_t   lookup_address_in_trie(AdrBk *, char *);
adrbk_cntr_t   lookup_in_abook_trie(AdrBk_Trie *, char *);
void           free_abook_trie(AdrBk_Trie **);
adrbk_cntr_t   re_sort_particular_entry(AdrBk *, a_c_arg_t);
void           move_ab_entry(AdrBk *, a_c_arg_t, a_c_arg_t);
void           insert_ab_entry(AdrBk *, a_c_arg_t, AdrBk_Entry *, int);
void           delete_ab_entry(AdrBk *, a_c_arg_t, int);
void           defvalue_ae(AdrBk_Entry *);


/*
 *  Open, read, and parse an address book.
 *
 * Args: pab      -- the PerAddrBook structure
 *       homedir  -- the user's home directory if specified
 *       warning  -- put "why failed" message to user here
 *                   (provide space of at least 201 chars)
 *
 * If filename is NULL, the default will be used in the homedir
 * passed in.  If homedir is NULL, the current dir will be used.
 * If filename is not NULL and is an absolute path, just the filename
 * will be used.  Otherwise, it will be used relative to the homedir, or
 * to the current dir depending on whether or not homedir is NULL.
 *
 * Expected addressbook file format is:
 *  <nickname>\t<fullname>\t<address_field>\t<fcc>\t<comment>
 *
 * The last two fields (\t<fcc>\t<comment>) are optional.
 *
 * Lines that start with SPACE are continuation lines.  Ends of lines are
 * treated as if they were spaces.  The address field is either a single
 * address or a list of comma-separated addresses inside parentheses.
 *
 * Fields missing from the end of an entry are considered blank.
 *
 * Commas in the address field will cause problems, as will tabs in any
 * field.
 *
 * There may be some deleted entries in the addressbook that don't get
 * used.  They look like regular entries except their nicknames start
 * with the string "#DELETED-YY/MM/DD#".
 */
AdrBk *
adrbk_open(PerAddrBook *pab, char *homedir, char *warning, size_t warninglen, int sort_rule)
{
    char   path[MAXPATH], *filename;
    AdrBk *ab;
    int    abook_validity_was_minusone = 0;


    filename = pab ? pab->filename : NULL;

    dprint((2, "- adrbk_open(%s) -\n", filename ? filename : ""));

    ab = (AdrBk *)fs_get(sizeof(AdrBk));
    memset(ab, 0, sizeof(*ab));

    ab->orig_filename = filename ? cpystr(filename) : NULL;

    if(pab->type & REMOTE_VIA_IMAP){
	int try_cache;

	ab->type           = Imap;

	if(!ab->orig_filename || *(ab->orig_filename) != '{'){
	    dprint((1, "adrbk_open: remote: filename=%s\n",
		   ab->orig_filename ? ab->orig_filename : "NULL"));
	    goto bail_out;
	}

	if(!(ab->rd = rd_new_remdata(RemImap, ab->orig_filename, REMOTE_ABOOK_SUBTYPE))){
	    dprint((1,
		   "adrbk_open: remote: new_remdata failed: %s\n",
		   ab->orig_filename ? ab->orig_filename : "NULL"));
	    goto bail_out;
	}
	
	/* Transfer responsibility for the storage object */
	ab->rd->so = pab->so;
	pab->so = NULL;

	try_cache = rd_read_metadata(ab->rd);

	if(ab->rd->lf)
	  ab->filename = cpystr(ab->rd->lf);

	/* Transfer responsibility for removal of temp file */
	if(ab->rd->flags & DEL_FILE){
	    ab->flags |= DEL_FILE;
	    ab->rd->flags &= ~DEL_FILE;
	}

	if(pab->access == MaybeRorW){
	    if(ab->rd->read_status == 'R')
	      pab->access = ab->rd->access = ReadOnly;
	    else
	      pab->access = ab->rd->access = ReadWrite;
	}
	else if(pab->access == ReadOnly){
	    /*
	     * Pass on readonly-ness from being a global addrbook.
	     * This should cause us to open the remote folder readonly,
	     * avoiding error messages about readonly-ness.
	     */
	    ab->rd->access = ReadOnly;
	}

	/*
	 * The plan is to fetch addrbook data and copy into local file.
	 * Then we open the local copy for reading. We use the IMAP STATUS
	 * command to tell us if we need to update from the remote addrbook.
	 *
	 * If access is NoExists, that probably means we had trouble
	 * opening the remote folder in the adrbk_access routine.
	 * In that case we'll use a cached copy if we have one.
	 */
	if(pab->access != NoExists){

bootstrap_nocheck_policy:
	    if(try_cache && ps_global->remote_abook_validity == -1 &&
	       !abook_validity_was_minusone)
	      abook_validity_was_minusone++;
	    else{
		rd_check_remvalid(ab->rd, 1L);
		abook_validity_was_minusone = 0;
	    }

	    /*
	     * If the cached info on this addrbook says it is readonly but
	     * it looks like it's been fixed now, change it to readwrite.
	     */
	    if(!(pab->type & GLOBAL) && ab->rd->read_status == 'R'){
		/*
		 * We go to this trouble since readonly addrbooks
		 * are likely a mistake. They are usually supposed to be
		 * readwrite. So we open it and check if it's been fixed.
		 */
		rd_check_readonly_access(ab->rd);
		if(ab->rd->read_status == 'W'){
		    pab->access = ab->rd->access = ReadWrite;
		    ab->rd->flags |= REM_OUTOFDATE;
		}
		else
		  pab->access = ab->rd->access = ReadOnly;
	    }
	      
	    if(ab->rd->flags & REM_OUTOFDATE){
		if(rd_update_local(ab->rd) != 0){
		    dprint((1,
			   "adrbk_open: remote: rd_update_local failed\n"));
		    /*
		     * Don't give up altogether. We still may be
		     * able to use a cached copy.
		     */
		}
		else{
		    dprint((7,
			   "%s: copied remote to local (%ld)\n",
			   ab->rd->rn ? ab->rd->rn : "?",
			   (long)ab->rd->last_use));
		}
	    }

	    if(pab->access == ReadWrite)
	      ab->rd->flags |= DO_REMTRIM;
	}

	/* If we couldn't get to remote folder, try using the cached copy */
	if(pab->access == NoExists || ab->rd->flags & REM_OUTOFDATE){
	    if(try_cache){
		pab->access = ab->rd->access = ReadOnly;
		ab->rd->flags |= USE_OLD_CACHE;
		q_status_message(SM_ORDER, 3, 4,
		 _("Can't contact remote address book server, using cached copy"));
		dprint((2,
	"Can't open remote addrbook %s, using local cached copy %s readonly\n",
		       ab->rd->rn ? ab->rd->rn : "?",
		       ab->rd->lf ? ab->rd->lf : "?"));
	    }
	    else
	      goto bail_out;
	}
    }
    else{
	ab->type = Local;

	/*------------ figure out and save name of file to open ---------*/
	if(filename == NULL){
	    if(homedir != NULL){
		build_path(path, homedir, ADRBK_NAME, sizeof(path));
		ab->filename = cpystr(path);
	    }
	    else
	      ab->filename = cpystr(ADRBK_NAME);
	}
	else{
	    if(is_absolute_path(filename)){
		ab->filename = cpystr(filename);
	    }
	    else{
		if(homedir != NULL){
		    build_path(path, homedir, filename, sizeof(path));
		    ab->filename = cpystr(path);
		}
		else
		  ab->filename = cpystr(filename);
	    }
	}
    }

    if(ab->filename && ab->filename[0]){
	char buf[MAXPATH];

	strncpy(buf, ab->filename, sizeof(buf)-4);
	buf[sizeof(buf)-4] = '\0';

	/*
	 * Our_filecopy is used in _WINDOWS to allow
	 * multiple pines to update the address book. The problem is
	 * that if a file is open it can't be deleted, so we need to keep
	 * the main filename closed most of the time.
	 * In Unix, our_filecopy just points to filename.
	 */

#ifdef	_WINDOWS
	/*
	 * If we can't write in the same directory as filename is in, put
	 * the copies in /tmp instead.
	 */
	if(!(ab->our_filecopy = tempfile_in_same_dir(ab->filename, "a3", NULL)))
	  ab->our_filecopy = temp_nam(NULL, "a3");
#endif	/* _WINDOWS */

	/*
	 * We don't need the copies on Unix because we can rename/delete
	 * open files. Turn the feature off by making the copies point to
	 * the originals.
	 */
	if(!ab->our_filecopy)
	  ab->our_filecopy = ab->filename;
    }
    else{
	dprint((1, "adrbk_open: ab->filename is NULL???\n"));
	goto bail_out;
    }


    /*
     * We're going to make our own copy of the address book file so that
     * we won't conflict with other instances of pine trying to change it.
     * In particular, on Windows the address book file cannot be deleted
     * or renamed into if it is open in another process.
     */
    ab->flags |= FILE_OUTOFDATE;
    if(copy_abook_to_tempfile(ab, warning, warninglen) < 0){
	dprint((1, "adrbk_open: copy_file failed\n"));
	if(abook_validity_was_minusone){
	    /*
	     * The file copy failed when it shouldn't have. If the user has
	     * remote_abook_validity == -1 then we'll go back and try to
	     * do the validity check in case that can get us the file we
	     * need to copy. Without the validity check first time we won't
	     * contact the imap server.
	     */
	    dprint((1, "adrbk_open: trying to bootstrap\n"));

	    if(ab->our_filecopy){
		if(ab->our_filecopy != ab->filename){
		    our_unlink(ab->our_filecopy);
		    fs_give((void **)&ab->our_filecopy);
		}
		
		ab->our_filecopy = NULL;
	    }

	    goto bootstrap_nocheck_policy;
	}

	goto bail_out;
    }

    if(!ab->fp)
      goto bail_out;

    ab->sort_rule = sort_rule;
    if(pab->access == ReadOnly)
      ab->sort_rule = AB_SORT_RULE_NONE;
      
    if(ab){
	/* allocate header for expanded lists list */
	ab->exp      = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S));
	/* first real element is NULL */
	ab->exp->next = (EXPANDED_S *)NULL;

	/* allocate header for checked entries list */
	ab->checks       = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S));
	/* first real element is NULL */
	ab->checks->next = (EXPANDED_S *)NULL;

	/* allocate header for selected entries list */
	ab->selects       = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S));
	/* first real element is NULL */
	ab->selects->next = (EXPANDED_S *)NULL;

	return(ab);
    }

bail_out:
    dprint((2, "adrbk_open: bailing: filenames=%s %s %s fp=%s\n",
	   ab->orig_filename ? ab->orig_filename : "NULL",
	   ab->filename ? ab->filename : "NULL",
	   ab->our_filecopy ? ab->our_filecopy : "NULL",
	   ab->fp ? "open" : "NULL"));

    if(ab->rd){
	ab->rd->flags &= ~DO_REMTRIM;
	rd_close_remdata(&ab->rd);
    }

    if(ab->fp)
      (void)fclose(ab->fp);

    if(ab->orig_filename)
      fs_give((void **) &ab->orig_filename);

    if(ab->our_filecopy && ab->our_filecopy != ab->filename){
	our_unlink(ab->our_filecopy);
	fs_give((void **) &ab->our_filecopy);
    }

    if(ab->filename)
      fs_give((void **) &ab->filename);

    if(pab->so){
	so_give(&(pab->so));
	pab->so = NULL;
    }

    fs_give((void **) &ab);

    return NULL;
}


/*
 * Copy the address book file to the temporary session copy. Also copy
 * the hashfile. Any of these files which don't exist will be created.
 *
 * Returns  0 success
 *         -1 failure
 */
int
copy_abook_to_tempfile(AdrBk *ab, char *warning, size_t warninglen)
{
    int    got_it, fd, c,
	   ret = -1,
	   we_cancel = 0;
    FILE  *fp_read = (FILE *)NULL,
	  *fp_write = (FILE *)NULL;
    char  *lc;
    time_t mtime;


    dprint((3, "copy_file(%s) -\n",
	       (ab && ab->filename) ? ab->filename : ""));

    if(!ab || !ab->filename || !ab->filename[0])
      goto get_out;

    if(!(ab->flags & FILE_OUTOFDATE))
      return(0);
    
    /* open filename for reading */
    fp_read = our_fopen(ab->filename, "rb");
    if(fp_read == NULL){

	/*
	 * filename probably doesn't exist so we try to create it
	 */

	/* don't want to create in these cases, should already be there */
	if(ab->type == Imap){
	    if(warning){
		if(ab->type == Imap){
		    snprintf(warning, warninglen,
		    /* TRANSLATORS: A temporary file for the address book can't
		       be opened. */
			        _("Temp addrbook file can't be opened: %s"),
				ab->filename);
		    warning[warninglen-1] = '\0';
		}
		else{
		    strncpy(warning, _("Address book doesn't exist"), warninglen);
		    warning[warninglen-1] = '\0';
		}
	    }

	    goto get_out;
	}

	q_status_message1(SM_INFO, 0, 3,
	                  _("Address book %.200s doesn't exist, creating"),
	                  (lc=last_cmpnt(ab->filename)) ? lc : ab->filename);
	dprint((2, "Address book %s doesn't exist, creating\n",
	       ab->filename ? ab->filename : "?"));

	/*
	 * Just use user's umask for permissions. Mode is "w" so the create
	 * will happen. We close it right after creating and open it in
	 * read mode again later.
	 */
        fp_read = our_fopen(ab->filename, "wb");  /* create */
        if(fp_read == NULL        ||
	   fclose(fp_read) == EOF ||
	   (fp_read = our_fopen(ab->filename, "rb")) == NULL){
            /*--- Create failed, bail out ---*/
	    if(warning){
		strncpy(warning, error_description(errno), warninglen);
		warning[warninglen-1] = '\0';
	    }

	    dprint((2, "create failed: %s\n",
		error_description(errno)));

	    goto get_out;
        }
    }

    /* record new change date of addrbook file */
    ab->last_change_we_know_about = get_adj_name_file_mtime(ab->filename);

    ab->last_local_valid_chk = get_adj_time();


    /* now there is an ab->filename and we have it open for reading */

    got_it = 0;

    /* copy ab->filename to ab->our_filecopy, preserving mtime */
    if(ab->filename != ab->our_filecopy){
	struct stat sbuf;
	struct utimbuf times;
	int valid_stat = 0;

	dprint((7, "Before abook copies\n"));
	if((fd = our_open(ab->our_filecopy, OPEN_WRITE_MODE, 0600)) < 0)
	  goto get_out;
	
	fp_write = fdopen(fd, "wb");
	rewind(fp_read);
	if(fstat(fileno(fp_read), &sbuf)){
	    q_status_message1(SM_INFO, 0, 3,
		 "Error: can't stat addrbook \"%.200s\"",
		 (lc=last_cmpnt(ab->filename)) ? lc : ab->filename);
	    dprint((2, "Error: can't stat addrbook \"%s\"\n",
		   ab->filename ? ab->filename : "?"));
	}
	else{
	    valid_stat++;
	    times.actime  = sbuf.st_atime;
	    times.modtime = sbuf.st_mtime;
	}

	while((c = getc(fp_read)) != EOF)
	  if(putc(c, fp_write) == EOF)
	    goto get_out;
	
	(void)fclose(fp_write);
	fp_write = (FILE *)NULL;
	if(valid_stat && our_utime(ab->our_filecopy, &times)){
	    q_status_message1(SM_INFO, 0, 3,
		 "Error: can't set mtime for \"%.200s\"",
		 (lc=last_cmpnt(ab->filename)) ? lc : ab->our_filecopy);
	    dprint((2, "Error: can't set mtime for \"%s\"\n",
		   ab->our_filecopy ? ab->our_filecopy : "?"));
	}

	(void)fclose(fp_read);
	fp_read = (FILE *)NULL;
	if(!(ab->fp = our_fopen(ab->our_filecopy, "rb")))
	  goto get_out;

	dprint((7, "After abook file copy\n"));
    }
    else{  /* already open to the right file */
	ab->fp = fp_read;
	fp_read = (FILE *)NULL;
    }

    /*
     * Now we've copied filename to our_filecopy.
     * Operate on the copy now. Ab->fp is open readonly on
     * our_filecopy.
     */

    got_it = 0;
    mtime = get_adj_name_file_mtime(ab->our_filecopy);
    we_cancel = busy_cue(NULL, NULL, 1);
    if(build_abook_datastruct(ab, (warning && !*warning) ? warning : NULL, warninglen)){
	if(we_cancel)
	  cancel_busy_cue(-1);

	dprint((2, "failed in build_abook_datastruct\n"));
	goto get_out;
    }

    if(ab->arr
       && build_abook_tries(ab, (warning && !*warning) ? warning : NULL)){
	if(we_cancel)
	  cancel_busy_cue(-1);

	dprint((2, "failed in build_abook_tries\n"));
	goto get_out;
    }

    if(we_cancel)
      cancel_busy_cue(-1);

    ab->flags &= ~FILE_OUTOFDATE;  /* turn off out of date flag */
    ret = 0;

get_out:
    if(fp_read)
      (void)fclose(fp_read);

    if(fp_write)
      (void)fclose(fp_write);

    if(ret < 0 && ab){
	if(ab->our_filecopy && ab->our_filecopy != ab->filename)
	  our_unlink(ab->our_filecopy);
    }

    return(ret);
}


/*
 * Returns an allocated copy of the directory which contains filename.
 */
char *
dir_containing(char *filename)
{
    char  dir[MAXPATH+1];
    char *dirp = NULL;

    if(filename){
	char *lc;

	if((lc = last_cmpnt(filename)) != NULL){
	    int to_copy;

	    to_copy = (lc - filename > 1) ? (lc - filename - 1) : 1;
	    strncpy(dir, filename, MIN(to_copy, sizeof(dir)-1));
	    dir[MIN(to_copy, sizeof(dir)-1)] = '\0';
	}
	else{
	    dir[0] = '.';
	    dir[1] = '\0';
	}

	dirp = cpystr(dir);
    }

    return(dirp);
}


/*
 * Checks whether or not the addrbook is sorted correctly according to
 * the SortType.  Returns 1 if is sorted correctly, 0 otherwise.
 */
int
adrbk_is_in_sort_order(AdrBk *ab, int be_quiet)
{
    adrbk_cntr_t entry;
    AdrBk_Entry *ae, *ae_prev;
    int (*cmp_func)(const qsort_t *, const qsort_t *);
    int we_cancel = 0;

    dprint((9, "- adrbk_is_in_sort_order -\n"));

    if(!ab)
      return 0;

    if(ab->sort_rule == AB_SORT_RULE_NONE || ab->count < 2)
      return 1;
    
    cmp_func = (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ?
					    cmp_ae_by_full_lists_last :
               (ab->sort_rule == AB_SORT_RULE_FULL) ?
					    cmp_ae_by_full :
               (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ?
					    cmp_ae_by_nick_lists_last :
            /* (ab->sort_rule == AB_SORT_RULE_NICK) */
					    cmp_ae_by_nick;

    ae_prev = adrbk_get_ae(ab, (a_c_arg_t) 0);

    if(!be_quiet)
      we_cancel = busy_cue(NULL, NULL, 1);

    for(entry = 1, ae = adrbk_get_ae(ab, (a_c_arg_t) entry);
	ae != (AdrBk_Entry *)NULL;
	ae = adrbk_get_ae(ab, (a_c_arg_t) (++entry))){

	    if((*cmp_func)((qsort_t *)&ae_prev, (qsort_t *)&ae) > 0){
		if(we_cancel)
		  cancel_busy_cue(-1);

		dprint((9, "- adrbk_is_in_sort_order : no (entry %ld) -\n", (long) entry));
		return 0;
	    }

	    ae_prev = ae;
    }

    if(we_cancel)
      cancel_busy_cue(-1);

    dprint((9, "- adrbk_is_in_sort_order : yes -\n"));

    return 1;
}


/*
 * Look through the ondisk address book and count the number of entries.
 *
 * Args  ab     -- address book pointer
 *      deleted -- pointer to location to return number of deleted entries
 *      warning -- place to put warning
 *
 * Returns number of non-deleted entries.
 */
adrbk_cntr_t
count_abook_entries_on_disk(AdrBk *ab, a_c_arg_t *deleted)
{
    FILE          *fp_in;
    char          *nickname;
    adrbk_cntr_t   count = 0;
    adrbk_cntr_t   deleted_count = 0;
    int		   rew = 1;
    char           nickbuf[50];

    if(!ab || !ab->fp)
      return -1;

    fp_in = ab->fp;

    while((nickname = get_next_abook_entry(fp_in, rew)) != NULL){
	rew = 0;
	strncpy(nickbuf, nickname, sizeof(nickbuf));
	nickbuf[sizeof(nickbuf)-1] = '\0';
	fs_give((void **) &nickname);
	if(strncmp(nickbuf, DELETED, DELETED_LEN) == 0
	   && isdigit((unsigned char)nickbuf[DELETED_LEN])
	   && isdigit((unsigned char)nickbuf[DELETED_LEN+1])
	   && nickbuf[DELETED_LEN+2] == '/'
	   && isdigit((unsigned char)nickbuf[DELETED_LEN+3])
	   && isdigit((unsigned char)nickbuf[DELETED_LEN+4])
	   && nickbuf[DELETED_LEN+5] == '/'
	   && isdigit((unsigned char)nickbuf[DELETED_LEN+6])
	   && isdigit((unsigned char)nickbuf[DELETED_LEN+7])
	   && nickbuf[DELETED_LEN+8] == '#'){
	    deleted_count++;
	    continue;
	}

	count++;
    }

    if(deleted)
      *deleted = (a_c_arg_t) deleted_count;

    return(count);
}


/*
 * Builds the data structures used with the address book which is
 * simply an array of entries.
 */
int
build_abook_datastruct(AdrBk *ab, char *warning, size_t warninglen)
{
    FILE          *fp_in;
    char          *nickname;
    char          *lc;
    a_c_arg_t      count, deleted;
    adrbk_cntr_t   used = 0, delused = 0;
    int            max_nick = 0,
		   max_full = 0, full_two = 0, full_three = 0,
		   max_fcc = 0, fcc_two = 0, fcc_three = 0,
		   max_addr = 0, addr_two = 0, addr_three = 0,
		   this_nick_width, this_full_width, this_addr_width,
		   this_fcc_width;
    WIDTH_INFO_S  *widths;
    int		   rew = 1, is_deleted;
    AdrBk_Entry   *ae;

    dprint((9, "- build_abook_datastruct -\n"));

    if(!ab || !ab->fp)
      return -1;

    errno = 0;

    fp_in = ab->fp;

    /*
     * If we used a list instead of an array to store the entries we
     * could avoid this pass through the file to count the entries, which
     * we use to allocate the array.
     *
     * Since we use entry_nums a lot to access address book entries it is
     * convenient to have an array for quick access, so we'll probably
     * leave this for now.
     */
    count = count_abook_entries_on_disk(ab, &deleted);

    if(count < 0)
      return -1;

    ab->count       = (adrbk_cntr_t) count;
    ab->del_count   = (adrbk_cntr_t) deleted;

    if(count > 0){
	ab->arr = (AdrBk_Entry *) fs_get(count * sizeof(AdrBk_Entry));
	memset(ab->arr, 0, count * sizeof(AdrBk_Entry));
    }

    if(deleted > 0){
	ab->del = (AdrBk_Entry *) fs_get(deleted * sizeof(AdrBk_Entry));
	memset(ab->del, 0, deleted * sizeof(AdrBk_Entry));
    }

    while((nickname = get_next_abook_entry(fp_in, rew)) != NULL){


	ae = NULL;
	rew = 0;
	is_deleted = 0;

	if(strncmp(nickname, DELETED, DELETED_LEN) == 0
	   && isdigit((unsigned char)nickname[DELETED_LEN])
	   && isdigit((unsigned char)nickname[DELETED_LEN+1])
	   && nickname[DELETED_LEN+2] == '/'
	   && isdigit((unsigned char)nickname[DELETED_LEN+3])
	   && isdigit((unsigned char)nickname[DELETED_LEN+4])
	   && nickname[DELETED_LEN+5] == '/'
	   && isdigit((unsigned char)nickname[DELETED_LEN+6])
	   && isdigit((unsigned char)nickname[DELETED_LEN+7])
	   && nickname[DELETED_LEN+8] == '#'){
	    is_deleted++;
	}

	ALARM_BLIP();
	if(!is_deleted && (long) used > MAX_ADRBK_SIZE){
	    q_status_message2(SM_ORDER | SM_DING, 4, 5,
		    "Max addrbook size is %.200s, %.200s too large, giving up",
			    long2string(MAX_ADRBK_SIZE),
			    (lc=last_cmpnt(ab->filename)) ? lc : ab->filename);
	    dprint((1, "build_ondisk: used=%ld > %s\n",
		   (long) used, long2string(MAX_ADRBK_SIZE)));
	    goto io_err;
	}

	if(is_deleted){
	    if(delused < ab->del_count)
	      ae = &ab->del[delused++];
	}
	else{
	    if(used < ab->count)
	      ae = &ab->arr[used++];
	}

	if(ae)
	  init_ae(ab, ae, nickname);

	fs_give((void **) &nickname);

	if(!ae || is_deleted)
	  continue;

	/*
	 * We're calculating the widths as we read in the data.
	 * We could just go with some default widths to save time.
	 */
        this_nick_width = 0;
	this_full_width = 0;
	this_addr_width = 0;
	this_fcc_width = 0;

	if(ae->nickname)
	  this_nick_width = (int) utf8_width(ae->nickname);

	if(ae->fullname)
	  this_full_width = (int) utf8_width(ae->fullname);

	if(ae->tag == Single){
	    if(ae->addr.addr)
	      this_addr_width = (int) utf8_width(ae->addr.addr);
	}
	else{
	    char **a2;

	    this_addr_width = 0;
	    for(a2 = ae->addr.list; *a2 != NULL; a2++)
	      this_addr_width = MAX(this_addr_width, (int) utf8_width(*a2));
	}

	if(ae->fcc)
	  this_fcc_width = (int) utf8_width(ae->fcc);

	max_nick = MAX(max_nick, this_nick_width);

	if(this_full_width > max_full){
	    full_three = full_two;
	    full_two   = max_full;
	    max_full   = this_full_width;
	}
	else if(this_full_width > full_two){
	    full_three = full_two;
	    full_two   = this_full_width;
	}
	else if(this_full_width > full_three){
	    full_three = this_full_width;
	}

	if(this_addr_width > max_addr){
	    addr_three = addr_two;
	    addr_two   = max_addr;
	    max_addr   = this_addr_width;
	}
	else if(this_addr_width > addr_two){
	    addr_three = addr_two;
	    addr_two   = this_addr_width;
	}
	else if(this_addr_width > addr_three){
	    addr_three = this_addr_width;
	}

	if(this_fcc_width > max_fcc){
	    fcc_three = fcc_two;
	    fcc_two   = max_fcc;
	    max_fcc   = this_fcc_width;
	}
	else if(this_fcc_width > fcc_two){
	    fcc_three = fcc_two;
	    fcc_two   = this_fcc_width;
	}
	else if(this_fcc_width > fcc_three){
	    fcc_three = this_fcc_width;
	}
    }

    widths = &ab->widths;
    widths->max_nickname_width  = MIN(max_nick, 99);
    widths->max_fullname_width  = MIN(max_full, 99);
    widths->max_addrfield_width = MIN(max_addr, 99);
    widths->max_fccfield_width  = MIN(max_fcc, 99);
    widths->third_biggest_fullname_width  = MIN(full_three, 99);
    widths->third_biggest_addrfield_width = MIN(addr_three, 99);
    widths->third_biggest_fccfield_width  = MIN(fcc_three, 99);

    dprint((9, "- build_abook_datastruct done -\n"));
    return 0;

io_err:
    if(warning && errno != 0){
	strncpy(warning, error_description(errno), warninglen);
	warning[warninglen-1] = '\0';
    }

    dprint((1, "build_ondisk: io_err: %s\n",
	   error_description(errno)));

    return -1;
}


/*
 * Builds the trees used for nickname and address lookups.
 */
int
build_abook_tries(AdrBk *ab, char *warning)
{
    adrbk_cntr_t entry_num;
    AdrBk_Entry *ae;
    int we_cancel = 0;

    if(!ab)
      return -1;

    dprint((9, "- build_abook_tries(%s) -\n", ab->filename));


    if(ab->nick_trie)
      free_abook_trie(&ab->nick_trie);

    if(ab->addr_trie)
      free_abook_trie(&ab->addr_trie);

    if(ab->full_trie)
      free_abook_trie(&ab->full_trie);

    if(ab->revfull_trie)
      free_abook_trie(&ab->revfull_trie);

    /*
     * Go through addrbook entries and add each to the tries it
     * belongs in.
     */
    for(entry_num = 0; entry_num < ab->count; entry_num++){
	ae = adrbk_get_ae(ab, (a_c_arg_t) entry_num);
	if(ae){
	    /* every nickname in the nick trie */
	    if(ae->nickname && ae->nickname[0])
	      add_entry_to_trie(&ab->nick_trie, ae->nickname, (a_c_arg_t) entry_num);

	    if(ae->fullname && ae->fullname[0]){
		char *reverse = NULL;
		char *forward = NULL;
		char *comma = NULL;

		/*
		 * We have some fullnames stored as Last, First. Put both in.
		 */
		if(ae->fullname[0] != '"'
		   && (comma=strindex(ae->fullname, ',')) != NULL
		   && comma - ae->fullname > 0){
		    forward = adrbk_formatname(ae->fullname, NULL, NULL);
		    if(forward && forward[0]){
			*comma = '\0';
			reverse = cpystr(ae->fullname);
			*comma = ',';
		    }
		    else{
			if(forward)
			  fs_give((void **) &forward);

			forward = ae->fullname;
		    }
		}
		else
		  forward = ae->fullname;

		if(forward){
		    char *addthis;

		    /*
		     * Make this add not only the full name as is (forward) but
		     * also add the middle name and last name (names after spaces).
		     * so that they'll be found.
		     */
		    for(addthis=forward;
			addthis && (*addthis);
			addthis = strindex(addthis, ' ')){
			while(*addthis == ' ')
			  addthis++;

			if(*addthis)
			  add_entry_to_trie(&ab->full_trie, addthis, (a_c_arg_t) entry_num);
		    }
		}

		if(reverse){
		  add_entry_to_trie(&ab->revfull_trie, reverse, (a_c_arg_t) entry_num);
		  fs_give((void **) &reverse);
		}

		if(forward && forward != ae->fullname)
		  fs_give((void **) &forward);
	    }

	    if(ae->tag == Single && ae->addr.addr && ae->addr.addr[0]){
		char     buf[1000];
		char    *tmp_a_string, *simple_addr = NULL;
		ADDRESS *addr = NULL;
		char    *fakedomain = "@";

		/*
		 * Isolate the actual address out of ae.
		 */
		tmp_a_string = cpystr(ae->addr.addr);
		rfc822_parse_adrlist(&addr, tmp_a_string, fakedomain);
		if(tmp_a_string)
		  fs_give((void **) &tmp_a_string);

		if(addr){
		    if(addr->mailbox && addr->host
		       && !(addr->host[0] == '@' && addr->host[1] == '\0'))
		      simple_addr = simple_addr_string(addr, buf, sizeof(buf));

		    /*
		     * If the fullname wasn't set in the addrbook entry there
		     * may still be one that is part of the address. We need
		     * to be careful because we may be in the middle of opening
		     * the address book right now. Don't call something like
		     * our_build_address because it will probably re-open this
		     * same addrbook infinitely.
		     */
		    if(!(ae->fullname && ae->fullname[0])
		       && addr->personal && addr->personal[0])
		      add_entry_to_trie(&ab->full_trie, addr->personal,
					(a_c_arg_t) entry_num);

		    mail_free_address(&addr);
		}

		if(simple_addr)
		  add_entry_to_trie(&ab->addr_trie, simple_addr, (a_c_arg_t) entry_num);
	    }
	}
    }

    dprint((9, "- build_abook_tries done -\n"));

    if(we_cancel)
      cancel_busy_cue(-1);

    return 0;
}


void
add_entry_to_trie(AdrBk_Trie **head, char *str, a_c_arg_t entry_num)
{
    AdrBk_Trie *temp, *trail;
    char *addthis, *p, buf[1000];

    if(!head || !str || !*str)
      return;

    /* add as lower case */

    for(p = str; *p && ((*p & 0x80) || !isupper((unsigned char) *p)); p++)
      ;

    if(*p){
	strncpy(buf, str, sizeof(buf));
	buf[sizeof(buf)-1] = '\0';
	for(p = buf; *p; p++)
	  if(!(*p & 0x80) && isupper((unsigned char) *p))
	    *p = tolower(*p);

	addthis = buf;
    }
    else
      addthis = str;

    temp = trail = (*head);

    /*
     * Find way down the trie, adding missing nodes as we go.
     */
    for(p = addthis; *p;){
	if(temp == NULL){
	    temp = (AdrBk_Trie *) fs_get(sizeof(*temp));
	    memset(temp, 0, sizeof(*temp));
	    temp->value = *p;
	    temp->entrynum = NO_NEXT;

	    if(*head == NULL)
	      *head = temp;
	    else
	      trail->down = temp;
	}
	else{
	    while((temp != NULL) && (temp->value != *p)){
		trail = temp;
		temp = temp->right;
	    }

	    /* wasn't there, add new node */
	    if(temp == NULL){
		temp = (AdrBk_Trie *) fs_get(sizeof(*temp));
		memset(temp, 0, sizeof(*temp));
		temp->value = *p;
		temp->entrynum = NO_NEXT;
		trail->right = temp;
	    }
	}

	if(*(++p)){
	    trail = temp;
	    temp = temp->down;
	}
    }

    /*
     * If entrynum is already filled in there must be an entry with
     * the same nickname earlier in the abook. Use that earlier entry.
     */
    if(temp != NULL && temp->entrynum == NO_NEXT)
      temp->entrynum = (adrbk_cntr_t) entry_num;
}


/*
 * Returns entry_num of first entry with this nickname, else NO_NEXT.
 */
adrbk_cntr_t
lookup_nickname_in_trie(AdrBk *ab, char *nickname)
{
    if(!ab || !nickname || !ab->nick_trie)
      return(-1L);

    return(lookup_in_abook_trie(ab->nick_trie, nickname));
}


/*
 * Returns entry_num of first entry with this address, else NO_NEXT.
 */
adrbk_cntr_t
lookup_address_in_trie(AdrBk *ab, char *address)
{
    dprint((9, "lookup_address_in_trie: %s\n", ab ? (ab->addr_trie ? (address ? address : "?") : "null addr_trie") : "null ab"));
    if(!ab || !address || !ab->addr_trie)
      return(-1L);

    return(lookup_in_abook_trie(ab->addr_trie, address));
}


adrbk_cntr_t
lookup_in_abook_trie(AdrBk_Trie *t, char *str)
{
    char        *p, *lookthisup;
    char         buf[1000];
    adrbk_cntr_t ret = NO_NEXT;

    if(!t || !str)
      return(ret);

    /* make lookup case independent */

    for(p = str; *p && !(*p & 0x80) && islower((unsigned char) *p); p++)
      ;

    if(*p){
	strncpy(buf, str, sizeof(buf));
	buf[sizeof(buf)-1] = '\0';
	for(p = buf; *p; p++)
	  if(!(*p & 0x80) && isupper((unsigned char) *p))
	    *p = tolower(*p);

	lookthisup = buf;
    }
    else
      lookthisup = str;

    p = lookthisup;

    /*
     * We usually return out from inside the loop (unless str == "").
     */
    while(*p){
	/* search for character at this level */
	while(t->value != *p){
	    if(t->right == NULL)
	      return(ret);		/* no match */

	    t = t->right;
	}

	if(*++p == '\0')		/* end of str, a match */
	  return(t->entrynum);

	/* need to go down to match next character */
	if(t->down == NULL)		/* no match */
	  return(ret);

	t = t->down;
    }

    return(ret);
}


void
free_abook_trie(AdrBk_Trie **trie)
{
    if(trie){
	if(*trie){
	    free_abook_trie(&(*trie)->down);
	    free_abook_trie(&(*trie)->right);
	    fs_give((void **) trie);
	}
    }
}


/*
 * Returns pointer to start of next address book entry from disk file.
 * The return will be in raw form from the file with newlines still
 * embedded. Or NULL at end of file.
 *
 * If rew is set, rewind the file and start over at beginning.
 */
char *
get_next_abook_entry(FILE *fp, int rew)
{
    char       *returned_lines = NULL, *p;
    char        line[1024];
    static int  will_be_done_next_time = 0;
    static long next_nickname_offset   = 0L;
    size_t      lsize;
    long        offset, saved_offset;
    long        len = 0L;

#define CHUNKSIZE 500

    lsize = sizeof(line);

    if(rew){
	will_be_done_next_time = 0;
	rewind(fp);
	/* skip leading (bogus) continuation lines */
	do{
	    offset  = ftell(fp);
	    line[0] = '\0';
	    line[lsize-2] = '\0';
	    p = fgets(line, lsize, fp);

	    if(p == NULL)
	      return(NULL);

	    /* line is too long to fit, read the rest and discard */
	    while(line[lsize-2] != '\0' && line[lsize-2] != '\n'
		  && p != NULL){

		/* get next lsize-1 characters, leaving line[0] */
		line[lsize-2] = '\0';
		p = fgets(line+1, lsize-1, fp);
	    }
	}while(line[0] == SPACE);

	/* offset is start of first good line now */
	next_nickname_offset = offset;
    }

    if(will_be_done_next_time)
      return(NULL);

    /* we set this up in rew==1 case or on previous call to this routine */
    offset = next_nickname_offset;

    /*
     * The rest is working on finding the start of the next entry so
     * skip continuation lines
     */
    do{
	next_nickname_offset = ftell(fp);
	line[0] = '\0';
	line[lsize-2] = '\0';
	p = fgets(line, lsize, fp);

	/* line is too long to fit, read the rest and discard */
	while(line[lsize-2] != '\0' && line[lsize-2] != '\n'
	      && p != NULL){

	    /* get next lsize-1 characters, leaving line[0] */
	    line[lsize-2] = '\0';
	    p = fgets(line+1, lsize-1, fp);
	}
    }while(line[0] == SPACE);

    /* next_nickname_offset is start of next entry now */

    if(!line[0])
      will_be_done_next_time = 1;

    len = next_nickname_offset - offset;

    returned_lines = (char *) fs_get((len + 1) * sizeof(char));

    saved_offset = ftell(fp);
    if(fseek(fp, offset, 0)){
	dprint((2, "get_next_ab_entry: trouble fseeking\n"));
	len = 0;
	fs_give((void **) &returned_lines);
    }
    else{
	if(fread(returned_lines, sizeof(char), (unsigned) len, fp) != len){
	    dprint((2, "get_next_ab_entry: trouble freading\n"));
	    len = 0;
	    fs_give((void **) &returned_lines);
	}
    }

    if(fseek(fp, saved_offset, 0)){
	dprint((2, "get_next_ab_entry: trouble fseeking to saved_offset\n"));
	len = 0;
	fs_give((void **) &returned_lines);
    }

    if(returned_lines)
      returned_lines[len] = '\0';

    return(returned_lines);
}


/*
 * Returns a pointer to the start of the mailbox@host part of this
 * address string, and a pointer to the end + 1.  The caller can then
 * replace the end char with \0 and call the hash function, then put
 * back the end char.  Start_addr and end_addr are assumed to be non-null.
 */
void
strip_addr_string(char *addrstr, char **start_addr, char **end_addr)
{
    register char *q;
    int in_quotes  = 0,
        in_comment = 0;
    char prev_char = '\0';

    if(!addrstr || !*addrstr){
	*start_addr = NULL;
	*end_addr = NULL;
	return;
    }

    *start_addr = addrstr;

    for(q = addrstr; *q; q++){
	switch(*q){
	  case '<':
	    if(!in_quotes && !in_comment){
		if(*++q){
		    *start_addr = q;
		    /* skip to > */
		    while(*q && *q != '>')
		      q++;

		    /* found > */
		    if(*q){
			*end_addr = q;
			return;
		    }
		    else
		      q--;
		}
	    }

	    break;

	  case LPAREN:
	    if(!in_quotes && !in_comment)
	      in_comment = 1;
	    break;

	  case RPAREN:
	    if(in_comment && prev_char != BSLASH)
	      in_comment = 0;
	    break;

	  case QUOTE:
	    if(in_quotes && prev_char != BSLASH)
	      in_quotes = 0;
	    else if(!in_quotes && !in_comment)
	      in_quotes = 1;
	    break;

	  default:
	    break;
	}

	prev_char = *q;
    }

    *end_addr = q;
}


/*
 * Fill in the passed in ae pointer by parsing the str that is passed.
 * 
 * Args   ab --
 *        ae -- pointer we want to fill in. The individual members of
 *                the ae struct will be allocated here
 *       str -- the string from the on-disk address book file for this entry
 *
 * Returns a pointer to ae or NULL if there are problems.
 */
AdrBk_Entry *
init_ae(AdrBk *ab, AdrBk_Entry *a, char *str)
{
    char *p;
    char *addrfield = (char *) NULL;
    char *addrfield_end;
    char *nickname, *fullname, *fcc, *extra;

    if(!ab){
	dprint((2, "init_ae: found trouble: NULL ab\n"));
	return((AdrBk_Entry *) NULL);
    }

    defvalue_ae(a);

    p = str;

    REPLACE_NEWLINES_WITH_SPACE(p);

    nickname = p;
    SKIP_TO_TAB(p);
    if(!*p){
	RM_END_SPACE(nickname, p);
	a->nickname = cpystr(nickname);
    }
    else{
	*p = '\0';
	RM_END_SPACE(nickname, p);
	a->nickname = cpystr(nickname);
	p++;
	SKIP_SPACE(p);
	fullname = p;
	SKIP_TO_TAB(p);
	if(!*p){
	    RM_END_SPACE(fullname, p);
	    a->fullname = cpystr(fullname);
	}
	else{
	    *p = '\0';
	    RM_END_SPACE(fullname, p);
	    a->fullname = cpystr(fullname);
	    p++;
	    SKIP_SPACE(p);
	    addrfield = p;
	    SKIP_TO_TAB(p);
	    if(!*p){
		RM_END_SPACE(addrfield, p);
	    }
	    else{
		*p = '\0';
		RM_END_SPACE(addrfield, p);
		p++;
		SKIP_SPACE(p);
		fcc = p;
		SKIP_TO_TAB(p);
		if(!*p){
		    RM_END_SPACE(fcc, p);
		    a->fcc = cpystr(fcc);
		}
		else{
		    char *src, *dst;

		    *p = '\0';
		    RM_END_SPACE(fcc, p);
		    a->fcc = cpystr(fcc);
		    p++;
		    SKIP_SPACE(p);
		    extra = p;
		    p = extra + strlen(extra);
		    RM_END_SPACE(extra, p);

		    /*
		     * When we wrap long comments we insert an extra colon
		     * in the wrap so we can spot it and take it back out.
		     * Pretty much a hack since we thought of it a long
		     * time after designing it, but it eliminates the limit
		     * on length of comments. Here we are looking for
		     * <SP> <SP> : <SP> or
		     * <SP> <SP> <SP> : <SP> and replacing it with <SP>.
		     * There could have been a single \n or \r\n, so that
		     * is why we check for 2 or 3 spaces before the colon.
		     * (This was another mistake.)
		     */
		    dst = src = extra;
		    while(*src != '\0'){
			if(*src == SPACE && *(src+1) == SPACE &&
			   *(src+2) == ':' && *(src+3) == SPACE){

			    /*
			     * If there was an extra space because of the
			     * CRLF (instead of LF) then we already put
			     * a SP in dst last time through the loop
			     * and don't need to add another.
			     */
			    if(src == extra || *(src-1) != SPACE)
			      *dst++ = *src;

			    src += 4;
			}
			else
			  *dst++ = *src++;
		    }

		    *dst = '\0';
		    a->extra = cpystr(extra);
		}
	    }
	}
    }

    /* decode and convert to UTF-8 if we need to */

    convert_possibly_encoded_str_to_utf8(&a->nickname);
    convert_possibly_encoded_str_to_utf8(&a->fullname);
    convert_possibly_encoded_str_to_utf8(&a->fcc);
    convert_possibly_encoded_str_to_utf8(&a->extra);

    /* parse addrfield */
    if(addrfield){
	if(*addrfield == '('){  /* it's a list */
	    a->tag = List;
	    p = addrfield;
	    addrfield_end = p + strlen(p);

	    /*
	     * Get rid of the parens.
	     * If this isn't true the input file is messed up.
	     */
	    if(p[strlen(p)-1] == ')'){
		char **ll;

		p[strlen(p)-1] = '\0';
		p++;
		a->addr.list = parse_addrlist(p);
		for(ll = a->addr.list; ll && *ll; ll++)
		  convert_possibly_encoded_str_to_utf8(ll);
	    }
	    else{
		/* put back what was there to start with */
		*addrfield_end = ')';
		a->addr.list = (char **)fs_get(sizeof(char *) * 2);
		a->addr.list[0] = cpystr(addrfield);
		a->addr.list[1] = NULL;
		dprint((2, "parsing error reading addressbook: missing right paren: %s\n",
			addrfield ? addrfield : "?"));
	    }
	}
	else{  /* A plain, single address */

	    a->tag       = Single;
	    a->addr.addr = cpystr(addrfield);
	    convert_possibly_encoded_str_to_utf8(&a->addr.addr);
	}
    }
    else{
	/*
	 * If no addrfield, assume an empty Single.
	 */
	a->addr.addr = cpystr("");
	a->tag       = Single;
    }

    return(a);
}


/*
 * Return the size of the address book 
 */
adrbk_cntr_t
adrbk_count(AdrBk *ab)
{
    return(ab ? ab->count : (adrbk_cntr_t) 0);
}


/*
 * Return a pointer to the ae that has index number "entry_num".
 */
AdrBk_Entry *
adrbk_get_ae(AdrBk *ab, a_c_arg_t entry_num)
{
    if(!ab || entry_num >= (a_c_arg_t) ab->count)
      return((AdrBk_Entry *) NULL);

    return(&ab->arr[entry_num]);
}


/*
 * Return a pointer to the deleted ae that has index number "entry_num".
 */
AdrBk_Entry *
adrbk_get_delae(AdrBk *ab, a_c_arg_t entry_num)
{
    if(!ab || entry_num >= (a_c_arg_t) ab->del_count)
      return((AdrBk_Entry *) NULL);

    return(&ab->del[entry_num]);
}


/*
 * Look up an entry in the address book given a nickname
 *
 * Args: ab       -- the address book
 *       nickname -- nickname to match
 *      entry_num -- if matched, return entry_num of match here
 *
 * Result: A pointer to an AdrBk_Entry is returned, or NULL if not found.
 *
 * Lookups usually need to be recursive in case the address
 * book references itself.  This is left to the next level up.
 * adrbk_clearrefs() is provided to clear all the reference tags in
 * the address book for loop detection.
 * When there are duplicates of the same nickname we return the first.
 * This can only happen if addrbook was edited externally.
 */
AdrBk_Entry *
adrbk_lookup_by_nick(AdrBk *ab, char *nickname, adrbk_cntr_t *entry_num)
{
    adrbk_cntr_t num;
    AdrBk_Entry *ae;

    dprint((5, "- adrbk_lookup_by_nick(%s) (in %s) -\n",
	   nickname ? nickname : "?",
	   (ab && ab->filename) ? ab->filename : "?"));

    if(!ab || !nickname || !nickname[0])
      return NULL;


    num = lookup_nickname_in_trie(ab, nickname);

    if(num != NO_NEXT){
	ae = adrbk_get_ae(ab, (a_c_arg_t) num);
	if(entry_num && ae)
	  *entry_num = num;

	return(ae);
    }
    else
      return((AdrBk_Entry *) NULL);
}


/*
 * Look up an entry in the address book given an address
 *
 * Args: ab       -- the address book
 *       address  -- address to match
 *      entry_num -- if matched, return entry_num of match here
 *
 * Result: A pointer to an AdrBk_Entry is returned, or NULL if not found.
 *
 * Note:  When there are multiple occurrences of an address in an addressbook,
 * which there will be if more than one nickname points to same address, then
 * we want this to match the first occurrence so that the fcc you get will
 * be predictable.
 */
AdrBk_Entry *
adrbk_lookup_by_addr(AdrBk *ab, char *address, adrbk_cntr_t *entry_num)
{
    adrbk_cntr_t num;
    AdrBk_Entry *ae;

    dprint((5, "- adrbk_lookup_by_addr(%s) (in %s) -\n",
	   address ? address : "?",
	   (ab && ab->filename) ? ab->filename : "?"));

    if(!ab || !address || !address[0])
      return((AdrBk_Entry *)NULL);

    num = lookup_address_in_trie(ab, address);

    if(num != NO_NEXT){
	ae = adrbk_get_ae(ab, (a_c_arg_t) num);
	if(entry_num && ae)
	  *entry_num = num;

	return(ae);
    }
    else
      return((AdrBk_Entry *)NULL);
}


/*
 * Format a full name.
 *
 * Args: fullname -- full name out of address book for formatting
 *          first -- Return a pointer to first name here.
 *           last -- Return a pointer to last name here.
 *
 * Result:  Returns pointer to name formatted for a mail header. Space is
 *          allocated here and should be freed by caller.
 *
 * We need this because we store full names as Last, First.
 * If the name has no comma, then no change is made.
 * Otherwise the text before the first comma is moved to the end and
 * the comma is deleted.
 *
 * Last and first have to be freed by caller.
 */
char *
adrbk_formatname(char *fullname, char **first, char **last)
{
    char       *comma;
    char       *new_name;

    if(first)
      *first = NULL;
    if(last)
      *last = NULL;

    /*
     * There is an assumption that the fullname is a UTF-8 string.
     */

    if(fullname[0] != '"'  && (comma = strindex(fullname, ',')) != NULL){
	size_t l;
        int last_name_len = comma - fullname;

        comma++;
        while(*comma && isspace((unsigned char)*comma))
	  comma++;

	if(first)
	  *first = cpystr(comma);

	if(last){
	    *last = (char *)fs_get((last_name_len + 1) * sizeof(char));
	    strncpy(*last, fullname, last_name_len); 
	    (*last)[last_name_len] = '\0';
	}

	l = strlen(comma) + 1 + last_name_len;
	new_name = (char *) fs_get((l+1) * sizeof(char));
        strncpy(new_name, comma, l);
	new_name[l] = '\0';
        strncat(new_name, " ", l+1-1-strlen(new_name));
	new_name[l] = '\0';
        strncat(new_name, fullname, MIN(last_name_len,l+1-1-strlen(new_name))); 
	new_name[l] = '\0';
    }
    else
      new_name = cpystr(fullname);

    return(new_name);
}


/*
 * Clear reference flags in preparation for a recursive lookup.
 *
 * For loop detection during address book look up.  This clears all the 
 * referenced flags, then as the lookup proceeds the referenced flags can 
 * be checked and set.
 */
void
adrbk_clearrefs(AdrBk *ab)
{
    adrbk_cntr_t entry_num;
    AdrBk_Entry *ae;

    dprint((9, "- adrbk_clearrefs -\n"));

    if(!ab)
      return;

    for(entry_num = 0; entry_num < ab->count; entry_num++){
	ae = adrbk_get_ae(ab, (a_c_arg_t) entry_num);
	ae->referenced = 0;
    }
}


/*
 *  Allocate a new AdrBk_Entry
 */
AdrBk_Entry *
adrbk_newentry(void)
{
    AdrBk_Entry *ae;

    ae = (AdrBk_Entry *) fs_get(sizeof(AdrBk_Entry));
    defvalue_ae(ae);

    return(ae);
}


/*
 *  Just sets ae values to default.
 *  Parts should be freed before calling this or they will leak.
 */
void
defvalue_ae(AdrBk_Entry *ae)
{
    ae->nickname    = empty;
    ae->fullname    = empty;
    ae->addr.addr   = empty;
    ae->fcc         = empty;
    ae->extra       = empty;
    ae->tag         = NotSet;
    ae->referenced  = 0;
}


AdrBk_Entry *
copy_ae(AdrBk_Entry *src)
{
    AdrBk_Entry *a;

    a = adrbk_newentry();
    a->tag = src->tag;
    a->nickname = cpystr(src->nickname ? src->nickname : "");
    a->fullname = cpystr(src->fullname ? src->fullname : "");
    a->fcc      = cpystr(src->fcc ? src->fcc : "");
    a->extra    = cpystr(src->extra ? src->extra : "");
    if(a->tag == Single)
      a->addr.addr = cpystr(src->addr.addr ? src->addr.addr : "");
    else if(a->tag == List){
	char **p;
	int    i, n;

	/* count list */
	for(p = src->addr.list; p && *p; p++)
	  ;/* do nothing */
	
	if(p == NULL)
	  n = 0;
	else
	  n = p - src->addr.list;

	a->addr.list = (char **)fs_get((n+1) * sizeof(char *));
	for(i = 0; i < n; i++)
	  a->addr.list[i] = cpystr(src->addr.list[i]);
	
	a->addr.list[n] = NULL;
    }

    return(a);
}


/*
 * Add an entry to the address book, or modify an existing entry
 *
 * Args: ab       -- address book to add to
 *  old_entry_num -- the entry we want to modify.  If this is NO_NEXT, then
 *                    we look up the nickname passed in to see if that's the
 *                    entry to modify, else it is a new entry.
 *       nickname -- the nickname for new entry
 *       fullname -- the fullname for new entry
 *       address  -- the address for new entry
 *       fcc      -- the fcc for new entry
 *       extra    -- the extra field for new entry
 *       tag      -- the type of new entry
 *  new_entry_num -- return entry_num of new or modified entry here
 * resort_happened -- means that more than just the current entry changed,
 *                     either something was added or order was changed
 *     enable_intr -- tell adrbk_write to enable interrupt handling
 *        be_quiet -- tell adrbk_write to not do percent done messages
 *        write_it -- only do adrbk_write if this is set
 *
 * Result: return code:  0 all went well
 *                      -2 error writing address book, check errno
 *		        -3 no modification, the tag given didn't match
 *                         existing tag
 *                      -4 tabs are in one of the fields passed in
 *
 * If the nickname exists in the address book already, the operation is
 * considered a modification even if the case does not match exactly,
 * otherwise it is an add.  The entry the operation occurs on is returned
 * in new.  All fields are set to those passed in; that is, passing in NULL
 * even on a modification will set those fields to NULL as opposed to leaving
 * them unchanged.  It is acceptable to pass in the current strings
 * in the entry in the case of modification.  For address lists, the
 * structure passed in is what is used, so the storage has to all have
 * come from fs_get().  If the pointer passed in is the same as
 * the current field, no change is made.
 */
int
adrbk_add(AdrBk *ab, a_c_arg_t old_entry_num, char *nickname, char *fullname,
	  char *address, char *fcc, char *extra, Tag tag, adrbk_cntr_t *new_entry_num,
	  int *resort_happened, int enable_intr, int be_quiet, int write_it)
{
    AdrBk_Entry *a;
    AdrBk_Entry *ae;
    adrbk_cntr_t old_enum;
    adrbk_cntr_t new_enum;
    int (*cmp_func)(const qsort_t *, const qsort_t *);
    int retval = 0;
    int need_write = 0;
    int set_mangled = 0;

    dprint((3, "- adrbk_add(%s) -\n", nickname ? nickname : ""));

    if(!ab)
      return -2;

    /* ---- Make sure there are no tabs in the stuff to add ------*/
    if((nickname != NULL && strindex(nickname, TAB) != NULL) ||
       (fullname != NULL && strindex(fullname, TAB) != NULL) ||
       (fcc != NULL && strindex(fcc, TAB) != NULL) ||
       (tag == Single && address != NULL && strindex(address, TAB) != NULL))
        return -4;

    /*
     * Are we adding or updating ?
     *
     * If old_entry_num was passed in, we're updating that.  If nickname
     * already exists, we're updating that entry.  Otherwise, this is an add.
     */
    if((adrbk_cntr_t)old_entry_num != NO_NEXT){
	ae = adrbk_get_ae(ab, old_entry_num);
	if(ae)
	  old_enum = (adrbk_cntr_t)old_entry_num;
    }
    else
      ae = adrbk_lookup_by_nick(ab, nickname, &old_enum);

    if(ae == NULL){  /*----- adding a new entry ----*/

	ae            = adrbk_newentry();
	ae->tag       = tag;
	if(nickname)
	  ae->nickname  = cpystr(nickname);
	if(fullname)
	  ae->fullname  = cpystr(fullname);
	if(fcc)
	  ae->fcc  = cpystr(fcc);
	if(extra)
	  ae->extra  = cpystr(extra);

	if(tag == Single)
          ae->addr.addr = cpystr(address);
	else
	  ae->addr.list = (char **)NULL;

	cmp_func = (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ?
						cmp_ae_by_full_lists_last :
		   (ab->sort_rule == AB_SORT_RULE_FULL) ?
						cmp_ae_by_full :
		   (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ?
						cmp_ae_by_nick_lists_last :
		/* (ab->sort_rule == AB_SORT_RULE_NICK) */
						cmp_ae_by_nick;

	if(ab->sort_rule == AB_SORT_RULE_NONE)  /* put it last */
	  new_enum = ab->count;
	else  /* Find slot for it */
	  for(new_enum = 0, a = adrbk_get_ae(ab, (a_c_arg_t) new_enum);
	      a != (AdrBk_Entry *)NULL;
	      a = adrbk_get_ae(ab, (a_c_arg_t) (++new_enum))){
		    if((*cmp_func)((qsort_t *)&a, (qsort_t *)&ae) >= 0)
			break;
	  }

	/* Insert ae before entry new_enum. */
	insert_ab_entry(ab, (a_c_arg_t) new_enum, ae, 0);

	if(F_OFF(F_EXPANDED_DISTLISTS,ps_global))
	  exp_add_nth(ab->exp, (a_c_arg_t)new_enum);

	exp_add_nth(ab->selects, (a_c_arg_t)new_enum);

	/*
	 * insert_ab_entry copies the pointers of ae so the things
	 * being pointed to (nickname, ...) are still in use.
	 * Don't free them but free the ae struct itself.
	 */
	fs_give((void **) &ae);

        /*---- return in pointer if requested -----*/
        if(new_entry_num)
	  *new_entry_num = new_enum;

        if(resort_happened)
	  *resort_happened = 1;

	if(write_it)
	  retval = adrbk_write(ab, (a_c_arg_t) new_enum, new_entry_num, &set_mangled,
			       enable_intr, be_quiet);
    }
    else{
        /*----- Updating an existing entry ----*/

	int fix_tries = 0;

	if(ae->tag != tag)
	  return -3;

        /*
	 * Instead of just freeing and reallocating here we attempt to re-use
	 * the space that was already allocated if possible.
	 */
	if(ae->nickname != nickname
	   && ae->nickname != NULL
	   && nickname != NULL
	   && strcmp(nickname, ae->nickname) != 0){
	    need_write++;
	    fix_tries++;
	    /* can use already alloc'd space */
            if(ae->nickname != NULL && nickname != NULL &&
	       strlen(nickname) <= strlen(ae->nickname)){

                strncpy(ae->nickname, nickname, strlen(ae->nickname)+1);
            }
	    else{
                if(ae->nickname != NULL && ae->nickname != empty)
                  fs_give((void **)&ae->nickname);

                ae->nickname = nickname ? cpystr(nickname) : nickname;
	    }
        }

	if(ae->fullname != fullname
	   && ae->fullname != NULL
	   && fullname != NULL
	   && strcmp(fullname, ae->fullname) != 0){
	    need_write++;
            if(ae->fullname != NULL && fullname != NULL &&
	       strlen(fullname) <= strlen(ae->fullname)){

                strncpy(ae->fullname, fullname, strlen(ae->fullname)+1);
            }
	    else{
                if(ae->fullname != NULL && ae->fullname != empty)
                  fs_give((void **)&ae->fullname);

                ae->fullname = fullname ? cpystr(fullname) : fullname;
	    }
        }

	if(ae->fcc != fcc
	   && ae->fcc != NULL
	   && fcc != NULL
	   && strcmp(fcc, ae->fcc) != 0){
	    need_write++;
            if(ae->fcc != NULL && fcc != NULL &&
	       strlen(fcc) <= strlen(ae->fcc)){

                strncpy(ae->fcc, fcc, strlen(ae->fcc)+1);
            }
	    else{
                if(ae->fcc != NULL && ae->fcc != empty)
                  fs_give((void **)&ae->fcc);

                ae->fcc = fcc ? cpystr(fcc) : fcc;
	    }
        }

	if(ae->extra != extra
	   && ae->extra != NULL
	   && extra != NULL
	   && strcmp(extra, ae->extra) != 0){
	    need_write++;
            if(ae->extra != NULL && extra != NULL &&
	       strlen(extra) <= strlen(ae->extra)){

                strncpy(ae->extra, extra, strlen(ae->extra)+1);
            }
	    else{
                if(ae->extra != NULL && ae->extra != empty)
                  fs_give((void **)&ae->extra);

                ae->extra = extra ? cpystr(extra) : extra;
	    }
        }

	if(tag == Single){
            /*---- Single ----*/
	    if(ae->addr.addr != address
	       && ae->addr.addr != NULL
	       && address != NULL
	       && strcmp(address, ae->addr.addr) != 0){
		need_write++;
		fix_tries++;
		if(ae->addr.addr != NULL && address != NULL &&
		   strlen(address) <= strlen(ae->addr.addr)){

		    strncpy(ae->addr.addr, address, strlen(ae->addr.addr)+1);
		}
		else{
		    if(ae->addr.addr != NULL && ae->addr.addr != empty)
		      fs_give((void **)&ae->addr.addr);

		    ae->addr.addr = address ? cpystr(address) : address;
		}
	    }
	}
	else{
            /*---- List -----*/
            /*
	     * We don't mess with lists here.
	     * The caller has to do it with adrbk_listadd().
	     */
	    ;/* do nothing */
	}


        /*---------- Make sure it's still in order ---------*/

	/*
	 * old_enum is where ae is currently located
	 * put it where it belongs
	 */
	if(need_write){
	    new_enum = re_sort_particular_entry(ab, (a_c_arg_t) old_enum);
	    if(old_enum != new_enum)
	      fix_tries++;
	}
	else
	  new_enum = old_enum;

        /*---- return in pointer if requested -----*/
        if(new_entry_num)
	  *new_entry_num = new_enum;

        if(resort_happened)
	  *resort_happened = (old_enum != new_enum);

	if(fix_tries)
	  repair_abook_tries(ab);

	if(write_it && need_write){
	    int sort_happened = 0;

	    retval = adrbk_write(ab, (a_c_arg_t) new_enum, new_entry_num,
			         &sort_happened, enable_intr, be_quiet);

	    set_mangled = sort_happened;

	    if(new_entry_num)
	      new_enum = (*new_entry_num);

	    if(resort_happened && (sort_happened || (old_enum != new_enum)))
	      *resort_happened = 1;
	}
	else
	  retval = 0;
    }

    if(set_mangled)
      ps_global->mangled_screen = 1;

    return(retval);
}


/*
 * Similar to adrbk_add, but lower cost. No sorting is done, the new entry
 * goes on the end. This won't work if it is an edit instead of an append.
 * The address book is not committed to disk.
 *
 * Args: ab       -- address book to add to
 *       nickname -- the nickname for new entry
 *       fullname -- the fullname for new entry
 *       address  -- the address for new entry
 *       fcc      -- the fcc for new entry
 *       extra    -- the extra field for new entry
 *       tag      -- the type of new entry
 *
 * Result: return code:  0 all went well
 *                      -2 error writing address book, check errno
 *		        -3 no modification, the tag given didn't match
 *                         existing tag
 *                      -4 tabs are in one of the fields passed in
 */
int
adrbk_append(AdrBk *ab, char *nickname, char *fullname, char *address, char *fcc,
	     char *extra, Tag tag, adrbk_cntr_t *new_entry_num)
{
    AdrBk_Entry *ae;

    dprint((3, "- adrbk_append(%s) -\n", nickname ? nickname : ""));

    if(!ab)
      return -2;

    /* ---- Make sure there are no tabs in the stuff to add ------*/
    if((nickname != NULL && strindex(nickname, TAB) != NULL) ||
       (fullname != NULL && strindex(fullname, TAB) != NULL) ||
       (fcc != NULL && strindex(fcc, TAB) != NULL) ||
       (tag == Single && address != NULL && strindex(address, TAB) != NULL))
        return -4;

    ae            = adrbk_newentry();
    ae->tag       = tag;
    if(nickname)
      ae->nickname  = cpystr(nickname);
    if(fullname)
      ae->fullname  = cpystr(fullname);
    if(fcc)
      ae->fcc  = cpystr(fcc);
    if(extra)
      ae->extra  = cpystr(extra);

    if(tag == Single)
      ae->addr.addr = cpystr(address);
    else
      ae->addr.list = (char **)NULL;

    if(new_entry_num)
      *new_entry_num = ab->count;

    insert_ab_entry(ab, (a_c_arg_t) ab->count, ae, 0);

    /*
     * insert_ab_entry copies the pointers of ae so the things
     * being pointed to (nickname, ...) are still in use.
     * Don't free them but free the ae struct itself.
     */
    fs_give((void **) &ae);

    return(0);
}


/*
 * The entire address book is assumed sorted correctly except perhaps for
 * entry number cur.  Put it in the correct place.  Return the new entry
 * number for cur.
 */
adrbk_cntr_t
re_sort_particular_entry(AdrBk *ab, a_c_arg_t cur)
{
    AdrBk_Entry  *ae_cur, *ae_prev, *ae_next, *ae_small_enough, *ae_big_enough;
    long small_enough;
    adrbk_cntr_t big_enough;
    adrbk_cntr_t new_entry_num;
    int (*cmp_func)(const qsort_t *, const qsort_t *);

    dprint((9, "- re_sort -\n"));

    cmp_func = (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ?
					    cmp_ae_by_full_lists_last :
	       (ab->sort_rule == AB_SORT_RULE_FULL) ?
					    cmp_ae_by_full :
	       (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ?
					    cmp_ae_by_nick_lists_last :
	    /* (ab->sort_rule == AB_SORT_RULE_NICK) */
					    cmp_ae_by_nick;

    new_entry_num = (adrbk_cntr_t) cur;

    if(ab->sort_rule == AB_SORT_RULE_NONE)
      return(new_entry_num);

    ae_cur = adrbk_get_ae(ab, cur);

    if(cur > 0)
      ae_prev  = adrbk_get_ae(ab, cur - 1);

    if(cur < ab->count -1)
      ae_next  = adrbk_get_ae(ab, cur + 1);

    /*
     * A possible optimization here would be to implement some sort of
     * binary search to find where it goes instead of stepping through the
     * entries one at a time.
     */
    if(cur > 0 &&
       (*cmp_func)((qsort_t *)&ae_cur,(qsort_t *)&ae_prev) < 0){
	/*--- Out of order, needs to be moved up ----*/
	for(small_enough = (long)cur - 2; small_enough >= 0L; small_enough--){
	  ae_small_enough = adrbk_get_ae(ab,(a_c_arg_t) small_enough);
	  if((*cmp_func)((qsort_t *)&ae_cur, (qsort_t *)&ae_small_enough) >= 0)
	    break;
	}
	new_entry_num = (adrbk_cntr_t)(small_enough + 1L);
	move_ab_entry(ab, cur, (a_c_arg_t) new_entry_num);
    }
    else if(cur < ab->count - 1 &&
	(*cmp_func)((qsort_t *)&ae_cur, (qsort_t *)&ae_next) > 0){
	/*---- Out of order needs, to be moved towards end of list ----*/
	for(big_enough = (adrbk_cntr_t)(cur + 2);
	    big_enough < ab->count;
	    big_enough++){
	  ae_big_enough = adrbk_get_ae(ab, (a_c_arg_t) big_enough);
	  if((*cmp_func)((qsort_t *)&ae_cur, (qsort_t *)&ae_big_enough) <= 0)
	    break;
	}
	new_entry_num = big_enough - 1;
	move_ab_entry(ab, cur, (a_c_arg_t) big_enough);
    }

    dprint((9, "- re_sort done -\n"));

    return(new_entry_num);
}


/*
 * Delete an entry from the address book
 *
 * Args: ab        -- the address book
 *       entry_num -- entry to delete
 *    save_deleted -- save deleted as a #DELETED- entry
 *     enable_intr -- tell adrbk_write to enable interrupt handling
 *        be_quiet -- tell adrbk_write to not do percent done messages
 *        write_it -- only do adrbk_write if this is set
 *
 * Result: returns:  0 if all went well
 *                  -1 if there is no such entry
 *                  -2 error writing address book, check errno
 */
int
adrbk_delete(AdrBk *ab, a_c_arg_t entry_num, int save_deleted, int enable_intr,
	     int be_quiet, int write_it)
{
    int retval = 0;
    int set_mangled = 0;

    dprint((3, "- adrbk_delete(%ld) -\n", (long)entry_num));

    if(!ab)
      return -2;

    delete_ab_entry(ab, entry_num, save_deleted);
    if(F_OFF(F_EXPANDED_DISTLISTS,ps_global))
      exp_del_nth(ab->exp, entry_num);

    exp_del_nth(ab->selects, entry_num);

    if(write_it)
      retval = adrbk_write(ab, 0, NULL, &set_mangled, enable_intr, be_quiet);

    if(set_mangled)
      ps_global->mangled_screen = 1;
    
    return(retval);
}


/*
 * Delete an address out of an address list
 *
 * Args: ab    -- the address book
 *   entry_num -- the address list we are deleting from
 *       addr  -- address in above list to be deleted
 *
 * Result: 0: Deletion complete, address book written
 *        -1: Address for deletion not found
 *        -2: Error writing address book. Check errno.
 *
 * The address to be deleted is located by matching the string.
 */
int
adrbk_listdel(AdrBk *ab, a_c_arg_t entry_num, char *addr)
{
    char **p, *to_free;
    AdrBk_Entry *ae;
    int ret;
    int set_mangled = 0;

    dprint((3, "- adrbk_listdel(%ld) -\n", (long) entry_num));

    if(!ab || entry_num >= ab->count)
      return -2;

    if(!addr)
      return -1;

    ae = adrbk_get_ae(ab, entry_num);

    if(ae->tag != List)
      return -1;

    for(p = ae->addr.list; *p; p++) 
      if(strcmp(*p, addr) == 0)
        break;

    if(*p == NULL)
      return -1;

    /* note storage to be freed */
    if(*p != empty)
      to_free = *p;
    else
      to_free = NULL;

    /* slide all the entries below up (including NULL) */
    for(; *p; p++)
      *p = *(p+1);

    if(to_free)
      fs_give((void **) &to_free);

    ret = adrbk_write(ab, 0, NULL, &set_mangled, 1, 0);

    if(set_mangled)
      ps_global->mangled_screen = 1;

    return(ret);
}


/*
 * Delete all addresses out of an address list
 *
 * Args: ab    -- the address book
 *   entry_num -- the address list we are deleting from
 *
 * Result: 0: Deletion complete, address book written
 *        -1: Address for deletion not found
 *        -2: Error writing address book. Check errno.
 */
int
adrbk_listdel_all(AdrBk *ab, a_c_arg_t entry_num)
{
    char **p;
    AdrBk_Entry *ae;

    dprint((3, "- adrbk_listdel_all(%ld) -\n", (long) entry_num));

    if(!ab || entry_num >= ab->count)
      return -2;

    ae = adrbk_get_ae(ab, entry_num);

    if(ae->tag != List)
      return -1;

    /* free old list */
    for(p = ae->addr.list; p && *p; p++) 
      if(*p != empty)
        fs_give((void **)p);
    
    if(ae->addr.list)
      fs_give((void **) &ae->addr.list);

    ae->addr.list = NULL;

    return 0;
}


/*
 * Add a list of addresses to an already existing address list
 *
 * Args: ab        -- the address book
 *       entry_num -- the address list we are adding to
 *       addrs     -- address list to be added
 *     enable_intr -- tell adrbk_write to enable interrupt handling
 *        be_quiet -- tell adrbk_write to not do percent done messages
 *        write_it -- only do adrbk_write if this is set
 *
 * Result: returns 0 : addition made, address book written
 *                -1 : addition to non-list attempted
 *                -2 : error writing address book -- check errno
 */
int
adrbk_nlistadd(AdrBk *ab, a_c_arg_t entry_num, adrbk_cntr_t *new_entry_num,
	       int *resort_happened, char **addrs,
	       int enable_intr, int be_quiet, int write_it)
{
    char **p;
    int    cur_size, size_of_additional_list, new_size;
    int    i, rc = 0;
    int    set_mangled = 0;
    AdrBk_Entry *ae;

    dprint((3, "- adrbk_nlistadd(%ld) -\n", (long) entry_num));

    if(!ab || entry_num >= ab->count)
      return -2;

    ae = adrbk_get_ae(ab, entry_num);

    if(ae->tag != List)
      return -1;

    /* count up size of existing list */    
    for(p = ae->addr.list; p != NULL && *p != NULL; p++)
      ;/* do nothing */

    cur_size = p - ae->addr.list;

    /* count up size of new list */    
    for(p = addrs; p != NULL && *p != NULL; p++)
      ;/* do nothing */

    size_of_additional_list = p - addrs;
    new_size = cur_size + size_of_additional_list;

    /* make room at end of list for it */
    if(cur_size == 0)
      ae->addr.list = (char **) fs_get(sizeof(char *) * (new_size + 1));
    else
      fs_resize((void **) &ae->addr.list, sizeof(char *) * (new_size + 1));

    /* Put new list at the end */
    for(i = cur_size; i < new_size; i++)
      (ae->addr.list)[i] = cpystr(addrs[i - cur_size]);

    (ae->addr.list)[new_size] = NULL;

    /*---- sort it into the correct place ------*/
    if(ab->sort_rule != AB_SORT_RULE_NONE)
      sort_addr_list(ae->addr.list);

    if(write_it)
      rc = adrbk_write(ab, entry_num, new_entry_num, &set_mangled, enable_intr, be_quiet);

    if(set_mangled){
	ps_global->mangled_screen = 1;
	if(resort_happened)
	  *resort_happened = 1;
    }

    return(rc);
}


/*
 * Set the valid variable if we determine that the address book has
 * been changed by something other than us. This means we should update
 * our view of the address book when next possible.
 *
 * Args    ab -- AdrBk handle
 *  do_it_now -- If > 0, check now regardless
 *               If = 0, check if time since last chk more than default
 *               If < 0, check if time since last chk more than -do_it_now
 */
void
adrbk_check_validity(AdrBk *ab, long int do_it_now)
{
    dprint((9, "- adrbk_check_validity(%s) -\n",
	   (ab && ab->filename) ? ab->filename : ""));

    if(!ab || ab->flags & FILE_OUTOFDATE)
      return;

    adrbk_check_local_validity(ab, do_it_now);

    if(ab->type == Imap && !(ab->flags & FILE_OUTOFDATE ||
			     ab->rd->flags & REM_OUTOFDATE))
      rd_check_remvalid(ab->rd, do_it_now);
}


/*
 * Set the valid variable if we determine that the address book has
 * been changed by something other than us. This means we should update
 * our view of the address book when next possible.
 *
 * Args    ab -- AdrBk handle
 *  do_it_now -- If > 0, check now regardless
 *               If = 0, check if time since last chk more than default
 *               If < 0, check if time since last chk more than -do_it_now
 */
void
adrbk_check_local_validity(AdrBk *ab, long int do_it_now)
{
    time_t mtime, chk_interval;

    dprint((9, "- adrbk_check_local_validity(%s) -\n",
	   (ab && ab->filename) ? ab->filename : ""));

    if(!ab)
      return;

    if(do_it_now < 0L){
	chk_interval = -1L * do_it_now;
	do_it_now = 0L;
    }
    else
      chk_interval = FILE_VALID_CHK_INTERVAL;

    if(!do_it_now &&
       get_adj_time() <= ab->last_local_valid_chk + chk_interval)
      return;
    
    ab->last_local_valid_chk = get_adj_time();

    /*
     * Check local file for a modification time change.
     * If this is out of date, don't even bother checking for the remote
     * folder being out of date. That will get fixed when we reopen.
     */
    if(!(ab->flags & FILE_OUTOFDATE) &&
       ab->last_change_we_know_about != (time_t)(-1) &&
       (mtime=get_adj_name_file_mtime(ab->filename)) != (time_t)(-1) &&
       ab->last_change_we_know_about != mtime){

	dprint((2, "adrbk_check_local_validity: addrbook %s has changed\n",
		ab->filename ? ab->filename : "?"));
	ab->flags |= FILE_OUTOFDATE;
    }
}


/*
 * See if we can re-use an existing stream.
 * 
 * [ We don't believe we need this anymore now that the stuff in pine.c ]
 * [ is recycling streams for us, but we haven't thought it through all ]
 * [ the way enough to get rid of this. Hubert 2003-07-09               ]
 *
 * Args  name     -- Name of folder we want a stream for.
 *
 * Returns -- A mail stream suitable for status cmd or append cmd.
 *            NULL if none were available.
 */
MAILSTREAM *
adrbk_handy_stream(char *name)
{
    MAILSTREAM *stat_stream = NULL;
    int         i;

    dprint((9, "- adrbk_handy_stream(%s) -\n", name ? name : "?"));

    stat_stream = sp_stream_get(name, SP_SAME);
    
    /*
     * Look through our imap streams to see if there is one we can use.
     */
    for(i = 0; !stat_stream && i < as.n_addrbk; i++){
	PerAddrBook *pab;

	pab = &as.adrbks[i];

	if(pab->address_book &&
	   pab->address_book->type == Imap &&
	   pab->address_book->rd &&
	   pab->address_book->rd->type == RemImap &&
	   same_stream(name, pab->address_book->rd->t.i.stream)){
	    stat_stream = pab->address_book->rd->t.i.stream;
	    pab->address_book->rd->last_use = get_adj_time();
	    dprint((7,
		   "%s: used other abook stream for status (%ld)\n",
		   pab->address_book->orig_filename
		     ? pab->address_book->orig_filename : "?",
		   (long)pab->address_book->rd->last_use));
	}
    }

    dprint((9, "adrbk_handy_stream: returning %s\n",
	   stat_stream ? "good stream" : "NULL"));

    return(stat_stream);
}


/*
 * Close address book
 *
 * All that is done here is to free the storage, since the address book is 
 * rewritten on every change.
 */
void
adrbk_close(AdrBk *ab)
{
    int we_cancel = 0;

    dprint((4, "- adrbk_close(%s) -\n",
	   (ab && ab->filename) ? ab->filename : ""));

    if(!ab)
      return;

    if(ab->rd)
      rd_close_remdata(&ab->rd);

    if(ab->fp)
      (void)fclose(ab->fp);

    if(ab->our_filecopy && ab->filename != ab->our_filecopy){
	our_unlink(ab->our_filecopy);
	fs_give((void**) &ab->our_filecopy);
    }

    if(ab->exp){
	exp_free(ab->exp);
	fs_give((void **)&ab->exp);  /* free head of list, too */
    }

    if(ab->checks){
	exp_free(ab->checks);
	fs_give((void **)&ab->checks);  /* free head of list, too */
    }

    if(ab->selects){
	exp_free(ab->selects);
	fs_give((void **)&ab->selects);  /* free head of list, too */
    }

    if(ab->arr){
	adrbk_cntr_t entry_num;

	/*
	 * ab->arr is an allocated array. Each element of the array contains
	 * several pointers that point to other allocated stuff, so we
	 * free_ae_parts to get those and then free the array after the loop.
	 */
	for(entry_num = 0; entry_num < ab->count; entry_num++)
	  free_ae_parts(&ab->arr[entry_num]);

	fs_give((void **) &ab->arr);
    }

    if(ab->del){
	adrbk_cntr_t entry_num;

	for(entry_num = 0; entry_num < ab->del_count; entry_num++)
	  free_ae_parts(&ab->del[entry_num]);

	fs_give((void **) &ab->del);
    }

    if(ab->nick_trie)
      free_abook_trie(&ab->nick_trie);

    if(ab->addr_trie)
      free_abook_trie(&ab->addr_trie);

    if(ab->full_trie)
      free_abook_trie(&ab->full_trie);

    if(ab->revfull_trie)
      free_abook_trie(&ab->revfull_trie);

    if(we_cancel)
      cancel_busy_cue(0);

    if(ab->filename){
	if(ab->flags & DEL_FILE)
	  our_unlink(ab->filename);

	fs_give((void**)&ab->filename);
    }

    if(ab->orig_filename)
      fs_give((void**)&ab->orig_filename);

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


void
adrbk_partial_close(AdrBk *ab)
{
    dprint((4, "- adrbk_partial_close(%s) -\n",
	   (ab && ab->filename) ? ab->filename : ""));

    exp_free(ab->exp);		/* leaves head of list */
    exp_free(ab->checks);	/* leaves head of list */
}


/*
 * It has been noticed that this stream is dead and it is about to
 * be removed from the stream pool. We may have a pointer to it for one
 * of the remote address books. We have to note that the pointer is stale.
 */
void
note_closed_adrbk_stream(MAILSTREAM *stream)
{
    PerAddrBook *pab;
    int i;

    if(!stream)
      return;

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(pab->address_book
	   && pab->address_book->type == Imap
	   && pab->address_book->rd
	   && pab->address_book->rd->type == RemImap
	   && (stream == pab->address_book->rd->t.i.stream)){
	    dprint((4, "- note_closed_adrbk_stream(%s) -\n",
		   (pab->address_book && pab->address_book->orig_filename)
		       ? pab->address_book->orig_filename : ""));
	    pab->address_book->rd->t.i.stream = NULL;
	}
    }
}


static adrbk_cntr_t tot_for_percent;
static adrbk_cntr_t entry_num_for_percent;

/*
 * Write out the address book.
 *
 * If be_quiet is set, don't turn on busy_cue.
 *
 * If enable_intr_handling is set, turn on and off interrupt handling.
 *
 * Format is as in comment in the adrbk_open routine.  Lines are wrapped
 * to be under 80 characters.  This is called on every change to the
 * address book.  Write is first to a temporary file,
 * which is then renamed to be the real address book so that we won't
 * destroy the real address book in case of something like a full file
 * system.
 *
 * Writing a temp file and then renaming has the bad side affect of
 * destroying links.  It also overrides any read only permissions on
 * the mail file since rename ignores such permissions.  However, we
 * handle readonly-ness in addrbook.c before we call this.
 * We retain the permissions by doing a stat on the old file and a
 * chmod on the new one (with file_attrib_copy).
 *
 * In pre-alpine pine the address book entries were encoded on disk.
 * We would be happy with raw UTF-8 now but in order to preserve some
 * backwards compatibility we encode the entries before writing.
 * We first try to translate to the user's character set and encode
 * in that, else we use UTF-8 but with 1522 encoding.
 *
 * Returns:   0 write was successful
 *           -2 write failed
 *           -5 interrupted
 */
int
adrbk_write(AdrBk *ab, a_c_arg_t current_entry_num, adrbk_cntr_t *new_entry_num,
	    int *sort_happened, int enable_intr_handling, int be_quiet)
{
    FILE                  *ab_stream = NULL;
    AdrBk_Entry           *ae = NULL;
    adrbk_cntr_t           entry_num;
#ifndef	DOS
    void                  (*save_sighup)(int);
#endif
    int                   max_nick = 0,
			  max_full = 0, full_two = 0, full_three = 0,
			  max_fcc = 0, fcc_two = 0, fcc_three = 0,
			  max_addr = 0, addr_two = 0, addr_three = 0,
			  this_nick_width, this_full_width, this_addr_width,
			  this_fcc_width, fd, i;
    int                   interrupt_happened = 0, we_cancel = 0, we_turned_on = 0;
    char                 *temp_filename = NULL;
    WIDTH_INFO_S         *widths;

    if(!ab)
      return -2;

    dprint((2, "- adrbk_write(\"%s\") - writing %lu entries\n",
	ab->filename ? ab->filename : "", (unsigned long) ab->count));
    
    errno = 0;

    adrbk_check_local_validity(ab, 1L);
    /* verify that file has not been changed by something else */
    if(ab->flags & FILE_OUTOFDATE){
	/* It has changed! */
	q_status_message(SM_ORDER | SM_DING, 5, 15,
	    /* TRANSLATORS: The address book was changed by something else so alpine
	       is not making the change the user wanted to make to avoid damaging
	       the address book. */
	    _("Addrbook changed by another process, aborting our change to avoid damage..."));
	dprint((1, "adrbk_write: addrbook %s changed while we had it open, aborting write\n",
		ab->filename ? ab->filename : "?"));
	longjmp(addrbook_changed_unexpectedly, 1);
	/*NOTREACHED*/
    }

    /*
     * Verify that remote folder has not been
     * changed by something else (not if checked in last 5 seconds).
     *
     * The -5 is so we won't check every time on a series of quick writes.
     */
    rd_check_remvalid(ab->rd, -5L);
    if(ab->type == Imap){
	int  ro;

	/*
	 * We'll eventually need this open to write to remote, so see if
	 * we can open it now.
	 */
	rd_open_remote(ab->rd);

	/*
	 * Did someone else change the remote copy?
	 */
	if((ro=rd_remote_is_readonly(ab->rd)) || ab->rd->flags & REM_OUTOFDATE){
	    if(ro == 1){
		q_status_message(SM_ORDER | SM_DING, 5, 15,
			    _("Can't access remote addrbook, aborting change..."));
		dprint((1,
			"adrbk_write: Can't write to remote addrbook %s, aborting write: open failed\n",
			ab->rd->rn ? ab->rd->rn : "?"));
	    }
	    else if(ro == 2){
		if(!(ab->rd->flags & NO_META_UPDATE)){
		    unsigned long save_chk_nmsgs;

		    /*
		     * Should have some non-type-specific method of doing this.
		     */
		    switch(ab->rd->type){
		      case RemImap:
			save_chk_nmsgs = ab->rd->t.i.chk_nmsgs;
			ab->rd->t.i.chk_nmsgs = 0;/* cause it to be OUTOFDATE */
			rd_write_metadata(ab->rd, 0);
			ab->rd->t.i.chk_nmsgs = save_chk_nmsgs;
			break;

		      default:
			q_status_message(SM_ORDER | SM_DING, 3, 5,
					 "Adrbk_write: Type not supported");
			break;
		    }
		}

		q_status_message(SM_ORDER | SM_DING, 5, 15,
	     _("No write permission for remote addrbook, aborting change..."));
	    }
	    else{
		q_status_message(SM_ORDER | SM_DING, 5, 15,
	     _("Remote addrbook changed, aborting our change to avoid damage..."));
		dprint((1,
			"adrbk_write: remote addrbook %s changed while we had it open, aborting write\n",
			ab->orig_filename ? ab->orig_filename : "?"));
	    }

	    rd_close_remote(ab->rd);

	    longjmp(addrbook_changed_unexpectedly, 1);
	    /*NOTREACHED*/
	}
    }

#ifndef	DOS
    save_sighup = (void (*)(int))signal(SIGHUP, SIG_IGN);
#endif

    /*
     * If we want to be able to modify the address book, we will
     * need a temp_filename in the same directory as our_filecopy.
     */
    if(!(ab->our_filecopy && ab->our_filecopy[0])
       || !(temp_filename = tempfile_in_same_dir(ab->our_filecopy,"a1",NULL))
       || (fd = our_open(temp_filename, OPEN_WRITE_MODE, 0600)) < 0){
	dprint((1, "adrbk_write(%s): failed opening temp file (%s)\n",
		ab->filename ? ab->filename : "?",
		temp_filename ? temp_filename : "NULL"));
        goto io_error;
    }

    ab_stream = fdopen(fd, "wb");
    if(ab_stream == NULL){
	dprint((1, "adrbk_write(%s): fdopen failed\n", temp_filename ? temp_filename : "?"));
        goto io_error;
    }

    if(adrbk_is_in_sort_order(ab, be_quiet)){
	if(sort_happened)
	  *sort_happened = 0;

	if(new_entry_num)
	  *new_entry_num = current_entry_num;
    }
    else{
	if(sort_happened)
	  *sort_happened = 1;

	(void) adrbk_sort(ab, current_entry_num, new_entry_num, be_quiet);
    }

    /* accept keyboard interrupts */
    if(enable_intr_handling)
      we_turned_on = intr_handling_on();

    if(!be_quiet){
	tot_for_percent = MAX(ab->count, 1);
	entry_num_for_percent = 0;
	we_cancel = busy_cue(_("Saving address book"), percent_abook_saved, 0);
    }

    writing = 1;

    /*
     * If there are any old deleted entries, copy them to new file.
     */
    if(ab->del_count > 0){
	char *nickname;
	long  delindex;

	/*
	 * If there are deleted entries old enough that we no longer want
	 * to save them, remove them from the list.
	 */
	for(delindex = (long) ab->del_count-1;
	    delindex >= 0L && ab->del_count > 0; delindex--){

	    ae = adrbk_get_delae(ab, (a_c_arg_t) delindex);
	    nickname = ae->nickname;

	    if(strncmp(nickname, DELETED, DELETED_LEN) == 0
	       && isdigit((unsigned char)nickname[DELETED_LEN])
	       && isdigit((unsigned char)nickname[DELETED_LEN+1])
	       && nickname[DELETED_LEN+2] == '/'
	       && isdigit((unsigned char)nickname[DELETED_LEN+3])
	       && isdigit((unsigned char)nickname[DELETED_LEN+4])
	       && nickname[DELETED_LEN+5] == '/'
	       && isdigit((unsigned char)nickname[DELETED_LEN+6])
	       && isdigit((unsigned char)nickname[DELETED_LEN+7])
	       && nickname[DELETED_LEN+8] == '#'){
		int year, month, day;
		struct tm *tm_before;
		time_t     now, before;

		now = time((time_t *)0);
		before = now - (time_t)ABOOK_DELETED_EXPIRE_TIME;
		tm_before = localtime(&before);
		tm_before->tm_mon++;

		/*
		 * Check to see if it is older than 100 days.
		 */
		year  = atoi(&nickname[DELETED_LEN]);
		month = atoi(&nickname[DELETED_LEN+3]);
		day   = atoi(&nickname[DELETED_LEN+6]);
		if(year < 95)
		  year += 100;  /* this breaks in year 2095 */
		
		/*
		 * remove it if it is more than 100 days old
		 */
		if(!(year > tm_before->tm_year
		     || (year == tm_before->tm_year
		         && month > tm_before->tm_mon)
		     || (year == tm_before->tm_year
		         && month == tm_before->tm_mon
		         && day >= tm_before->tm_mday))){

		    /* it's old, dump it */
		    free_ae_parts(ae);
		    ab->del_count--;
		    /* patch the array by moving everything below up */
		    if(delindex < ab->del_count){
			memmove(&ab->del[delindex],
			        &ab->del[delindex+1],
				(ab->del_count - delindex) *
							sizeof(AdrBk_Entry));
		    }
		}
	    }
	}

	/* write out what remains */
	if(ab->del_count){
	    for(delindex = 0L; delindex < ab->del_count; delindex++){
		ae = adrbk_get_delae(ab, (a_c_arg_t) delindex);
		if(write_single_abook_entry(ae, ab_stream, NULL, NULL,
					    NULL, NULL) == EOF){
		    dprint((1, "adrbk_write(%s): failed writing deleted entry\n",
			    temp_filename ? temp_filename : "?"));
		    goto io_error;
		}
	    }
	}
	else{
	    /* nothing left, get rid of del array */
	    fs_give((void **) &ab->del);
	}
    }

    if(ab->del_count)
      dprint((4, "  adrbk_write: saving %ld deleted entries\n",
			     (long) ab->del_count));

    for(entry_num = 0; entry_num < ab->count; entry_num++){
	entry_num_for_percent++;

	ALARM_BLIP();

	ae = adrbk_get_ae(ab, (a_c_arg_t) entry_num);

	if(ae == (AdrBk_Entry *) NULL){
	    dprint((1, "adrbk_write(%s): can't find ae while writing addrbook, entry_num = %ld\n",
		    ab->filename ? ab->filename : "?",
		    (long) entry_num));
	    goto io_error;
	}

	/* write to temp file */
	if(write_single_abook_entry(ae, ab_stream, &this_nick_width,
		&this_full_width, &this_addr_width, &this_fcc_width) == EOF){
	    dprint((1, "adrbk_write(%s): failed writing for entry %ld\n",
		    temp_filename ? temp_filename : "?",
		    (long) entry_num));
	    goto io_error;
	}

	/* keep track of widths */
	max_nick = MAX(max_nick, this_nick_width);
	if(this_full_width > max_full){
	    full_three = full_two;
	    full_two   = max_full;
	    max_full   = this_full_width;
	}
	else if(this_full_width > full_two){
	    full_three = full_two;
	    full_two   = this_full_width;
	}
	else if(this_full_width > full_three){
	    full_three = this_full_width;
	}

	if(this_addr_width > max_addr){
	    addr_three = addr_two;
	    addr_two   = max_addr;
	    max_addr   = this_addr_width;
	}
	else if(this_addr_width > addr_two){
	    addr_three = addr_two;
	    addr_two   = this_addr_width;
	}
	else if(this_addr_width > addr_three){
	    addr_three = this_addr_width;
	}

	if(this_fcc_width > max_fcc){
	    fcc_three = fcc_two;
	    fcc_two   = max_fcc;
	    max_fcc   = this_fcc_width;
	}
	else if(this_fcc_width > fcc_two){
	    fcc_three = fcc_two;
	    fcc_two   = this_fcc_width;
	}
	else if(this_fcc_width > fcc_three){
	    fcc_three = this_fcc_width;
	}

	/*
	 * Check to see if we've been interrupted.  We check at the bottom
	 * of the loop so that we can handle an interrupt in the last
	 * iteration.
	 */
	if(enable_intr_handling && ps_global->intr_pending){
	    interrupt_happened++;
	    goto io_error;
	}
    }

    if(we_cancel){
	cancel_busy_cue(-1);
	we_cancel = 0;
    }

    if(enable_intr_handling && we_turned_on){
	intr_handling_off();
	we_turned_on = 0;
    }

    if(fclose(ab_stream) == EOF){
	dprint((1, "adrbk_write: fclose for %s failed\n",
	       temp_filename ? temp_filename : "?"));
	goto io_error;
    }

    ab_stream = (FILE *) NULL;

    file_attrib_copy(temp_filename, ab->our_filecopy);
    if(ab->fp){
	(void) fclose(ab->fp);
	ab->fp = NULL;			/* in case of problems */
    }

    if((i=rename_file(temp_filename, ab->our_filecopy)) < 0){
	dprint((1, "adrbk_write: rename(%s, %s) failed: %s\n",
		   temp_filename ? temp_filename : "?",
		   ab->our_filecopy ? ab->our_filecopy : "?",
		   error_description(errno)));
#ifdef	_WINDOWS
	if(i == -5){
	    q_status_message2(SM_ORDER | SM_DING, 5, 7,
			  _("Can't replace address book %.200sfile \"%.200s\""),
			      (ab->type == Imap) ? "cache " : "",
			      ab->filename);
	    q_status_message(SM_ORDER | SM_DING, 5, 7,
    _("If another Alpine is running, quit that Alpine before updating address book."));
	}
#endif	/* _WINDOWS */
	goto io_error;
    }

    /* reopen fp to new file */
    if(!(ab->fp = our_fopen(ab->our_filecopy, "rb"))){
	dprint((1, "adrbk_write: can't reopen %s\n",
	       ab->our_filecopy ? ab->our_filecopy : "?"));
	goto io_error;
    }

    if(temp_filename){
	our_unlink(temp_filename);
	fs_give((void **) &temp_filename);
    }

    /*
     * Now copy our_filecopy back to filename.
     */
    if(ab->filename != ab->our_filecopy){
	int   c, err = 0;
	char *lc;

	if(!(ab->filename && ab->filename[0])
	   || !(temp_filename = tempfile_in_same_dir(ab->filename,"a1",NULL))
	   || (fd = our_open(temp_filename, OPEN_WRITE_MODE, 0600)) < 0){
	    dprint((1,
		    "adrbk_write(%s): failed opening temp file (%s)\n",
		    ab->filename ? ab->filename : "?",
		    temp_filename ? temp_filename : "NULL"));
	    err++;
	}

	if(!err)
	  ab_stream = fdopen(fd, "wb");
	  
	if(ab_stream == NULL){
	    dprint((1, "adrbk_write(%s): fdopen failed\n",
		    temp_filename ? temp_filename : "?"));
	    err++;
	}

	if(!err){
	    rewind(ab->fp);
	    while((c = getc(ab->fp)) != EOF)
	      if(putc(c, ab_stream) == EOF){
		  err++;
		  break;
	      }
	}

	if(!err && fclose(ab_stream) == EOF)
	  err++;

	if(!err){
#ifdef	_WINDOWS
	    int tries = 3;
#endif

	    file_attrib_copy(temp_filename, ab->filename);

	    /*
	     * This could fail if somebody else has it open, but they should
	     * only have it open for a short time.
	     */
	    while(!err && rename_file(temp_filename, ab->filename) < 0){
#ifdef	_WINDOWS
		if(i == -5){
		    if(--tries <= 0)
		      err++;

		    if(!err){
			q_status_message2(SM_ORDER, 0, 3,
			      _("Replace of \"%.200s\" failed, trying %.200s"),
			      (lc=last_cmpnt(ab->filename)) ? lc : ab->filename,
			      (tries > 1) ? "again" : "one more time");
			display_message('x');
			sleep(3);
		    }
		}
#else	/* UNIX */
		err++;
#endif	/* UNIX */
	    }
	}

	if(err){
	    q_status_message1(SM_ORDER | SM_DING, 5, 5,
	       _("Copy of addrbook to \"%.200s\" failed, changes NOT saved!"),
	       (lc=last_cmpnt(ab->filename)) ? lc : ab->filename);
	    dprint((2, "adrbk_write: failed copying our_filecopy (%s)back to filename (%s): %s, continuing without a net\n",
		   ab->our_filecopy ? ab->our_filecopy : "?",
		   ab->filename ? ab->filename : "?",
		   error_description(errno)));
	}
    }

    widths = &ab->widths;
    widths->max_nickname_width  = MIN(max_nick, 99);
    widths->max_fullname_width  = MIN(max_full, 99);
    widths->max_addrfield_width = MIN(max_addr, 99);
    widths->max_fccfield_width  = MIN(max_fcc, 99);
    widths->third_biggest_fullname_width  = MIN(full_three, 99);
    widths->third_biggest_addrfield_width = MIN(addr_three, 99);
    widths->third_biggest_fccfield_width  = MIN(fcc_three, 99);

    /* record new change date of addrbook file */
    ab->last_change_we_know_about = get_adj_name_file_mtime(ab->filename);
    
#ifndef	DOS
    (void)signal(SIGHUP, save_sighup);
#endif

    /*
     * If this is a remote addressbook, copy the file over.
     * If it fails we warn but continue to operate on the changed,
     * locally cached addressbook file.
     */
    if(ab->type == Imap){
	int   e;
	char datebuf[200];

	datebuf[0] = '\0';
	if(we_cancel)
	  cancel_busy_cue(-1);

	we_cancel = busy_cue(_("Copying to remote addressbook"), NULL, 1);
	/*
	 * We don't want a cookie upgrade to blast our data in rd->lf by
	 * copying back the remote data before doing the upgrade, and then
	 * proceeding to finish with that data. So we tell rd_upgrade_cookie
	 * about that here.
	 */
	ab->rd->flags |= BELIEVE_CACHE;
	if((e = rd_update_remote(ab->rd, datebuf)) != 0){
	    if(e == -1){
		q_status_message2(SM_ORDER | SM_DING, 3, 5,
			_("Error opening temporary addrbook file %.200s: %.200s"),
				ab->rd->lf, error_description(errno));
		dprint((1,
		       "adrbk_write: error opening temp file %s\n",
		       ab->rd->lf ? ab->rd->lf : "?"));
	    }
	    else{
		q_status_message2(SM_ORDER | SM_DING, 3, 5,
				_("Error copying to %.200s: %.200s"),
				ab->rd->rn, error_description(errno));
		dprint((1,
		       "adrbk_write: error copying from %s to %s\n",
		       ab->rd->lf ? ab->rd->lf : "?",
		       ab->rd->rn ? ab->rd->rn : "?"));
	    }
	    
	    q_status_message(SM_ORDER | SM_DING, 5, 5,
       _("Copy of addrbook to remote folder failed, changes NOT saved remotely"));
	}
	else{
	    rd_update_metadata(ab->rd, datebuf);
	    ab->rd->read_status = 'W';
	    dprint((7,
		   "%s: copied local to remote in adrbk_write (%ld)\n",
		   ab->rd->rn ? ab->rd->rn : "?",
		   (long)ab->rd->last_use));
	}

	ab->rd->flags &= ~BELIEVE_CACHE;
    }

    writing = 0;

    if(we_cancel)
      cancel_busy_cue(0);

    if(temp_filename){
	our_unlink(temp_filename);
	fs_give((void **) &temp_filename);
    }

    return 0;


io_error:
    if(we_cancel)
      cancel_busy_cue(-1);

    if(enable_intr_handling && we_turned_on)
      intr_handling_off();

#ifndef	DOS
    (void)signal(SIGHUP, save_sighup);
#endif
    if(interrupt_happened){
	q_status_message(0, 1, 2, _("Interrupt!  Reverting to previous version"));
	display_message('x');
	dprint((1, "adrbk_write(%s): Interrupt\n",
		ab->filename ? ab->filename : "?"));
    }
    else
      dprint((1, "adrbk_write(%s) (%s): some sort of io_error\n",
	      ab->filename ? ab->filename : "?",
	      ab->our_filecopy ? ab->our_filecopy : "?"));

    writing = 0;

    if(ab_stream != NULL){
	fclose(ab_stream);
	ab_stream = (FILE *)NULL;
    }

    if(temp_filename){
	our_unlink(temp_filename);
	fs_give((void **) &temp_filename);
    }

    adrbk_partial_close(ab);

    if(interrupt_happened){
	errno = EINTR;  /* for nicer error message */
	return -5;
    }
    else
      return -2;
}


/*
 * Writes one addrbook entry with wrapping.  Fills in widths
 * for display purposes. Returns 0, or EOF on error.
 *
 * Continuation lines always start with spaces.  Tabs are treated as
 * separators, never as whitespace.  When we output tab separators we
 * always put them on the ends of lines, never on the start of a line
 * after a continuation.  That is, there is always something printable
 * after continuation spaces.
 */
int
write_single_abook_entry(AdrBk_Entry *ae, FILE *fp, int *ret_nick_width,
			 int *ret_full_width, int *ret_addr_width, int *ret_fcc_width)
{
    int len = 0;
    int nick_width = 0, full_width = 0, addr_width = 0, fcc_width = 0;
    int tmplen, this_len;
    char *write_this = NULL;

    if(fp == (FILE *) NULL){
	dprint((1, "write_single_abook_entry: fp is NULL\n"));
	return(EOF);
    }

    if(ae->nickname){
	nick_width = utf8_width(ae->nickname);

	write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
			    tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->nickname);

	this_len = strlen(write_this ? write_this : "");

	/*
	 * We aren't too concerned with where it wraps.
	 * Long lines are ok as long as they aren't super long.
	 */
	if(len > 100 || (len+this_len > 150 && len > 20)){
	    if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
		dprint((1,
		    "write_single_abook_entry: fputs ind1 failed\n"));
		return(EOF);
	    }

	    len = this_len + INDENT;
	}

	len += this_len;

	if(fputs(write_this, fp) == EOF){
	    dprint((1,
		"write_single_abook_entry: fputs nick failed\n"));
	    return(EOF);
	}
    }
    else
      nick_width = 0;

    putc(TAB, fp);
    len++;

    if(ae->fullname){
	full_width = utf8_width(ae->fullname);

	write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
			    tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->fullname);

	this_len = strlen(write_this ? write_this : "");


	if(len > 100 || (len+this_len > 150 && len > 20)){
	    if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
		dprint((1,
		    "write_single_abook_entry: fputs ind2 failed\n"));
		return(EOF);
	    }

	    len = this_len + INDENT;
	}

	len += this_len;

	if(fputs(write_this, fp) == EOF){
	    dprint((1,
		"write_single_abook_entry: fputs full failed\n"));
	    return(EOF);
	}
    }
    else
      full_width = 0;

    putc(TAB, fp);
    len++;

    /* special case, make sure empty list has () */
    if(ae->tag == List && ae->addr.list == NULL){
	addr_width = 0;
	putc('(', fp);
	putc(')', fp);
	len += 2;
    }
    else if(ae->addr.addr != NULL || ae->addr.list != NULL){
	if(ae->tag == Single){
	    /*----- Single: just one address ----*/
	    if(ae->addr.addr){
		addr_width = utf8_width(ae->addr.addr);

		write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
				    tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->addr.addr);

		this_len = strlen(write_this ? write_this : "");

		if(len > 100 || (len+this_len > 150 && len > 20)){
		    if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
			dprint((1,
			    "write_single_abook_entry: fputs ind3 failed\n"));
			return(EOF);
		    }

		    len = this_len + INDENT;
		}

		len += this_len;

		if(fputs(write_this, fp) == EOF){
		    dprint((1,
			"write_single_abook_entry: fputs addr failed\n"));
		    return(EOF);
		}
	    }
	    else
	      addr_width = 0;
	}
	else if(ae->tag == List){
	    register char **a2;

	    /*----- List: a distribution list ------*/
	    putc('(', fp);
	    len++;
	    addr_width = 0;
	    for(a2 = ae->addr.list; *a2 != NULL; a2++){
		if(*a2){
		    if(a2 != ae->addr.list){
			putc(',', fp);
			len++;
		    }

		    addr_width = MAX(addr_width, utf8_width(*a2));

		    write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
					tmp_20k_buf+10000, SIZEOF_20KBUF-10000, *a2);

		    this_len = strlen(write_this ? write_this : "");

		    if(len > 100 || (len+this_len > 150 && len > 20)){
			if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
			    dprint((1,
				"write_single_abook_entry: fputs ind3 failed\n"));
			    return(EOF);
			}

			len = this_len + INDENT;
		    }

		    len += this_len;

		    if(fputs(write_this, fp) == EOF){
			dprint((1,
			    "write_single_abook_entry: fputs addrl failed\n"));
			return(EOF);
		    }
		}
	    }

	    putc(')', fp);
	    len++;
	}
    }

    /* If either fcc or extra exists, output both, otherwise, neither */
    if((ae->fcc && ae->fcc[0]) || (ae->extra && ae->extra[0])){
	putc(TAB, fp);
	len++;

	if(ae->fcc && ae->fcc[0]){
	    fcc_width = utf8_width(ae->fcc);

	    write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
				tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->fcc);

	    this_len = strlen(write_this ? write_this : "");

	    if(len > 100 || (len+this_len > 150 && len > 20)){
		if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
		    dprint((1,
			"write_single_abook_entry: fputs ind4 failed\n"));
		    return(EOF);
		}

		len = this_len + INDENT;
	    }

	    len += this_len;

	    if(fputs(write_this, fp) == EOF){
		dprint((1,
		    "write_single_abook_entry: fputs fcc failed\n"));
		return(EOF);
	    }
	}
	else
	  fcc_width = 0;

	putc(TAB, fp);
	len++;

	if(ae->extra && ae->extra[0]){
	    int space;
	    char *cur, *end;
	    char *extra_copy;

	    write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000,
				tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->extra);

	    /*
	     * Copy ae->extra and replace newlines with spaces.
	     * The reason we do this is because the continuation lines
	     * produced by rfc1522_encode may interact with the
	     * continuation lines produced below in a bad way.
	     * In particular, what can happen is that the 1522 continuation
	     * newline can be followed immediately by a newline produced
	     * below. That breaks the continuation since that is a
	     * line with nothing on it. Just turn 1522 continuations into
	     * spaces, which work fine with 1522_decode.
	     */
	    extra_copy = cpystr(write_this);
	    REPLACE_NEWLINES_WITH_SPACE(extra_copy);
	   
	    tmplen = strlen(extra_copy);

	    if(len > 100 || (len+tmplen > 150 && len > 20)){
		if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){
		    dprint((1,
			"write_single_abook_entry: fprintf indent failed\n"));
		    return(EOF);
		}

		len = INDENT;
	    }

	    space = MAX(70 - len, 5);
	    cur = extra_copy;
	    end = cur + tmplen;
	    while(cur < end){
		if(end-cur > space){
		    int i;

		    /* find first space after spot we want to break */
		    for(i = space; cur+i < end && cur[i] != SPACE; i++) 
		      ;
		    
		    cur[i] = '\0';
		    if(fputs(cur, fp) == EOF){
			dprint((1,
			    "write_single_abook_entry: fputs extra failed\n"));
			return(EOF);
		    }

		    cur += (i+1);

		    if(cur < end){
			if(fprintf(fp, "%s%s", NEWLINE, INDENTXTRA) == EOF){
			    dprint((1,
			"write_single_abook_entry: fprintf indent failed\n"));
			    return(EOF);
			}

			space = 70 - INDENT;
		    }
		}
		else{
		    if(fputs(cur, fp) == EOF){
			dprint((1,
			    "write_single_abook_entry: fputs extra failed\n"));
			return(EOF);
		    }

		    cur = end;
		}
	    }

	    if(extra_copy)
	      fs_give((void **)&extra_copy);
	}
    }
    else
      fcc_width = 0;

    fprintf(fp, "%s", NEWLINE);
    
    if(ret_nick_width)
      *ret_nick_width = nick_width;
    if(ret_full_width)
      *ret_full_width = full_width;
    if(ret_addr_width)
      *ret_addr_width = addr_width;
    if(ret_fcc_width)
      *ret_fcc_width = fcc_width;

    return(0);
}


char *
backcompat_encoding_for_abook(char *buf1, size_t buf1len, char *buf2,
			      size_t buf2len, char *srcstr)
{
    char *encoded = NULL;
    char *p;
    int its_ascii = 1;


    for(p = srcstr; *p && its_ascii; p++)
      if(*p & 0x80)
	its_ascii = 0;

    /* if it is ascii, go with that */
    if(its_ascii)
      encoded = srcstr;
    else{
	char *trythischarset = NULL;

	/*
	 * If it is possible to translate the UTF-8
	 * string into the user's character set then
	 * do that. For backwards compatibility with
	 * old pines.
	 */
	if(ps_global->keyboard_charmap && ps_global->keyboard_charmap[0])
	  trythischarset = ps_global->keyboard_charmap;
	else if(ps_global->display_charmap && ps_global->display_charmap[0])
	  trythischarset = ps_global->display_charmap;

	if(trythischarset){
	    SIZEDTEXT src, dst;

	    src.data = (unsigned char *) srcstr;
	    src.size = strlen(srcstr);
	    memset(&dst, 0, sizeof(dst));
	    if(utf8_cstext(&src, trythischarset, &dst, 0)){
		if(dst.data){
		    strncpy(buf1, (char *) dst.data, buf1len);
		    buf1[buf1len-1] = '\0';
		    fs_give((void **) &dst.data);
		    encoded = rfc1522_encode(buf2, buf2len,
					     (unsigned char *) buf1, trythischarset);
		    if(encoded)
		      REPLACE_NEWLINES_WITH_SPACE(encoded);
		}
	    }
	}

	if(!encoded){
	    encoded = rfc1522_encode(buf1, buf1len, (unsigned char *) srcstr, "UTF-8");
	    if(encoded)
	      REPLACE_NEWLINES_WITH_SPACE(encoded);
	}
    }

    return(encoded);
}


int
percent_abook_saved(void)
{
    return((int)(((unsigned long)entry_num_for_percent * (unsigned long)100) /
	(unsigned long)tot_for_percent));
}


/*
 * Free memory associated with entry ae.
 *
 * Args:  ae  -- Address book entry to be freed.
 */
void
free_ae(AdrBk_Entry **ae)
{
    if(!ae || !(*ae))
      return;

    free_ae_parts(*ae);
    fs_give((void **) ae);
}


/*
 * Free memory associated with entry ae but not ae itself.
 */
void
free_ae_parts(AdrBk_Entry *ae)
{
    char **p;

    if(!ae)
      return;

    if(ae->nickname && ae->nickname != empty)
      fs_give((void **) &ae->nickname);

    if(ae->fullname && ae->fullname != empty)
      fs_give((void **) &ae->fullname);

    if(ae->tag == Single){
        if(ae->addr.addr && ae->addr.addr != empty)
          fs_give((void **) &ae->addr.addr);
    }
    else if(ae->tag == List){
        if(ae->addr.list){
            for(p = ae->addr.list; *p; p++) 
              if(*p != empty)
	        fs_give((void **) p);

            fs_give((void **) &ae->addr.list);
        }
    }

    if(ae->fcc && ae->fcc != empty)
      fs_give((void **) &ae->fcc);

    if(ae->extra && ae->extra != empty)
      fs_give((void **) &ae->extra);

    defvalue_ae(ae);
}


/*
 * Inserts element new_ae before element put_it_before_this.
 */
void
insert_ab_entry(AdrBk *ab, a_c_arg_t put_it_before_this, AdrBk_Entry *new_ae,
		int use_deleted_list)
{
    adrbk_cntr_t before, old_count, new_count;
    AdrBk_Entry *ae_before, *ae_before_next;

    dprint((7, "- insert_ab_entry(before_this=%ld) -\n", (long) put_it_before_this));

    before = (adrbk_cntr_t) put_it_before_this;

    if(!ab || before == NO_NEXT || (!use_deleted_list && before > ab->count)
       || (use_deleted_list && before > ab->del_count)){
	;
    }
    else{
	/*
	 * add space for new entry to array
	 * slide entries [before ... old_count-1] down one
	 * put new_ae into opened up slot
	 */
	old_count = use_deleted_list ? ab->del_count : ab->count;
	new_count = old_count + 1;
	if(old_count == 0){
	    if(use_deleted_list){
		if(ab->del)		/* shouldn't happen */
		  fs_give((void **) &ab->del);

		/* first entry in new array */
		ab->del = (AdrBk_Entry *) fs_get(new_count * sizeof(AdrBk_Entry));
		defvalue_ae(ab->del);
	    }
	    else{
		if(ab->arr)			/* shouldn't happen */
		  fs_give((void **) &ab->arr);

		/* first entry in new array */
		ab->arr = (AdrBk_Entry *) fs_get(new_count * sizeof(AdrBk_Entry));
		defvalue_ae(ab->arr);
	    }
	}
	else{
	    if(use_deleted_list){
		fs_resize((void **) &ab->del, new_count * sizeof(AdrBk_Entry));
		defvalue_ae(&ab->del[new_count-1]);
	    }
	    else{
		fs_resize((void **) &ab->arr, new_count * sizeof(AdrBk_Entry));
		defvalue_ae(&ab->arr[new_count-1]);
	    }
	}

	if(use_deleted_list){
	    ab->del_count = new_count;

	    ae_before = adrbk_get_delae(ab, (a_c_arg_t) before);
	    if(ae_before){
		if(before < old_count){
		    ae_before_next = adrbk_get_delae(ab, (a_c_arg_t) (before+1));
		    memmove(ae_before_next, ae_before,
			    (old_count-before) * sizeof(AdrBk_Entry));
		}

		memcpy(ae_before, new_ae, sizeof(AdrBk_Entry));
	    }
	}
	else{
	    ab->count = new_count;

	    ae_before = adrbk_get_ae(ab, (a_c_arg_t) before);
	    if(ae_before){
		if(before < old_count){
		    ae_before_next = adrbk_get_ae(ab, (a_c_arg_t) (before+1));
		    memmove(ae_before_next, ae_before,
			    (old_count-before) * sizeof(AdrBk_Entry));
		}

		memcpy(ae_before, new_ae, sizeof(AdrBk_Entry));
	    }
	}

    }

    if(!use_deleted_list)
      repair_abook_tries(ab);
}


/*
 * Moves element move_this_one before element put_it_before_this.
 */
void
move_ab_entry(AdrBk *ab, a_c_arg_t move_this_one, a_c_arg_t put_it_before_this)
{
    adrbk_cntr_t m, before;
    AdrBk_Entry  ae_tmp;
    AdrBk_Entry *ae_m, *ae_m_next, *ae_before, *ae_before_prev, *ae_before_next;

    dprint((7, "- move_ab_entry(move_this=%ld,before_this=%ld) -\n", (long) move_this_one, (long) put_it_before_this));

    m      = (adrbk_cntr_t) move_this_one;
    before = (adrbk_cntr_t) put_it_before_this;

    if(!ab || m == NO_NEXT || before == NO_NEXT
       || m == before || m+1 == before || before > ab->count){
	;
    }
    else if(m+1 < before){
	/*
	 * copy m
	 * slide entries [m+1 ... before-1] up one ("up" means smaller indices)
	 * put m into opened up slot
	 */
	ae_m  = adrbk_get_ae(ab, (a_c_arg_t) m);
	ae_m_next  = adrbk_get_ae(ab, (a_c_arg_t) (m+1));
	ae_before_prev  = adrbk_get_ae(ab, (a_c_arg_t) (before-1));
	if(ae_m && ae_m_next && ae_before_prev){
	    memcpy(&ae_tmp, ae_m, sizeof(ae_tmp));
	    memmove(ae_m, ae_m_next, (before-m-1) * sizeof(ae_tmp));
	    memcpy(ae_before_prev, &ae_tmp, sizeof(ae_tmp));
	}
    }
    else if(m > before){
	/*
	 * copy m
	 * slide entries [before ... m-1] down one
	 * put m into opened up slot
	 */
	ae_m  = adrbk_get_ae(ab, (a_c_arg_t) m);
	ae_before = adrbk_get_ae(ab, (a_c_arg_t) before);
	ae_before_next = adrbk_get_ae(ab, (a_c_arg_t) (before+1));
	if(ae_m && ae_before && ae_before_next){
	    memcpy(&ae_tmp, ae_m, sizeof(ae_tmp));
	    memmove(ae_before_next, ae_before, (m-before) * sizeof(ae_tmp));
	    memcpy(ae_before, &ae_tmp, sizeof(ae_tmp));
	}
    }

    dprint((9, "- move_ab_entry: done -\n"));

    repair_abook_tries(ab);
}


/*
 * Deletes element delete_this_one from in-core data structure.
 * If save_it is set the deleted entry is moved to the deleted_list.
 */
void
delete_ab_entry(AdrBk *ab, a_c_arg_t delete_this_one, int save_it)
{
    adrbk_cntr_t d;
    AdrBk_Entry *ae_deleted, *ae_deleted_next;

    dprint((7, "- delete_ab_entry(delete_this=%ld,save_it=%d) -\n", (long) delete_this_one, save_it));

    d = (adrbk_cntr_t) delete_this_one;

    if(!ab || d == NO_NEXT || d >= ab->count){
	;
    }
    else{
	/*
	 * Move the entry to the deleted_list if asked to.
	 */
	ae_deleted  = adrbk_get_ae(ab, (a_c_arg_t) d);
	if(ae_deleted){
	  if(save_it){
	    char *oldnick, *newnick;
	    size_t len;
	    struct tm *tm_now;
	    time_t now;

	    /*
	     * First prepend the prefix
	     * #DELETED-YY/MM/DD#
	     * to the nickname.
	     */
	    now = time((time_t *) NULL);
	    tm_now = localtime(&now);

	    oldnick = ae_deleted->nickname;
	    len = strlen(oldnick) + DELETED_LEN + strlen("YY/MM/DD#");

	    newnick = (char *) fs_get((len+1) * sizeof(char));
	    snprintf(newnick, len+1, "%s%02d/%02d/%02d#%s",
		     DELETED, (tm_now->tm_year)%100, tm_now->tm_mon+1,
		     tm_now->tm_mday, oldnick ? oldnick : "");
	    newnick[len] = '\0';

	    if(ae_deleted->nickname && ae_deleted->nickname != empty)
	      fs_give((void **) &ae_deleted->nickname);

	    ae_deleted->nickname = newnick;

	    /*
	     * Now insert this entry in the deleted_list.
	     */
	    insert_ab_entry(ab, (a_c_arg_t) ab->del_count, ae_deleted, 1);
	  }
	  else
	    free_ae_parts(ae_deleted);
	}

	/*
	 * slide entries [deleted+1 ... count-1] up one
	 */
	if(d+1 < ab->count){
	    ae_deleted = adrbk_get_ae(ab, (a_c_arg_t) d);
	    ae_deleted_next = adrbk_get_ae(ab, (a_c_arg_t) (d+1));
	    if(ae_deleted && ae_deleted_next)
	      memmove(ae_deleted, ae_deleted_next, (ab->count-d-1) * sizeof(AdrBk_Entry));
	}

	ab->count--;

	if(ab->count > 0)
	  fs_resize((void **) &ab->arr, ab->count * sizeof(AdrBk_Entry));
	else
	  fs_give((void **) &ab->arr);
    }

    repair_abook_tries(ab);
}


/*
 * We may want to be smarter about this and repair instead of
 * rebuild these.
 */
void
repair_abook_tries(AdrBk *ab)
{
    if(ab->arr){
	AdrBk_Trie *save_nick_trie, *save_addr_trie,
		   *save_full_trie, *save_revfull_trie;

	save_nick_trie = ab->nick_trie;
	ab->nick_trie = NULL;
	save_addr_trie = ab->addr_trie;
	ab->addr_trie = NULL;
	save_full_trie = ab->full_trie;
	ab->full_trie = NULL;
	save_revfull_trie = ab->revfull_trie;
	ab->revfull_trie = NULL;
	if(build_abook_tries(ab, NULL)){
	    dprint((2, "trouble rebuilding tries, restoring\n"));
	    if(ab->nick_trie)
	      free_abook_trie(&ab->nick_trie);

	    /* better than nothing */
	    ab->nick_trie = save_nick_trie;

	    if(ab->addr_trie)
	      free_abook_trie(&ab->addr_trie);

	    ab->addr_trie = save_addr_trie;

	    if(ab->full_trie)
	      free_abook_trie(&ab->full_trie);

	    ab->full_trie = save_full_trie;

	    if(ab->revfull_trie)
	      free_abook_trie(&ab->revfull_trie);

	    ab->revfull_trie = save_revfull_trie;
	}
	else{
	    if(save_nick_trie)
	      free_abook_trie(&save_nick_trie);

	    if(save_addr_trie)
	      free_abook_trie(&save_addr_trie);

	    if(save_full_trie)
	      free_abook_trie(&save_full_trie);

	    if(save_revfull_trie)
	      free_abook_trie(&save_revfull_trie);
	}
    }
}


/*
 * Free the list of distribution lists which have been expanded.
 * Leaves the head of the list alone.
 *
 * Args:  exp_head -- Head of the expanded list.
 */
void
exp_free(EXPANDED_S *exp_head)
{
    EXPANDED_S *e, *the_next_one;

    e = exp_head ? exp_head->next : NULL;

    if(!e)
      return;

    while(e){
	the_next_one = e->next;
	fs_give((void **)&e);
	e = the_next_one;
    }

    exp_head->next = (EXPANDED_S *)NULL;
}


/*
 * Is entry n expanded?
 *
 * Args:  exp_head -- Head of the expanded list.
 *        n        -- The entry num to check
 */
int
exp_is_expanded(EXPANDED_S *exp_head, a_c_arg_t n)
{
    register EXPANDED_S *e;
    adrbk_cntr_t nn;

    nn = (adrbk_cntr_t)n;

    e = exp_head ? exp_head->next : NULL;

    /*
     * The list is kept ordered, so we search until we find it or are
     * past it.
     */
    while(e){
	if(e->ent >= nn)
	  break;
	
	e = e->next;
    }
    
    return(e && e->ent == nn);
}


/*
 * How many entries expanded in this addrbook.
 *
 * Args:  exp_head -- Head of the expanded list.
 */
int
exp_howmany_expanded(EXPANDED_S *exp_head)
{
    register EXPANDED_S *e;
    int                  cnt = 0;

    e = exp_head ? exp_head->next : NULL;

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


/*
 * Are any entries expanded?
 *
 * Args:  exp_head -- Head of the expanded list.
 */
int
exp_any_expanded(EXPANDED_S *exp_head)
{
    return(exp_head && exp_head->next != NULL);
}


/*
 * Return next entry num in list.
 *
 * Args:  cur -- Current position in the list.
 *
 * Result: Returns the number of the next entry, or NO_NEXT if there is
 *	   no next entry.  As a side effect, the cur pointer is incremented.
 */
adrbk_cntr_t
exp_get_next(EXPANDED_S **cur)
{
    adrbk_cntr_t ret = NO_NEXT;

    if(cur && *cur && (*cur)->next){
	ret  = (*cur)->next->ent;
	*cur = (*cur)->next;
    }

    return(ret);
}


/*
 * Mark entry n as being expanded.
 *
 * Args:  exp_head -- Head of the expanded list.
 *        n        -- The entry num to mark
 */
void
exp_set_expanded(EXPANDED_S *exp_head, a_c_arg_t n)
{
    register EXPANDED_S *e;
    EXPANDED_S *new;
    adrbk_cntr_t nn;

    nn = (adrbk_cntr_t)n;
    if(!exp_head)
      alpine_panic("exp_head not set in exp_set_expanded");

    for(e = exp_head; e->next; e = e->next)
      if(e->next->ent >= nn)
	break;
    
    if(e->next && e->next->ent == nn) /* already there */
      return;

    /* add new after e */
    new       = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S));
    new->ent  = nn;
    new->next = e->next;
    e->next   = new;
}


/*
 * Mark entry n as being *not* expanded.
 *
 * Args:  exp_head -- Head of the expanded list.
 *        n        -- The entry num to mark
 */
void
exp_unset_expanded(EXPANDED_S *exp_head, a_c_arg_t n)
{
    register EXPANDED_S *e;
    EXPANDED_S *delete_this_one = NULL;
    adrbk_cntr_t nn;

    nn = (adrbk_cntr_t)n;
    if(!exp_head)
      alpine_panic("exp_head not set in exp_unset_expanded");

    for(e = exp_head; e->next; e = e->next)
      if(e->next->ent >= nn)
	break;
    
    if(e->next && e->next->ent == nn){
	delete_this_one = e->next;
	e->next = e->next->next;
    }

    if(delete_this_one)
      fs_give((void **)&delete_this_one);
}


/*
 * Adjust the "expanded" list to correspond to addrbook entry n being
 * deleted.
 *
 * Args:  exp_head -- Head of the expanded list.
 *        n        -- The entry num being deleted
 */
void
exp_del_nth(EXPANDED_S *exp_head, a_c_arg_t n)
{
    register EXPANDED_S *e;
    int delete_when_done = 0;
    adrbk_cntr_t nn;

    nn = (adrbk_cntr_t)n;
    if(!exp_head)
      alpine_panic("exp_head not set in exp_del_nth");

    e = exp_head->next;
    while(e && e->ent < nn)
      e = e->next;
    
    if(e){
	if(e->ent == nn){
	    delete_when_done++;
	    e = e->next;
	}

	while(e){
	    e->ent--; /* adjust entry nums */
	    e = e->next;
	}

	if(delete_when_done)
	  exp_unset_expanded(exp_head, n);
    }
}


/*
 * Adjust the "expanded" list to correspond to a new addrbook entry being
 * added between current entries n-1 and n.
 *
 * Args:  exp_head -- Head of the expanded list.
 *        n        -- The entry num being added
 *
 * The new entry is not marked expanded.
 */
void
exp_add_nth(EXPANDED_S *exp_head, a_c_arg_t n)
{
    register EXPANDED_S *e;
    adrbk_cntr_t nn;

    nn = (adrbk_cntr_t)n;
    if(!exp_head)
      alpine_panic("exp_head not set in exp_add_nth");

    e = exp_head->next;
    while(e && e->ent < nn)
      e = e->next;
    
    while(e){
	e->ent++; /* adjust entry nums */
	e = e->next;
    }
}


static AdrBk *ab_for_sort;

/*
 * Compare two address book entries.  Args are AdrBk_Entry **'s.
 * Sorts lists after simple addresses and then sorts on Fullname field.
 */
int
cmp_ae_by_full_lists_last(const qsort_t *a, const qsort_t *b)
{
    AdrBk_Entry **x = (AdrBk_Entry **)a,
                **y = (AdrBk_Entry **)b;
    int result;

    if((*x)->tag == List && (*y)->tag == Single)
      result = 1;
    else if((*x)->tag == Single && (*y)->tag == List)
      result = -1;
    else{
	register char *p, *q, *r, *s;

	p = (*x)->fullname;
	if(*p == '"' && *(p+1))
	  p++;

	q = (*y)->fullname;
	if(*q == '"' && *(q+1))
	  q++;

	r = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
					   SIZEOF_20KBUF, p);
	
	s = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf+10000,
					   SIZEOF_20KBUF-10000, q);

	result = (*pcollator)(r, s);
	if(result == 0)
	  result = (*pcollator)((*x)->nickname, (*y)->nickname);
    }
      
    return(result);
}


/*
 * Compare two address book entries.  Args are adrbk_cntr_t *'s (element #'s).
 * Sorts lists after simple addresses and then sorts on Fullname field.
 */
int
cmp_cntr_by_full_lists_last(const qsort_t *a, const qsort_t *b)
{
    adrbk_cntr_t *x = (adrbk_cntr_t *)a,  /* *x is an element_number */
                 *y = (adrbk_cntr_t *)b;
    AdrBk_Entry  *x_ae,
		 *y_ae;

    if(ps_global->intr_pending)
      longjmp(jump_over_qsort, 1);

    ALARM_BLIP();

    x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x));
    y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y));

    return(cmp_ae_by_full_lists_last((const qsort_t *) &x_ae,
				     (const qsort_t *) &y_ae));
}


/*
 * Compare two address book entries.  Args are AdrBk_Entry **'s.
 * Sorts on Fullname field.
 */
int
cmp_ae_by_full(const qsort_t *a, const qsort_t *b)
{
    AdrBk_Entry **x = (AdrBk_Entry **)a,
                **y = (AdrBk_Entry **)b;
    int result;
    register char *p, *q, *r, *s;

    p = (*x)->fullname;
    if(*p == '"' && *(p+1))
      p++;

    q = (*y)->fullname;
    if(*q == '"' && *(q+1))
      q++;

    r = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
				       SIZEOF_20KBUF, p);
    s = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf+10000,
				       SIZEOF_20KBUF-10000, q);
    result = (*pcollator)(r, s);
    if(result == 0)
      result = (*pcollator)((*x)->nickname, (*y)->nickname);
      
    return(result);
}


/*
 * Compare two address book entries.  Args are adrbk_cntr_t *'s (element #'s).
 * Sorts on Fullname field.
 */
int
cmp_cntr_by_full(const qsort_t *a, const qsort_t *b)
{
    adrbk_cntr_t *x = (adrbk_cntr_t *)a,  /* *x is an element_number */
                 *y = (adrbk_cntr_t *)b;
    AdrBk_Entry  *x_ae,
		 *y_ae;

    if(ps_global->intr_pending)
      longjmp(jump_over_qsort, 1);

    ALARM_BLIP();

    x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x));
    y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y));

    return(cmp_ae_by_full((const qsort_t *) &x_ae, (const qsort_t *) &y_ae));
}


/*
 * Compare two address book entries.  Args are AdrBk_Entry **'s.
 * Sorts lists after simple addresses and then sorts on Nickname field.
 */
int
cmp_ae_by_nick_lists_last(const qsort_t *a, const qsort_t *b)
{
    AdrBk_Entry **x = (AdrBk_Entry **)a,
                **y = (AdrBk_Entry **)b;
    int result;

    if((*x)->tag == List && (*y)->tag == Single)
      result = 1;
    else if((*x)->tag == Single && (*y)->tag == List)
      result = -1;
    else
      result = (*pcollator)((*x)->nickname, (*y)->nickname);

    return(result);
}


/*
 * Compare two address book entries.  Args are adrbk_cntr_t *'s (element #'s).
 * Sorts lists after simple addresses and then sorts on Nickname field.
 */
int
cmp_cntr_by_nick_lists_last(const qsort_t *a, const qsort_t *b)
{
    adrbk_cntr_t *x = (adrbk_cntr_t *)a,  /* *x is an element_number */
                 *y = (adrbk_cntr_t *)b;
    AdrBk_Entry  *x_ae,
		 *y_ae;

    if(ps_global->intr_pending)
      longjmp(jump_over_qsort, 1);

    ALARM_BLIP();

    x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x));
    y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y));

    return(cmp_ae_by_nick_lists_last((const qsort_t *) &x_ae,
				     (const qsort_t *) &y_ae));
}


/*
 * Compare two address book entries.  Args are AdrBk_Entry **'s.
 * Sorts on Nickname field.
 */
int
cmp_ae_by_nick(const qsort_t *a, const qsort_t *b)
{
    AdrBk_Entry **x = (AdrBk_Entry **)a,
                **y = (AdrBk_Entry **)b;

    return((*pcollator)((*x)->nickname, (*y)->nickname));
}


/*
 * Compare two address book entries.  Args are adrbk_cntr_t *'s (element #'s).
 * Sorts on Nickname field.
 */
int
cmp_cntr_by_nick(const qsort_t *a, const qsort_t *b)
{
    adrbk_cntr_t *x = (adrbk_cntr_t *)a,  /* *x is an element_number */
                 *y = (adrbk_cntr_t *)b;
    AdrBk_Entry  *x_ae,
		 *y_ae;

    if(ps_global->intr_pending)
      longjmp(jump_over_qsort, 1);

    ALARM_BLIP();

    x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x));
    y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y));

    return(cmp_ae_by_nick((const qsort_t *) &x_ae, (const qsort_t *) &y_ae));
}


/*
 * For sorting a simple list of pointers to addresses (skip initial quotes)
 */
int
cmp_addr(const qsort_t *a1, const qsort_t *a2)
{
    char *x = *(char **)a1, *y = *(char **)a2;
    char *r, *s;

    if(x && *x == '"')
      x++;

    if(y && *y == '"')
      y++;
    
    r = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
				       SIZEOF_20KBUF, x);
    s = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf+10000,
				       SIZEOF_20KBUF-10000, y);
    return((*pcollator)(r, s));
}


/*
 * Sort an array of strings, except skip initial quotes.
 */
void
sort_addr_list(char **list)
{
    register char **p;

    /* find size of list */
    for(p = list; *p != NULL; p++)
      ;/* do nothing */

    qsort((qsort_t *)list, (size_t)(p - list), sizeof(char *), cmp_addr);
}


/*
 * Sort this address book.
 *
 * Args: ab            -- address book to sort
 *   current_entry_num -- see next description
 *   new_entry_num     -- return new entry_num of current_entry_num here
 *
 * Result: return code:  0 all went well
 *                      -2 error writing address book, check errno
 *
 * The sorting strategy is to allocate an array of length ab->count which
 * contains the element numbers 0, 1, ..., ab->count - 1, representing the
 * entries in the addrbook, of course.  Sort the array, then use that
 * result to swap ae's in the in-core addrbook array before writing it out.
 */
int
adrbk_sort(AdrBk *ab, a_c_arg_t current_entry_num, adrbk_cntr_t *new_entry_num, int be_quiet)
{
    adrbk_cntr_t *sort_array, *inv, tmp;
    long i, j, hi, count;
    int skip_the_sort = 0, we_cancel = 0, we_turned_on = 0;
    AdrBk_Entry ae_tmp, *ae_i, *ae_hi;
    EXPANDED_S *e, *e2, *smallest;

    dprint((5, "- adrbk_sort -\n"));

    count = (long) (ab->count);

    if(!ab)
      return -2;

    if(ab->sort_rule == AB_SORT_RULE_NONE)
      return 0;
    
    if(count < 2)
      return 0;

    sort_array = (adrbk_cntr_t *) fs_get(count * sizeof(adrbk_cntr_t));
    inv        = (adrbk_cntr_t *) fs_get(count * sizeof(adrbk_cntr_t));
    
    for(i = 0L; i < count; i++)
      sort_array[i] = (adrbk_cntr_t) i;
	
    ab_for_sort = ab;

    if(setjmp(jump_over_qsort))
      skip_the_sort = 1;

    if(!skip_the_sort){
	we_turned_on = intr_handling_on();
	if(!be_quiet)
	  we_cancel = busy_cue(_("Sorting address book"), NULL, 0);

	qsort((qsort_t *)sort_array,
	    (size_t)count,
	    sizeof(adrbk_cntr_t),
	    (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ?
						cmp_cntr_by_full_lists_last :
	    (ab->sort_rule == AB_SORT_RULE_FULL) ?
						cmp_cntr_by_full :
	    (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ?
						cmp_cntr_by_nick_lists_last :
	    /* (ab->sort_rule == AB_SORT_RULE_NICK) */
						cmp_cntr_by_nick);
    }

    dprint((9, "- adrbk_sort: done with first sort -\n"));

    if(we_turned_on)
      intr_handling_off();

    if(skip_the_sort){
	q_status_message(SM_ORDER, 3, 3,
	    _("Address book sort cancelled, using old order for now"));
	goto skip_the_write_too;
    }

    dprint((5, "- adrbk_sort (%s)\n",
	    ab->sort_rule==AB_SORT_RULE_FULL_LISTS ? "FullListsLast" :
	     ab->sort_rule==AB_SORT_RULE_FULL ? "Fullname" :
	      ab->sort_rule==AB_SORT_RULE_NICK_LISTS ? "NickListLast" :
	       ab->sort_rule==AB_SORT_RULE_NICK ? "Nickname" : "unknown"));

    /*
     * Rearrange the in-core array of ae's to be in the new order.
     * We can do that by sorting the inverse into the correct order in parallel.
     */
    for(i = 0L; i < count; i++)
      inv[sort_array[i]] = i;

    if(new_entry_num && (adrbk_cntr_t) current_entry_num >= 0
       && (adrbk_cntr_t) current_entry_num < count){
	*new_entry_num = inv[(adrbk_cntr_t) current_entry_num];
    }

    /*
     * The expanded and selected lists will be wrong now. Correct them.
     * First the expanded list.
     */
    e = ab->exp ? ab->exp->next : NULL;
    while(e){
	if(e->ent >= 0 && e->ent < count)
	  e->ent = inv[e->ent];

	e = e->next;
    }

    /*
     * And sort into ascending order as expected by the exp_ routines.
     */
    e = ab->exp ? ab->exp->next : NULL;
    while(e){
	/* move smallest to e */
	e2 = e;
	smallest = e;
	while(e2){
	    if(e2->ent != NO_NEXT && e2->ent >= 0 && e2->ent < count && e2->ent < smallest->ent)
	      smallest = e2;

	    e2 = e2->next;
	}

	/* swap values in e and smallest */
	if(e != smallest){
	    tmp = e->ent;
	    e->ent = smallest->ent;
	    smallest->ent = tmp;
	}

	e = e->next;
    }

    /*
     * Same thing for the selected list.
     */
    e = ab->selects ? ab->selects->next : NULL;
    while(e){
	if(e->ent >= 0 && e->ent < count)
	  e->ent = inv[e->ent];

	e = e->next;
    }

    e = ab->selects ? ab->selects->next : NULL;
    while(e){
	/* move smallest to e */
	e2 = e;
	smallest = e;
	while(e2){
	    if(e2->ent != NO_NEXT && e2->ent >= 0 && e2->ent < count && e2->ent < smallest->ent)
	      smallest = e2;

	    e2 = e2->next;
	}

	/* swap values in e and smallest */
	if(e != smallest){
	    tmp = e->ent;
	    e->ent = smallest->ent;
	    smallest->ent = tmp;
	}

	e = e->next;
    }

    for(i = 0L; i < count; i++){
	if(i != inv[i]){
	    /* find inv[j] which = i */
	    for(j = i+1; j < count; j++){
		if(i == inv[j]){
		    hi = j;
		    break;
		}
	    }

	    /* swap i and hi */
	    ae_i  = adrbk_get_ae(ab, (a_c_arg_t) i);
	    ae_hi = adrbk_get_ae(ab, (a_c_arg_t) hi);
	    if(ae_i && ae_hi){
		memcpy(&ae_tmp, ae_i, sizeof(ae_tmp));
		memcpy(ae_i, ae_hi, sizeof(ae_tmp));
		memcpy(ae_hi, &ae_tmp, sizeof(ae_tmp));
	    }
	    /* else can't happen */

	    inv[hi] = inv[i];
	}
    }

    if(we_cancel)
      cancel_busy_cue(0);

    repair_abook_tries(ab);

    dprint((9, "- adrbk_sort: done with rearranging -\n"));

skip_the_write_too:
    if(we_cancel)
      cancel_busy_cue(0);

    if(sort_array)
      fs_give((void **) &sort_array);

    if(inv)
      fs_give((void **) &inv);

    return 0;
}


/*
 * Returns 1 if any addrbooks are in Open state, 0 otherwise.
 *
 * This is a test for ostatus == Open, not for whether or not the address book
 * FILE is opened.
 */
int
any_ab_open(void)
{
    int i, ret = 0;

    if(as.initialized)
      for(i = 0; ret == 0 && i < as.n_addrbk; i++)
	if(as.adrbks[i].ostatus == Open)
	  ret++;
    
    return(ret);
}


/*
 * Make sure addrbooks are minimally initialized.
 */
void
init_ab_if_needed(void)
{
    dprint((9, "- init_ab_if_needed -\n"));

    if(!as.initialized)
      (void)init_addrbooks(Closed, 0, 0, 1);
}


/*
 * Sets everything up to get started.
 *
 * Args: want_status      -- The desired OpenStatus for all addrbooks.
 *       reset_to_top     -- Forget about the old location and put cursor
 *                           at top.
 *       open_if_only_one -- If want_status is HalfOpen and there is only
 *                           section to look at, then promote want_status
 *                           to Open.
 *       ro_warning       -- Set ReadOnly warning global
 *
 * Return: 1 if ok, 0 if problem
 */
int
init_addrbooks(OpenStatus want_status, int reset_to_top, int open_if_only_one, int ro_warning)
{
    register PerAddrBook *pab;
    char *q, **t;
    long line;

    dprint((4, "-- init_addrbooks(%s, %d, %d, %d) --\n",
		    want_status==Open ?
				"Open" :
				want_status==HalfOpen ?
					"HalfOpen" :
				   want_status==ThreeQuartOpen ?
					"ThreeQuartOpen" :
					want_status==NoDisplay ?
						"NoDisplay" :
						"Closed",
		    reset_to_top, open_if_only_one, ro_warning));

    as.l_p_page = ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)
					       - HEADER_ROWS(ps_global);
    if(as.l_p_page <= 0)
      as.no_op_possbl++;
    else
      as.no_op_possbl = 0;

    as.ro_warning = ro_warning;

    /* already been initialized */
    if(as.initialized){
	int i;

	/*
	 * Special case. If there is only one addressbook we start the
	 * user out with that open.
	 */
	if(want_status == HalfOpen &&
	   ((open_if_only_one && as.n_addrbk == 1 && as.n_serv == 0) ||
	    (F_ON(F_EXPANDED_ADDRBOOKS, ps_global) &&
	     F_ON(F_CMBND_ABOOK_DISP, ps_global))))
	  want_status = Open;

	/* open to correct state */
	for(i = 0; i < as.n_addrbk; i++)
	  init_abook(&as.adrbks[i], want_status);

	if(reset_to_top){
	    warp_to_beginning();
	    as.top_ent     = 0L;
	    line           = first_selectable_line(0L);
	    if(line == NO_LINE)
	      as.cur_row = 0L;
	    else
	      as.cur_row = line;

	    if(as.cur_row >= as.l_p_page)
	      as.top_ent += (as.cur_row - as.l_p_page + 1);

	    as.old_cur_row = as.cur_row;
	}

	dprint((4, "init_addrbooks: already initialized: %d books\n",
				    as.n_addrbk));
        return((as.n_addrbk + as.n_serv) ? 1 : 0);
    }

    as.initialized = 1;

    /* count directory servers */
    as.n_serv = 0;
    as.n_impl = 0;
#ifdef	ENABLE_LDAP
    if(ps_global->VAR_LDAP_SERVERS &&
       ps_global->VAR_LDAP_SERVERS[0] &&
       ps_global->VAR_LDAP_SERVERS[0][0])
	for(t = ps_global->VAR_LDAP_SERVERS; t[0] && t[0][0]; t++){
	    LDAP_SERV_S *info;

	    as.n_serv++;
	    info = break_up_ldap_server(*t);
	    as.n_impl += (info && info->impl) ? 1 : 0;
	    if(info)
	      free_ldap_server_info(&info);
	}
#endif	/* ENABLE_LDAP */

    /* count addressbooks */
    as.how_many_personals = 0;
    if(ps_global->VAR_ADDRESSBOOK &&
       ps_global->VAR_ADDRESSBOOK[0] &&
       ps_global->VAR_ADDRESSBOOK[0][0])
	for(t = ps_global->VAR_ADDRESSBOOK; t[0] && t[0][0]; t++)
	  as.how_many_personals++;

    as.n_addrbk = as.how_many_personals;
    if(ps_global->VAR_GLOB_ADDRBOOK &&
       ps_global->VAR_GLOB_ADDRBOOK[0] &&
       ps_global->VAR_GLOB_ADDRBOOK[0][0])
	for(t = ps_global->VAR_GLOB_ADDRBOOK; t[0] && t[0][0]; t++)
	  as.n_addrbk++;

    if(want_status == HalfOpen &&
       ((open_if_only_one && as.n_addrbk == 1 && as.n_serv == 0) ||
	(F_ON(F_EXPANDED_ADDRBOOKS, ps_global) &&
	 F_ON(F_CMBND_ABOOK_DISP, ps_global))))
      want_status = Open;


    /*
     * allocate array of PerAddrBooks
     * (we don't give this up until we exit Pine, but it's small)
     */
    if(as.n_addrbk){
	as.adrbks = (PerAddrBook *)fs_get(as.n_addrbk * sizeof(PerAddrBook));
	memset((void *)as.adrbks, 0, as.n_addrbk * sizeof(PerAddrBook));

	/* init PerAddrBook data */
	for(as.cur = 0; as.cur < as.n_addrbk; as.cur++){
	    char *nickname = NULL,
		 *filename = NULL;

	    if(as.cur < as.how_many_personals)
	      q = ps_global->VAR_ADDRESSBOOK[as.cur];
	    else
	      q = ps_global->VAR_GLOB_ADDRBOOK[as.cur - as.how_many_personals];

	    pab = &as.adrbks[as.cur];
	    
	    /* Parse entry for optional nickname and filename */
	    get_pair(q, &nickname, &filename, 0, 0);

	    if(nickname && !*nickname)
	      fs_give((void **)&nickname);

	    strncpy(tmp_20k_buf, filename, SIZEOF_20KBUF);
	    fs_give((void **)&filename);

	    filename = tmp_20k_buf;
	    if(nickname == NULL)
	      pab->abnick = cpystr(filename);
	    else
	      pab->abnick = nickname;

	    if(*filename == '~')
	      fnexpand(filename, SIZEOF_20KBUF);

	    if(*filename == '{' || is_absolute_path(filename)){
		pab->filename = cpystr(filename); /* fully qualified */
	    }
	    else{
		char book_path[MAXPATH+1];
		char *lc = last_cmpnt(ps_global->pinerc);

		book_path[0] = '\0';
		if(lc != NULL){
		    strncpy(book_path, ps_global->pinerc,
			    MIN(lc - ps_global->pinerc, sizeof(book_path)-1));
		    book_path[MIN(lc - ps_global->pinerc,
				  sizeof(book_path)-1)] = '\0';
		}

		strncat(book_path, filename,
			sizeof(book_path)-1-strlen(book_path));
		pab->filename = cpystr(book_path);
	    }

	    if(*pab->filename == '{')
	      pab->type |= REMOTE_VIA_IMAP;

	    if(as.cur >= as.how_many_personals)
	      pab->type |= GLOBAL;

	    pab->access = adrbk_access(pab);

	    /* global address books are forced readonly */
	    if(pab->type & GLOBAL && pab->access != NoAccess)
	      pab->access = ReadOnly;

	    pab->ostatus  = TotallyClosed;

	    /*
	     * and remember that the memset above initializes everything
	     * else to 0
	     */

	    init_abook(pab, want_status);
	}
    }

    /*
     * Have to reset_to_top in this case since this is the first open,
     * regardless of the value of the argument, since these values haven't been
     * set before here.
     */
    as.cur         = 0;
    as.top_ent     = 0L;
    warp_to_beginning();
    line           = first_selectable_line(0L);

    if(line == NO_LINE)
      as.cur_row = 0L;
    else
      as.cur_row = line;

    if(as.cur_row >= as.l_p_page){
	as.top_ent += (as.cur_row - as.l_p_page + 1);
	as.cur_row = as.l_p_page - 1;
    }

    as.old_cur_row = as.cur_row;

    return((as.n_addrbk + as.n_serv) ? 1 : 0);
}


/*
 * Something was changed in options screen, so need to start over.
 */
void
addrbook_reset(void)
{
    dprint((4, "- addrbook_reset -\n"));
    completely_done_with_adrbks();
}


/*
 * Sort was changed in options screen. Since we only sort normally
 * when we actually make a change to the address book, we need to
 * go out of our way to sort here.
 */
void
addrbook_redo_sorts(void)
{
    int i;
    PerAddrBook *pab;
    AdrBk *ab;

    dprint((4, "- addrbook_redo_sorts -\n"));

    addrbook_reset();
    init_ab_if_needed();

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	init_abook(pab, NoDisplay);
	ab = pab->address_book;

	if(!adrbk_is_in_sort_order(ab, 0))
	  adrbk_write(ab, 0, NULL, NULL, 1, 0);
    }

    addrbook_reset();
}


/*
 * Returns type of access allowed on this addrbook.
 */
AccessType
adrbk_access(PerAddrBook *pab)
{
    char       fbuf[MAXPATH+1];
    AccessType access = NoExists;
    CONTEXT_S *dummy_cntxt = NULL;

    dprint((9, "- addrbook_access -\n"));

    if(pab && pab->type & REMOTE_VIA_IMAP){
	/*
	 * Open_fcc creates the folder if it didn't already exist.
	 */
	if((pab->so = open_fcc(pab->filename, &dummy_cntxt, 1,
			       "Error: ",
			       " Can't fetch remote addrbook.")) != NULL){
	    /*
	     * We know the folder is there but don't know what access
	     * rights we have until we try to select it, which we don't
	     * want to do unless we have to. So delay evaluating.
	     */
	    access = MaybeRorW;
	}
    }
    else if(pab){	/* local file */
#if defined(NO_LOCAL_ADDRBOOKS)
	/* don't allow any access to local addrbooks */
	access = NoAccess;
#else /* !NO_LOCAL_ADDRBOOKS) */
	build_path(fbuf, is_absolute_path(pab->filename) ? NULL
							 : ps_global->home_dir,
		   pab->filename, sizeof(fbuf));

#if defined(DOS) || defined(OS2)
	/*
	 * Microsoft networking causes some access calls to do a DNS query (!!)
	 * when it is turned on. In particular, if there is a / in the filename
	 * this seems to happen. So, just don't allow it.
	 */
	if(strindex(fbuf, '/') != NULL){
	    dprint((2, "\"/\" not allowed in addrbook name\n"));
	    return NoAccess;
	}
#else /* !DOS */
	/* also prevent backslash in non-DOS addrbook names */
	if(strindex(fbuf, '\\') != NULL){
	    dprint((2, "\"\\\" not allowed in addrbook name\n"));
	    return NoAccess;
	}
#endif /* !DOS */

	if(can_access(fbuf, ACCESS_EXISTS) == 0){
	    if(can_access(fbuf, EDIT_ACCESS) == 0){
		char *dir, *p;

		dir = ".";
		if((p = last_cmpnt(fbuf)) != NULL){
		    *--p = '\0';
		    dir  = *fbuf ? fbuf : "/";
		}

#if	defined(DOS) || defined(OS2)
		/*
		 * If the dir has become a drive letter and : (e.g. "c:")
		 * then append a "\".  The library function access() in the
		 * win 16 version of MSC seems to require this.
		 */
		if(isalpha((unsigned char) *dir)
		   && *(dir+1) == ':' && *(dir+2) == '\0'){
		    *(dir+2) = '\\';
		    *(dir+3) = '\0';
		}
#endif	/* DOS || OS2 */

		/*
		 * Even if we can edit the address book file itself, we aren't
		 * going to be able to change it unless we can also write in
		 * the directory that contains it (because we write into a
		 * temp file and then rename).
		 */
		if(can_access(dir, EDIT_ACCESS) == 0)
		  access = ReadWrite;
		else{
		    access = ReadOnly;
		    q_status_message1(SM_ORDER, 2, 2,
				      "Address book directory (%.200s) is ReadOnly",
				      dir);
		}
	    }
	    else if(can_access(fbuf, READ_ACCESS) == 0)
	      access = ReadOnly;
	    else
	      access = NoAccess;
	}
#endif /* !NO_LOCAL_ADDRBOOKS) */
    }
    
    return(access);
}


/*
 * Trim back remote address books if necessary.
 */
void
trim_remote_adrbks(void)
{
    register PerAddrBook *pab;
    int i;

    dprint((2, "- trim_remote_adrbks -\n"));

    if(!as.initialized)
      return;

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(pab->ostatus != TotallyClosed && pab->address_book
	   && pab->address_book->rd)
	  rd_trim_remdata(&pab->address_book->rd);
    }
}


/*
 * Free and close everything.
 */
void
completely_done_with_adrbks(void)
{
    register PerAddrBook *pab;
    int i;

    dprint((2, "- completely_done_with_adrbks -\n"));

    ab_nesting_level = 0;

    if(!as.initialized)
      return;

    for(i = 0; i < as.n_addrbk; i++)
      init_abook(&as.adrbks[i], TotallyClosed);

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];

	if(pab->filename)
	  fs_give((void **)&pab->filename);

	if(pab->abnick)
	  fs_give((void **)&pab->abnick);
    }

    done_with_dlc_cache();

    if(as.adrbks)
      fs_give((void **)&as.adrbks);

    as.n_addrbk    = 0;
    as.initialized = 0;
}


/*
 * Initialize or re-initialize this address book.
 *
 *  Args: pab        -- the PerAddrBook ptr
 *       want_status -- desired OpenStatus for this address book
 */
void
init_abook(PerAddrBook *pab, OpenStatus want_status)
{
    register OpenStatus new_status;

    dprint((4, "- init_abook -\n"));
    dprint((7, "    addrbook nickname = %s filename = %s",
	   pab->abnick ? pab->abnick : "<null>",
	   pab->filename ? pab->filename : "<null>"));
    dprint((7, "  ostatus was %s, want %s\n",
	   pab->ostatus==Open ? "Open" :
	     pab->ostatus==HalfOpen ? "HalfOpen" :
	       pab->ostatus==ThreeQuartOpen ? "ThreeQuartOpen" :
		 pab->ostatus==NoDisplay ? "NoDisplay" :
		   pab->ostatus==Closed ? "Closed" : "TotallyClosed",
	   want_status==Open ? "Open" :
	     want_status==HalfOpen ? "HalfOpen" :
	       want_status==ThreeQuartOpen ? "ThreeQuartOpen" :
		 want_status==NoDisplay ? "NoDisplay" :
		   want_status==Closed ? "Closed" : "TotallyClosed"));

    new_status = want_status;  /* optimistic default */

    if(want_status == TotallyClosed){
	if(pab->address_book != NULL){
	    adrbk_close(pab->address_book);
	    pab->address_book = NULL;
	}

	if(pab->so != NULL){
	    so_give(&pab->so);
	    pab->so = NULL;
	}
    }
    /*
     * If we don't need it, release some addrbook memory by calling
     * adrbk_partial_close().
     */
    else if((want_status == Closed || want_status == HalfOpen) &&
	pab->address_book != NULL){
	adrbk_partial_close(pab->address_book);
    }
    /* If we want the addrbook read in and it hasn't been, do so */
    else if(want_status == Open || want_status == NoDisplay){
      if(pab->address_book == NULL){  /* abook handle is not currently active */
	if(pab->access != NoAccess){
	    char warning[800]; /* place to put a warning */
	    int sort_rule;

	    warning[0] = '\0';
	    if(pab->access == ReadOnly)
	      sort_rule = AB_SORT_RULE_NONE;
	    else
	      sort_rule = ps_global->ab_sort_rule;

	    pab->address_book = adrbk_open(pab,
		   ps_global->home_dir, warning, sizeof(warning), sort_rule);

	    if(pab->address_book == NULL){
		pab->access = NoAccess;
		if(want_status == Open){
		    new_status = HalfOpen;  /* best we can do */
		    q_status_message1(SM_ORDER | SM_DING, *warning?1:3, 4,
				      _("Error opening/creating address book %.200s"),
				      pab->abnick);
		    if(*warning)
			q_status_message2(SM_ORDER, 3, 4, "%.200s: %.200s",
			    as.n_addrbk > 1 ? pab->abnick : "addressbook",
			    warning);
		}
		else
		    new_status  = Closed;

		dprint((1, "Error opening address book %s: %s\n",
			  pab->abnick ? pab->abnick : "?",
			  error_description(errno)));
	    }
	    else{
		if(pab->access == NoExists)
		  pab->access = ReadWrite;

		if(pab->access == ReadWrite){
		    /*
		     * Add forced entries if there are any.  These are
		     * entries that are always supposed to show up in
		     * personal address books.  They're specified in the
		     * global config file.
		     */
		    add_forced_entries(pab->address_book);
		}

		new_status = want_status;
		dprint((2, "Address book %s (%s) opened with %ld items\n",
			pab->abnick ? pab->abnick : "?",
			pab->filename ? pab->filename : "?",
			(long)adrbk_count(pab->address_book)));
		if(*warning){
		    dprint((1,
				 "Addressbook parse error in %s (%s): %s\n",
				 pab->abnick ? pab->abnick : "?",
				 pab->filename ? pab->filename : "?",
				 warning));
		    if(!pab->gave_parse_warnings && want_status == Open){
			pab->gave_parse_warnings++;
			q_status_message2(SM_ORDER, 3, 4, "%.200s: %.200s",
			    as.n_addrbk > 1 ? pab->abnick : "addressbook",
			    warning);
		    }
		}
	    }
	}
	else{
	    if(want_status == Open){
		new_status = HalfOpen;  /* best we can do */
		q_status_message1(SM_ORDER | SM_DING, 3, 4,
		   "Insufficient permissions for opening address book %.200s",
		   pab->abnick);
	    }
	    else
	      new_status = Closed;
	}
      }
      /*
       * File handle was already open but we were in Closed or HalfOpen
       * state. Check to see if someone else has changed the addrbook
       * since we first opened it.
       */
      else if((pab->ostatus == Closed || pab->ostatus == HalfOpen) &&
	      ps_global->remote_abook_validity > 0)
	(void)adrbk_check_and_fix(pab, 1, 0, 0);
    }

    pab->ostatus = new_status;
}


/*
 * Does a validity check on all the open address books.
 *
 * Return -- number of address books with invalid data
 */
int
adrbk_check_all_validity_now(void)
{
    int          i;
    int          something_out_of_date = 0;
    PerAddrBook *pab;

    dprint((7, "- adrbk_check_all_validity_now -\n"));

    if(as.initialized){
	for(i = 0; i < as.n_addrbk; i++){
	    pab = &as.adrbks[i];
	    if(pab->address_book){
		adrbk_check_validity(pab->address_book, 1L);
		if(pab->address_book->flags & FILE_OUTOFDATE ||
		   (pab->address_book->rd &&
		    pab->address_book->rd->flags & REM_OUTOFDATE))
		  something_out_of_date++;
	    }
	}
    }

    return(something_out_of_date);
}


/*
 * Fix out-of-date address book. This only fixes the AdrBk part of the
 * problem, not the pab and display. It closes and reopens the address_book
 * file, clears the cache, reads new data.
 *
 * Arg        pab -- Pointer to the PerAddrBook data for this addrbook
 *           safe -- It is safe to apply the fix
 *       low_freq -- This is a low frequency check (longer between checks)
 *
 * Returns non-zero if addrbook was fixed
 */
int
adrbk_check_and_fix(PerAddrBook *pab, int safe, int low_freq, int check_now)
{
    int  ret = 0;
    long chk_interval;

    if(!as.initialized || !pab)
      return(ret);

    dprint((7, "- adrbk_check_and_fix(%s) -\n",
	   pab->filename ? pab->filename : "?"));

    if(pab->address_book){
	if(check_now)
	  chk_interval = 1L;
	else if(low_freq)
	  chk_interval = -60L * MAX(LOW_FREQ_CHK_INTERVAL,
				    ps_global->remote_abook_validity + 5);
	else
	  chk_interval = 0L;

	adrbk_check_validity(pab->address_book, chk_interval);

	if(pab->address_book->flags & FILE_OUTOFDATE ||
	   (pab->address_book->rd &&
	    pab->address_book->rd->flags & REM_OUTOFDATE &&
	    !(pab->address_book->rd->flags & USER_SAID_NO))){
	    if(safe){
		OpenStatus save_status;
		int        save_rem_abook_valid = 0;

		dprint((2, "adrbk_check_and_fix %s: fixing %s\n",
		       debug_time(0,0,ps_global->signal_in_progress),
		       pab->filename ? pab->filename : "?"));
		if(ab_nesting_level > 0){
		    q_status_message3(SM_ORDER, 0, 2,
				     "Resyncing address book%.200s%.200s%.200s",
				     as.n_addrbk > 1 ? " \"" : "",
				     as.n_addrbk > 1 ? pab->abnick : "",
				     as.n_addrbk > 1 ? "\"" : "");
		    display_message('x');
		    fflush(stdout);
		}

		ret++;
		save_status = pab->ostatus;

		/* don't do the trim right now */
		if(pab->address_book->rd)
		  pab->address_book->rd->flags &= ~DO_REMTRIM;

		/*
		 * Have to change this from -1 or we won't actually do
		 * the resync.
		 */
		if((pab->address_book->rd &&
		    pab->address_book->rd->flags & REM_OUTOFDATE) &&
		   ps_global->remote_abook_validity == -1){
		    save_rem_abook_valid = -1;
		    ps_global->remote_abook_validity = 0;
		}

		init_abook(pab, TotallyClosed);

		pab->so = NULL;
		/* this sets up pab->so, so is important */
		pab->access = adrbk_access(pab);

		/*
		 * If we just re-init to HalfOpen... we won't actually
		 * open the address book, which was open before. That
		 * would be fine but it's a little nicer if we can open
		 * it now so that we don't defer the resync until
		 * the next open, which would be a user action for sure.
		 * Right now we may be here during a newmail check
		 * timeout, so this is a good time to do the resync.
		 */
		if(save_status == HalfOpen ||
		   save_status == ThreeQuartOpen ||
		   save_status == Closed)
		  init_abook(pab, NoDisplay);

		init_abook(pab, save_status);

		if(save_rem_abook_valid)
		  ps_global->remote_abook_validity = save_rem_abook_valid;

		if(ab_nesting_level > 0 && pab->ostatus == save_status)
		    q_status_message3(SM_ORDER, 0, 2,
				     "Resynced address book%.200s%.200s%.200s",
				     as.n_addrbk > 1 ? " \"" : "",
				     as.n_addrbk > 1 ? pab->abnick : "",
				     as.n_addrbk > 1 ? "\"" : "");
	    }
	    else
	      dprint((2,
		     "adrbk_check_and_fix: not safe to fix %s\n",
		     pab->filename ? pab->filename : "?"));
	}
    }

    return(ret);
}


static time_t last_check_and_fix_all;
/*
 * Fix out of date address books. This only fixes the AdrBk part of the
 * problem, not the pab and display. It closes and reopens the address_book
 * files, clears the caches, reads new data.
 *
 * Args      safe -- It is safe to apply the fix
 *       low_freq -- This is a low frequency check (longer between checks)
 *
 * Returns non-zero if an addrbook was fixed
 */
int
adrbk_check_and_fix_all(int safe, int low_freq, int check_now)
{
    int          i, ret = 0;
    PerAddrBook *pab;

    if(!as.initialized)
      return(ret);

    dprint((7, "- adrbk_check_and_fix_all -\n"));

    last_check_and_fix_all = get_adj_time();

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(pab->address_book)
	  ret += adrbk_check_and_fix(pab, safe, low_freq, check_now);
    }

    return(ret);
}


/*
 *
 */
void
adrbk_maintenance(void)
{
    static time_t last_time_here = 0;
    time_t        now;
    int           i;
    long          low_freq_interval;

    dprint((9, "- adrbk_maintenance -\n"));

    if(!as.initialized)
      return;
    
    now = get_adj_time();

    if(now < last_time_here + 120)
      return;

    last_time_here = now;

    low_freq_interval = MAX(LOW_FREQ_CHK_INTERVAL,
			    ps_global->remote_abook_validity + 5);

    /* check the time here to make it cheap */
    if(ab_nesting_level == 0 &&
       ps_global->remote_abook_validity > 0 &&
       now > last_check_and_fix_all + low_freq_interval * 60L)
      (void)adrbk_check_and_fix_all(1, 1, 0);

    /* close down idle connections */
    for(i = 0; i < as.n_addrbk; i++){
	PerAddrBook *pab;

	pab = &as.adrbks[i];

	if(pab->address_book &&
	   pab->address_book->type == Imap &&
	   pab->address_book->rd &&
	   rd_stream_exists(pab->address_book->rd)){
	    dprint((7,
		   "adrbk_maint: %s: idle cntr %ld (%ld)\n",
		   pab->address_book->orig_filename
		     ? pab->address_book->orig_filename : "?",
		   (long)(now - pab->address_book->rd->last_use),
		   (long)pab->address_book->rd->last_use));

	    if(now > pab->address_book->rd->last_use + IMAP_IDLE_TIMEOUT){
		dprint((2,
		    "adrbk_maint %s: closing idle (%ld secs) connection: %s\n",
		    debug_time(0,0,ps_global->signal_in_progress),
		    (long)(now - pab->address_book->rd->last_use),
		    pab->address_book->orig_filename
		      ? pab->address_book->orig_filename : "?"));
		rd_close_remote(pab->address_book->rd);
	    }
	    else{
		/*
		 * If we aren't going to close it, we ping it instead to
		 * make sure it stays open and doesn't timeout on us.
		 * This shouldn't be necessary unless the server has a
		 * really short timeout. If we got killed for some reason
		 * we set imap.stream to NULL.
		 * Instead of just pinging, we may as well check for
		 * updates, too.
		 */
		if(ab_nesting_level == 0 &&
		   ps_global->remote_abook_validity > 0){
		    time_t save_last_use;

		    /*
		     * We shouldn't count this as a real last_use.
		     */
		    save_last_use = pab->address_book->rd->last_use;
		    (void)adrbk_check_and_fix(pab, 1, 0, 1);
		    pab->address_book->rd->last_use = save_last_use;
		}
		/* just ping it if not safe to fix it */
		else if(!rd_ping_stream(pab->address_book->rd)){
		    dprint((2,
		      "adrbk_maint: %s: abook stream closed unexpectedly: %s\n",
		      debug_time(0,0,ps_global->signal_in_progress),
		      pab->address_book->orig_filename
		        ? pab->address_book->orig_filename : "?"));
		}
	    }
	}
    }
}


/*
 * Parses a string of comma-separated addresses or nicknames into an
 * array.
 *
 * Returns an allocated, null-terminated list, or NULL.
 */
char **
parse_addrlist(char *addrfield)
{
#define LISTCHUNK  500   /* Alloc this many addresses for list at a time */
    char **al, **ad;
    char *next_addr, *cur_addr, *p, *q;
    int slots = LISTCHUNK;

    if(!addrfield)
      return((char **)NULL);

    /* allocate first chunk */
    slots = LISTCHUNK;
    al    = (char **)fs_get(sizeof(char *) * (slots+1));
    ad    = al;

    p = addrfield;

    /* skip any leading whitespace */
    for(q = p; *q && *q == SPACE; q++)
      ;/* do nothing */

    next_addr = (*q) ? q : NULL;

    /* Loop adding each address in list to array al */
    for(cur_addr = next_addr; cur_addr; cur_addr = next_addr){

	next_addr = skip_to_next_addr(cur_addr);

	q = cur_addr;
	SKIP_SPACE(q);

	/* allocate more space */
	if((ad-al) >= slots){
	    slots += LISTCHUNK;
	    fs_resize((void **)&al, sizeof(char *) * (slots+1));
	    ad = al + slots - LISTCHUNK;
	}

	if(*q)
	  *ad++ = cpystr(q);
    }

    *ad++ = NULL;

    /* free up any excess we've allocated */
    fs_resize((void **)&al, sizeof(char *) * (ad - al));
    return(al);
}


/*
 * Args  cur -- pointer to the start of the current addr in list.
 *
 * Returns a pointer to the start of the next addr or NULL if there are
 * no more addrs.
 *
 * Side effect: current addr has trailing white space removed
 * and is null terminated.
 */
char *
skip_to_next_addr(char *cur)
{
    register char *p,
		  *q;
    char          *ret_pointer;
    int in_quotes  = 0,
        in_comment = 0;
    char prev_char = '\0';

    /*
     * Find delimiting comma or end.
     * Quoted commas and commented commas don't count.
     */
    for(q = cur; *q; q++){
	switch(*q){
	  case COMMA:
	    if(!in_quotes && !in_comment)
	      goto found_comma;
	    break;

	  case LPAREN:
	    if(!in_quotes && !in_comment)
	      in_comment = 1;
	    break;

	  case RPAREN:
	    if(in_comment && prev_char != BSLASH)
	      in_comment = 0;
	    break;

	  case QUOTE:
	    if(in_quotes && prev_char != BSLASH)
	      in_quotes = 0;
	    else if(!in_quotes && !in_comment)
	      in_quotes = 1;
	    break;

	  default:
	    break;
	}

	prev_char = *q;
    }
    
found_comma:
    if(*q){  /* trailing comma case */
	*q = '\0';
	ret_pointer = q + 1;
    }
    else
      ret_pointer = NULL;  /* no more addrs after cur */

    /* remove trailing white space from cur */
    for(p = q - 1; p >= cur && isspace((unsigned char)*p); p--)
      *p = '\0';
    
    return(ret_pointer);
}


/*
 * Add entries specified by system administrator.  If the nickname already
 * exists, it is not touched.
 */
void
add_forced_entries(AdrBk *abook)
{
    AdrBk_Entry *abe;
    char *nickname, *fullname, *address;
    char *end_of_nick, *end_of_full, **t;


    if(!ps_global->VAR_FORCED_ABOOK_ENTRY ||
       !ps_global->VAR_FORCED_ABOOK_ENTRY[0] ||
       !ps_global->VAR_FORCED_ABOOK_ENTRY[0][0])
	return;

    for(t = ps_global->VAR_FORCED_ABOOK_ENTRY; t[0] && t[0][0]; t++){
	nickname = *t;

	/*
	 * syntax for each element is
	 * nick[whitespace]|[whitespace]Fullname[WS]|[WS]Address
	 */

	/* find end of nickname */
	end_of_nick = nickname;
	while(*end_of_nick
	      && !isspace((unsigned char)*end_of_nick)
	      && *end_of_nick != '|')
	  end_of_nick++;

	/* find the pipe character between nickname and fullname */
	fullname = end_of_nick;
	while(*fullname && *fullname != '|')
	  fullname++;
	
	if(*fullname)
	  fullname++;

	*end_of_nick = '\0';
	abe = adrbk_lookup_by_nick(abook, nickname, NULL);

	if(!abe){  /* If it isn't there, add it */

	    /* skip whitespace before fullname */
	    fullname = skip_white_space(fullname);

	    /* find the pipe character between fullname and address */
	    end_of_full = fullname;
	    while(*end_of_full && *end_of_full != '|')
	      end_of_full++;

	    if(!*end_of_full){
		dprint((2,
		    "missing | in forced-abook-entry \"%s\"\n",
		    nickname ? nickname : "?"));
		continue;
	    }

	    address = end_of_full + 1;

	    /* skip whitespace before address */
	    address = skip_white_space(address);

	    if(*address == '('){
		dprint((2,
		   "no lists allowed in forced-abook-entry \"%s\"\n",
		   address ? address : "?"));
		continue;
	    }

	    /* go back and remove trailing white space from fullname */
	    while(*end_of_full == '|' || isspace((unsigned char)*end_of_full)){
		*end_of_full = '\0';
		end_of_full--;
	    }

	    dprint((2,
	       "Adding forced abook entry \"%s\"\n", nickname ? nickname : ""));

	    (void)adrbk_add(abook,
			   NO_NEXT,
			   nickname,
			   fullname,
			   address,
			   NULL,
			   NULL,
			   Single,
			   (adrbk_cntr_t *)NULL,
			   (int *)NULL,
			   1,
			   0,
			   1);
	}
    }
}

/* Go through the list of addressbooks and check if any
 * of them point to the given stream.
 */
int
any_addressbook_in_remote_stream(MAILSTREAM *stream)
{
  int rv = 0;
  int i = 0, num = 0;
  char *nickname = NULL;
  char *filename = NULL;
  char *q = NULL;

  do{
    if(ps_global->VAR_ADDRESSBOOK &&
       ps_global->VAR_ADDRESSBOOK[num] &&
       ps_global->VAR_ADDRESSBOOK[num][0]){
	q = ps_global->VAR_ADDRESSBOOK[num++];
	i = num;
    }
    else if(ps_global->VAR_GLOB_ADDRBOOK &&
	    ps_global->VAR_GLOB_ADDRBOOK[i-num] &&
	    ps_global->VAR_GLOB_ADDRBOOK[i-num][0]){
	q = ps_global->VAR_GLOB_ADDRBOOK[i - num];
	i++;
    } else q = NULL;
    if(q != NULL){
      get_pair(q, &nickname, &filename, 0, 0);

      if(nickname) fs_give((void **)&nickname);

      if(filename){
	if(*filename == '{' 
	   && same_stream(filename, stream) != NULL)
	 rv = 1;
	fs_give((void **)&filename);
      }
    }
  } while (rv == 0 && q != NULL);

  return rv;
}
