#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: adrbkcmd.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
#endif

/*
 * ========================================================================
 * Copyright 2013-2018 Eduardo Chappa
 * Copyright 2006-2008 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

/*====================================================================== 
    adrbkcmds.c
    Commands called from the addrbook screens.
 ====*/


#include "headers.h"
#include "adrbkcmd.h"
#include "addrbook.h"
#include "takeaddr.h"
#include "status.h"
#include "keymenu.h"
#include "mailview.h"
#include "mailcmd.h"
#include "mailindx.h"
#include "radio.h"
#include "folder.h"
#include "reply.h"
#include "help.h"
#include "titlebar.h"
#include "signal.h"
#include "roleconf.h"
#include "send.h"
#include "../pith/adrbklib.h"
#include "../pith/addrbook.h"
#include "../pith/abdlc.h"
#include "../pith/ablookup.h"
#include "../pith/bldaddr.h"
#include "../pith/ldap.h"
#include "../pith/state.h"
#include "../pith/filter.h"
#include "../pith/conf.h"
#include "../pith/msgno.h"
#include "../pith/addrstring.h"
#include "../pith/remote.h"
#include "../pith/url.h"
#include "../pith/util.h"
#include "../pith/detoken.h"
#include "../pith/stream.h"
#include "../pith/send.h"
#include "../pith/list.h"
#include "../pith/busy.h"
#include "../pith/icache.h"
#include "../pith/osdep/color.h"


/* internal prototypes */
int	       url_hilite_abook(long, char *, LT_INS_S **, void *);
int            process_abook_view_cmd(int, MSGNO_S *, SCROLL_S *);
int            expand_addrs_for_pico(struct headerentry *, char ***);
char          *view_message_for_pico(char **);
int            verify_nick(char *, char **, char **, BUILDER_ARG *, int *);
int            verify_addr(char *, char **, char **, BUILDER_ARG *, int *);
int            pico_sendexit_for_adrbk(struct headerentry *, void(*)(void), int, char **);
char          *pico_cancelexit_for_adrbk(char *, void (*)(void));
char          *pico_cancel_for_adrbk_take(void (*)(void));
char          *pico_cancel_for_adrbk_edit(void (*)(void));
int            ab_modify_abook_list(int, int, int, char *, char *, char *);
int            convert_abook_to_remote(struct pine *, PerAddrBook *, char *, size_t, int);
int            any_rule_files_to_warn_about(struct pine *);
int            verify_folder_name(char *,char **,char **,BUILDER_ARG *, int *);
int            verify_server_name(char *,char **,char **,BUILDER_ARG *, int *);
int            verify_abook_nick(char *, char **,char **,BUILDER_ARG *, int *);
int            do_the_shuffle(int *, int, int, char **);
void           ab_compose_internal(BuildTo, int);
int            ab_export(struct pine *, long, int, int);
VCARD_INFO_S  *prepare_abe_for_vcard(struct pine *, AdrBk_Entry *, int);
void           write_single_tab_entry(gf_io_t, VCARD_INFO_S *);
int            percent_done_copying(void);
int            cmp_action_list(const qsort_t *, const qsort_t *);
void           set_act_list_member(ACTION_LIST_S *, a_c_arg_t, PerAddrBook *, PerAddrBook *, char *);
void           convert_pinerc_to_remote(struct pine *, char *);

#ifdef	ENABLE_LDAP
typedef struct _saved_query {
    char *qq,
	 *cn,
	 *sn,
	 *gn,
	 *mail,
	 *org,
	 *unit,
	 *country,
	 *state,
	 *locality,
	 *custom;
} SAVED_QUERY_S;

int            process_ldap_cmd(int, MSGNO_S *, SCROLL_S *);
int            pico_simpleexit(struct headerentry *, void (*)(void), int, char **);
char          *pico_simplecancel(void (*)(void));
void           save_query_parameters(SAVED_QUERY_S *);
SAVED_QUERY_S *copy_query_parameters(SAVED_QUERY_S *);
void           free_query_parameters(SAVED_QUERY_S **);
int            restore_query_parameters(struct headerentry *, char ***);

static char *expander_address;
#endif	/* ENABLE_LDAP */

static char *fakedomain = "@";


#define	VIEW_ABOOK_NONE		0
#define	VIEW_ABOOK_EDITED	1
#define	VIEW_ABOOK_WARPED	2

/*
 * View an addrbook entry.
 * Call scrolltool to do the work.
 */
void
view_abook_entry(struct pine *ps, long int cur_line)
{
    AdrBk_Entry *abe;
    STORE_S    *in_store, *out_store;
    char       *string, *errstr;
    SCROLL_S	sargs;
    HANDLE_S   *handles = NULL;
    URL_HILITE_S uh;
    gf_io_t	pc, gc;
    int         cmd, abook_indent;
    long	offset = 0L;
    char        b[500];

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

    if(is_addr(cur_line)){
	abe = ae(cur_line);
	if(!abe){
	    q_status_message(SM_ORDER, 0, 3, _("Error reading entry"));
	    return;
	}
    }
    else{
	q_status_message(SM_ORDER, 0, 3, _("Nothing to view"));
	return;
    }

    if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS))){
	q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error allocating space."));
	return;
    }

    abook_indent = utf8_width(_("Nickname")) + 2;

    /* TRANSLATORS: Nickname is a shorthand name for something */
    utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
    so_puts(in_store, b);
    if(abe->nickname)
      so_puts(in_store, abe->nickname);

    so_puts(in_store, "\015\012");

    /* TRANSLATORS: Full name is the name that goes with an email address.
       For example, in
       Fred Flintstone <fred@bedrock.org>
       Fred Flintstone is the Full Name. */
    utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fullname"));
    so_puts(in_store, b);
    if(abe->fullname)
      so_puts(in_store, abe->fullname);

    so_puts(in_store, "\015\012");

    /* TRANSLATORS: Fcc is an abbreviation for File carbon copy. It is like
       a cc which is a copy of a message that goes to somebody other than the
       main recipient, only this is a copy of a message which is put into a
       file on the user's computer. */
    utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fcc"));
    so_puts(in_store, b);
    if(abe->fcc)
      so_puts(in_store, abe->fcc);

    so_puts(in_store, "\015\012");

    utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, AB_COMMENT_STR);
    so_puts(in_store, b);
    if(abe->extra)
      so_puts(in_store, abe->extra);

    so_puts(in_store, "\015\012");

    /* TRANSLATORS: Addresses refers to email Addresses */
    utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
    so_puts(in_store, b);
    if(abe->tag == Single){
	char *tmp = abe->addr.addr ? abe->addr.addr : "";
#ifdef	ENABLE_LDAP
	if(!strncmp(tmp, QRUN_LDAP, LEN_QRL))
	  string = LDAP_DISP;
	else
#endif
	  string = tmp;

	so_puts(in_store, string);
    }
    else{
	char **ll;

	for(ll = abe->addr.list; ll && *ll; ll++){
	    if(ll != abe->addr.list){
		so_puts(in_store, "\015\012");
		so_puts(in_store, repeat_char(abook_indent+2, SPACE));
	    }

#ifdef	ENABLE_LDAP
	    if(!strncmp(*ll, QRUN_LDAP, LEN_QRL))
	      string = LDAP_DISP;
	    else
#endif
	      string = *ll;

	    so_puts(in_store, string);

	    if(*(ll+1))	/* not the last one */
	      so_puts(in_store, ",");
	}
    }

    so_puts(in_store, "\015\012");

    do{
	if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
	    so_give(&in_store);
	    q_status_message(SM_ORDER | SM_DING, 3, 3,
			     _("Error allocating space."));
	    return;
	}

	so_seek(in_store, 0L, 0);

	init_handles(&handles);
	gf_filter_init();

	if(F_ON(F_VIEW_SEL_URL, ps_global)
	   || F_ON(F_VIEW_SEL_URL_HOST, ps_global)
	   || F_ON(F_SCAN_ADDR, ps_global))
	  gf_link_filter(gf_line_test,
			 gf_line_test_opt(url_hilite_abook,
					  gf_url_hilite_opt(&uh,&handles,0)));

	gf_link_filter(gf_wrap, gf_wrap_filter_opt(ps->ttyo->screen_cols - 4,
						   ps->ttyo->screen_cols,
						   NULL, abook_indent+2,
						   GFW_HANDLES));
	gf_link_filter(gf_nvtnl_local, NULL);

	gf_set_so_readc(&gc, in_store);
	gf_set_so_writec(&pc, out_store);

	if((errstr = gf_pipe(gc, pc)) != NULL){
	    so_give(&in_store);
	    so_give(&out_store);
	    free_handles(&handles);
	    q_status_message1(SM_ORDER | SM_DING, 3, 3,
			      /* TRANSLATORS: %s is the error message */
			      _("Can't format entry: %s"), errstr);
	    return;
	}

	gf_clear_so_writec(out_store);
	gf_clear_so_readc(in_store);

	memset(&sargs, 0, sizeof(SCROLL_S));
	sargs.text.text	   = so_text(out_store);
	sargs.text.src	   = CharStar;
	sargs.text.desc	   = _("expanded entry");
	sargs.text.handles = handles;

	if(offset){		/* resize?  preserve paging! */
	    sargs.start.on	   = Offset;
	    sargs.start.loc.offset = offset;
	    offset = 0L;
	}
	  
	/* TRANSLATORS: a screen title. We are viewing an address book */
	sargs.bar.title	  = _("ADDRESS BOOK (View)");
	sargs.bar.style	  = TextPercent;
	sargs.proc.tool	  = process_abook_view_cmd;
	sargs.proc.data.i = VIEW_ABOOK_NONE;
	sargs.resize_exit = 1;
	sargs.help.text	  = h_abook_view;
	/* TRANSLATORS: help screen title */
	sargs.help.title  = _("HELP FOR ADDRESS BOOK VIEW");
	sargs.keys.menu	  = &abook_view_keymenu;
	setbitmap(sargs.keys.bitmap);

	if(handles)
	  sargs.keys.menu->how_many = 2;
	else{
	    sargs.keys.menu->how_many = 1;
	    clrbitn(OTHER_KEY, sargs.keys.bitmap);
	}

	if((cmd = scrolltool(&sargs)) == MC_RESIZE)
	  offset = sargs.start.loc.offset;

	so_give(&out_store);
	free_handles(&handles);
    }
    while(cmd == MC_RESIZE);

    so_give(&in_store);

    if(sargs.proc.data.i != VIEW_ABOOK_NONE){
	long old_l_p_p, old_top_ent, old_cur_row;

	if(sargs.proc.data.i == VIEW_ABOOK_WARPED){
	    /*
	     * Warped means we got plopped down somewhere in the display
	     * list so that we don't know where we are relative to where
	     * we were before we warped.  The current line number will
	     * be zero, since that is what the warp would have set.
	     */
	    as.top_ent = first_line(0L - as.l_p_page/2L);
	    as.cur_row = 0L - as.top_ent;
	}
	else if(sargs.proc.data.i == VIEW_ABOOK_EDITED){
	    old_l_p_p   = as.l_p_page;
	    old_top_ent = as.top_ent;
	    old_cur_row = as.cur_row;
	}

	/* Window size may have changed while in pico. */
	ab_resize();

	/* fix up what ab_resize messed up */
	if(sargs.proc.data.i != VIEW_ABOOK_WARPED && old_l_p_p == as.l_p_page){
	    as.top_ent     = old_top_ent;
	    as.cur_row     = old_cur_row;
	    as.old_cur_row = old_cur_row;
	}

	cur_line = as.top_ent+as.cur_row;
    }

    ps->mangled_screen = 1;
}


int
url_hilite_abook(long int linenum, char *line, LT_INS_S **ins, void *local)
{
    register char *lp;

    if((lp = strchr(line, ':')) &&
       !strncmp(line, AB_COMMENT_STR, strlen(AB_COMMENT_STR)))
      (void) url_hilite(linenum, lp + 1, ins, local);

    return(0);
}


int
process_abook_view_cmd(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
{
    int rv = 1, i;
    PerAddrBook *pab;
    AddrScrn_Disp *dl;
    static ESCKEY_S text_or_vcard[] = {
	/* TRANSLATORS: Text refers to plain old text, probably the text of
	   an email message */
	{'t', 't', "T", N_("Text")},
	/* TRANSLATORS: A VCard is kind of like an electronic business card. It is not
	   something specific to alpine, it is universal. */
	{'v', 'v', "V", N_("VCard")},
	{-1, 0, NULL, NULL}};

    ps_global->next_screen = SCREEN_FUN_NULL;

    switch(cmd){
      case MC_EDIT :
	/*
	 * MC_EDIT works a little differently from the other cmds here.
	 * The others return 0 to scrolltool so that we are still in
	 * the view screen. This one is different because we may have
	 * changed what we're viewing. We handle that by returning 1
	 * to scrolltool and setting the sparms opt union's gint
	 * to the value below.
	 *
	 * (Late breaking news. Now we're going to return 1 from all these
	 * commands and then in the caller we're going to bounce back out
	 * to the index view instead of the view of the individual entry.
	 * So there is some dead code around for now.)
	 * 
	 * Then, in the view_abook_entry function we check the value
	 * of this field on scrolltool's return and if it is one of
	 * the two special values below view_abook_entry resets the
	 * current line if necessary, flushes the display cache (in
	 * ab_resize) and loops back and starts over, effectively
	 * putting us back in the view screen but with updated
	 * contents. A side effect is that the screen above that (the
	 * abook index) will also have been flushed and corrected by
	 * the ab_resize.
	 */
	pab = &as.adrbks[cur_addr_book()];
	if(pab && pab->access == ReadOnly){
	    /* TRANSLATORS: Address book can be viewed but not changed */
	    q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
	    rv = 0;
	    break;
	}

	if(adrbk_check_all_validity_now()){
	    if(resync_screen(pab, AddrBookScreen, 0)){
		q_status_message(SM_ORDER | SM_DING, 3, 4,
		 /* TRANSLATORS: The address book was changed by some other
		    process. The user is being told that their change (their
		    Update) has been canceled and they should try again. */
		 _("Address book changed. Update cancelled. Try again."));
		ps_global->mangled_screen = 1;
		break;
	    }
	}

	if(pab && pab->access == ReadOnly){
	    q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
	    rv = 0;
	    break;
	}

	/*
	 * Arguments all come from globals, not from the arguments to
	 * process_abook_view_cmd. It would be a little cleaner if the
	 * information was contained in att, I suppose.
	 */
	if(is_addr(as.cur_row+as.top_ent)){
	    AdrBk_Entry *abe, *abe_copy;
	    a_c_arg_t    entry;
	    int          warped = 0;

	    dl = dlist(as.top_ent+as.cur_row);
	    entry = dl->elnum;
	    abe = adrbk_get_ae(pab->address_book, entry);
	    abe_copy = copy_ae(abe);
	    dprint((9,"Calling edit_entry to edit from view\n"));
	    /* TRANSLATORS: update as in update an address book entry */
	    edit_entry(pab->address_book, abe_copy, entry,
		       abe->tag, 0, &warped, _("update"));
	    /*
	     * The ABOOK_EDITED case doesn't mean that we necessarily
	     * changed something, just that we might have but we know
	     * we didn't change the sort order (causing the warp).
	     */
	    sparms->proc.data.i = warped
				   ? VIEW_ABOOK_WARPED : VIEW_ABOOK_EDITED;

	    free_ae(&abe_copy);
	    rv = 1;			/* causes scrolltool to return */
	}
	else{
	    q_status_message(SM_ORDER, 0, 4,
			     "Something wrong, entry not updateable");
	    rv = 0;
	    break;
	}

	break;

      case MC_SAVE :
	pab = &as.adrbks[cur_addr_book()];
	/*
	 * Arguments all come from globals, not from the arguments to
	 * process_abook_view_cmd. It would be a little cleaner if the
	 * information was contained in att, I suppose.
	 */
	(void)ab_save(ps_global, pab ? pab->address_book : NULL,
		      as.top_ent+as.cur_row, -FOOTER_ROWS(ps_global), 0);
	rv = 1;
	break;

      case MC_COMPOSE :
	(void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 0);
	rv = 1;
	break;

      case MC_ROLE :
	(void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 1);
	rv = 1;
	break;

      case MC_FORWARD :
	rv = 1;
	/* TRANSLATORS: A question with two choices for the answer. Forward
	   as text means to include the text of the message being forwarded
	   in the new message. Forward as a Vcard attachment means to
	   attach it to the message in a special format so it is recognizable
	   as a Vcard. */
	i = radio_buttons(_("Forward as text or forward as Vcard attachment ? "),
			  -FOOTER_ROWS(ps_global), text_or_vcard, 't', 'x',
			  h_ab_text_or_vcard, RB_NORM);
	switch(i){
	  case 'x':
	    q_status_message(SM_INFO, 0, 2, _("Address book forward cancelled"));
	    rv = 0;
	    break;

	  case 't':
	    forward_text(ps_global, sparms->text.text, sparms->text.src);
	    break;

	  case 'v':
	    (void)ab_forward(ps_global, as.top_ent+as.cur_row, 0);
	    break;

	  default:
	    q_status_message(SM_ORDER, 3, 3,
			     "can't happen in process_abook_view_cmd");
	    break;
	}

	break;

      default:
	alpine_panic("Unexpected command in process_abook_view_cmd");
	break;
    }

    return(rv);
}


/*
 * Give expanded view of this address entry.
 * Call scrolltool to do the work.
 *
 * Args: headents -- The headerentry array from pico.
 *              s -- Unused here.
 *
 * Returns -- Always 0.
 */
int
expand_addrs_for_pico(struct headerentry *headents, char ***s)
{
    BuildTo      bldto;
    STORE_S     *store;
    char        *error = NULL, *addr = NULL, *fullname = NULL, *address = NULL;
    SAVE_STATE_S state;
    ADDRESS     *adrlist = NULL, *a;
    int          j, address_index = -1, fullname_index = -1, no_a_fld = 0;
    void       (*redraw)(void) = ps_global->redrawer;

    char        *tmp, *tmp2, *tmp3;
    SCROLL_S	 sargs;
    AdrBk_Entry  abe;
    char         fakeaddrpmt[500];
    int          abook_indent;

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

    abook_indent = utf8_width(_("Nickname")) + 2;
    utf8_snprintf(fakeaddrpmt, sizeof(fakeaddrpmt), "%-*.*w: ", abook_indent, abook_indent, _("Address"));

    if(s)
      *s = NULL;

    ps_global->redrawer = NULL;
    fix_windsize(ps_global);

    ab_nesting_level++;
    save_state(&state);

    for(j=0;
	headents[j].name != NULL && (address_index < 0 || fullname_index < 0);
	j++){
	if(!strncmp(headents[j].name, "Address", 7) || !strncmp(headents[j].name, _("Address"), strlen(_("Address"))))
	  address_index = j;
	else if(!strncmp(headents[j].name, "Fullname", 8) || !strncmp(headents[j].name, _("Fullname"), strlen(_("Fullname"))))
	  fullname_index = j;
    }

    if(address_index >= 0)
      address = *headents[address_index].realaddr;
    else{
	address_index = 1000;	/* a big number */
	no_a_fld++;
    }

    if(fullname_index >= 0)
      fullname = adrbk_formatname(*headents[fullname_index].realaddr,
				  NULL, NULL);

    memset(&abe, 0, sizeof(abe));
    if(fullname)
      abe.fullname = cpystr(fullname);
    
    if(address){
	char *tmp_a_string;

	tmp_a_string = cpystr(address);
	rfc822_parse_adrlist(&adrlist, tmp_a_string, fakedomain);
	fs_give((void **)&tmp_a_string);
	if(adrlist && adrlist->next){
	    int cnt = 0;

	    abe.tag = List;
	    for(a = adrlist; a; a = a->next)
	      cnt++;
	    
	    abe.addr.list = (char **)fs_get((cnt+1) * sizeof(char *));
	    cnt = 0;
	    for(a = adrlist; a; a = a->next){
		if(a->host && a->host[0] == '@')
		  abe.addr.list[cnt++] = cpystr(a->mailbox);
		else if(a->host && a->host[0] && a->mailbox && a->mailbox[0])
		  abe.addr.list[cnt++] =
				  cpystr(simple_addr_string(a, tmp_20k_buf,
							    SIZEOF_20KBUF));
	    }

	    abe.addr.list[cnt] = NULL;
	}
	else{
	    abe.tag = Single;
	    abe.addr.addr = address;
	}

	if(adrlist)
	  mail_free_address(&adrlist);

	bldto.type = Abe;
	bldto.arg.abe = &abe;
	our_build_address(bldto, &addr, &error, NULL, NULL);
	if(error){
	    q_status_message1(SM_ORDER, 3, 4, "%s", error);
	    fs_give((void **)&error);
	}
	
	if(addr){
	    tmp_a_string = cpystr(addr);
	    rfc822_parse_adrlist(&adrlist, tmp_a_string, ps_global->maildomain);
	    fs_give((void **)&tmp_a_string);
	}
    }
#ifdef	ENABLE_LDAP
    else if(no_a_fld && expander_address){
	WP_ERR_S wp_err;

	memset(&wp_err, 0, sizeof(wp_err));
	adrlist = wp_lookups(expander_address, &wp_err, 0);
	if(fullname && *fullname){
	    if(adrlist){
		if(adrlist->personal)
		  fs_give((void **)&adrlist->personal);
		
		adrlist->personal = cpystr(fullname);
	    }
	}

	if(wp_err.error){
	    q_status_message1(SM_ORDER, 3, 4, "%s", wp_err.error);
	    fs_give((void **)&wp_err.error);
	}
    }
#endif
    
    if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
	q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error allocating space."));
	restore_state(&state);
	return(0);
    }

    for(j = 0; j < address_index && headents[j].name != NULL; j++){
	if((tmp = fold(*headents[j].realaddr,
		      ps_global->ttyo->screen_cols,
		      ps_global->ttyo->screen_cols,
		      headents[j].prompt,
		      repeat_char(headents[j].prwid, SPACE), FLD_NONE)) != NULL){
	    so_puts(store, tmp);
	    fs_give((void **)&tmp);
	}
    }

    /*
     * There is an assumption that Addresses is the last field.
     */
    tmp3 = cpystr(repeat_char(headents[0].prwid + 2, SPACE));
    if(adrlist){
	for(a = adrlist; a; a = a->next){
	    ADDRESS *next_addr;
	    char    *bufp;
	    size_t   len;

	    next_addr = a->next;
	    a->next = NULL;
	    len = est_size(a);
	    bufp = (char *) fs_get(len * sizeof(char));
	    (void) addr_string(a, bufp, len);
	    a->next = next_addr;

	    /*
	     * Another assumption, all the prwids are the same.
	     */
	    if((tmp = fold(bufp,
			  ps_global->ttyo->screen_cols,
			  ps_global->ttyo->screen_cols,
			  (a == adrlist) ? (no_a_fld
					      ? fakeaddrpmt
					      : headents[address_index].prompt)
					 : tmp3+2,
			  tmp3, FLD_NONE)) != NULL){
		so_puts(store, tmp);
		fs_give((void **) &tmp);
	    }

	    fs_give((void **) &bufp);
	}
    }
    else{
	tmp2 = NULL;
	if((tmp = fold(tmp2=cpystr("<none>"),
		      ps_global->ttyo->screen_cols,
		      ps_global->ttyo->screen_cols,
		      no_a_fld ? fakeaddrpmt
			       : headents[address_index].prompt,
		      tmp3, FLD_NONE)) != NULL){
	    so_puts(store, tmp);
	    fs_give((void **) &tmp);
	}

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

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

    memset(&sargs, 0, sizeof(SCROLL_S));
    sargs.text.text = so_text(store);
    sargs.text.src  = CharStar;
    sargs.text.desc = _("expanded entry");
    /* TRANSLATORS: screen title for viewing an address book entry in Rich
       view mode. Rich just means it is expanded to include everything. */
    sargs.bar.title = _("ADDRESS BOOK (Rich View)");
    sargs.bar.style = TextPercent;
    sargs.keys.menu = &abook_text_km;
    setbitmap(sargs.keys.bitmap);

    scrolltool(&sargs);
    so_give(&store);

    restore_state(&state);
    ab_nesting_level--;

    if(addr)
      fs_give((void **)&addr);
    
    if(adrlist)
      mail_free_address(&adrlist);

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

    if(abe.fullname)
      fs_give((void **)&abe.fullname);

    if(abe.tag == List && abe.addr.list)
      free_list_array(&(abe.addr.list));

    ps_global->redrawer = redraw;

    return(0);
}


/*
 * Callback from TakeAddr editing screen to see message that was being
 * viewed.  Call scrolltool to do the work.
 */
char *
view_message_for_pico(char **error)
{
    STORE_S     *store;
    gf_io_t      pc;
    void       (*redraw)(void) = ps_global->redrawer;
    SourceType   src = CharStar;
    SCROLL_S	 sargs;

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

    ps_global->redrawer = NULL;
    fix_windsize(ps_global);

#ifdef DOS
    src = TmpFileStar;
#endif

    if(!(store = so_get(src, NULL, EDIT_ACCESS))){
	q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error allocating space."));
	return(NULL);
    }

    gf_set_so_writec(&pc, store);

    format_message(msgno_for_pico_callback, env_for_pico_callback,
		   body_for_pico_callback, NULL, FM_NEW_MESS | FM_DISPLAY, pc);

    gf_clear_so_writec(store);

    memset(&sargs, 0, sizeof(SCROLL_S));
    sargs.text.text = so_text(store);
    sargs.text.src  = src;
    sargs.text.desc = _("expanded entry");
    /* TRANSLATORS: this is a screen title */
    sargs.bar.title = _("MESSAGE TEXT");
    sargs.bar.style = TextPercent;
    sargs.keys.menu = &abook_text_km;
    setbitmap(sargs.keys.bitmap);

    scrolltool(&sargs);

    so_give(&store);

    ps_global->redrawer = redraw;

    return(NULL);
}


/*
prompt::name::help::prwid::maxlen::realaddr::
builder::affected_entry::next_affected::selector::key_label::fileedit::nickcmpl
display_it::break_on_comma::is_attach::rich_header::only_file_chars::
single_space::sticky::dirty::start_here::blank::KS_ODATAVAR
*/
static struct headerentry headents_for_edit[]={
  {"Nickname  : ",  N_("Nickname"),  h_composer_abook_nick, 12, 0, NULL,
   /* TRANSLATORS: To AddrBk is a command that takes the user to
      the address book screen to select an entry from there. */
   verify_nick,   NULL, NULL, addr_book_nick_for_edit, N_("To AddrBk"), NULL, abook_nickname_complete,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Fullname  : ",  N_("Fullname"),  h_composer_abook_full, 12, 0, NULL,
   NULL,          NULL, NULL, view_message_for_pico,   N_("To Message"), NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  /* TRANSLATORS: a File Copy is a copy of a sent message saved in a regular
     file on the computer's disk */
  {"Fcc       : ",  N_("FileCopy"),  h_composer_abook_fcc, 12, 0, NULL,
   /* TRANSLATORS: To Folders */
   NULL,          NULL, NULL, folders_for_fcc,         N_("To Fldrs"), NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Comment   : ",  N_("Comment"),   h_composer_abook_comment, 12, 0, NULL,
   NULL,          NULL, NULL, view_message_for_pico,   N_("To Message"), NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Addresses : ",  N_("Addresses"), h_composer_abook_addrs, 12, 0, NULL,
   verify_addr,   NULL, NULL, addr_book_change_list,   N_("To AddrBk"), NULL, abook_nickname_complete,
   1, 1, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {NULL, NULL, NO_HELP, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
};
#define NNN_NICK    0
#define NNN_FULL    1
#define NNN_FCC     2
#define NNN_COMMENT 3
#define NNN_ADDR    4
#define NNN_END     5

static char        *nick_saved_for_pico_check;
static AdrBk       *abook_saved_for_pico_check;

/*
 * Args: abook  -- Address book handle
 *       abe    -- AdrBk_Entry of old entry to work on.  If NULL, this will
 *                 be a new entry.  This has to be a pointer to a copy of
 *                 an abe that won't go away until we finish this function.
 *                 In other words, don't pass in a pointer to an abe in
 *                 the cache, copy it first.  The tag on this abe is only
 *                 used to decide whether to read abe->addr.list or
 *                 abe->addr.addr, not to determine what the final result
 *                 will be.  That's determined solely by how many addresses
 *                 there are after the user edits.
 *       entry  -- The entry number of the old entry that we will be changing.
 *      old_tag -- If we're changing an old entry, then this is the tag of
 *                 that old entry.
 *     readonly -- Call pico with readonly flag
 *       warped -- We warped to a new part of the addrbook
 *                 (We also overload warped in a couple places and use it's
 *                  being set as an indicator of whether we are Taking or
 *                  not.  It will be NULL if we are Taking.)
 */
void
edit_entry(AdrBk *abook, AdrBk_Entry *abe, a_c_arg_t entry, Tag old_tag, int readonly, int *warped, char *cmd)
{
    AdrBk_Entry local_abe;
    struct headerentry *he;
    PICO pbf;
    STORE_S *msgso;
    adrbk_cntr_t old_entry_num, new_entry_num = NO_NEXT;
    int rc = 0, resort_happened = 0, list_changed = 0, which_addrbook;
    int editor_result, i = 0, add, n_end, ldap = 0;
    char *nick, *full, *fcc, *comment, *fname, *pp;
    char **orig_addrarray = NULL;
    char **new_addrarray = NULL;
    char *comma_sep_addr = NULL;
    char **p, **q;
    Tag new_tag;
    char titlebar[40];
    char nickpmt[100], fullpmt[100], fccpmt[100], cmtpmt[100], addrpmt[100]; 
    int  abook_indent;
    long length;
    SAVE_STATE_S   state;  /* For saving state of addrbooks temporarily */

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

    old_entry_num = (adrbk_cntr_t) entry;
    save_state(&state);
    abook_saved_for_pico_check = abook;

    add = (abe == NULL);  /* doing add or change? */
    if(add){
	local_abe.nickname  = "";
	local_abe.fullname  = "";
	local_abe.fcc       = "";
	local_abe.extra     = "";
	local_abe.addr.addr = "";
	local_abe.tag       = NotSet;
	abe = &local_abe;
	old_entry_num = NO_NEXT;
    }

    new_tag = abe->tag;

#ifdef	ENABLE_LDAP
    expander_address = NULL;
    if(abe->tag == Single &&
       abe->addr.addr &&
       !strncmp(abe->addr.addr,QRUN_LDAP,LEN_QRL)){
	ldap = 1;
	expander_address = cpystr(abe->addr.addr);
	removing_double_quotes(expander_address);
    }
    else if(abe->tag == List &&
	    abe->addr.list &&
	    abe->addr.list[0] &&
	    !abe->addr.list[1] &&
            !strncmp(abe->addr.list[0],QRUN_LDAP,LEN_QRL)){
	ldap = 2;
	expander_address = cpystr(abe->addr.list[0]);
	removing_double_quotes(expander_address);
    }
#endif

    standard_picobuf_setup(&pbf);
    pbf.exittest      = pico_sendexit_for_adrbk;
    pbf.canceltest    = warped ? pico_cancel_for_adrbk_edit
				: pico_cancel_for_adrbk_take;
    pbf.expander      = expand_addrs_for_pico;
    pbf.ctrlr_label   = _("RichView");
    /* xgettext: c-format */
    if(readonly)
      /* TRANSLATORS: screen titles */
      snprintf(titlebar, sizeof(titlebar), _("ADDRESS BOOK (View)"));
    else
      snprintf(titlebar, sizeof(titlebar), _("ADDRESS BOOK (%c%s)"),
	       islower((unsigned char)(*cmd))
				    ? toupper((unsigned char)*cmd)
				    : *cmd, (cmd+1));

    pbf.pine_anchor   = set_titlebar(titlebar,
				      ps_global->mail_stream,
				      ps_global->context_current,
				      ps_global->cur_folder,ps_global->msgmap, 
				      0, FolderName, 0, 0, NULL);
    pbf.pine_flags   |= P_NOBODY;
    if(readonly)
      pbf.pine_flags |= P_VIEW;

    /* An informational message */
    if((msgso = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
	pbf.msgtext = (void *)so_text(msgso);
	/*
	 * It's nice if we can make it so these lines make sense even if
	 * they don't all make it on the screen, because the user can't
	 * scroll down to see them.  So just make each line a whole sentence
	 * that doesn't need the others below it to make sense.
	 */
	if(add){
	    so_puts(msgso,
/*
 * TRANSLATORS: The following lines go together to form a screen of
 * explanation about how to edit an address book entry.
 */
_("\n Fill in the fields. It is ok to leave fields blank."));
	    so_puts(msgso,
_("\n To form a list, just enter multiple comma-separated addresses."));
	}
	else{
	    so_puts(msgso,
/* TRANSLATORS: Same here, but a different version of the screen. */
_("\n Change any of the fields. It is ok to leave fields blank."));
	    if(ldap)
	      so_puts(msgso,
_("\n Since this entry does a directory lookup you may not edit the address field."));
	    else
	      so_puts(msgso,
_("\n Additional comma-separated addresses may be entered in the address field."));
	}

	so_puts(msgso,
_("\n Press \"^X\" to save the entry, \"^C\" to cancel, \"^G\" for help."));
	so_puts(msgso,
_("\n If you want to use quotation marks inside the Fullname field, it is best"));
	so_puts(msgso,
_("\n to use single quotation marks; for example: George 'Husky' Washington."));
    }

    he = (struct headerentry *) fs_get((NNN_END+1) * sizeof(struct headerentry));
    memset((void *)he, 0, (NNN_END+1) * sizeof(struct headerentry));
    pbf.headents      = he;

    abook_indent = utf8_width(_("Nickname")) + 2;

    /* make a copy of each field */
    nick = cpystr(abe->nickname ? abe->nickname : "");
    removing_leading_and_trailing_white_space(nick);
    nick_saved_for_pico_check = cpystr(nick);
    he[NNN_NICK]          = headents_for_edit[NNN_NICK];
    he[NNN_NICK].realaddr = &nick;
    utf8_snprintf(nickpmt, sizeof(nickpmt), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
    he[NNN_NICK].prompt   = nickpmt;
    he[NNN_NICK].prwid    = abook_indent+2;
    if(F_OFF(F_ENABLE_TAB_COMPLETE,ps_global))
      he[NNN_NICK].nickcmpl = NULL;

    full = cpystr(abe->fullname ? abe->fullname : "");
    removing_leading_and_trailing_white_space(full);
    he[NNN_FULL]          = headents_for_edit[NNN_FULL];
    he[NNN_FULL].realaddr = &full;
    utf8_snprintf(fullpmt, sizeof(fullpmt), "%-*.*w: ", abook_indent, abook_indent, _("Fullname"));
    he[NNN_FULL].prompt   = fullpmt;
    he[NNN_FULL].prwid    = abook_indent+2;

    fcc = cpystr(abe->fcc ? abe->fcc : "");
    removing_leading_and_trailing_white_space(fcc);
    he[NNN_FCC]          = headents_for_edit[NNN_FCC];
    he[NNN_FCC].realaddr = &fcc;
    utf8_snprintf(fccpmt, sizeof(fccpmt), "%-*.*w: ", abook_indent, abook_indent, _("Fcc"));
    he[NNN_FCC].prompt   = fccpmt;
    he[NNN_FCC].prwid    = abook_indent+2;

    comment = cpystr(abe->extra ? abe->extra : "");
    removing_leading_and_trailing_white_space(comment);
    he[NNN_COMMENT]          = headents_for_edit[NNN_COMMENT];
    he[NNN_COMMENT].realaddr = &comment;
    utf8_snprintf(cmtpmt, sizeof(cmtpmt), "%-*.*w: ", abook_indent, abook_indent, _("Comment"));
    he[NNN_COMMENT].prompt   = cmtpmt;
    he[NNN_COMMENT].prwid    = abook_indent+2;

    n_end = NNN_END;
    if(ldap)
      n_end--;

    if(!ldap){
	he[NNN_ADDR]          = headents_for_edit[NNN_ADDR];
	he[NNN_ADDR].realaddr = &comma_sep_addr;
	utf8_snprintf(addrpmt, sizeof(addrpmt), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
	he[NNN_ADDR].prompt   = addrpmt;
	he[NNN_ADDR].prwid    = abook_indent+2;
	if(F_OFF(F_ENABLE_TAB_COMPLETE,ps_global))
	  he[NNN_NICK].nickcmpl = NULL;

	if(abe->tag == Single){
	    if(abe->addr.addr){
		orig_addrarray = (char **) fs_get(2 * sizeof(char *));
		orig_addrarray[0] = cpystr(abe->addr.addr);
		orig_addrarray[1] = NULL;
	    }
	}
	else if(abe->tag == List){
	    if(listmem_count_from_abe(abe) > 0){
		orig_addrarray = (char **) fs_get(
				      (size_t)(listmem_count_from_abe(abe) + 1)
					* sizeof(char *));
		for(q = orig_addrarray, p = abe->addr.list; p && *p; p++, q++)
		  *q = cpystr(*p);
		
		*q = NULL;
	    }
	}

	/* figure out how large a string we need to allocate */
	length = 0L;
	for(p = orig_addrarray; p && *p; p++)
	  length += (strlen(*p) + 2);
	
	if(length)
	  length -= 2L;

	pp = comma_sep_addr = (char *) fs_get((size_t)(length+1L) * sizeof(char));
	*pp = '\0';
	for(p = orig_addrarray; p && *p; p++){
	    sstrncpy(&pp, *p, length-(pp-comma_sep_addr));
	    if(*(p+1))
	      sstrncpy(&pp, ", ", length-(pp-comma_sep_addr));
	}

	comma_sep_addr[length] = '\0';

	if(verify_addr(comma_sep_addr, NULL, NULL, NULL, NULL) < 0)
	  he[NNN_ADDR].start_here = 1;
    }

    he[n_end] = headents_for_edit[NNN_END];
    for(i = 0; i < n_end; i++){
	/* no callbacks in some cases */
	if(readonly || ((i == NNN_FULL || i == NNN_COMMENT) && !env_for_pico_callback)){
	    he[i].selector  = NULL;
	    he[i].key_label = NULL;
	}

	/* no builders for readonly */
	if(readonly)
	  he[i].builder = NULL;
    }

    /* pass to pico and let user change them */
    editor_result = pico(&pbf);
    ps_global->mangled_screen = 1;
    standard_picobuf_teardown(&pbf);

    if(editor_result & COMP_GOTHUP)
      hup_signal();
    else{
	fix_windsize(ps_global);
	init_signals();
    }

    if(editor_result & COMP_CANCEL){
	if(!readonly)
	  /* TRANSLATOR: Something like
	     Address book save cancelled */
	  q_status_message1(SM_INFO, 0, 2, _("Address book %s cancelled"), cmd);
    }
    else if(editor_result & COMP_EXIT){
	if(pico_usingcolor())
	   clear_index_cache(ps_global->mail_stream, 0);
	removing_leading_and_trailing_white_space(nick);
	removing_leading_and_trailing_white_space(full);
	removing_leading_and_trailing_white_space(fcc);
	removing_leading_and_trailing_white_space(comment);
	removing_leading_and_trailing_white_space(comma_sep_addr);

	/* not needed if pico is returning UTF-8 */
	convert_possibly_encoded_str_to_utf8(&nick);
	convert_possibly_encoded_str_to_utf8(&full);
	convert_possibly_encoded_str_to_utf8(&fcc);
	convert_possibly_encoded_str_to_utf8(&comment);
	convert_possibly_encoded_str_to_utf8(&comma_sep_addr);

	/* don't allow adding null entry */
	if(add && !*nick && !*full && !*fcc && !*comment && !*comma_sep_addr)
	  goto outtahere;

	/*
	 * comma_sep_addr is now the string which has been edited
	 */
	if(comma_sep_addr)
	  new_addrarray = parse_addrlist(comma_sep_addr);

	if(!ldap && (!new_addrarray || !new_addrarray[0]))
	  q_status_message(SM_ORDER, 3, 5, _("Warning: entry has no addresses"));

	if(!new_addrarray || !new_addrarray[0] || !new_addrarray[1])
	  new_tag = Single;  /* one or zero addresses means its a Single */
	else
	  new_tag = List;    /* more than one addresses means its a List */

	if(new_tag == List && old_tag == List){
	    /*
	     * If Taking, make sure we write it even if user didn't edit
	     * it any further.
	     */
	    if(!warped)
	      list_changed++;
	    else if(he[NNN_ADDR].dirty)
	      for(q = orig_addrarray, p = new_addrarray; p && *p && q && *q; p++, q++)
	        if(strcmp(*p, *q) != 0){
		    list_changed++;
		    break;
	        }
	    
	    if(!list_changed && he[NNN_ADDR].dirty
	      && ((!(p && *p) && (q && *q)) || ((p && *p) && !(q && *q))))
	      list_changed++;

	    if(list_changed){
		/*
		 * need to delete old list members and add new members below
		 */
		rc = adrbk_listdel_all(abook, (a_c_arg_t) old_entry_num);
	    }
	    else{
		/* don't need new_addrarray */
		free_list_array(&new_addrarray);
	    }

	    if(comma_sep_addr)
	      fs_give((void **) &comma_sep_addr);
	}
	else if((new_tag == List && old_tag == Single)
	       || (new_tag == Single && old_tag == List)){
	    /* delete old entry */
	    rc = adrbk_delete(abook, (a_c_arg_t) old_entry_num, 0, 0, 0, 0);
	    old_entry_num = NO_NEXT;
	    if(comma_sep_addr && new_tag == List)
	      fs_give((void **) &comma_sep_addr);
	}

	/*
	 * This will be an edit in the cases where the tag didn't change
	 * and an add in the cases where it did.
	 */
	if(rc == 0)
	  rc = adrbk_add(abook,
		       (a_c_arg_t)old_entry_num,
		       nick,
		       he[NNN_FULL].dirty ? full : abe->fullname,
		       new_tag == Single ? (ldap == 1 ? abe->addr.addr :
					      ldap == 2 ? abe->addr.list[0] :
					        comma_sep_addr)
					 : NULL,
		       fcc,
		       he[NNN_COMMENT].dirty ? comment : abe->extra,
		       new_tag,
		       &new_entry_num,
		       &resort_happened,
		       1,
		       0,
		       (new_tag != List || !new_addrarray));
    }
    
    if(rc == 0 && new_tag == List && new_addrarray)
      rc = adrbk_nlistadd(abook, (a_c_arg_t) new_entry_num, &new_entry_num,
			  &resort_happened, new_addrarray, 1, 0, 1);

    restore_state(&state);

    if(rc == -2 || rc == -3){
	q_status_message1(SM_ORDER | SM_DING, 3, 4,
			_("Error updating address book: %s"),
			rc == -2 ? error_description(errno) : "Alpine bug");
    }
    else if(rc == 0
       && strucmp(nick, nick_saved_for_pico_check) != 0
       && (editor_result & COMP_EXIT)){
	int added_to;

	for(added_to = 0; added_to < as.n_addrbk; added_to++)
	  if(abook_saved_for_pico_check == as.adrbks[added_to].address_book)
	    break;
	
	if(added_to >= as.n_addrbk)
	  added_to = -1;

	fname = addr_lookup(nick, &which_addrbook, added_to);
	if(fname){
	    q_status_message4(SM_ORDER, 5, 9,
	       /* TRANSLATORS: The first %s is the nickname the user is
	          trying to use that exists in another address book.
		  The second %s is the name of the other address book.
		  The third %s is " as " because it will say something
		  like also exists in <name> as <description>. */
	       _("Warning! Nickname %s also exists in \"%s\"%s%s"),
		nick, as.adrbks[which_addrbook].abnick,
		(fname && *fname) ? _(" as ") : "",
		(fname && *fname) ? fname : "");
	    fs_give((void **)&fname);
	}
    }

    if(resort_happened || list_changed){
	DL_CACHE_S dlc_restart;

	dlc_restart.adrbk_num = as.cur;
	dlc_restart.dlcelnum  = new_entry_num;
	switch(new_tag){
	  case Single:
	    dlc_restart.type = DlcSimple;
	    break;
	  
	  case List:
	    dlc_restart.type = DlcListHead;
	    break;

	  default:
	    break;
	}

	warp_to_dlc(&dlc_restart, 0L);
	if(warped)
	  *warped = 1;
    }

outtahere:
    if(he)
      free_headents(&he);

    if(msgso)
      so_give(&msgso);

    if(nick)
      fs_give((void **)&nick);
    if(full)
      fs_give((void **)&full);
    if(fcc)
      fs_give((void **)&fcc);
    if(comment)
      fs_give((void **)&comment);

    if(comma_sep_addr)
      fs_give((void **)&comma_sep_addr);
    if(nick_saved_for_pico_check)
      fs_give((void **)&nick_saved_for_pico_check);

    free_list_array(&orig_addrarray);
    free_list_array(&new_addrarray);
#ifdef	ENABLE_LDAP
    if(expander_address)
      fs_give((void **) &expander_address);
#endif
}


/*ARGSUSED*/
int
verify_nick(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
{
    char *tmp;

    dprint((7, "- verify_nick - (%s)\n", given ? given : "nul"));

    tmp = cpystr(given);
    removing_leading_and_trailing_white_space(tmp);

    if(nickname_check(tmp, error)){
	fs_give((void **)&tmp);
	if(mangled){
	    if(ps_global->mangled_screen)
	      *mangled |= BUILDER_SCREEN_MANGLED;
	    else if(ps_global->mangled_footer)
	      *mangled |= BUILDER_FOOTER_MANGLED;
	}

	return -2;
    }

    if(ps_global->remote_abook_validity > 0 &&
       adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0) && mangled)
      *mangled |= BUILDER_SCREEN_MANGLED;

    ab_nesting_level++;
    if(strucmp(tmp, nick_saved_for_pico_check) != 0
       && adrbk_lookup_by_nick(abook_saved_for_pico_check,
			       tmp, (adrbk_cntr_t *)NULL)){
	if(error){
	    char buf[MAX_NICKNAME + 80];

	    /* TRANSLATORS: The %s is the nickname of an entry that is already
	       in the address book */
	    snprintf(buf, sizeof(buf), _("\"%s\" already in address book."), tmp);
	    buf[sizeof(buf)-1] = '\0';
	    *error = cpystr(buf);
	}
	
	ab_nesting_level--;
	fs_give((void **)&tmp);
	if(mangled){
	    if(ps_global->mangled_screen)
	      *mangled |= BUILDER_SCREEN_MANGLED;
	    else if(ps_global->mangled_footer)
	      *mangled |= BUILDER_FOOTER_MANGLED;
	}
	
	return -2;
    }

    ab_nesting_level--;
    if(expanded)
      *expanded = tmp;
    else
      fs_give((void **)&tmp);

    /* This is so pico will erase any old message */
    if(error)
      *error = cpystr("");

    if(mangled){
	if(ps_global->mangled_screen)
	  *mangled |= BUILDER_SCREEN_MANGLED;
	else if(ps_global->mangled_footer)
	  *mangled |= BUILDER_FOOTER_MANGLED;
    }

    return 0;
}


/*
 * Args: to      -- the passed in line to parse
 *       full_to -- Address of a pointer to return the full address in.
 *		    This will be allocated here and freed by the caller.
 *                  However, this function is just going to copy "to".
 *                  (special case for the route-addr-hack in build_address_int)
 *                  We're just looking for the error messages.
 *       error   -- Address of a pointer to return an error message in.
 *		    This will be allocated here and freed by the caller.
 *       fcc     -- This should be passed in NULL.
 *                  This builder doesn't support affected_entry's.
 *
 * Result:  0 is returned if address was OK, 
 *         -2 if address wasn't OK.
 *
 * Side effect: Can flush addrbook entry cache entries so they need to be
 * re-fetched afterwords.
 */
int
verify_addr(char *to, char **full_to, char **error, BUILDER_ARG *fcc, int *mangled)
{
    register char *p;
    int            ret_val;
    BuildTo        bldto;
    jmp_buf        save_jmp_buf;
    int           *save_nesting_level;

    dprint((7, "- verify_addr - (%s)\n", to ? to : "nul"));

    /* check to see if to string is empty to avoid work */
    for(p = to; p && *p && isspace((unsigned char)(*p)); p++)
      ;/* do nothing */

    if(!p || !*p){
	if(full_to)
	  *full_to = cpystr(to ? to : "");  /* because pico does a strcmp() */

	return 0;
    }

    if(full_to != NULL)
      *full_to = (char *)NULL;

    if(error != NULL)
      *error = (char *)NULL;
    
    /*
     * If we end up jumping back here because somebody else changed one of
     * our addrbooks out from underneath us, we may well leak some memory.
     * That's probably ok since this will be very rare.
     */
    memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
    save_nesting_level = cpyint(ab_nesting_level);
    if(setjmp(addrbook_changed_unexpectedly)){
	if(full_to && *full_to)
	  fs_give((void **)full_to);

	/* TRANSLATORS: This is sort of an error, something unexpected has
	   happened and alpine is re-initializing the address book. */
	q_status_message(SM_ORDER, 3, 5, _("Resetting address book..."));
	dprint((1,
	    "RESETTING address book... verify_addr(%s)!\n", to ? to : "?"));
	addrbook_reset();
	ab_nesting_level = *save_nesting_level;
    }

    bldto.type    = Str;
    bldto.arg.str = to;

    if(ps_global->remote_abook_validity > 0 &&
       adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0) && mangled)
      *mangled |= BUILDER_SCREEN_MANGLED;

    ab_nesting_level++;

    ret_val = build_address_internal(bldto, full_to, error, NULL, NULL, NULL,
				     save_and_restore, 1, mangled);

    ab_nesting_level--;
    if(save_nesting_level)
      fs_give((void **)&save_nesting_level);

    if(full_to && *full_to && ret_val >= 0)
      removing_leading_and_trailing_white_space(*full_to);

    /* This is so pico will erase the old message */
    if(error != NULL && *error == NULL)
      *error = cpystr("");

    if(ret_val < 0)
      ret_val = -2;  /* cause pico to stay on same header line */

    memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
    return(ret_val);
}


/*
 *  Call back for pico to prompt the user for exit confirmation
 *
 * Returns: either NULL if the user accepts exit, or string containing
 *	 reason why the user declined.
 */      
int
pico_sendexit_for_adrbk(struct headerentry *he, void (*redraw_pico)(void),
			int allow_flowed, char **result)
{
    char *rstr = NULL;
    void (*redraw)(void) = ps_global->redrawer;

    ps_global->redrawer = redraw_pico;
    fix_windsize(ps_global);
    
    /* TRANSLATORS: A question */
    switch(want_to(_("Exit and save changes "), 'y', 0, NO_HELP, WT_NORM)){
      case 'y':
	break;

      case 'n':
	rstr = _("Use ^C to abandon changes you've made");
	break;
    }

    if(result)
      *result = rstr;

    ps_global->redrawer = redraw;
    return((rstr == NULL) ? 0 : 1);
}


/*
 *  Call back for pico to prompt the user for exit confirmation
 *
 * Returns: either NULL if the user accepts exit, or string containing
 *	 reason why the user declined.
 */      
char *
pico_cancelexit_for_adrbk(char *word, void (*redraw_pico)(void))
{
    char prompt[90];
    char *rstr = NULL;
    void (*redraw)(void) = ps_global->redrawer;

    /* TRANSLATORS: A question. The %s is a noun describing what is being cancelled. */
    snprintf(prompt, sizeof(prompt), _("Cancel %s (answering \"Yes\" will abandon any changes made) "), word);
    ps_global->redrawer = redraw_pico;
    fix_windsize(ps_global);
    
    switch(want_to(prompt, 'y', 'x', NO_HELP, WT_NORM)){
      case 'y':
	rstr = "";
	break;

      case 'n':
      case 'x':
	break;
    }

    ps_global->redrawer = redraw;
    return(rstr);
}


char *
pico_cancel_for_adrbk_take(void (*redraw_pico)(void))
{
    return(pico_cancelexit_for_adrbk(_("take"), redraw_pico));
}


char *
pico_cancel_for_adrbk_edit(void (*redraw_pico)(void))
{
    return(pico_cancelexit_for_adrbk(_("changes"), redraw_pico));
}


/*
prompt::name::help::prwid::maxlen::realaddr::
builder::affected_entry::next_affected::selector::key_label::fileedit::
display_it::break_on_comma::is_attach::rich_header::only_file_chars::
single_space::sticky::dirty::start_here::blank::KS_ODATAVAR
*/
static struct headerentry headents_for_add[]={
  {"Server Name : ",  N_("Server"),  h_composer_abook_add_server, 14, 0, NULL,
   verify_server_name, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Folder Name : ",  N_("Folder"),  h_composer_abook_add_folder, 14, 0, NULL,
   verify_folder_name, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"NickName    : ",  N_("Nickname"),  h_composer_abook_add_nick, 14, 0, NULL,
   verify_abook_nick,  NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {NULL, NULL, NO_HELP, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
};
#define NN_SERVER 0
#define NN_FOLDER 1
#define NN_NICK   2
#define NN_END    3

/*
 * Args:             global -- Add a global address book, not personal.
 *           add_after_this -- This is the addrbook number which should come
 *                             right before the new addrbook we're adding, if
 *                             that makes sense. If this is -1, append to end
 *                             of the list.
 *
 * Returns:  addrbook number of new addrbook, or
 *           -1, no addrbook added
 */
int
ab_add_abook(int global, int add_after_this)
{
    int ret;

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

    ret = ab_modify_abook_list(0, global, add_after_this, NULL, NULL, NULL);

    if(ret >= 0)
      q_status_message(SM_ORDER, 0, 3,
		       _("New address book added. Use \"$\" to adjust order"));
    
    return(ret);
}


/*
 * Args:          global -- Add a global address book, not personal.
 *             abook_num -- Abook num of the entry we are editing.
 *                  serv -- Default server.
 *                folder -- Default folder.
 *                  nick -- Default nickname.
 *
 * Returns:  abook_num if successful,
 *           -1, if not
 */
int
ab_edit_abook(int global, int abook_num, char *serv, char *folder, char *nick)
{
    dprint((2, "- ab_edit_abook -\n"));

    return(ab_modify_abook_list(1, global, abook_num, serv, folder, nick));
}

static int the_one_were_editing;


/*
 * Args:            edit -- Edit existing entry
 *                global -- Add a global address book, not personal.
 *             abook_num -- This is the addrbook number which should come
 *                            right before the new addrbook we're adding, if
 *                            that makes sense. If this is -1, append to end
 *                            of the list.
 *                            If we are editing instead of adding, this is
 *                            the abook number of the entry we are editing.
 *              def_serv -- Default server.
 *              def_fold -- Default folder.
 *              def_nick -- Default nickname.
 *
 * Returns:  addrbook number of new addrbook, or
 *           -1, no addrbook added
 */
int
ab_modify_abook_list(int edit, int global, int abook_num, char *def_serv, char *def_fold, char *def_nick)
{
    struct headerentry *he;
    PICO pbf;
    STORE_S *msgso;
    int editor_result, i, how_many_in_list, new_abook_num, num_in_list;
    int ret = 0;
    char *server, *folder, *nickname;
    char *new_item = NULL;
    EditWhich ew;
    AccessType remember_access_result;
    PerAddrBook *pab;
    char titlebar[100];
    char         **list, **new_list = NULL;
    char         tmp[1000+MAXFOLDER];
    int abook_indent;
    char servpmt[100], foldpmt[100], nickpmt[100];
    struct variable *vars = ps_global->vars;

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

    if(ps_global->readonly_pinerc){
	if(edit)
	  /* TRANSLATORS: Change was the name of the command the user was
	     trying to perform. It is what was cancelled. */
	  q_status_message(SM_ORDER, 0, 3, _("Change cancelled: config file not changeable"));
	else
	  /* TRANSLATORS: Add was the command that is being cancelled. */
	  q_status_message(SM_ORDER, 0, 3, _("Add cancelled: config file not changeable"));

	return -1;
    }

    ew = Main;

    if((global && vars[V_GLOB_ADDRBOOK].is_fixed) ||
       (!global && vars[V_ADDRESSBOOK].is_fixed)){
	if(global)
	  /* TRANSLATORS: Operation was cancelled because the system management
	     does not allow the changing of global address books */
	  q_status_message(SM_ORDER, 0, 3, _("Cancelled: Sys. Mgmt. does not allow changing global address books"));
	else
	  q_status_message(SM_ORDER, 0, 3, _("Cancelled: Sys. Mgmt. does not allow changing address books"));
	
	return -1;
    }

    init_ab_if_needed();

    if(edit){
	if((!global &&
		(abook_num < 0 || abook_num >= as.how_many_personals)) ||
	   (global &&
	        (abook_num < as.how_many_personals ||
		    abook_num >= as.n_addrbk))){
	    dprint((1, "Programming botch in ab_modify_abook_list: global=%d abook_num=%d n_addrbk=%d\n", global, abook_num, as.n_addrbk));
	    q_status_message(SM_ORDER, 0, 3, "Programming botch, bad abook_num");
	    return -1;
	}

	the_one_were_editing = abook_num;
    }
    else
      the_one_were_editing = -1;

    standard_picobuf_setup(&pbf);
    pbf.exittest      = pico_sendexit_for_adrbk;
    pbf.canceltest    = pico_cancel_for_adrbk_edit;
    if(edit)
      /* TRANSLATORS: screen title */
      strncpy(titlebar, _("CHANGE ADDRESS BOOK"), sizeof(titlebar));
    else
      /* TRANSLATORS: screen title */
      strncpy(titlebar, _("ADD ADDRESS BOOK"), sizeof(titlebar));

    titlebar[sizeof(titlebar)-1] = '\0';
    pbf.pine_anchor   = set_titlebar(titlebar,
				      ps_global->mail_stream,
				      ps_global->context_current,
				      ps_global->cur_folder,ps_global->msgmap, 
				      0, FolderName, 0, 0, NULL);
    pbf.pine_flags   |= P_NOBODY;

    /* An informational message */
    if((msgso = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
	int lines_avail;
char *t1 =
/* TRANSLATORS: The next few lines go together to explain how to add
   the address book entry the user is working on. */
_(" To add a local address book that will be accessed *only* by Alpine running\n on this machine, leave the server field blank.");
char *t2 =
_(" To add an address book that will be accessed by IMAP, fill in the\n server name.");
char *t3 =
_(" (NOTE: An address book cannot be accessed by IMAP from\n one Alpine and as a local file from another. It is either always accessed\n by IMAP or it is a local address book which is never accessed by IMAP.)");
char *t4 =
_(" In the Folder field, type the remote folder name or local file name.");
char *t5 =
_(" In the Nickname field, give the address book a nickname or leave it blank.");
char *t6 =
_(" To get help specific to an item, press ^G.");
char *t7 =
_(" To exit and save the configuration, press ^X. To cancel, press ^C.");

	pbf.msgtext = (void *)so_text(msgso);
	/*
	 * It's nice if we can make it so these lines make sense even if
	 * they don't all make it on the screen, because the user can't
	 * scroll down to see them.
	 *
	 * The 3 is the number of fields to be defined, the 1 is for a
	 * single blank line after the field definitions.
	 */
	lines_avail = ps_global->ttyo->screen_rows - HEADER_ROWS(ps_global) -
			FOOTER_ROWS(ps_global) - 3 - 1;

	if(lines_avail >= 15){		/* extra blank line */
	    so_puts(msgso, "\n");
	    lines_avail--;
	}

	if(lines_avail >= 2){
	    so_puts(msgso, t1);
	    lines_avail -= 2;
	}

	if(lines_avail >= 5){
	    so_puts(msgso, "\n\n");
	    so_puts(msgso, t2);
	    so_puts(msgso, t3);
	    lines_avail -= 5;
	}
	else if(lines_avail >= 3){
	    so_puts(msgso, "\n\n");
	    so_puts(msgso, t2);
	    lines_avail -= 3;
	}

	if(lines_avail >= 2){
	    so_puts(msgso, "\n\n");
	    so_puts(msgso, t4);
	    lines_avail -= 2;
	}

	if(lines_avail >= 2){
	    so_puts(msgso, "\n\n");
	    so_puts(msgso, t5);
	    lines_avail -= 2;
	}

	if(lines_avail >= 3){
	    so_puts(msgso, "\n\n");
	    so_puts(msgso, t6);
	    so_puts(msgso, "\n");
	    so_puts(msgso, t7);
	}
	else if(lines_avail >= 2){
	    so_puts(msgso, "\n\n");
	    so_puts(msgso, t7);
	}
    }

    he = (struct headerentry *)fs_get((NN_END+1) * sizeof(struct headerentry));
    memset((void *)he, 0, (NN_END+1) * sizeof(struct headerentry));
    pbf.headents      = he;

    abook_indent = utf8_width(_("Server Name")) + 2;

    /* make a copy of each field */
    server = cpystr(def_serv ? def_serv : "");
    he[NN_SERVER]          = headents_for_add[NN_SERVER];
    he[NN_SERVER].realaddr = &server;
    utf8_snprintf(servpmt, sizeof(servpmt), "%-*.*w: ", abook_indent, abook_indent, _("Server Name"));
    he[NN_SERVER].prompt   = servpmt;
    he[NN_SERVER].prwid    = abook_indent+2;

    folder = cpystr(def_fold ? def_fold : "");
    he[NN_FOLDER]          = headents_for_add[NN_FOLDER];
    he[NN_FOLDER].realaddr = &folder;
    utf8_snprintf(foldpmt, sizeof(foldpmt), "%-*.*w: ", abook_indent, abook_indent, _("Folder Name"));
    he[NN_FOLDER].prompt   = foldpmt;
    he[NN_FOLDER].prwid    = abook_indent+2;

    nickname = cpystr(def_nick ? def_nick : "");
    he[NN_NICK]          = headents_for_add[NN_NICK];
    he[NN_NICK].realaddr = &nickname;
    utf8_snprintf(nickpmt, sizeof(nickpmt), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
    he[NN_NICK].prompt   = nickpmt;
    he[NN_NICK].prwid    = abook_indent+2;

    he[NN_END] = headents_for_add[NN_END];

    /* pass to pico and let user change them */
    editor_result = pico(&pbf);
    standard_picobuf_teardown(&pbf);

    if(editor_result & COMP_GOTHUP){
	ret = -1;
	hup_signal();
    }
    else{
	fix_windsize(ps_global);
	init_signals();
    }

    if(editor_result & COMP_CANCEL){
	ret = -1;
	if(edit)
	  q_status_message(SM_ORDER, 0, 3, _("Address book change is cancelled"));
	else
	  q_status_message(SM_ORDER, 0, 3, _("Address book add is cancelled"));
    }
    else if(editor_result & COMP_EXIT){
	if(edit &&
	   !strcmp(server, def_serv ? def_serv : "") &&
	   !strcmp(folder, def_fold ? def_fold : "") &&
	   !strcmp(nickname, def_nick ? def_nick : "")){
	    ret = -1;
	    if(edit)
	      q_status_message(SM_ORDER, 0, 3, _("No change: Address book change is cancelled"));
	    else
	      q_status_message(SM_ORDER, 0, 3, _("No change: Address book add is cancelled"));
	}
	else{
	    if(global){
		list = VAR_GLOB_ADDRBOOK;
		how_many_in_list = as.n_addrbk - as.how_many_personals;
		if(edit)
		  new_abook_num = abook_num;
		else if(abook_num < 0)
		  new_abook_num  = as.n_addrbk;
		else
		  new_abook_num  = MAX(MIN(abook_num + 1, as.n_addrbk),
				       as.how_many_personals);

		num_in_list      = new_abook_num - as.how_many_personals;
	    }
	    else{
		list = VAR_ADDRESSBOOK;
		how_many_in_list = as.how_many_personals;
		new_abook_num = abook_num;
		if(edit)
		  new_abook_num = abook_num;
		else if(abook_num < 0)
		  new_abook_num  = as.how_many_personals;
		else
		  new_abook_num  = MIN(abook_num + 1, as.how_many_personals);

		num_in_list      = new_abook_num;
	    }

	    if(!edit)
	      how_many_in_list++;		/* for new abook */

	    removing_leading_and_trailing_white_space(server);
	    removing_leading_and_trailing_white_space(folder);
	    removing_leading_and_trailing_white_space(nickname);

	    /* convert nickname to UTF-8 */
	    if(nickname){
		char *conv;

		conv = convert_to_utf8(nickname, NULL, 0);
		if(conv){
		    fs_give((void **) &nickname);
		    nickname = conv;
		}
	    }

	    /* eliminate surrounding brackets */
	    if(server[0] == '{' && server[strlen(server)-1] == '}'){
		char *p;

		server[strlen(server)-1] = '\0';
		for(p = server; *p; p++)
		  *p = *(p+1);
	    }

	    snprintf(tmp, sizeof(tmp), "%s%s%s%.*s",
		    *server ? "{" : "",
		    *server ? server : "",
		    *server ? "}" : "",
		    MAXFOLDER, folder);
	    tmp[sizeof(tmp)-1] = '\0';
	    
	    new_item = put_pair(nickname, tmp);

	    if(!new_item || *new_item == '\0'){
		if(edit)
		  q_status_message(SM_ORDER, 0, 3, _("Address book change is cancelled"));
		else
		  q_status_message(SM_ORDER, 0, 3, _("Address book add is cancelled"));

		ret = -1;
		goto get_out;
	    }

	    /* allocate for new list */
	    new_list = (char **)fs_get((how_many_in_list + 1) * sizeof(char *));

	    /* copy old list up to where we will insert new entry */
	    for(i = 0; i < num_in_list; i++)
	      new_list[i] = cpystr(list[i]);
	    
	    /* insert the new entry */
	    new_list[i++] = cpystr(new_item);

	    /* copy rest of old list, skip current if editing */
	    for(; i < how_many_in_list; i++)
	      new_list[i] = cpystr(list[edit ? i : (i-1)]);

	    new_list[i] = NULL;

	    /* this frees old variable contents for us */
	    if(set_variable_list(global ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK,
				 new_list, TRUE, ew)){
		if(edit)
		  q_status_message(SM_ORDER, 0, 3, _("Change cancelled: couldn't save configuration file"));
		else
		  q_status_message(SM_ORDER, 0, 3, _("Add cancelled: couldn't save configuration file"));

		set_current_val(&vars[global ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK],
				TRUE, FALSE);
		ret = -1;
		goto get_out;
	    }

	    ret = new_abook_num;
	    set_current_val(&vars[global ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK],
			    TRUE, FALSE);

	    addrbook_reset();
	    init_ab_if_needed();

	    /*
	     * Test to see if this definition is going to work.
	     * Error messages are a good side effect.
	     */
	    pab = &as.adrbks[num_in_list];
	    init_abook(pab, NoDisplay);
	    remember_access_result = pab->access;
	    addrbook_reset();
	    init_ab_if_needed();
	    /* if we had trouble, give a clue to user (other than error msg) */
	    if(remember_access_result == NoAccess){
		pab = &as.adrbks[num_in_list];
		pab->access = remember_access_result;
	    }
	}
    }
    
get_out:

    if(he)
      free_headents(&he);

    if(new_list)
      free_list_array(&new_list);

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

    if(msgso)
      so_give(&msgso);

    if(server)
      fs_give((void **)&server);
    if(folder)
      fs_give((void **)&folder);
    if(nickname)
      fs_give((void **)&nickname);
    
    return(ret);
}


int
any_addrbooks_to_convert(struct pine *ps)
{
    PerAddrBook *pab;
    int          i, count = 0;

    init_ab_if_needed();

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(pab && !(pab->type & REMOTE_VIA_IMAP) && !(pab->type & GLOBAL))
	  count++;
    }

    return(count);
}


int
convert_addrbooks_to_remote(struct pine *ps, char *rem_folder_prefix, size_t len)
{
    PerAddrBook *pab;
    int          i, count = 0, ret = 0;

    init_ab_if_needed();

    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(pab && !(pab->type & REMOTE_VIA_IMAP) && !(pab->type & GLOBAL))
	  count++;
    }

    for(i = 0; ret != -1 && i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(pab && !(pab->type & REMOTE_VIA_IMAP) && !(pab->type & GLOBAL))
	  ret = convert_abook_to_remote(ps, pab, rem_folder_prefix, len, count);
    }

    return(ret);
}


/*
 * Returns -1 if cancelled, -2 on error, 0 otherwise.
 */
int
convert_abook_to_remote(struct pine *ps, PerAddrBook *pab, char *rem_folder_prefix, size_t len, int count)
{
#define DEF_ABOOK_NAME "remote_addrbook"
    char  local_file[MAILTMPLEN];
    char  rem_abook[MAILTMPLEN+3], prompt[MAILTMPLEN], old_nick[MAILTMPLEN];
    char *p = NULL, *err_msg = NULL, *q;
    char *serv = NULL, *nick = NULL, *file = NULL, *folder = NULL;
    int   ans, rc, offset, i, abook_num = -1, flags = OE_APPEND_CURRENT;
    HelpType help;

    snprintf(old_nick, sizeof(old_nick), "%s%s%s",
	    count > 1 ? " \"" : "",
	    count > 1 ? pab->abnick : "",
	    count > 1 ? "\"" : "");
    old_nick[sizeof(old_nick)-1] = '\0';

    snprintf(prompt, sizeof(prompt), _("Convert addressbook%s to a remote addrbook "), old_nick);
    prompt[sizeof(prompt)-1] = '\0';
    if((ans=want_to(prompt, 'y', 'x', h_convert_abook, WT_NORM)) != 'y')
      return(ans == 'n' ? 0 : -1);

    /* make sure the addrbook has been opened before, so that the file exists */
    if(pab->ostatus == Closed || pab->ostatus == HalfOpen){
	(void)init_addrbooks(NoDisplay, 0, 0, 0);
	(void)init_addrbooks(Closed, 0, 0, 0);
    }

    if(pab->filename){
	strncpy(local_file, pab->filename, sizeof(local_file)-1);
	local_file[sizeof(local_file)-1] = '\0';
#if	defined(DOS)
	p = strrindex(pab->filename, '\\');
#else
	p = strrindex(pab->filename, '/');
#endif
    }

    strncpy(rem_abook, rem_folder_prefix, sizeof(rem_abook)-3);
    if(!*rem_abook){
	/* TRANSLATORS: The user is defining an address book which will be
	   stored on another server. This is called a remote addrbook and
	   this is a question asking for the name of the server. */
	snprintf(prompt, sizeof(prompt), _("Name of server to contain remote addrbook : "));
	prompt[sizeof(prompt)-1] = '\0';
	help = NO_HELP;
	while(1){
	    rc = optionally_enter(rem_abook, -FOOTER_ROWS(ps), 0,
				  sizeof(rem_abook), prompt, NULL,
				  help, &flags);
	    removing_leading_and_trailing_white_space(rem_abook);
	    if(rc == 3){
		help = help == NO_HELP ? h_convert_pinerc_server : NO_HELP;
	    }
	    else if(rc == 1){
		cmd_cancelled(NULL);
		return(-1);
	    }
	    else if(rc == 0){
		if(*rem_abook){
		    /* add brackets */
		    offset = strlen(rem_abook);
		    for(i = offset; i >= 0; i--)
		      rem_abook[i+1] = rem_abook[i];
		    
		    rem_abook[0] = '{';
		    rem_abook[++offset] = '}';
		    rem_abook[++offset] = '\0';
		    break;
		}
	    }
	}
    }

    if(*rem_abook){
	if(p && count > 1)
	  strncat(rem_abook, p+1,
		  sizeof(rem_abook)-1-strlen(rem_abook));
	else
	  strncat(rem_abook, DEF_ABOOK_NAME,
		  sizeof(rem_abook)-1-strlen(rem_abook));
    }

    if(*rem_abook){
	file = cpystr(rem_abook);
	if(pab->abnick){
	    nick = (char *)fs_get((MAX(strlen(pab->abnick),strlen("Address Book"))+8) * sizeof(char));
	    snprintf(nick, sizeof(nick), "Remote %s",
	            (pab->abnick && !strcmp(pab->abnick, DF_ADDRESSBOOK))
			? "Address Book" : pab->abnick);
	    nick[sizeof(nick)-1] = '\0';
	}
	else
	  nick = cpystr("Remote Address Book");
	
	if(file && *file == '{'){
	    q = file + 1;
	    if((p = strindex(file, '}'))){
		*p = '\0';
		serv = q;
		folder = p+1;
	    }
	    else if(file)
	      fs_give((void **)&file);
	}
	else
	  folder = file;
    }
	
    q_status_message(SM_ORDER, 3, 5,
       _("You now have a chance to change the name of the remote addrbook..."));
    abook_num = ab_modify_abook_list(0, 0, -1, serv, folder, nick);

    /* extract folder name of new abook so we can copy to it */
    if(abook_num >= 0){
	char    **lval;
	EditWhich ew = Main;

	lval = LVAL(&ps->vars[V_ADDRESSBOOK], ew);
	get_pair(lval[abook_num], &nick, &file, 0, 0);
	if(nick)
	  fs_give((void **)&nick);
	
	if(file){
	    strncpy(rem_abook, file, sizeof(rem_abook)-1);
	    rem_abook[sizeof(rem_abook)-1] = '\0';
	    fs_give((void **)&file);
	}
    }

    /* copy the abook */
    if(abook_num >= 0 && copy_abook(local_file, rem_abook, &err_msg)){
	if(err_msg){
	    q_status_message(SM_ORDER | SM_DING, 7, 10, err_msg);
	    fs_give((void **)&err_msg);
	}

	return(-2);
    }
    else if(abook_num >= 0){			/* give user some info */
	STORE_S  *store;
	SCROLL_S  sargs;
	char     *beg, *end;

	/*
	 * Save the hostname in rem_folder_prefix so we can use it again
	 * for other conversions if needed.
	 */
	if((beg = rem_abook)
	   && (*beg == '{' || (*beg == '*' && *++beg == '{'))
	   && (end = strindex(rem_abook, '}'))){
	    rem_folder_prefix[0] = '{';
	    strncpy(rem_folder_prefix+1, beg+1, MIN(end-beg,len-2));
	    rem_folder_prefix[MIN(end-beg,len-2)] = '}';
	    rem_folder_prefix[MIN(end-beg+1,len-1)] = '\0';
	}

	if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
	    q_status_message(SM_ORDER | SM_DING, 7, 10,
			     _("Error allocating space for message."));
	    return(-2);
	}

	/* TRANSLATORS: Several lines in a row here that go together. */
	snprintf(prompt, sizeof(prompt), _("\nYour addressbook%s has been copied to the"), old_nick);
	prompt[sizeof(prompt)-1] = '\0';
	so_puts(store, prompt);
	so_puts(store, _("\nremote folder \""));
	so_puts(store, rem_abook);
	so_puts(store, "\".");
	so_puts(store, _("\nA definition for this remote address book has been added to your list"));
	so_puts(store, _("\nof address books. The definition for the address book it was copied"));
	so_puts(store, _("\nfrom is also still there. You may want to remove that after you"));
	so_puts(store, _("\nare confident that the new address book is complete and working."));
	so_puts(store, _("\nUse the Setup/AddressBooks command to do that.\n"));

	memset(&sargs, 0, sizeof(SCROLL_S));
	sargs.text.text  = so_text(store);
	sargs.text.src   = CharStar;
	sargs.text.desc  = _("Remote Address Book Information");
	/* TRANSLATORS: a screen title */
	sargs.bar.title  = _("ABOUT REMOTE ABOOK");
	sargs.help.text  = NO_HELP;
	sargs.help.title = NULL;

	scrolltool(&sargs);

	so_give(&store);	/* free resources associated with store */
	ps->mangled_screen = 1;
    }

    return(0);
}


int
any_sigs_to_convert(struct pine *ps)
{
    char       *sigfile, *litsig;
    long        rflags;
    PAT_STATE   pstate;
    PAT_S      *pat;
    PAT_LINE_S *patline;

    /* first check main signature file */
    sigfile = ps->VAR_SIGNATURE_FILE;
    litsig = ps->VAR_LITERAL_SIG;

    if(sigfile && *sigfile && !litsig && sigfile[strlen(sigfile)-1] != '|' &&
       !IS_REMOTE(sigfile))
      return(1);

    rflags = (ROLE_DO_ROLES | PAT_USE_MAIN);
    if(any_patterns(rflags, &pstate)){
	set_pathandle(rflags);
	for(patline = *cur_pat_h ?  (*cur_pat_h)->patlinehead : NULL;
	    patline; patline = patline->next){
	    for(pat = patline->first; pat; pat = pat->next){

		/*
		 * See detoken() for when a sig file is used with a role.
		 */
		sigfile = pat->action ? pat->action->sig : NULL;
		litsig = pat->action ? pat->action->litsig : NULL;

		if(sigfile && *sigfile && !litsig &&
		   sigfile[strlen(sigfile)-1] != '|' &&
		   !IS_REMOTE(sigfile))
		  return(1);
	    }
	}
    }

    return(0);
}


int
any_rule_files_to_warn_about(struct pine *ps)
{
    long        rflags;
    PAT_STATE   pstate;
    PAT_S      *pat;

    rflags = (ROLE_DO_ROLES | ROLE_DO_INCOLS | ROLE_DO_SCORES |
	      ROLE_DO_FILTER | ROLE_DO_OTHER | ROLE_DO_SRCH | PAT_USE_MAIN);
    if(any_patterns(rflags, &pstate)){
	for(pat = first_pattern(&pstate);
	    pat;
	    pat = next_pattern(&pstate)){
	    if(pat->patline && pat->patline->type == File)
	      break;
	}

	if(pat)
	  return(1);
    }

    return(0);
}


int
convert_sigs_to_literal(struct pine *ps, int interactive)
{
    EditWhich   ew = Main;
    char       *sigfile, *litsig, *cstring_version, *nick, *src = NULL;
    char        prompt[MAILTMPLEN];
    STORE_S    *store;
    SCROLL_S	sargs;
    long        rflags;
    int         ans;
    PAT_STATE   pstate;
    PAT_S      *pat;
    PAT_LINE_S *patline;

    /* first check main signature file */
    sigfile = ps->VAR_SIGNATURE_FILE;
    litsig = ps->VAR_LITERAL_SIG;

    if(sigfile && *sigfile && !litsig && sigfile[strlen(sigfile)-1] != '|' &&
       !IS_REMOTE(sigfile)){
	if(interactive){
	    snprintf(prompt,sizeof(prompt),
		    /* TRANSLATORS: A literal sig is a way to store a signature
		       in alpine. It isn't a very descriptive name. Instead of
		       storing it in its own file it is stored in the configuration
		       file. */
		    _("Convert signature file \"%s\" to a literal sig "),
		    sigfile);
	    prompt[sizeof(prompt)-1] = '\0';
	    ClearBody();
	    ps->mangled_body = 1;
	    if((ans=want_to(prompt, 'y', 'x', h_convert_sig, WT_NORM)) == 'x'){
		cmd_cancelled(NULL);
		return(-1);
	    }
	}
	else
	  ans = 'y';

	if(ans == 'y' && (src = get_signature_file(sigfile, 0, 0, 0)) != NULL){
	    cstring_version = string_to_cstring(src);
	    set_variable(V_LITERAL_SIG, cstring_version, 0, 0, ew);

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

	    fs_give((void **)&src);

	    if(interactive){
		if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
		    q_status_message(SM_ORDER | SM_DING, 7, 10,
				     _("Error allocating space for message."));
		    return(-1);
		}

		snprintf(prompt, sizeof(prompt),
    /* TRANSLATORS: following lines go together */
    _("\nYour signature file \"%s\" has been converted"), sigfile);
		prompt[sizeof(prompt)-1] = '\0';
		so_puts(store, prompt);
		so_puts(store,
    _("\nto a literal signature, which means it is contained in your"));
		so_puts(store,
    _("\nAlpine configuration instead of being in a file of its own."));
		so_puts(store,
    _("\nIf that configuration is copied to a remote folder then the"));
		so_puts(store,
    _("\nsignature will be available remotely also."));
		so_puts(store,
    _("\nChanges to the signature file itself will no longer have any"));
		so_puts(store,
    _("\neffect on Alpine but you may still edit the signature with the"));
		so_puts(store,
    _("\nSetup/Signature command.\n"));

		memset(&sargs, 0, sizeof(SCROLL_S));
		sargs.text.text  = so_text(store);
		sargs.text.src   = CharStar;
		sargs.text.desc  = _("Literal Signature Information");
		/* TRANSLATORS: screen title */
		sargs.bar.title  = _("ABOUT LITERAL SIG");
		sargs.help.text  = NO_HELP;
		sargs.help.title = NULL;

		scrolltool(&sargs);

		so_give(&store);
		ps->mangled_screen = 1;
	    }
	}
    }

    rflags = (ROLE_DO_ROLES | PAT_USE_MAIN);
    if(any_patterns(rflags, &pstate)){
	set_pathandle(rflags);
	for(patline = *cur_pat_h ?  (*cur_pat_h)->patlinehead : NULL;
	    patline; patline = patline->next){
	    for(pat = patline->first; pat; pat = pat->next){

		/*
		 * See detoken() for when a sig file is used with a role.
		 */
		sigfile = pat->action ? pat->action->sig : NULL;
		litsig = pat->action ? pat->action->litsig : NULL;
		nick = (pat->action && pat->action->nick && pat->action->nick[0]) ? pat->action->nick : NULL;

		if(sigfile && *sigfile && !litsig &&
		   sigfile[strlen(sigfile)-1] != '|' &&
		   !IS_REMOTE(sigfile)){
		    if(interactive){
			snprintf(prompt,sizeof(prompt),
		 /* TRANSLATORS: asking whether a signature file should be converted to what
		    we call a literal signature, which is one contained in the regular
		    configuration file. Think of the set of 4 %s arguments as a
		    single argument which is the name of the signature file. */
		 _("Convert signature file \"%s\"%s%s%s to a literal sig "),
				sigfile,
				nick ? " in role \"" : "",
				nick ? nick : "",
				nick ? "\"" : "");
			prompt[sizeof(prompt)-1] = '\0';
			ClearBody();
			ps->mangled_body = 1;
			if((ans=want_to(prompt, 'y', 'x',
					h_convert_sig, WT_NORM)) == 'x'){
			    cmd_cancelled(NULL);
			    return(-1);
			}
		    }
		    else
		      ans = 'y';

		    if(ans == 'y' &&
		       (src = get_signature_file(sigfile,0,0,0)) != NULL){

			cstring_version = string_to_cstring(src);

			if(pat->action->litsig)
			  fs_give((void **)&pat->action->litsig);

			pat->action->litsig = cstring_version;
			fs_give((void **)&src);

			set_pathandle(rflags);
			if(patline->type == Literal)
			  (*cur_pat_h)->dirtypinerc = 1;
			else
			  patline->dirty = 1;

			if(write_patterns(rflags) == 0){
			    if(interactive){
				/*
				 * Flush out current_vals of anything we've
				 * possibly changed.
				 */
				close_patterns(ROLE_DO_ROLES | PAT_USE_CURRENT);

				if(!(store=so_get(CharStar,NULL,EDIT_ACCESS))){
				    q_status_message(SM_ORDER | SM_DING, 7, 10,
					 _("Error allocating space for message."));
				    return(-1);
				}

				snprintf(prompt, sizeof(prompt),
		    /* TRANSLATORS: Keep the %s's together, they are sort of
		       the name of the file. */
		    _("Your signature file \"%s\"%s%s%s has been converted"),
					sigfile,
					nick ? " in role \"" : "",
					nick ? nick : "",
					nick ? "\"" : "");
				prompt[sizeof(prompt)-1] = '\0';
				so_puts(store, prompt);
				so_puts(store,
	    /* TRANSLATORS: several lines that go together */
	    _("\nto a literal signature, which means it is contained in your"));
				so_puts(store,
	    _("\nAlpine configuration instead of being in a file of its own."));
				so_puts(store,
	    _("\nIf that configuration is copied to a remote folder then the"));
				so_puts(store,
	    _("\nsignature will be available remotely also."));
				so_puts(store,
	    _("\nChanges to the signature file itself will no longer have any"));
				so_puts(store,
	    _("\neffect on Alpine. You may edit the signature with the"));
				so_puts(store,
	    _("\nSetup/Rules/Roles command.\n"));

				memset(&sargs, 0, sizeof(SCROLL_S));
				sargs.text.text  = so_text(store);
				sargs.text.src   = CharStar;
				sargs.text.desc  =
					    _("Literal Signature Information");
				/* TRANSLATORS: a screen title */
				sargs.bar.title  = _("ABOUT LITERAL SIG");
				sargs.help.text  = NO_HELP;
				sargs.help.title = NULL;

				scrolltool(&sargs);

				so_give(&store);
				ps->mangled_screen = 1;
			    }
			}
			else if(interactive){
			  q_status_message(SM_ORDER | SM_DING, 7, 10,
					   /* TRANSLATORS: config is an abbreviation for configuration */
					   _("Error writing rules config."));
			}
			else{
			    /* TRANSLATORS: sig is signature */
			    fprintf(stderr, _("Error converting role sig\n"));
			    return(-1);
			}
		    }
		}
	    }
	}
    }

    return(0);
}


void
warn_about_rule_files(struct pine *ps)
{
    STORE_S    *store;
    SCROLL_S	sargs;

    if(any_rule_files_to_warn_about(ps)){
	if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
	    q_status_message(SM_ORDER | SM_DING, 7, 10,
			     _("Error allocating space for message."));
	    return;
	}

	/* TRANSLATORS: several lines that go together */
	so_puts(store, _("\nSome of your Rules are contained in Rule files instead of being directly"));
	so_puts(store, _("\ncontained in your Alpine configuration file. To make those rules"));
	so_puts(store, _("\navailable remotely you will need to move them out of the files."));
	so_puts(store, _("\nThat can be done using the Shuffle command in the appropriate"));
	so_puts(store, _("\nSetup/Rules subcommands.\n"));

	memset(&sargs, 0, sizeof(SCROLL_S));
	sargs.text.text  = so_text(store);
	sargs.text.src   = CharStar;
	sargs.text.desc  = _("Rule Files Information");
	/* TRANSLATORS: a screen title */
	sargs.bar.title  = _("ABOUT RULE FILES");
	sargs.help.text  = NO_HELP;
	sargs.help.title = NULL;

	scrolltool(&sargs);

	so_give(&store);
	ps->mangled_screen = 1;
    }
}


void
convert_to_remote_config(struct pine *ps, int edit_exceptions)
{
    char       rem_pinerc_prefix[MAILTMPLEN];
    char      *beg, *end;
    CONTEXT_S *context;
    int        abooks, sigs;

    if(edit_exceptions){
	/* TRANSLATORS: The exceptions command (X) was typed but it doesn't make sense */
	q_status_message(SM_ORDER, 3, 5,
			 _("eXceptions does not make sense with this command"));
	return;
    }

    if(!ps->prc)
      alpine_panic("NULL prc in convert_to_remote_config");

    dprint((2, "convert_to_remote_config\n"));
    
    if(ps->prc->type == RemImap){	/* pinerc is already remote */
	char prompt[MAILTMPLEN];

	/*
	 * Check to see if there is anything at all to do. If there are
	 * address books to convert, sigfiles to convert, or rule files
	 * to comment on, we have something to do. Otherwise, just bail.
	 */
	abooks = any_addrbooks_to_convert(ps);
	sigs   = any_sigs_to_convert(ps);

	if(abooks || sigs){
	    if(abooks && sigs)
	      /* TRANSLATORS: AddressBooks is Address Books */
	      snprintf(prompt, sizeof(prompt), _("Config is already remote, convert AddressBooks and signature files "));
	    else if(abooks)
	      snprintf(prompt, sizeof(prompt), _("Config is already remote, convert AddressBooks "));
	    else
	      snprintf(prompt, sizeof(prompt), _("Config is already remote, convert signature files "));

	    prompt[sizeof(prompt)-1] = '\0';
	    if(want_to(prompt, 'y', 'x',
		       (abooks && sigs) ? h_convert_abooks_and_sigs :
			abooks           ? h_convert_abooks :
			 sigs             ? h_convert_sigs : NO_HELP,
		       WT_NORM) != 'y'){
		cmd_cancelled(NULL);
		return;
	    }
	}
    }

    /*
     * Figure out a good default for where to put the remote config.
     * If the default collection is remote we'll take the hostname and
     * and modifiers from there. If not, we'll try to get the hostname from
     * the inbox-path. In either case, we use the home directory on the
     * server, not the directory where the folder collection is (if different).
     * If we don't have a clue, we'll ask user.
     */
    if((context = default_save_context(ps->context_list)) != NULL &&
       IS_REMOTE(context_apply(rem_pinerc_prefix, context, "",
	         sizeof(rem_pinerc_prefix)))){
	/* just use the host from the default collection, not the whole path */
	if((end = strrindex(rem_pinerc_prefix, '}')) != NULL)
	  *(end + 1) = '\0';
    }
    else{
	/* use host from inbox path */
	rem_pinerc_prefix[0] = '\0';
	if((beg = ps->VAR_INBOX_PATH)
	   && (*beg == '{' || (*beg == '*' && *++beg == '{'))
	   && (end = strindex(ps->VAR_INBOX_PATH, '}'))){
	    rem_pinerc_prefix[0] = '{';
	    strncpy(rem_pinerc_prefix+1, beg+1,
		    MIN(end-beg, sizeof(rem_pinerc_prefix)-2));
	    rem_pinerc_prefix[MIN(end-beg, sizeof(rem_pinerc_prefix)-2)] = '}';
	    rem_pinerc_prefix[MIN(end-beg+1,sizeof(rem_pinerc_prefix)-1)]='\0';
	}
    }

    /* ask about converting addrbooks to remote abooks */
    if(ps->prc->type != RemImap || abooks)
      if(convert_addrbooks_to_remote(ps, rem_pinerc_prefix,
				     sizeof(rem_pinerc_prefix)) == -1){
	  cmd_cancelled(NULL);
	  return;
      }

    /* ask about converting sigfiles to literal sigs */
    if(ps->prc->type != RemImap || sigs)
      if(convert_sigs_to_literal(ps, 1) == -1){
	  cmd_cancelled(NULL);
	  return;
      }

    warn_about_rule_files(ps);

    /* finally, copy the config file */
    if(ps->prc->type == Loc)
      convert_pinerc_to_remote(ps, rem_pinerc_prefix);
    else if(!(abooks || sigs))
      q_status_message(SM_ORDER, 3, 5,
		       _("Cannot copy config file since it is already remote."));
}


void
convert_pinerc_to_remote(struct pine *ps, char *rem_pinerc_prefix)
{
#define DEF_FOLDER_NAME "remote_pinerc"
    char       prompt[MAILTMPLEN], rem_pinerc[MAILTMPLEN];
    char      *err_msg = NULL;
    int        i, rc, offset;
    HelpType   help;
    int        flags = OE_APPEND_CURRENT;

    ClearBody();
    ps->mangled_body = 1;
    strncpy(rem_pinerc, rem_pinerc_prefix, sizeof(rem_pinerc)-1);
    rem_pinerc[sizeof(rem_pinerc)-1] = '\0';

    if(*rem_pinerc == '\0'){
	snprintf(prompt, sizeof(prompt), _("Name of server to contain remote Alpine config : "));
	prompt[sizeof(prompt)-1] = '\0';
	help = NO_HELP;
	while(1){
	    rc = optionally_enter(rem_pinerc, -FOOTER_ROWS(ps), 0,
				  sizeof(rem_pinerc), prompt, NULL,
				  help, &flags);
	    removing_leading_and_trailing_white_space(rem_pinerc);
	    if(rc == 3){
		help = help == NO_HELP ? h_convert_pinerc_server : NO_HELP;
	    }
	    else if(rc == 1){
		cmd_cancelled(NULL);
		return;
	    }
	    else if(rc == 0){
		if(*rem_pinerc){
		    /* add brackets */
		    offset = strlen(rem_pinerc);
		    for(i = offset; i >= 0; i--)
		      if(i+1 < sizeof(rem_pinerc))
		        rem_pinerc[i+1] = rem_pinerc[i];
		    
		    rem_pinerc[0] = '{';
		    if(offset+2 < sizeof(rem_pinerc)){
			rem_pinerc[++offset] = '}';
			rem_pinerc[++offset] = '\0';
		    }

		    break;
		}
	    }
	}
    }

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

    /*
     * Add a default folder name.
     */
    if(*rem_pinerc){
	/*
	 * Add /user= to modify hostname so that user won't be asked who they
	 * are each time they login.
	 */
	if(!strstr(rem_pinerc, "/user=") && ps->VAR_USER_ID &&
	   ps->VAR_USER_ID[0]){
	    char *p;

	    p = rem_pinerc + strlen(rem_pinerc) - 1;
	    if(*p == '}')	/* this should be the case */
	      snprintf(p, sizeof(rem_pinerc)-(p-rem_pinerc), "/user=\"%s\"}", ps->VAR_USER_ID); 

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

	strncat(rem_pinerc, DEF_FOLDER_NAME,
		sizeof(rem_pinerc) - strlen(rem_pinerc) - 1);
	rem_pinerc[sizeof(rem_pinerc)-1] = '\0';
    }

    /* ask user about folder name for remote config */
    snprintf(prompt, sizeof(prompt), _("Folder to contain remote config : "));
    prompt[sizeof(prompt)-1] = '\0';
    help = NO_HELP;
    while(1){
        rc = optionally_enter(rem_pinerc, -FOOTER_ROWS(ps), 0, 
			      sizeof(rem_pinerc), prompt, NULL, help, &flags);
	removing_leading_and_trailing_white_space(rem_pinerc);
        if(rc == 0 && *rem_pinerc){
	    break;
	}

        if(rc == 3){
	    help = (help == NO_HELP) ? h_convert_pinerc_folder : NO_HELP;
	}
	else if(rc == 1 || rem_pinerc[0] == '\0'){
	    cmd_cancelled(NULL);
	    return;
	}
    }

#ifndef	_WINDOWS
    /*
     * If we are on a Unix system, writing to a remote config, we want the
     * remote config to work smoothly from a PC, too. If we don't have a
     * user-id on the PC then we will be asked for our password.
     * So add user-id to the pinerc before we copy it.
     */
    if(!ps->vars[V_USER_ID].main_user_val.p && ps->VAR_USER_ID)
      ps->vars[V_USER_ID].main_user_val.p = cpystr(ps->VAR_USER_ID);
    
    ps->vars[V_USER_ID].is_used = 1;	/* so it will write to pinerc */
    ps->prc->outstanding_pinerc_changes = 1;
#endif

    if(ps->prc->outstanding_pinerc_changes)
      write_pinerc(ps, Main, WRP_NONE);

#ifndef	_WINDOWS
    ps->vars[V_USER_ID].is_used = 0;
#endif

    /* copy the pinerc */
    if(copy_pinerc(ps->prc->name, rem_pinerc, &err_msg)){
	if(err_msg){
	    q_status_message(SM_ORDER | SM_DING, 7, 10, err_msg);
	    fs_give((void **)&err_msg);
	}
	
	return;
    }

    /* tell user about command line flags */
    if(ps->prc->type != RemImap){
	STORE_S *store;
	SCROLL_S sargs;

	if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
	    q_status_message(SM_ORDER | SM_DING, 7, 10,
			     _("Error allocating space for message."));
	    return;
	}

	/* TRANSLATORS: several lines that go together */
	so_puts(store, _("\nYou may want to save a copy of this information!"));
	so_puts(store, _("\n\nYour Alpine configuration data has been copied to"));
	so_puts(store, "\n\n   ");
	so_puts(store, rem_pinerc);
	so_puts(store, "\n");
	so_puts(store, _("\nTo use that remote configuration from this computer you will"));
	so_puts(store, _("\nhave to change the way you start Alpine by using the command line option"));
	so_puts(store, _("\n\"alpine -p <remote_folder>\". The command should probably be"));
#ifdef	_WINDOWS
	so_puts(store, "\n\n   ");
	so_puts(store, "alpine -p ");
	so_puts(store, rem_pinerc);
	so_puts(store, "\n");
	so_puts(store, _("\nWith PC-Alpine, you may want to create a shortcut which"));
	so_puts(store, _("\nhas the required arguments."));
#else
	so_puts(store, "\n\n   ");
	so_puts(store, "alpine -p \"");
	so_puts(store, rem_pinerc);
	so_puts(store, "\"\n");
	so_puts(store, _("\nThe quotes are there around the last argument to protect the special"));
	so_puts(store, _("\ncharacters in the folder name (like braces) from the command shell"));
	so_puts(store, _("\nyou use. If you are not running Alpine from a command shell which knows"));
	so_puts(store, _("\nabout quoting, it is possible you will have to remove those quotes"));
	so_puts(store, _("\nfrom the command. For example, if you also use PC-Alpine you will probably"));
	so_puts(store, _("\nwant to create a shortcut, and you would not need the quotes there."));
	so_puts(store, _("\nWithout the quotes, the command might look like"));
	so_puts(store, "\n\n   ");
	so_puts(store, "alpine -p ");
	so_puts(store, rem_pinerc);
	so_puts(store, "\n");
	so_puts(store, _("\nConsider creating an alias or shell script to execute this command to make"));
	so_puts(store, _("\nit more convenient."));
#endif
	so_puts(store, _("\n\nIf you want to use your new remote configuration for this session, quit"));
	so_puts(store, _("\nAlpine now and restart with the changed command line options mentioned above.\n"));

	memset(&sargs, 0, sizeof(SCROLL_S));
	sargs.text.text  = so_text(store);
	sargs.text.src   = CharStar;
	sargs.text.desc  = _("Remote Config Information");
	/* TRANSLATORS: a screen title */
	sargs.bar.title  = _("ABOUT REMOTE CONFIG");
	sargs.help.text  = NO_HELP;
	sargs.help.title = NULL;

	scrolltool(&sargs);

	so_give(&store);	/* free resources associated with store */
	ps->mangled_screen = 1;
    }
}


int
verify_folder_name(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
{
    char *tmp;

    tmp = cpystr(given ? given : "");
    removing_leading_and_trailing_white_space(tmp);

    if(expanded)
      *expanded = tmp;
    else
      fs_give((void **)&tmp);
    
    if(error)
      *error = cpystr("");

    return 0;
}


int
verify_server_name(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
{
    char *tmp;

    tmp = cpystr(given ? given : "");
    removing_leading_and_trailing_white_space(tmp);

    if(*tmp){
	/*
	 * could try to verify the hostname here
	 */
    }

    if(expanded)
      *expanded = tmp;
    else
      fs_give((void **)&tmp);
    
    if(error)
      *error = cpystr("");

    return 0;
}


int
verify_abook_nick(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
{
    int   i;
    char *tmp;

    tmp = cpystr(given ? given : "");
    removing_leading_and_trailing_white_space(tmp);

    if(strindex(tmp, '"')){
	fs_give((void **)&tmp);
	if(error)
	  /* TRANSLATORS: Double quote refers to the " character */
	  *error = cpystr(_("Double quote not allowed in nickname"));
	
	return -2;
    }

    for(i = 0; i < as.n_addrbk; i++)
      if(i != the_one_were_editing && !strcmp(tmp, as.adrbks[i].abnick))
	break;
    
    if(i < as.n_addrbk){
	fs_give((void **)&tmp);

	if(error)
	  *error = cpystr(_("Nickname is already being used"));
	
	return -2;
    }

    if(expanded)
      *expanded = tmp;
    else
      fs_give((void **)&tmp);
    
    if(error)
      *error = cpystr("");

    return 0;
}


/*
 * Delete an addressbook.
 *
 * Args: cur_line     -- The current line position (in global display list)
 *			 of cursor
 *       command_line -- The screen line on which to prompt
 *       err          -- Points to error message
 *
 * Returns -- 0, deleted addrbook
 *            -1, addrbook not deleted
 */
int
ab_del_abook(long int cur_line, int command_line, char **err)
{
    int          abook_num, varnum, delete_data = 0,
		 num_in_list, how_many_in_list, i, cnt, warn_about_revert = 0;
    char       **list, **new_list, **t, **lval;
    char         tmp[200];
    PerAddrBook *pab;
    struct variable *vars = ps_global->vars;
    EditWhich    ew;
    enum {NotSet,
	  Modify,
	  RevertToDefault,
	  OverRideDefault,
	  DontChange} modify_config;

    /* restrict address book config to normal config file */
    ew = Main;

    if(ps_global->readonly_pinerc){
	if(err)
	  *err = _("Delete cancelled: config file not changeable");
	
	return -1;
    }

    abook_num = adrbk_num_from_lineno(cur_line);

    pab = &as.adrbks[abook_num];

    dprint((2, "- ab_del_abook(%s) -\n",
	   pab->abnick ? pab->abnick : "?"));

    varnum = (pab->type & GLOBAL) ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK;

    if(vars[varnum].is_fixed){
	if(err){
	    if(pab->type & GLOBAL)
	      *err =
    _("Cancelled: Sys. Mgmt. does not allow changing global address book config");
	    else
	      *err =
    _("Cancelled: Sys. Mgmt. does not allow changing address book config");
	}

	return -1;
    }

    /*
     * Deal with reverting to default values of the address book
     * variables, or with user deleting a default value.
     */
    modify_config = NotSet;
    
    /* First count how many address books are in the user's config. */
    cnt = 0;
    lval = LVAL(&vars[varnum], ew);
    if(lval && lval[0])
      for(t = lval; *t != NULL; t++)
	cnt++;
    
    /*
     * Easy case, we can just delete one from the user's list.
     */
    if(cnt > 1){
	modify_config = Modify;
    }
    /*
     * Also easy. We'll revert to the default if it exists, and warn
     * the user about that.
     */
    else if(cnt == 1){
	modify_config = RevertToDefault;
	/* see if there's a default to revert to */
	cnt = 0;
	if(vars[varnum].global_val.l && vars[varnum].global_val.l[0])
	  for(t = vars[varnum].global_val.l; *t != NULL; t++)
	    cnt++;
	
	warn_about_revert = cnt;
    }
    /*
     * User is already using the default. Split it into two cases. If there
     * is one address book in default ask user if they want to delete that
     * default from their config. If there is more than one, ask them if
     * they want to ignore all the defaults or just delete this one.
     */
    else{
	/* count how many in default */
	cnt = 0;
	if(vars[varnum].global_val.l && vars[varnum].global_val.l[0])
	  for(t = vars[varnum].global_val.l; *t != NULL; t++)
	    cnt++;
	
	if(cnt > 1){
	    static ESCKEY_S opts[] = {
		/* TRANSLATORS: Ignore All means ignore all of the default values,
		   and Remove One means just remove this one default value. */
		{'i', 'i', "I", N_("Ignore All")},
		{'r', 'r', "R", N_("Remove One")},
		{-1, 0, NULL, NULL}};

	    snprintf(tmp, sizeof(tmp),
	    /* TRANSLATORS: %s is an adjective modifying address books */
	   _("Ignore all default %s address books or just remove this one ? "),
	       /* TRANSLATORS: global or personal address books */
	       pab->type & GLOBAL ? _("global") : _("personal"));
	    tmp[sizeof(tmp)-1] = '\0';
	    switch(radio_buttons(tmp, command_line, opts, 'i', 'x',
		   h_ab_del_ignore, RB_NORM)){
	      case 'i':
		modify_config = OverRideDefault;
		break;
		
	      case 'r':
		modify_config = Modify;
		break;
		
	      case 'x':
		if(err)
		  *err = _("Delete cancelled");

		return -1;
	    }
	}
	else{
	    /* TRANSLATORS: a question */
	    switch(want_to(_("Delete this default address book from config "),
		 'n', 'x', h_ab_del_default, WT_NORM)){
	      case 'n':
	      case 'x':
	        if(err)
		  *err = _("Delete cancelled");

	        return -1;
	    
	      case 'y':
		modify_config = OverRideDefault;
	        break;
	    }
	}
    }

    /*
     * ReadWrite means it exists and MaybeRorW means it is remote and we
     * haven't selected it yet to know our access permissions. The remote
     * folder should have been created, though, unless we didn't even have
     * permissions for that, in which case we got some error messages earlier.
     */
    if(pab->access == ReadWrite || pab->access == MaybeRorW){
	static ESCKEY_S o[] = {
	    /* TRANSLATORS: user is asked whether to remove just data files, just configuration,
	       or both for an address book. */
	    {'d', 'd', "D", N_("Data")},
	    {'c', 'c', "C", N_("Config")},
	    {'b', 'b', "B", N_("Both")},
	    {-1, 0, NULL, NULL}};

	switch(radio_buttons(_("Delete data, config, or both ? "),
			     command_line, o, 'c', 'x',
			     (modify_config == RevertToDefault)
			       ? h_ab_del_data_revert
			       : h_ab_del_data_modify,
			     RB_NORM)){
	  case 'b':				/* Delete Both */
	    delete_data = 1;
	    break;
	    
	  case 'd':				/* Delete only Data */
	    modify_config = DontChange;
	    delete_data = 1;
	    break;

	  case 'c':				/* Delete only Config */
	    break;
	    
	  case 'x':				/* Cancel */
	  default:
	    if(err)
	      *err = _("Delete cancelled");

	    return -1;
	}
    }
    else{
	/*
	 * Deleting config for address book which doesn't yet exist (hasn't
	 * ever been opened).
	 */
	/* TRANSLATORS: a question */
	switch(want_to(_("Delete configuration for highlighted addressbook "),
		       'n', 'x',
		       (modify_config == RevertToDefault)
		         ? h_ab_del_config_revert
			 : h_ab_del_config_modify,
		       WT_NORM)){
          case 'n':
          case 'x':
	  default:
	    if(err)
	      *err = _("Delete cancelled");

	    return -1;
	
          case 'y':
	    break;
	}
    }

    if(delete_data){
	char warning[800];

	dprint((5, "deleting addrbook data\n"));
	warning[0] = '\0';

	/*
	 * In order to delete the address book it is easiest if we open
	 * it first. That fills in the filenames we want to delete.
	 */
	if(pab->address_book == NULL){
	    warning[300] = '\0';
	    pab->address_book = adrbk_open(pab, ps_global->home_dir,
					   &warning[300], sizeof(warning)-300,
					   AB_SORT_RULE_NONE);
	    /*
	     * Couldn't get it open.
	     */
	    if(pab->address_book == NULL){
		if(warning[300])
		  /* TRANSLATORS: %s is an error message */
		  snprintf(warning, 300, _("Can't delete data: %s"), &warning[300]);
		else
		  strncpy(warning, _("Can't delete address book data"), 100);
	    }
	}

	/*
	 * If we have it open, set the delete bits and close to get the
	 * local copies. Delete the remote folder by hand.
	 */
	if(pab->address_book){
	    char       *file, *origfile = NULL;
	    int         f=0, o=0;

	    /*
	     * We're about to destroy addrbook data, better ask again.
	     */
	    if(pab->address_book->count > 0){
		char prompt[100];

		/* TRANSLATORS: a question */
		snprintf(prompt, sizeof(prompt),
			_("About to delete the contents of address book (%ld entries), really delete "), (long) adrbk_count(pab->address_book));
		prompt[sizeof(prompt)-1] = '\0';

		switch(want_to(prompt, 'n', 'n', h_ab_really_delete, WT_NORM)){
		  case 'y':
		    break;
		    
		  case 'n':
		  default:
		    if(err)
		      *err = _("Delete cancelled");

		    return -1;
		}
	    }

	    pab->address_book->flags |= DEL_FILE;
	    file = cpystr(pab->address_book->filename);
	    if(pab->type & REMOTE_VIA_IMAP)
	      origfile = cpystr(pab->address_book->orig_filename);

	    /*
	     * In order to avoid locking problems when we delete the
	     * remote folder, we need to actually close the remote stream
	     * instead of just putting it back in the stream pool.
	     * So we will remove this stream from the re-usable portion
	     * of the stream pool by clearing the SP_USEPOOL flag.
	     * Init_abook(pab, TotallyClosed) via rd_close_remdata is
	     * going to pine_mail_close it.
	     */
	    if(pab->type & REMOTE_VIA_IMAP
	       && pab->address_book
	       && pab->address_book->type == Imap
	       && pab->address_book->rd
	       && rd_stream_exists(pab->address_book->rd)){

		sp_unflag(pab->address_book->rd->t.i.stream, SP_USEPOOL);
	    }

	    /* This deletes the files because of DEL_ bits we set above. */
	    init_abook(pab, TotallyClosed);

	    /*
	     * Delete the remote folder.
	     */
	    if(pab->type & REMOTE_VIA_IMAP){
		REMDATA_S  *rd;
		int         exists;

		ps_global->c_client_error[0] = '\0';
		if(!pine_mail_delete(NULL, origfile) &&
		   ps_global->c_client_error[0] != '\0'){
		    dprint((1, "%s: %s\n", origfile ? origfile : "?",
			   ps_global->c_client_error));
		}

		/* delete line from metadata */
		rd = rd_new_remdata(RemImap, origfile, NULL);
		rd_write_metadata(rd, 1);
		rd_close_remdata(&rd);

		/* Check to see if it's still there */
		if((exists=folder_exists(NULL, origfile)) &&
		   (exists != FEX_ERROR)){
		    o++;
		    dprint((1, "Trouble deleting %s\n",
			   origfile ? origfile : "?"));
		}
	    }

	    if(can_access(file, ACCESS_EXISTS) == 0){
		f++;
		dprint((1, "Trouble deleting %s\n",
		       file ? file : "?"));
	    }

	    if(f || o){
		snprintf(warning, sizeof(warning), _("Trouble deleting data %s%s%s%s"),
		      f ? file : "",
		      (f && o) ? (o ? ", " : " and ") : "",
		      o ? " and " : "",
		      o ? origfile : "");
		warning[sizeof(warning)-1] = '\0';
	    }

	    fs_give((void **) &file);
	    if(origfile)
	      fs_give((void **) &origfile);
	}

	if(*warning){
	    q_status_message(SM_ORDER, 3, 3, warning);
	    dprint((1, "%s\n", warning));
	    display_message(NO_OP_COMMAND);
	}
	else if(modify_config == DontChange)
	  q_status_message(SM_ORDER, 0, 1, _("Addressbook data deleted"));
    }

    if(modify_config == DontChange){
	/*
	 * We return -1 to indicate that the addrbook wasn't deleted (as far
	 * as we're concerned) but we don't fill in err so that no error
	 * message will be printed.
	 * Since the addrbook is still an addrbook we need to reinitialize it.
	 */
	pab->access = adrbk_access(pab);
	if(pab->type & GLOBAL && pab->access != NoAccess)
	  pab->access = ReadOnly;
	
	init_abook(pab, HalfOpen);
	return -1;
    }
    else if(modify_config == Modify){
	list = vars[varnum].current_val.l;
	if(pab->type & GLOBAL){
	    how_many_in_list = as.n_addrbk - as.how_many_personals - 1;
	    num_in_list      = abook_num - as.how_many_personals;
	}
	else{
	    how_many_in_list = as.how_many_personals - 1;
	    num_in_list      = abook_num;
	}
    }
    else if(modify_config == OverRideDefault)
      how_many_in_list = 1;
    else if(modify_config == RevertToDefault)
      how_many_in_list = 0;
    else
      q_status_message(SM_ORDER, 3, 3, "can't happen in ab_del_abook");

    /* allocate for new list */
    if(how_many_in_list)
      new_list = (char **)fs_get((how_many_in_list + 1) * sizeof(char *));
    else
      new_list = NULL;

    /*
     * This case is both for modifying the users user_val and for the
     * case where the user wants to modify the global_val default and
     * use the modified version for his or her new user_val. We just
     * copy from the existing global_val, deleting the one addrbook
     * and put the result in user_val.
     */
    if(modify_config == Modify){
	/* copy old list up to where we will delete entry */
	for(i = 0; i < num_in_list; i++)
	  new_list[i] = cpystr(list[i]);
	
	/* copy rest of old list */
	for(; i < how_many_in_list; i++)
	  new_list[i] = cpystr(list[i+1]);
	
	new_list[i] = NULL;
    }
    else if(modify_config == OverRideDefault){
	new_list[0] = cpystr("");
	new_list[1] = NULL;
    }

    /* this also frees old variable contents for us */
    if(set_variable_list(varnum, new_list, TRUE, ew)){
	if(err)
	  *err = _("Delete cancelled: couldn't save pine configuration file");

	set_current_val(&vars[varnum], TRUE, FALSE);
	free_list_array(&new_list);

	return -1;
    }

    set_current_val(&vars[varnum], TRUE, FALSE);

    if(warn_about_revert){
	/* TRANSLATORS: the %s may be "global " or nothing */
	snprintf(tmp, sizeof(tmp), _("Reverting to default %saddress books"),
		pab->type & GLOBAL ? _("global ") : "");
	tmp[sizeof(tmp)-1] = '\0';
	q_status_message(SM_ORDER, 3, 4, tmp);
    }

    free_list_array(&new_list);
    
    return 0;
}


/*
 * Shuffle addrbooks.
 *
 * Args:    pab -- Pab from current addrbook.
 *        slide -- return value, tells how far to slide the cursor. If slide
 *                   is negative, slide it up, if positive, slide it down.
 * command_line -- The screen line on which to prompt
 *          msg -- Points to returned message, if any. Should be freed by
 *                   caller.
 *
 * Result: Two address books are swapped in the display order. If the shuffle
 *         crosses the Personal/Global boundary, then instead of swapping
 *         two address books the highlighted abook is moved from one section
 *         to the other.
 *          >= 0 on success.
 *          < 0 on failure, no changes.
 *          > 0 If the return value is greater than zero it means that we've
 *                reverted one of the variables to its default value. That
 *                means we've added at least one new addrbook, so the caller
 *                should reset. The value returned is the number of the
 *                moved addrbook + 1 (+1 so it won't be confused with zero).
 *          = 0 If the return value is zero we've just moved addrbooks around.
 *                No reset need be done.
 */
int
ab_shuffle(PerAddrBook *pab, int *slide, int command_line, char **msg)
{
    ESCKEY_S opts[3];
    char     tmp[200];
    int      i, deefault, rv, target = 0;
    int      up_into_empty = 0, down_into_empty = 0;
    HelpType help;
    struct variable *vars = ps_global->vars;

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

    *slide = 0;

    if(ps_global->readonly_pinerc){
	if(msg)
	  *msg = cpystr(_("Shuffle cancelled: config file not changeable"));
	
	return -1;
    }

    /* Move it up or down? */
    i = 0;
    opts[i].ch      = 'u';
    opts[i].rval    = 'u';
    opts[i].name    = "U";
    /* TRANSLATORS: shuffle something Up or Down in a list */
    opts[i++].label = N_("Up");

    opts[i].ch      = 'd';
    opts[i].rval    = 'd';
    opts[i].name    = "D";
    opts[i++].label = N_("Down");

    opts[i].ch = -1;
    deefault = 'u';

    if(pab->type & GLOBAL){
	if(vars[V_GLOB_ADDRBOOK].is_fixed){
	    if(msg)
	      *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing global address book config"));
	    
	    return -1;
	}

	if(as.cur == 0){
	    if(as.config)
	      up_into_empty++;
	    else{					/* no up */
		opts[0].ch = -2;
		deefault = 'd';
	    }
	}

	if(as.cur == as.n_addrbk - 1)			/* no down */
	  opts[1].ch = -2;
    }
    else{
	if(vars[V_ADDRESSBOOK].is_fixed){
	    if(msg)
	      *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing address book config"));
	    
	    return -1;
	}

	if(as.cur == 0){				/* no up */
	    opts[0].ch = -2;
	    deefault = 'd';
	}

	if(as.cur == as.n_addrbk - 1){
	    if(as.config)
	      down_into_empty++;
	    else
	      opts[1].ch = -2;				/* no down */
	}
    }

    snprintf(tmp, sizeof(tmp), _("Shuffle \"%s\" %s%s%s ? "),
	    pab->abnick,
	    (opts[0].ch != -2) ? _("UP") : "",
	    (opts[0].ch != -2 && opts[1].ch != -2) ? " or " : "",
	    (opts[1].ch != -2) ? _("DOWN") : "");
    tmp[sizeof(tmp)-1] = '\0';
    help = (opts[0].ch == -2) ? h_ab_shuf_down
			      : (opts[1].ch == -2) ? h_ab_shuf_up
						   : h_ab_shuf;

    rv = radio_buttons(tmp, command_line, opts, deefault, 'x',
		       help, RB_NORM);

    ps_global->mangled_footer = 1;

    if((rv == 'u' && up_into_empty) || (rv == 'd' && down_into_empty))
      target = -1;
    else
      target = as.cur + (rv == 'u' ? -1 : 1);

    if(rv == 'x'){
	if(msg)
	  *msg = cpystr(_("Shuffle cancelled"));
	
	return -1;
    }
    else
      return(do_the_shuffle(slide, as.cur, target, msg));
}


/*
 * Actually shuffle the config variables and address books structures around.
 *
 * Args:  anum1, anum2 -- The numbers of the address books
 *                 msg -- Points to returned message, if any.
 *
 * Returns: >= 0 on success.
 *          < 0 on failure, no changes.
 *          > 0 If the return value is greater than zero it means that we've
 *                reverted one of the variables to its default value. That
 *                means we've added at least one new addrbook, so the caller
 *                should reset. The value returned is the number of the
 *                moved addrbook + 1 (+1 so it won't be confused with zero).
 *          = 0 If the return value is zero we've just moved addrbooks around.
 *                No reset need be done.
 *
 * Anum1 is the one that we want to move, anum2 is the one that it will be
 * swapped with. When anum1 and anum2 are on the opposite sides of the
 * Personal/Global boundary then instead of swapping we just move anum1 to
 * the other side of the boundary.
 *
 * Anum2 of -1 means it is a swap into the other type of address book, which
 * is currently empty.
 */
int
do_the_shuffle(int *slide, int anum1, int anum2, char **msg)
{
    PerAddrBook *pab;
    enum {NotSet, Pers, Glob, Empty} type1, type2;
    int          i, j, retval = -1;
    struct variable *vars = ps_global->vars;
    char **lval;
    EditWhich ew;
    char *cancel_msg = _("Shuffle cancelled: couldn't save configuration file");

    dprint((5, "- do_the_shuffle(%d, %d) -\n", anum1, anum2));

    /* restrict address book config to normal config file */
    ew = Main;

    if(anum1 == -1)
      type1 = Empty;
    else{
	pab = &as.adrbks[anum1];
	type1 = (pab->type & GLOBAL) ? Glob : Pers;
    }

    if(type1 == Empty){
	if(msg)
	  *msg =
	      cpystr(_("Shuffle cancelled: highlight entry you wish to shuffle"));

	return(retval);
    }

    if(anum2 == -1)
      type2 = Empty;
    else{
	pab = &as.adrbks[anum2];
	type2 = (pab->type & GLOBAL) ? Glob : Pers;
    }

    if(type2 == Empty)
      type2 = (type1 == Pers) ? Glob : Pers;

    if((type1 == Pers || type2 == Pers) && vars[V_ADDRESSBOOK].is_fixed){
	if(msg)
	  *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing address book configuration"));

	return(retval);
    }

    if((type1 == Glob || type2 == Glob) && vars[V_GLOB_ADDRBOOK].is_fixed){
	if(msg)
	  *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing global address book config"));

	return(retval);
    }

    /*
     * There are two cases. If the shuffle is two address books within the
     * same variable, then they just swap places. If it is a shuffle of an
     * addrbook from one side of the boundary to the other, just that one
     * is moved.
     */
    if((type1 == Glob && type2 == Glob) ||
       (type1 == Pers && type2 == Pers)){
	int          how_many_in_list, varnum;
	int          anum1_rel, anum2_rel;	/* position in specific list */
	char       **list, **new_list;
	PerAddrBook  tmppab;

	*slide = (anum1 < anum2) ? LINES_PER_ABOOK : -1 * LINES_PER_ABOOK;

	if(type1 == Pers){
	    how_many_in_list = as.how_many_personals;
	    list             = VAR_ADDRESSBOOK;
	    varnum           = V_ADDRESSBOOK;
	    anum1_rel        = anum1;
	    anum2_rel        = anum2;
	}
	else{
	    how_many_in_list = as.n_addrbk - as.how_many_personals;
	    list             = VAR_GLOB_ADDRBOOK;
	    varnum           = V_GLOB_ADDRBOOK;
	    anum1_rel        = anum1 - as.how_many_personals;
	    anum2_rel        = anum2 - as.how_many_personals;
	}

	/* allocate for new list, same size as old list */
	new_list = (char **)fs_get((how_many_in_list + 1) * sizeof(char *));

	/* fill in new_list */
	for(i = 0; i < how_many_in_list; i++){
	    /* swap anum1 and anum2 */
	    if(i == anum1_rel)
	      j = anum2_rel;
	    else if(i == anum2_rel)
	      j = anum1_rel;
	    else
	      j = i;

	    new_list[i] = cpystr(list[j]);
	}

	new_list[i] = NULL;

	if(set_variable_list(varnum, new_list, TRUE, ew)){
	    if(msg)
	      *msg = cpystr(cancel_msg);

	    /* restore old values */
	    set_current_val(&vars[varnum], TRUE, FALSE);
	    free_list_array(&new_list);
	    return(retval);
	}

	retval = 0;
	set_current_val(&vars[varnum], TRUE, FALSE);
	free_list_array(&new_list);

	/* Swap PerAddrBook structs */
	tmppab = as.adrbks[anum1];
	as.adrbks[anum1] = as.adrbks[anum2];
	as.adrbks[anum2] = tmppab;
    }
    else if((type1 == Pers && type2 == Glob) ||
            (type1 == Glob && type2 == Pers)){
	int how_many_in_srclist, how_many_in_dstlist;
	int srcvarnum, dstvarnum, srcanum;
	int cnt, warn_about_revert = 0;
	char **t;
	char **new_src, **new_dst, **srclist, **dstlist;
	char   tmp[200];
	enum {NotSet, Modify, RevertToDefault, OverRideDefault} modify_config;

	/*
	 * how_many_in_srclist = # in orig src list (Pers or Glob list).
	 * how_many_in_dstlist = # in orig dst list
	 * srcanum = # of highlighted addrbook that is being shuffled
	 */
	if(type1 == Pers){
	    how_many_in_srclist = as.how_many_personals;
	    how_many_in_dstlist = as.n_addrbk - as.how_many_personals;
	    srclist             = VAR_ADDRESSBOOK;
	    dstlist             = VAR_GLOB_ADDRBOOK;
	    srcvarnum           = V_ADDRESSBOOK;
	    dstvarnum           = V_GLOB_ADDRBOOK;
	    srcanum             = as.how_many_personals - 1;
	    *slide              = (how_many_in_srclist == 1)
				    ? (LINES_PER_ADD_LINE + XTRA_LINES_BETWEEN)
				    : XTRA_LINES_BETWEEN;
	}
	else{
	    how_many_in_srclist = as.n_addrbk - as.how_many_personals;
	    how_many_in_dstlist = as.how_many_personals;
	    srclist             = VAR_GLOB_ADDRBOOK;
	    dstlist             = VAR_ADDRESSBOOK;
	    srcvarnum           = V_GLOB_ADDRBOOK;
	    dstvarnum           = V_ADDRESSBOOK;
	    srcanum             = as.how_many_personals;
	    *slide              = (how_many_in_dstlist == 0)
				    ? (LINES_PER_ADD_LINE + XTRA_LINES_BETWEEN)
				    : XTRA_LINES_BETWEEN;
	    *slide              = -1 * (*slide);
	}


	modify_config = Modify;
	if(how_many_in_srclist == 1){
	    /*
	     * Deal with reverting to default values of the address book
	     * variables, or with user deleting a default value.
	     */
	    modify_config = NotSet;
	    
	    /*
	     * Count how many address books are in the user's config.
	     * This has to be one or zero, because how_many_in_srclist == 1.
	     */
	    cnt = 0;
	    lval = LVAL(&vars[srcvarnum], ew);
	    if(lval && lval[0])
	      for(t = lval; *t != NULL; t++)
		cnt++;
	    
	    /*
	     * We'll revert to the default if it exists, and warn
	     * the user about that.
	     */
	    if(cnt == 1){
		modify_config = RevertToDefault;
		/* see if there's a default to revert to */
		cnt = 0;
		if(vars[srcvarnum].global_val.l &&
		   vars[srcvarnum].global_val.l[0])
		  for(t = vars[srcvarnum].global_val.l; *t != NULL; t++)
		    cnt++;
		
		warn_about_revert = cnt;
		if(warn_about_revert > 1 && type1 == Pers)
		  *slide = LINES_PER_ABOOK * warn_about_revert +
						      XTRA_LINES_BETWEEN;
	    }
	    /*
	     * User is already using the default.
	     */
	    else if(cnt == 0){
		modify_config = OverRideDefault;
	    }
	}

	/*
	 * We're adding one to the dstlist, so need how_many + 1 + 1.
	 */
	new_dst = (char **)fs_get((how_many_in_dstlist + 2) * sizeof(char *));
	j = 0;

	/*
	 * Because the Personal list comes before the Global list, when
	 * we move to Global we're inserting a new first element into
	 * the global list (the dstlist).
	 *
	 * When we move from Global to Personal, we're appending a new
	 * last element onto the personal list (the dstlist).
	 */
	if(type2 == Glob)
	  new_dst[j++] = cpystr(srclist[how_many_in_srclist-1]);

	for(i = 0; i < how_many_in_dstlist; i++)
	  new_dst[j++] = cpystr(dstlist[i]);

	if(type2 == Pers)
	  new_dst[j++] = cpystr(srclist[0]);

	new_dst[j] = NULL;

	/*
	 * The srclist is complicated by the reverting to default
	 * behaviors.
	 */
	if(modify_config == Modify){
	    /*
	     * In this case we're just removing one from the srclist
	     * so the new_src is of size how_many -1 +1.
	     */
	    new_src = (char **)fs_get((how_many_in_srclist) * sizeof(char *));
	    j = 0;

	    for(i = 0; i < how_many_in_srclist-1; i++)
	      new_src[j++] = cpystr(srclist[i + ((type1 == Glob) ? 1 : 0)]);

	    new_src[j] = NULL;
	}
	else if(modify_config == OverRideDefault){
	    /*
	     * We were using default and will now revert to nothing.
	     */
	    new_src = (char **)fs_get(2 * sizeof(char *));
	    new_src[0] = cpystr("");
	    new_src[1] = NULL;
	}
	else if(modify_config == RevertToDefault){
	    /*
	     * We are moving our last user variable out and reverting
	     * to the default value for this variable.
	     */
	    new_src = NULL;
	}

	if(set_variable_list(dstvarnum, new_dst, TRUE, ew) ||
	   set_variable_list(srcvarnum, new_src, TRUE, ew)){
	    if(msg)
	      *msg = cpystr(cancel_msg);

	    /* restore old values */
	    set_current_val(&vars[dstvarnum], TRUE, FALSE);
	    set_current_val(&vars[srcvarnum], TRUE, FALSE);
	    free_list_array(&new_dst);
	    free_list_array(&new_src);
	    return(retval);
	}
	
	set_current_val(&vars[dstvarnum], TRUE, FALSE);
	set_current_val(&vars[srcvarnum], TRUE, FALSE);
	free_list_array(&new_dst);
	free_list_array(&new_src);

	retval = (type1 == Pers && warn_about_revert)
		    ? (warn_about_revert + 1) : (srcanum + 1);

	/*
	 * This is a tough case. We're adding one or more new address books
	 * in this case so we need to reset the addrbooks and start over.
	 * We return the number of the address book we just moved after the
	 * reset so that the caller can focus attention on the moved one.
	 * Actually, we return 1+the number so that we can tell it apart
	 * from a return of zero, which just means everything is ok.
	 */
	if(warn_about_revert){
	    snprintf(tmp, sizeof(tmp),
     "This address book now %s, reverting to default %s address %s",
		    (type1 == Glob) ? "Personal" : "Global",
		    (type1 == Glob) ? "Global" : "Personal",
		    warn_about_revert > 1 ? "books" : "book");
	    tmp[sizeof(tmp)-1] = '\0';
	    if(msg)
	      *msg = cpystr(tmp);
	}
	else{
	    /*
	     * Modify PerAddrBook struct and adjust boundary.
	     * In this case we aren't swapping two addrbooks, but just modifying
	     * one from being global to personal or the reverse. It will
	     * still be the same element in the as.adrbks array.
	     */
	    pab = &as.adrbks[srcanum];
	    if(type2 == Glob){
		as.how_many_personals--;
		pab->type |= GLOBAL;
		if(pab->access != NoAccess)
		  pab->access = ReadOnly;
	    }
	    else{
		as.how_many_personals++;
		pab->type &= ~GLOBAL;
		if(pab->access != NoAccess && pab->access != MaybeRorW)
		  pab->access = ReadWrite;
	    }

	    snprintf(tmp, sizeof(tmp),
		    "This address book now %s",
		    (type1 == Glob) ? "Personal" : "Global");
	    tmp[sizeof(tmp)-1] = '\0';
	    if(msg)
	      *msg = cpystr(tmp);
	}
    }

    return(retval);
}


int
ab_compose_to_addr(long int cur_line, int agg, int allow_role)
{
    AddrScrn_Disp *dl;
    AdrBk_Entry   *abe;
    SAVE_STATE_S   state;
    BuildTo        bldto;

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

    save_state(&state);

    bldto.type    = Str;
    bldto.arg.str = NULL;

    if(agg){
	int    i;
	size_t incr = 100, avail, alloced;
	char  *to = NULL;

	to = (char *)fs_get(incr);
	*to = '\0';
	avail = incr;
	alloced = incr;

	/*
	 * Run through all of the selected entries
	 * in all of the address books.
	 * Put the nicknames together into one long
	 * string with comma separators.
	 */
	for(i = 0; i < as.n_addrbk; i++){
	    adrbk_cntr_t num;
	    PerAddrBook *pab;
	    EXPANDED_S  *next_one;

	    pab = &as.adrbks[i];
	    if(pab->address_book)
	      next_one = pab->address_book->selects;
	    else
	      continue;

	    while((num = entry_get_next(&next_one)) != NO_NEXT){
		char          *a_string;
		AddrScrn_Disp  fake_dl;

		abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);

		/*
		 * Since we're picking up address book entries
		 * directly from the address books and have
		 * no knowledge of the display lines they came
		 * from, we don't know the dl's that go with
		 * them.  We need to pass a dl to abe_to_nick
		 * but it really is only going to use the
		 * type in this case.
		 */
		dl = &fake_dl;
		dl->type = (abe->tag == Single) ? Simple : ListHead;
		a_string = abe_to_nick_or_addr_string(abe, dl, i);

		while(abe && avail < (size_t)strlen(a_string)+1){
		    alloced += incr;
		    avail   += incr;
		    fs_resize((void **)&to, alloced);
		}

		if(!*to){
		    strncpy(to, a_string, alloced);
		    to[alloced-1] = '\0';
		}
		else{
		    strncat(to, ",", alloced-strlen(to)-1);
		    to[alloced-1] = '\0';
		    strncat(to, a_string, alloced-strlen(to)-1);
		    to[alloced-1] = '\0';
		}

		avail -= (strlen(a_string) + 1);
		fs_give((void **)&a_string);
	    }
	}

	bldto.type    = Str;
	bldto.arg.str = to;
    }
    else{
	if(is_addr(cur_line)){

	    dl  = dlist(cur_line);
	    abe = ae(cur_line);

	    if(dl->type == ListEnt){
		bldto.type    = Str;
		bldto.arg.str = cpystr(listmem(cur_line));
	    }
	    else{
		bldto.type    = Abe;
		bldto.arg.abe = abe;
	    }
	}
    }

    if(bldto.type == Str && bldto.arg.str == NULL)
      bldto.arg.str = cpystr("");

    ab_compose_internal(bldto, allow_role);

    restore_state(&state);

    if(bldto.type == Str && bldto.arg.str)
      fs_give((void **)&bldto.arg.str);
    
    /*
     * Window size may have changed in composer.
     * Pine_send will have reset the window size correctly,
     * but we still have to reset our address book data structures.
     */
    ab_resize();
    ps_global->mangled_screen = 1;
    return(1);
}


/*
 * Used by the two compose routines.
 */
void
ab_compose_internal(BuildTo bldto, int allow_role)
{
    int		   good_addr;
    char          *addr, *fcc, *error = NULL;
    ACTION_S      *role = NULL;
    void (*prev_screen)(struct pine *) = ps_global->prev_screen,
	 (*redraw)(void) = ps_global->redrawer;

    if(allow_role)
      ps_global->redrawer = NULL;

    ps_global->next_screen = SCREEN_FUN_NULL;

    fcc  = NULL;
    addr = NULL;

    good_addr = (our_build_address(bldto, &addr, &error, &fcc, NULL) >= 0);
	
    if(error){
	q_status_message1(SM_ORDER, 3, 4, "%s", error);
	fs_give((void **)&error);
    }

    if(!good_addr && addr && *addr)
      fs_give((void **)&addr); /* relying on fs_give setting addr to NULL */

    if(allow_role){
	/* Setup role */
	if(role_select_screen(ps_global, &role, MC_COMPOSE) < 0){
	    cmd_cancelled("Composition");
	    ps_global->next_screen = prev_screen;
	    ps_global->redrawer = redraw;
	    return;
	}

	/*
	 * If default role was selected (NULL) we need to make up a role which
	 * won't do anything, but will cause compose_mail to think there's
	 * already a role so that it won't try to confirm the default.
	 */
	if(role)
	  role = copy_action(role);
	else{
	    role = (ACTION_S *)fs_get(sizeof(*role));
	    memset((void *)role, 0, sizeof(*role));
	    role->nick = cpystr("Default Role");
	}
    }

    compose_mail(addr, fcc, role, NULL, NULL);

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

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


/*
 * Export addresses into a file.
 *
 * Args: cur_line     -- The current line position (in global display list)
 *			 of cursor
 *       command_line -- The screen line on which to prompt
 *
 * Returns -- 1 if the export is done
 *            0 if not
 */
int
ab_export(struct pine *ps, long int cur_line, int command_line, int agg)
{
    int		   ret = 0, i, retflags = GER_NONE;
    int            r, orig_errno, failure = 0;
    struct variable *vars = ps->vars;
    char     filename[MAXPATH+1], full_filename[MAXPATH+1];
    STORE_S *store;
    gf_io_t  pc;
    long     start_of_append;
    char    *addr = NULL, *error = NULL;
    BuildTo  bldto;
    char    *p;
    int      good_addr, plur, vcard = 0, tab = 0;
    static HISTORY_S *history = NULL;
    AdrBk_Entry *abe;
    VCARD_INFO_S *vinfo;
    static ESCKEY_S ab_export_opts[] = {
	{ctrl('T'), 10, "^T", N_("To Files")},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL}};
    static ESCKEY_S vcard_or_addresses[] = {
	{'a', 'a', "A", N_("Address List")},
	{'v', 'v', "V", N_("VCard")},
	/* TRANSLATORS: TabSep is a command key label meaning Tab Separated List */
	{'t', 't', "T", N_("TabSep")},
	{-1, 0, NULL, NULL}};


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

    if(ps->restricted){
	q_status_message(SM_ORDER, 0, 3,
	    "Alpine demo can't export addresses to files");
	return(ret);
    }

    while(1){
	i = radio_buttons(_("Export list of addresses, vCard format, or Tab Separated ? "),
			  command_line, vcard_or_addresses, 'a', 'x',
			  NO_HELP, RB_NORM|RB_RET_HELP);
	if(i == 3){
	    /* TRANSLATORS: a screen title */
	    helper(h_ab_export_vcard, _("HELP FOR EXPORT FORMAT"),
		   HLPD_SIMPLE);
	    ps_global->mangled_screen = 1;
	}
	else
	  break;
    }

    switch(i){
      case 'x':
	q_status_message(SM_INFO, 0, 2, _("Address book export cancelled"));
	return(ret);
    
      case 'a':
	break;

      case 'v':
	vcard++;
	break;

      case 't':
	tab++;
	break;

      default:
	q_status_message(SM_ORDER, 3, 3, "can't happen in ab_export");
	return(ret);
    }

    if(agg)
      plur = 1;
    else{
	abe = ae(cur_line);
	plur = (abe && abe->tag == List);
    }

    filename[0] = '\0';
    r = 0;

    if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
	ab_export_opts[++r].ch  =  ctrl('I');
	ab_export_opts[r].rval  = 11;
	ab_export_opts[r].name  = "TAB";
	ab_export_opts[r].label = N_("Complete");
    }

    ab_export_opts[++r].ch = -1;

    r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
			    plur ? _("addresses") : _("address"),
			    _("EXPORT"), ab_export_opts,
			    &retflags, command_line, GE_IS_EXPORT, &history);

    if(r < 0){
	switch(r){
	  case -1:
	    q_status_message(SM_INFO, 0, 2, _("Address book export cancelled"));
	    break;

	  case -2:
	    q_status_message1(SM_ORDER, 0, 2,
		_("Can't export to file outside of %s"), VAR_OPER_DIR);
	    break;
	}

	goto fini;
    }

    dprint((5, "Opening file \"%s\" for export\n",
	   full_filename ? full_filename : "?"));

    if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
	q_status_message2(SM_ORDER | SM_DING, 3, 4,
		  _("Error opening file \"%s\" for address export: %s"),
		  full_filename, error_description(errno));
	goto fini;
    }

    /*
     * The write_single_vcard_entry function wants a pc.
     */
    if(vcard || tab)
      gf_set_so_writec(&pc, store);

    start_of_append = so_tell(store);

    if(agg){
	for(i = 0; !failure && i < as.n_addrbk; i++){
	    adrbk_cntr_t num;
	    PerAddrBook *pab;
	    EXPANDED_S  *next_one;

	    pab = &as.adrbks[i];
	    if(pab->address_book)
	      next_one = pab->address_book->selects;
	    else
	      continue;

	    while(!failure && (num = entry_get_next(&next_one)) != NO_NEXT){

		abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
		if((vcard || tab) && abe){
		    /*
		     * There is no place to store the charset information
		     * so we don't ask for it.
		     */
		    if(!(vinfo=prepare_abe_for_vcard(ps, abe, 1)))
		      failure++;
		    else{
			if(vcard)
			  write_single_vcard_entry(ps, pc, vinfo);
			else
			  write_single_tab_entry(pc, vinfo);

			free_vcard_info(&vinfo);
		    }
		}
		else if(abe){
		    bldto.type    = Abe;
		    bldto.arg.abe = abe;
		    error         = NULL;
		    addr          = NULL;
		    good_addr = (our_build_address(bldto,&addr,&error,NULL,NULL) >= 0);
	
		    if(error){
			q_status_message1(SM_ORDER, 0, 4, "%s", error);
			fs_give((void **)&error);
		    }

		    /* rfc1522_decode the addr */
		    if(addr){
			size_t len;

			len = 4*strlen(addr)+1;
			p = (char *)fs_get(len * sizeof(char));
			if(rfc1522_decode_to_utf8((unsigned char *)p,len,addr) == (unsigned char *)p){
			    fs_give((void **)&addr);
			    addr = p;
			}
			else
			  fs_give((void **)&p);
		    }

		    if(good_addr){
			int   quoted = 0;

			/*
			 * Change the unquoted commas into newlines.
			 * Not worth it to do complicated quoting,
			 * just consider double quotes.
			 */
			for(p = addr; *p; p++){
			    if(*p == '"')
			      quoted = !quoted;
			    else if(!quoted && *p == ','){
				*p++ = '\n';
				removing_leading_white_space(p);
				p--;
			    }
			}

			if(!so_puts(store, addr) || !so_puts(store, NEWLINE)){
			    orig_errno = errno;
			    failure    = 1;
			}
		    }

		    if(addr)
		      fs_give((void **)&addr);
		}
	    }
	}
    }
    else{
	AddrScrn_Disp *dl;

	dl  = dlist(cur_line);
	abe = ae(cur_line);
	if((vcard || tab) && abe){
	    if(!(vinfo=prepare_abe_for_vcard(ps, abe, 1)))
	      failure++;
	    else{
		if(vcard)
		  write_single_vcard_entry(ps, pc, vinfo);
		else
		  write_single_tab_entry(pc, vinfo);

		free_vcard_info(&vinfo);
	    }
	}
	else{

	    if(dl->type == ListHead && listmem_count_from_abe(abe) == 0){
		error = _("List is empty, nothing to export!");
		good_addr = 0;
	    }
	    else if(dl->type == ListEnt){
		bldto.type    = Str;
		bldto.arg.str = listmem(cur_line);
		good_addr = (our_build_address(bldto,&addr,&error,NULL,NULL) >= 0);
	    }
	    else{
		bldto.type    = Abe;
		bldto.arg.abe = abe;
		good_addr = (our_build_address(bldto,&addr,&error,NULL,NULL) >= 0);
	    }

	    if(error){
		q_status_message1(SM_ORDER, 3, 4, "%s", error);
		fs_give((void **)&error);
	    }

	    /* Have to rfc1522_decode the addr */
	    if(addr){
		size_t len;
		len = 4*strlen(addr)+1;
		p = (char *)fs_get(len * sizeof(char));
		if(rfc1522_decode_to_utf8((unsigned char *)p,len,addr) == (unsigned char *)p){
		    fs_give((void **)&addr);
		    addr = p;
		}
		else
		  fs_give((void **)&p);
	    }

	    if(good_addr){
		int   quoted = 0;

		/*
		 * Change the unquoted commas into newlines.
		 * Not worth it to do complicated quoting,
		 * just consider double quotes.
		 */
		for(p = addr; *p; p++){
		    if(*p == '"')
		      quoted = !quoted;
		    else if(!quoted && *p == ','){
			*p++ = '\n';
			removing_leading_white_space(p);
			p--;
		    }
		}

		if(!so_puts(store, addr) || !so_puts(store, NEWLINE)){
		    orig_errno = errno;
		    failure    = 1;
		}
	    }

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

    if(vcard || tab)
      gf_clear_so_writec(store);

    if(so_give(&store))				/* release storage */
      failure++;

    if(failure){
	our_truncate(full_filename, (off_t)start_of_append);
	dprint((1, "FAILED Export: file \"%s\" : %s\n",
	       full_filename ? full_filename : "?",
	       error_description(orig_errno)));
	q_status_message2(SM_ORDER | SM_DING, 3, 4,
		  _("Error exporting to \"%s\" : %s"),
		  filename, error_description(orig_errno));
    }
    else{
	ret = 1;
	q_status_message3(SM_ORDER,0,3,
			  "%s %s to file \"%s\"",
			  (vcard || tab) ? (agg ? "Entries" : "Entry")
			        : (plur ? "Addresses" : "Address"),
			  retflags & GER_OVER
			      ? "overwritten"
			      : retflags & GER_APPEND ? "appended" : "exported",
			  filename);
    }

fini:
    ps->mangled_footer = 1;
    return(ret);
}


/*
 * Forward an address book entry or entries via email attachment.
 *
 *   We use the vCard standard to send entries. We group multiple entries
 * using the BEGIN/END construct of vCard, not with multiple MIME parts.
 * A limitation of vCard is that there can be only one charset for the
 * whole group we send, so we might lose that information.
 *
 * Args: cur_line     -- The current line position (in global display list)
 *			 of cursor
 *       command_line -- The screen line on which to prompt
 */
int
ab_forward(struct pine *ps, long int cur_line, int agg)
{
    AddrScrn_Disp *dl;
    AdrBk_Entry   *abe;
    ENVELOPE      *outgoing = NULL;
    BODY          *pb, *body = NULL;
    PART         **pp;
    char          *sig;
    gf_io_t        pc;
    int            i, ret = 0;
    VCARD_INFO_S  *vinfo;
    ACTION_S      *role = NULL;

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

    if(!agg){
	dl  = dlist(cur_line);
	if(dl->type != ListHead && dl->type != Simple)
	  return(ret);

	abe = ae(cur_line);
	if(!abe){
	    q_status_message(SM_ORDER, 3, 3, _("Trouble accessing current entry"));
	    return(ret);
	}
    }

    outgoing             = mail_newenvelope();
    outgoing->message_id = generate_message_id();
    if(agg && as.selections > 1)
      outgoing->subject = cpystr("Forwarded address book entries from Alpine");
    else
      outgoing->subject = cpystr("Forwarded address book entry from Alpine");

    body                                      = mail_newbody();
    body->type                                = TYPEMULTIPART;
    /*---- The TEXT part/body ----*/
    body->nested.part                       = mail_newbody_part();
    body->nested.part->body.type            = TYPETEXT;
    /*--- Allocate an object for the body ---*/
    if((body->nested.part->body.contents.text.data =
				(void *)so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
	int       did_sig = 0;
	long      rflags = ROLE_COMPOSE;
	PAT_STATE dummy;

	pp = &(body->nested.part->next);

	if(nonempty_patterns(rflags, &dummy)){
	    /*
	     * This is really more like Compose, even though it
	     * is called Forward.
	     */
	    if(confirm_role(rflags, &role))
	      role = combine_inherited_role(role);
	    else{			/* cancel */
		role = NULL;
		cmd_cancelled("Composition");
		goto bomb;
	    }
	}

	if(role)
	  q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""),
			    role->nick);

	if((sig = detoken(role, NULL, 2, 0, 1, NULL, NULL)) != NULL){
	    if(*sig){
		so_puts((STORE_S *)body->nested.part->body.contents.text.data,
			sig);
		did_sig++;
	    }

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

	/* so we don't have an empty part */
	if(!did_sig)
	  so_puts((STORE_S *)body->nested.part->body.contents.text.data, "\n");
    }
    else{
	q_status_message(SM_ORDER | SM_DING, 3, 4,
			 _("Problem creating space for message text"));
	goto bomb;
    }


    /*---- create the attachment, and write abook entry into it ----*/
    *pp             = mail_newbody_part();
    pb              = &((*pp)->body);
    pb->type        = TYPETEXT;
    pb->encoding    = ENCOTHER;  /* let data decide */
    pb->id          = generate_message_id();
    pb->subtype     = cpystr("DIRECTORY");
    if(agg && as.selections > 1)
      pb->description = cpystr("Alpine addressbook entries");
    else
      pb->description = cpystr("Alpine addressbook entry");

    pb->parameter = NULL;
    set_parameter(&pb->parameter, "profile", "vCard");

    if((pb->contents.text.data = (void *)so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
	int are_some_unqualified = 0, expand_nicks = 0;
	adrbk_cntr_t num;
	PerAddrBook *pab;
	EXPANDED_S  *next_one;
	 
	gf_set_so_writec(&pc, (STORE_S *) pb->contents.text.data);
    
	if(agg){
	    for(i = 0; i < as.n_addrbk && !are_some_unqualified; i++){

		pab = &as.adrbks[i];
		if(pab->address_book)
		  next_one = pab->address_book->selects;
		else
		  continue;
		
		while((num = entry_get_next(&next_one)) != NO_NEXT &&
		      !are_some_unqualified){

		    abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
		    if(abe->tag == Single){
			if(abe->addr.addr && abe->addr.addr[0]
			   && !strindex(abe->addr.addr, '@'))
			  are_some_unqualified++;
		    }
		    else{
			char **ll;

			for(ll = abe->addr.list; ll && *ll; ll++){
			    if(!strindex(*ll, '@')){
				are_some_unqualified++;
				break;
			    }
			}
		    }
		}
	    }
	}
	else{
	    /*
	     * Search through the addresses to see if there are any
	     * that are unqualified, and so would be different if
	     * expanded.
	     */
	    if(abe->tag == Single){
		if(abe->addr.addr && abe->addr.addr[0]
		   && !strindex(abe->addr.addr, '@'))
		  are_some_unqualified++;
	    }
	    else{
		char **ll;

		for(ll = abe->addr.list; ll && *ll; ll++){
		    if(!strindex(*ll, '@')){
			are_some_unqualified++;
			break;
		    }
		}
	    }
	}

	if(are_some_unqualified){
	    switch(want_to(_("Expand nicknames"), 'y', 'x', h_ab_forward,WT_NORM)){
	      case 'x':
		gf_clear_so_writec((STORE_S *) pb->contents.text.data);
		q_status_message(SM_INFO, 0, 2, _("Address book forward cancelled"));
		goto bomb;
		
	      case 'y':
		expand_nicks = 1;
		break;
		
	      case 'n':
		expand_nicks = 0;
		break;
	    }

	    ps->mangled_footer = 1;
	}

	if(agg){
	    for(i = 0; i < as.n_addrbk; i++){

		pab = &as.adrbks[i];
		if(pab->address_book)
		  next_one = pab->address_book->selects;
		else
		  continue;
		
		while((num = entry_get_next(&next_one)) != NO_NEXT){

		    abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
		    if(!(vinfo=prepare_abe_for_vcard(ps, abe, expand_nicks))){
			gf_clear_so_writec((STORE_S *) pb->contents.text.data);
			goto bomb;
		    }
		    else{
			write_single_vcard_entry(ps, pc, vinfo);
			free_vcard_info(&vinfo);
		    }
		}
	    }
	}
	else{
	    if(!(vinfo=prepare_abe_for_vcard(ps, abe, expand_nicks))){
		gf_clear_so_writec((STORE_S *) pb->contents.text.data);
		goto bomb;
	    }
	    else{
		write_single_vcard_entry(ps, pc, vinfo);
		free_vcard_info(&vinfo);
	    }
	}

	/* This sets parameter charset, if necessary, and encoding */
	set_mime_type_by_grope(pb);
	set_charset_possibly_to_ascii(pb, "UTF-8");
	pb->size.bytes =
		strlen((char *)so_text((STORE_S *)pb->contents.text.data));
    }
    else{
	q_status_message(SM_ORDER | SM_DING, 3, 4,
			 _("Problem creating space for message text"));
	goto bomb;
    }

    gf_clear_so_writec((STORE_S *) pb->contents.text.data);

    pine_send(outgoing, &body, _("FORWARDING ADDRESS BOOK ENTRY"), role, NULL,
 	      NULL, NULL, NULL, NULL, 0);
    
    ps->mangled_screen = 1;
    ret = 1;

bomb:
    if(outgoing)
      mail_free_envelope(&outgoing);

    if(body)
      pine_free_body(&body);
    
    free_action(&role);
    return(ret);
}


/*
 * Given an Adrbk_Entry fill in some of the fields in a VCARD_INFO_S
 * for use by write_single_vcard_entry. The returned structure is freed
 * by the caller.
 */
VCARD_INFO_S *
prepare_abe_for_vcard(struct pine *ps, AdrBk_Entry *abe, int expand_nicks)
{
    VCARD_INFO_S *vinfo = NULL;
    char         *init_addr = NULL, *addr = NULL, *astring;
    int           cnt;
    ADDRESS      *adrlist = NULL;

    if(!abe)
      return(vinfo);

    vinfo = (VCARD_INFO_S *) fs_get(sizeof(*vinfo));
    memset((void *) vinfo, 0, sizeof(*vinfo));

    if(abe->nickname && abe->nickname[0]){
	vinfo->nickname = (char **) fs_get((1+1) * sizeof(char *));
	vinfo->nickname[0] = cpystr(abe->nickname);
	vinfo->nickname[1] = NULL;
    }

    if(abe->fcc && abe->fcc[0]){
	vinfo->fcc = (char **) fs_get((1+1) * sizeof(char *));
	vinfo->fcc[0] = cpystr(abe->fcc);
	vinfo->fcc[1] = NULL;
    }

    if(abe->extra && abe->extra[0]){
	vinfo->note = (char **) fs_get((1+1) * sizeof(char *));
	vinfo->note[0] = cpystr(abe->extra);
	vinfo->note[1] = NULL;
    }
    
    if(abe->fullname && abe->fullname[0]){
	char *fn, *last = NULL, *middle = NULL, *first = NULL;

	fn = adrbk_formatname(abe->fullname, &first, &last);
	if(fn){
	    if(*fn){
		vinfo->fullname = (char **)fs_get((1+1) * sizeof(char *));
		vinfo->fullname[0] = fn;
		vinfo->fullname[1] = NULL;
	    }
	    else
	      fs_give((void **)&fn);
	}

	if(last && *last){
	    if(first && (middle=strindex(first, ' '))){
		*middle++ = '\0';
		middle = skip_white_space(middle);
	    }

	    vinfo->last = last;
	    vinfo->first = first;
	    vinfo->middle = middle ? cpystr(middle) : NULL;
	    first = NULL;
	    last = NULL;
	}

	if(last)
	  fs_give((void **)&last);
	if(first)
	  fs_give((void **)&first);
    }

    /* expand nicknames and fully-qualify unqualified names */
    if(expand_nicks){
	char    *error = NULL;
	BuildTo  bldto;
	ADDRESS *a;

	if(abe->tag == Single)
	  init_addr = cpystr(abe->addr.addr);
	else{
	    char **ll;
	    char  *p;
	    long   length;

	    /* figure out how large a string we need to allocate */
	    length = 0L;
	    for(ll = abe->addr.list; ll && *ll; ll++)
	      length += (strlen(*ll) + 2);

	    if(length)
	      length -= 2L;
	    
	    init_addr = (char *)fs_get((size_t)(length+1L) * sizeof(char));
	    p = init_addr;
	
	    for(ll = abe->addr.list; ll && *ll; ll++){
		sstrncpy(&p, *ll, length-(p-init_addr));
		if(*(ll+1))
		  sstrncpy(&p, ", ", length-(p-init_addr));
	    }

	    init_addr[length] = '\0';
	}

	bldto.type    = Str;
	bldto.arg.str = init_addr; 
	our_build_address(bldto, &addr, &error, NULL, NULL);
	if(error){
	    q_status_message1(SM_ORDER, 3, 4, "%s", error);
	    fs_give((void **)&error);
	    free_vcard_info(&vinfo);
	    return(NULL);
	}

	if(addr)
	  rfc822_parse_adrlist(&adrlist, addr, ps->maildomain);
	
	for(cnt = 0, a = adrlist; a; a = a->next)
	  cnt++;

	vinfo->email = (char **)fs_get((cnt+1) * sizeof(char *));

	for(cnt = 0, a = adrlist; a; a = a->next){
	    char    *bufp;
	    ADDRESS *next_addr;
	    size_t   len;

	    next_addr = a->next;
	    a->next = NULL;
	    len = est_size(a);
	    bufp = (char *) fs_get(len * sizeof(char));
	    astring = addr_string(a, bufp, len);
	    a->next = next_addr;
	    vinfo->email[cnt++] = cpystr(astring ? astring : "");
	    fs_give((void **)&bufp);
	}

	vinfo->email[cnt] = NULL;
    }
    else{ /* don't expand or qualify */
	if(abe->tag == Single){
	    astring =
	       (abe->addr.addr && abe->addr.addr[0]) ? abe->addr.addr : "";
	    vinfo->email = (char **)fs_get((1+1) * sizeof(char *));
	    vinfo->email[0] = cpystr(astring);
	    vinfo->email[1] = NULL;
	}
	else{
	    char **ll;

	    for(cnt = 0, ll = abe->addr.list; ll && *ll; ll++)
	      cnt++;

	    vinfo->email = (char **)fs_get((cnt+1) * sizeof(char *));
	    for(cnt = 0, ll = abe->addr.list; ll && *ll; ll++)
	      vinfo->email[cnt++] = cpystr(*ll);

	    vinfo->email[cnt] = NULL;
	}
    }

    return(vinfo);
}


void
free_vcard_info(VCARD_INFO_S **vinfo)
{
    if(vinfo && *vinfo){
	if((*vinfo)->nickname)
	  free_list_array(&(*vinfo)->nickname);
	if((*vinfo)->fullname)
	  free_list_array(&(*vinfo)->fullname);
	if((*vinfo)->fcc)
	  free_list_array(&(*vinfo)->fcc);
	if((*vinfo)->note)
	  free_list_array(&(*vinfo)->note);
	if((*vinfo)->title)
	  free_list_array(&(*vinfo)->title);
	if((*vinfo)->tel)
	  free_list_array(&(*vinfo)->tel);
	if((*vinfo)->email)
	  free_list_array(&(*vinfo)->email);

	if((*vinfo)->first)
	  fs_give((void **)&(*vinfo)->first);
	if((*vinfo)->middle)
	  fs_give((void **)&(*vinfo)->middle);
	if((*vinfo)->last)
	  fs_give((void **)&(*vinfo)->last);

	fs_give((void **)vinfo);
    }
}


/*
 * 
 */
void
write_single_vcard_entry(struct pine *ps, gf_io_t pc, VCARD_INFO_S *vinfo)
{
    char  *decoded, *tmp2, *tmp = NULL, *hdr;
    char **ll;
    int    i, did_fn = 0, did_n = 0;
    int    cr;
    char   eol[3];
#define FOLD_BY 75

    if(!vinfo)
      return;

#if defined(DOS) || defined(OS2)
      cr = 1;
#else
      cr = 0;
#endif

    if(cr)
      strncpy(eol, "\r\n", sizeof(eol));
    else
      strncpy(eol, "\n", sizeof(eol));

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

    gf_puts("BEGIN:VCARD", pc);
    gf_puts(eol, pc);
    gf_puts("VERSION:3.0", pc);
    gf_puts(eol, pc);

    for(i = 0; i < 7; i++){
	switch(i){
	  case 0:
	    ll = vinfo->nickname;
	    hdr = "NICKNAME:";
	    break;

	  case 1:
	    ll = vinfo->fullname;
	    hdr = "FN:";
	    break;

	  case 2:
	    ll = vinfo->email;
	    hdr = "EMAIL:";
	    break;

	  case 3:
	    ll = vinfo->title;
	    hdr = "TITLE:";
	    break;

	  case 4:
	    ll = vinfo->note;
	    hdr = "NOTE:";
	    break;

	  case 5:
	    ll = vinfo->fcc;
	    hdr = "X-FCC:";
	    break;

	  case 6:
	    ll = vinfo->tel;
	    hdr = "TEL:";
	    break;
	  
	  default:
	    alpine_panic("can't happen in write_single_vcard_entry");
	}

	for(; ll && *ll; ll++){
	    decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf), SIZEOF_20KBUF, *ll);

	    tmp = vcard_escape(decoded);
	    if(tmp){
		if((tmp2 = fold(tmp, FOLD_BY, FOLD_BY, hdr, " ", FLD_PWS | (cr ? FLD_CRLF : 0))) != NULL){
		    gf_puts(tmp2, pc);
		    fs_give((void **)&tmp2);
		    if(i == 1)
		      did_fn++;
		}

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

    if(vinfo->last && vinfo->last[0]){
	char *pl, *pf, *pm;

	pl = vcard_escape(vinfo->last);
	pf = (vinfo->first && *vinfo->first) ? vcard_escape(vinfo->first)
					     : NULL;
	pm = (vinfo->middle && *vinfo->middle) ? vcard_escape(vinfo->middle)
					       : NULL;
	snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s%s%s",
		(pl && *pl) ? pl : "", 
		((pf && *pf) || (pm && *pm)) ? ";" : "",
		(pf && *pf) ? pf : "", 
		(pm && *pm) ? ";" : "", 
		(pm && *pm) ? pm : "");

	if((tmp2 = fold(tmp_20k_buf, FOLD_BY, FOLD_BY, "N:", " ",
		       FLD_PWS | (cr ? FLD_CRLF : 0))) != NULL){
	    gf_puts(tmp2, pc);
	    fs_give((void **)&tmp2);
	    did_n++;
	}

	if(pl)
	  fs_give((void **)&pl);
	if(pf)
	  fs_give((void **)&pf);
	if(pm)
	  fs_give((void **)&pm);
    }

    /*
     * These two types are required in draft-ietf-asid-mime-vcard-06, which
     * is April 98 and is in last call.
     */
    if(!did_fn || !did_n){
	if(did_n){
	    snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s%s%s",
		    (vinfo->first && *vinfo->first) ? vinfo->first : "",
		    (vinfo->first && *vinfo->first &&
		     vinfo->middle && *vinfo->middle) ? " " : "", 
		    (vinfo->middle && *vinfo->middle) ? vinfo->middle : "",
		    (((vinfo->first && *vinfo->first) ||
		      (vinfo->middle && *vinfo->middle)) &&
		     vinfo->last && *vinfo->last) ? " " : "", 
		    (vinfo->last && *vinfo->last) ? vinfo->last : "");

	    tmp = vcard_escape(tmp_20k_buf);
	    if(tmp){
		if((tmp2 = fold(tmp, FOLD_BY, FOLD_BY, "FN:", " ",
			       FLD_PWS | (cr ? FLD_CRLF : 0))) != NULL){
		    gf_puts(tmp2, pc);
		    fs_give((void **)&tmp2);
		    did_n++;
		}

		fs_give((void **)&tmp);
	    }
	}
	else{
	    if(!did_fn){
		gf_puts("FN:<Unknown>", pc);
		gf_puts(eol, pc);
	    }

	    gf_puts("N:<Unknown>", pc);
	    gf_puts(eol, pc);
	}
    }

    gf_puts("END:VCARD", pc);
    gf_puts(eol, pc);
}


/*
 * 
 */
void
write_single_tab_entry(gf_io_t pc, VCARD_INFO_S *vinfo)
{
    char  *decoded, *tmp = NULL;
    char **ll;
    int    i, first;
    char  *eol;

    if(!vinfo)
      return;

#if defined(DOS) || defined(OS2)
      eol = "\r\n";
#else
      eol = "\n";
#endif

    for(i = 0; i < 4; i++){
	switch(i){
	  case 0:
	    ll = vinfo->nickname;
	    break;

	  case 1:
	    ll = vinfo->fullname;
	    break;

	  case 2:
	    ll = vinfo->email;
	    break;

	  case 3:
	    ll = vinfo->note;
	    break;

	  default:
	    alpine_panic("can't happen in write_single_tab_entry");
	}

	if(i)
	  gf_puts("\t", pc);

	for(first = 1; ll && *ll; ll++){

	    decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf), SIZEOF_20KBUF, *ll);
	    tmp = vcard_escape(decoded);
	    if(tmp){
		if(i == 2 && !first)
		  gf_puts(",", pc);
		else
		  first = 0;

		gf_puts(tmp, pc);
		fs_give((void **)&tmp);
	    }
	}
    }

    gf_puts(eol, pc);
}


/*
 * for ab_save percent done
 */
static int total_to_copy;
static int copied_so_far;
int
percent_done_copying(void)
{
    return((copied_so_far * 100) / total_to_copy);
}

int
cmp_action_list(const qsort_t *a1, const qsort_t *a2)
{
    ACTION_LIST_S *x = (ACTION_LIST_S *)a1;
    ACTION_LIST_S *y = (ACTION_LIST_S *)a2;

    if(x->pab != y->pab)
      return((x->pab > y->pab) ? 1 : -1);  /* order doesn't matter */
    
    /*
     * The only one that matters is when both x and y have dup lit.
     * For the others, just need to be consistent so sort will terminate.
     */
    if(x->dup){
	if(y->dup)
	  return((x->num_in_dst > y->num_in_dst) ? -1
		   : (x->num_in_dst == y->num_in_dst) ? 0 : 1);
	else
	  return(-1);
    }
    else if(y->dup)
      return(1);
    else
      return((x->num > y->num) ? -1 : (x->num == y->num) ? 0 : 1);
}


/*
 * Copy a bunch of address book entries to a particular address book.
 *
 * Args      abook -- the current addrbook handle
 *        cur_line -- the current line the cursor is on
 *    command_line -- the line to prompt on
 *             agg -- 1 if this is an aggregate copy
 *
 * Returns  1 if successful, 0 if not
 */
int
ab_save(struct pine *ps, AdrBk *abook, long int cur_line, int command_line, int agg)
{
    PerAddrBook   *pab_dst, *pab;
    SAVE_STATE_S   state;  /* For saving state of addrbooks temporarily */
    int            rc, i;
    int		   how_many_dups = 0, how_many_to_copy = 0, skip_dups = 0;
    int		   how_many_no_action = 0, ret = 1;
    int		   err = 0, need_write = 0, we_cancel = 0;
    int            act_list_size, special_case = 0;
    adrbk_cntr_t   num, new_entry_num;
    char           warn[2][MAX_NICKNAME+1];
    char           warning[MAX_NICKNAME+1];
    char           tmp[MAX(200,2*MAX_NICKNAME+80)];
    ACTION_LIST_S *action_list = NULL, *al;
    static ESCKEY_S save_or_export[] = {
	{'s', 's', "S", N_("Save")},
	{'e', 'e', "E", N_("Export")},
	{-1, 0, NULL, NULL}};

    if(!agg)
      snprintf(tmp, sizeof(tmp), _("Save highlighted entry to address book or Export to filesystem ? "));
    else if(as.selections > 1)
      snprintf(tmp, sizeof(tmp), _("Save selected entries to address book or Export to filesystem ? "));
    else if(as.selections == 1)
      snprintf(tmp, sizeof(tmp), _("Save selected entry to address book or Export to filesystem ? "));
    else
      snprintf(tmp, sizeof(tmp), _("Save to address book or Export to filesystem ? "));

    i = radio_buttons(tmp, -FOOTER_ROWS(ps), save_or_export, 's', 'x',
		      h_ab_save_exp, RB_NORM);
    switch(i){
      case 'x':
	q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
	return(0);

      case 'e':
	return(ab_export(ps, cur_line, command_line, agg));

      case 's':
	break;

      default:
        q_status_message(SM_ORDER, 3, 3, "can't happen in ab_save");
	return(0);
    }

    pab_dst = setup_for_addrbook_add(&state, command_line, _("Save"));
    if(!pab_dst)
      goto get_out;

    pab = &as.adrbks[as.cur];

    dprint((2, "- ab_save: %s -> %s (agg=%d)-\n",
	   pab->abnick ? pab->abnick : "?",
	   pab_dst->abnick ? pab_dst->abnick : "?", agg));

    if(agg)
      act_list_size = as.selections;
    else
      act_list_size = 1;

    action_list = (ACTION_LIST_S *)fs_get((act_list_size+1) *
						    sizeof(ACTION_LIST_S));
    memset((void *)action_list, 0, (act_list_size+1) * sizeof(ACTION_LIST_S));
    al = action_list;

    if(agg){

	for(i = 0; i < as.n_addrbk; i++){
	    EXPANDED_S    *next_one;

	    pab = &as.adrbks[i];
	    if(pab->address_book)
	      next_one = pab->address_book->selects;
	    else
	      continue;

	    while((num = entry_get_next(&next_one)) != NO_NEXT){
		if(pab != pab_dst &&
		   pab->ostatus != Open &&
		   pab->ostatus != NoDisplay)
		  init_abook(pab, NoDisplay);

		if(pab->ostatus != Open && pab->ostatus != NoDisplay){
		    q_status_message1(SM_ORDER, 0, 4,
				 _("Can't re-open address book %s to save from"),
				 pab->abnick);
		    err++;
		    goto get_out;
		}

		set_act_list_member(al, (a_c_arg_t)num, pab_dst, pab, warning);
		if(al->skip)
		  how_many_no_action++;
		else{
		    if(al->dup){
			if(how_many_dups < 2 && warning[0]){
			    strncpy(warn[how_many_dups], warning, MAX_NICKNAME);
			    warn[how_many_dups][MAX_NICKNAME] = '\0';
			}
		    
			how_many_dups++;
		    }

		    how_many_to_copy++;
		}

		al++;
	    }
	}
    }
    else{
	if(is_addr(cur_line)){
	    AddrScrn_Disp *dl;

	    dl = dlist(cur_line);

	    if(dl->type == ListEnt)
	      special_case++;

	    if(pab && dl){
		num = dl->elnum;
		set_act_list_member(al, (a_c_arg_t)num, pab_dst, pab, warning);
	    }
	    else
	      al->skip = 1;

	    if(al->skip)
	      how_many_no_action++;
	    else{
		if(al->dup){
		    if(how_many_dups < 2 && warning[0]){
			strncpy(warn[how_many_dups], warning, MAX_NICKNAME);
			warn[how_many_dups][MAX_NICKNAME] = '\0';
		    }
		
		    how_many_dups++;
		}

		how_many_to_copy++;
	    }
	}
	else{
	    q_status_message(SM_ORDER, 0, 4, _("No current entry to save"));
	    goto get_out;
	}
    }

    if(how_many_to_copy == 0 && how_many_no_action == 1 && act_list_size == 1)
      special_case++;

    if(special_case){
	TA_STATE_S tas, *tasp;

	/* Not going to use the action_list now */
	if(action_list)
	  fs_give((void **)&action_list);

	tasp = &tas;
	tas.state = state;
	tas.pab   = pab_dst;
	take_this_one_entry(ps, &tasp, abook, cur_line);

	/*
	 * If take_this_one_entry or its children didn't do this for
	 * us, we do it here.
	 */
	if(tas.pab)
	  restore_state(&(tas.state));

	/*
	 * We don't have enough information to know what to return.
	 */
	return(0);
    }

    /* nothing to do (except for special Take case below) */
    if(how_many_to_copy == 0){
	if(how_many_no_action == 0){
	    err++;
	    goto get_out;
	}
	else{
	    restore_state(&state);

	    if(how_many_no_action > 1)
	      snprintf(tmp, sizeof(tmp), _("Saved %d entries to %s"), how_many_no_action, pab_dst->abnick);
	    else
	      snprintf(tmp, sizeof(tmp), _("Saved %d entry to %s"), how_many_no_action, pab_dst->abnick);

	    tmp[sizeof(tmp)-1] = '\0';
	    q_status_message(SM_ORDER, 0, 4, tmp);
	    if(action_list)
	      fs_give((void **)&action_list);

	    return(ret);
	}
    }

    /*
     * If there are some nicknames which already exist in the selected
     * abook, ask user what to do.
     */
    if(how_many_dups > 0){
	if(how_many_dups == 1)
	  snprintf(tmp, sizeof(tmp), _("Entry with nickname \"%.*s\" already exists, replace "),
		  MAX_NICKNAME, warn[0]);
	else if(how_many_dups == 2)
	  snprintf(tmp, sizeof(tmp),
		  _("Nicknames \"%.*s\" and \"%.*s\" already exist, replace "),
		  MAX_NICKNAME, warn[0], MAX_NICKNAME, warn[1]);
	else
	  snprintf(tmp, sizeof(tmp), _("%d of the nicknames already exist, replace "),
		  how_many_dups);

	tmp[sizeof(tmp)-1] = '\0';
	
	switch(want_to(tmp, 'n', 'x', h_ab_copy_dups, WT_NORM)){
	  case 'n':
	    skip_dups++;
	    if(how_many_to_copy == how_many_dups){
		restore_state(&state);
		if(action_list)
		  fs_give((void **)&action_list);

		q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
		return(ret);
	    }

	    break;

	  case 'y':
	    break;

	  case 'x':
	    err++;
	    goto get_out;
	}
    }

    /*
     * Because the deletes happen immediately we have to delete from high
     * entry number towards lower entry numbers so that we are deleting
     * the correct entries. In order to do that we'll sort the action_list
     * to give us a safe order.
     */
    if(!skip_dups && how_many_dups > 1)
      qsort((qsort_t *)action_list, (size_t)as.selections, sizeof(*action_list),
	    cmp_action_list);

    /*
     * Set up the busy alarm percent counters.
     */
    total_to_copy = how_many_to_copy - (skip_dups ? how_many_dups : 0);
    copied_so_far = 0;
    we_cancel = busy_cue(_("Saving entries"),
			 (total_to_copy > 4) ? percent_done_copying : NULL, 0);

    /*
     * Add the list of entries to the destination abook.
     */
    for(al = action_list; al && al->pab; al++){
	AdrBk_Entry   *abe;
	
	if(al->skip || (skip_dups && al->dup))
	  continue;

	if(!(abe = adrbk_get_ae(al->pab->address_book, (a_c_arg_t) al->num))){
	    q_status_message1(SM_ORDER | SM_DING, 3, 5,
			      _("Error saving entry: %s"),
			      error_description(errno));
	    err++;
	    goto get_out;
	}

	/*
	 * Delete existing dups and replace them.
	 */
	if(al->dup){

	    /* delete the existing entry */
	    rc = 0;
	    if(adrbk_delete(pab_dst->address_book,
			    (a_c_arg_t)al->num_in_dst, 1, 0, 0, 0) == 0){
		need_write++;
	    }
	    else{
		q_status_message2(SM_ORDER | SM_DING, 3, 5,
				 _("Error replacing entry in %s: %s"),
				 pab_dst->abnick,
				 error_description(errno));
		err++;
		goto get_out;
	    }
	}

	/*
	 * Now we have a clean slate to work with.
	 * Add (sorted in correctly) or append abe to the destination
	 * address book.
	 */
	if(total_to_copy <= 1)
	  rc = adrbk_add(pab_dst->address_book,
		         NO_NEXT,
		         abe->nickname,
		         abe->fullname,
		         abe->tag == Single ? abe->addr.addr : NULL,
		         abe->fcc,
		         abe->extra,
		         abe->tag,
		         &new_entry_num,
		         (int *)NULL,
		         0,
		         0,
		         0);
	else
	  rc = adrbk_append(pab_dst->address_book,
		            abe->nickname,
		            abe->fullname,
		            abe->tag == Single ? abe->addr.addr : NULL,
		            abe->fcc,
		            abe->extra,
		            abe->tag,
			    &new_entry_num);

	if(rc == 0)
	  need_write++;
	
	/*
	 * If the entry we copied is a list, we also have to add
	 * the list members to the copy.
	 */
	if(rc == 0 && abe->tag == List){
	    int save_sort_rule;

	    /*
	     * We want it to copy the list in the exact order
	     * without sorting it.
	     */
	    save_sort_rule = pab_dst->address_book->sort_rule;
	    pab_dst->address_book->sort_rule = AB_SORT_RULE_NONE;

	    rc = adrbk_nlistadd(pab_dst->address_book,
				(a_c_arg_t)new_entry_num, NULL, NULL,
				abe->addr.list,
				0, 0, 0);

	    pab_dst->address_book->sort_rule = save_sort_rule;
	}

	if(rc != 0){
	    if(abe && abe->nickname)
	      q_status_message2(SM_ORDER | SM_DING, 3, 5, _("Error saving %s: %s"), abe->nickname, error_description(errno));
	    else
	      q_status_message1(SM_ORDER | SM_DING, 3, 5, _("Error saving entry: %s"), error_description(errno));
	    err++;
	    goto get_out;
	}

	copied_so_far++;
    }

    if(need_write){
	int sort_happened = 0;

	if(adrbk_write(pab_dst->address_book, 0, NULL, &sort_happened, 0, 1)){
	    err++;
	    goto get_out;
	}

	if(sort_happened)
	  ps_global->mangled_screen = 1;
    }

get_out:
    if(we_cancel)
      cancel_busy_cue(1);

    restore_state(&state);
    if(action_list)
      fs_give((void **)&action_list);

    ps_global->mangled_footer = 1;

    if(err){
	ret = 0;
	if(need_write)
	  q_status_message(SM_ORDER | SM_DING, 3, 4,
			   _("Save only partially completed"));
	else
	  q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
    }
    else if (how_many_to_copy + how_many_no_action -
	     (skip_dups ? how_many_dups : 0) > 0){

	ret = 1;
	snprintf(tmp, sizeof(tmp), "Saved %d %s to %s",
		how_many_to_copy + how_many_no_action -
		(skip_dups ? how_many_dups : 0),
		((how_many_to_copy + how_many_no_action -
		  (skip_dups ? how_many_dups : 0)) > 1) ? "entries" : "entry",
		pab_dst->abnick);
	tmp[sizeof(tmp)-1] = '\0';
	q_status_message(SM_ORDER, 0, 4, tmp);
    }

    return(ret);
}


/*
 * Warn should point to an array of size MAX_NICKNAME+1.
 */
void
set_act_list_member(ACTION_LIST_S *al, a_c_arg_t numarg, PerAddrBook *pab_dst, PerAddrBook *pab, char *warn)
{
    AdrBk_Entry   *abe1, *abe2;
    adrbk_cntr_t   num;

    num = (adrbk_cntr_t)numarg;

    al->pab = pab;
    al->num = num;

    /* skip if they're copying from and to same addrbook */
    if(pab == pab_dst)
      al->skip = 1;
    else{
	abe1 = adrbk_get_ae(pab->address_book, numarg);
	if(abe1 && abe1->nickname && abe1->nickname[0]){
	    adrbk_cntr_t dst_enum;

	    abe2 = adrbk_lookup_by_nick(pab_dst->address_book,
					abe1->nickname, &dst_enum);
	    /*
	     * This nickname already exists in the destn address book.
	     */
	    if(abe2){
		/* If it isn't different, no problem. Check it out. */
		if(abes_are_equal(abe1, abe2))
		  al->skip = 1;
		else{
		    strncpy(warn, abe1->nickname, MAX_NICKNAME);
		    warn[MAX_NICKNAME] = '\0';
		    al->dup = 1;
		    al->num_in_dst = dst_enum;
		}
	    }
	}
    }
}


/*
 * Print out the display list.
 */
int
ab_print(int agg)
{
    int do_entry = 0, curopen;
    char *prompt;

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

    curopen = cur_is_open();
    if(!agg && curopen){
	static ESCKEY_S prt[] = {
	    {'a', 'a', "A", N_("AddressBook")},
	    {'e', 'e', "E", N_("Entry")},
	    {-1, 0, NULL, NULL}};

	prompt = _("Print Address Book or just this Entry? ");
	switch(radio_buttons(prompt, -FOOTER_ROWS(ps_global), prt, 'a', 'x',
			     NO_HELP, RB_NORM)){
	  case 'x' :
	    q_status_message(SM_INFO, 0, 2, _("Address book print cancelled"));
	    ps_global->mangled_footer = 1;
	    return 0;

	  case 'e':
	    do_entry = 1;
	    break;

	  default:
	  case 'a':
	    break;
	}
    }

    /* TRANSLATORS: This is input for
       Print something1 using something2. The thing we're
       defining here is something1. */
    if(agg)
      prompt = _("selected entries");
    else{
	if(!curopen)
	  prompt = _("address book list");
	else if(do_entry)
	  prompt = _("entry");
	else
	  prompt = _("address book");
    }

    if(open_printer(prompt) == 0){
	DL_CACHE_S     dlc_buf, *match_dlc;
	AddrScrn_Disp *dl; 
	AdrBk_Entry   *abe;
	long           save_line;
	char          *addr;
	char           spaces[100];
	char           more_spaces[100];
	char           b[500];
	int            abook_indent;

	save_line = as.top_ent + as.cur_row;
	match_dlc = get_dlc(save_line);
	dlc_buf   = *match_dlc;
	match_dlc = &dlc_buf;

	if(do_entry){		/* print an individual addrbook entry */

	    abook_indent = utf8_width(_("Nickname")) + 2;

	    snprintf(spaces, sizeof(spaces), "%*.*s", abook_indent+2, abook_indent+2, "");
	    snprintf(more_spaces, sizeof(more_spaces), "%*.*s",
		     abook_indent+4, abook_indent+4, "");

	    dl = dlist(save_line);
	    abe = ae(save_line);

	    if(abe){
		int      are_some_unqualified = 0, expand_nicks = 0;
		char    *string, *tmp;
		ADDRESS *adrlist = NULL;

		/*
		 * Search through the addresses to see if there are any
		 * that are unqualified, and so would be different if
		 * expanded.
		 */
		if(abe->tag == Single){
		    if(abe->addr.addr && abe->addr.addr[0]
		       && !strindex(abe->addr.addr, '@'))
		      are_some_unqualified++;
		}
		else{
		    char **ll;

		    for(ll = abe->addr.list; ll && *ll; ll++){
			if(!strindex(*ll, '@')){
			    are_some_unqualified++;
			    break;
			}
		    }
		}

		if(are_some_unqualified){
		    switch(want_to("Expand nicknames", 'y', 'x', h_ab_forward,
								    WT_NORM)){
		      case 'x':
			q_status_message(SM_INFO, 0, 2, _("Address book print cancelled"));
			ps_global->mangled_footer = 1;
			return 0;
			
		      case 'y':
			expand_nicks = 1;
			break;
			
		      case 'n':
			expand_nicks = 0;
			break;
		    }
		}

		/* expand nicknames and fully-qualify unqualified names */
		if(expand_nicks){
		    char    *error = NULL;
		    BuildTo  bldto;
		    char    *init_addr = NULL;

		    if(abe->tag == Single)
		      init_addr = cpystr(abe->addr.addr);
		    else{
			char **ll;
			char  *p;
			long   length;

			/* figure out how large a string we need to allocate */
			length = 0L;
			for(ll = abe->addr.list; ll && *ll; ll++)
			  length += (strlen(*ll) + 2);

			if(length)
			  length -= 2L;
			
			init_addr = (char *)fs_get((size_t)(length+1L) *
								sizeof(char));
			p = init_addr;
		    
			for(ll = abe->addr.list; ll && *ll; ll++){
			    sstrncpy(&p, *ll, length-(p-init_addr));
			    if(*(ll+1))
			      sstrncpy(&p, ", ", length-(p-init_addr));
			}

			init_addr[length] = '\0';
		    }

		    bldto.type    = Str;
		    bldto.arg.str = init_addr; 
		    our_build_address(bldto, &addr, &error, NULL, NULL);
		    if(init_addr)
		      fs_give((void **)&init_addr);

		    if(error){
			q_status_message1(SM_ORDER, 0, 4, "%s", error);
			fs_give((void **)&error);

			ps_global->mangled_footer = 1;
			return 0;
		    }

		    if(addr){
			rfc822_parse_adrlist(&adrlist, addr,
					     ps_global->maildomain);
			fs_give((void **)&addr);
		    }

		    /* Will use adrlist to do the printing below */
		}

		tmp = abe->nickname ? abe->nickname : "";
		string = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp);
		utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
		if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
		    print_text(tmp);
		    fs_give((void **)&tmp);
		}

		tmp = abe->fullname ? abe->fullname : "";
		string = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp);
		utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fullname"));
		if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
		    print_text(tmp);
		    fs_give((void **)&tmp);
		}

		tmp = abe->fcc ? abe->fcc : "";
		string = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp);
		utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fcc"));
		if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
		    print_text(tmp);
		    fs_give((void **)&tmp);
		}

		tmp = abe->extra ? abe->extra : "";
		{unsigned char *p, *bb = NULL;
		 size_t n, len;
		 if((n = 4*strlen(tmp)) > SIZEOF_20KBUF-1){
		     len = n+1;
		     p = bb = (unsigned char *)fs_get(len * sizeof(char));
		 }
		 else{
		     len = SIZEOF_20KBUF;
		     p = (unsigned char *)tmp_20k_buf;
		 }

		 string = (char *)rfc1522_decode_to_utf8(p, len, tmp);
		 utf8_snprintf(b, len, "%-*.*w: ", abook_indent, abook_indent, _("Comment"));
		 if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
		    print_text(tmp);
		    fs_give((void **)&tmp);
		 }

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

		/*
		 * Print addresses
		 */

		if(expand_nicks){
		    ADDRESS *a;

		    for(a = adrlist; a; a = a->next){
			char    *bufp;
			ADDRESS *next_addr;
			size_t   len;

			next_addr = a->next;
			a->next = NULL;
			len = est_size(a);
			bufp = (char *) fs_get(len * sizeof(char));
			tmp = addr_string(a, bufp, len);
			a->next = next_addr;
			string = (char *)rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf+10000,
								SIZEOF_20KBUF-10000, tmp);
			utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
			if((tmp = fold(string, 80, 80,
				      (a == adrlist) ? b : spaces,
				      more_spaces, FLD_NONE)) != NULL){
			    print_text(tmp);
			    fs_give((void **)&tmp);
			}

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

		    if(adrlist)
		      mail_free_address(&adrlist);
		    else{
			utf8_snprintf(b, sizeof(b), "%-*.*w:\n", abook_indent, abook_indent, _("Addresses"));
		        print_text(b);
		    }
		}
		else{ /* don't expand or qualify */
		    if(abe->tag == Single){
			tmp = abe->addr.addr ? abe->addr.addr : "";
			string = (char *)rfc1522_decode_to_utf8((unsigned char *) (tmp_20k_buf+10000),
								SIZEOF_20KBUF-10000, tmp);
			utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
			if((tmp = fold(string, 80, 80, b,
				      more_spaces, FLD_NONE)) != NULL){
			    print_text(tmp);
			    fs_give((void **)&tmp);
			}
		    }
		    else{
			char **ll;

			if(!abe->addr.list || !abe->addr.list[0]){
			    utf8_snprintf(b, sizeof(b), "%-*.*w:\n", abook_indent, abook_indent, _("Addresses"));
			    print_text(b);
			}

			for(ll = abe->addr.list; ll && *ll; ll++){
			    string = (char *)rfc1522_decode_to_utf8((unsigned char *) (tmp_20k_buf+10000),
								    SIZEOF_20KBUF-10000, *ll);
			    utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
			    if((tmp = fold(string, 80, 80,
					  (ll == abe->addr.list)
					    ? b :  spaces,
					  more_spaces, FLD_NONE)) != NULL){
				print_text(tmp);
				fs_give((void **)&tmp);
			    }
			}
		    }
		}
	    }
	}
	else{
	    long         lineno;
	    char         lbuf[6*MAX_SCREEN_COLS + 1];
	    char        *p;
	    int          i, savecur, savezoomed;
	    OpenStatus   savestatus;
	    PerAddrBook *pab;

	    if(agg){		/* print all selected entries */
		if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
		    savezoomed = as.zoomed;
		    /*
		     * Fool display code into thinking display is zoomed, so
		     * we'll skip the unselected entries. Since this feature
		     * causes all abooks to be displayed we don't have to
		     * step through each addrbook.
		     */
		    as.zoomed = 1;
		    warp_to_beginning();
		    lineno = 0L;
		    for(dl = dlist(lineno);
			dl->type != End;
			dl = dlist(++lineno)){

			switch(dl->type){
			  case Beginning:
			  case ListClickHere:
			  case ListEmpty:
			  case ClickHereCmb:
			  case Text:
			  case TitleCmb:
			  case Empty:
			  case ZoomEmpty:
			  case AskServer:
			    continue;
			  default:
			    break;
			}

			p = get_abook_display_line(lineno, 0, NULL, NULL,
						   NULL, lbuf, sizeof(lbuf));
			print_text1("%s\n", p);
		    }

		    as.zoomed = savezoomed;
		}
		else{		/* print all selected entries */
		    savecur    = as.cur;
		    savezoomed = as.zoomed;
		    as.zoomed  = 1;

		    for(i = 0; i < as.n_addrbk; i++){
			pab = &as.adrbks[i];
			if(!(pab->address_book &&
			     any_selected(pab->address_book->selects)))
			  continue;
			
			/*
			 * Print selected entries from addrbook i.
			 * We have to put addrbook i into Open state so
			 * that the display code will work right.
			 */
			as.cur = i;
			savestatus = pab->ostatus;
			init_abook(pab, Open);
			init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i);
			(void)calculate_field_widths();
			warp_to_beginning();
			lineno = 0L;

			for(dl = dlist(lineno);
			    dl->type != End;
			    dl = dlist(++lineno)){

			    switch(dl->type){
			      case Beginning:
			      case ListClickHere:
			      case ClickHereCmb:
				continue;
			      default:
				break;
			    }

			    p = get_abook_display_line(lineno, 0, NULL, NULL,
						       NULL, lbuf,sizeof(lbuf));
			    print_text1("%s\n", p);
			}

			init_abook(pab, savestatus);
		    }

		    as.cur     = savecur;
		    as.zoomed  = savezoomed;
		    /* restore the display for the current addrbook  */
		    init_disp_form(&as.adrbks[as.cur],
				   ps_global->VAR_ABOOK_FORMATS, as.cur);
		}
	    }
	    else{	/* print either the abook list or a single abook */
		int anum;
		DL_CACHE_S *dlc;

		savezoomed = as.zoomed;
		as.zoomed = 0;

		if(curopen){	/* print a single address book */
		    anum = adrbk_num_from_lineno(as.top_ent+as.cur_row);
		    warp_to_top_of_abook(anum);
		    if(F_ON(F_CMBND_ABOOK_DISP,ps_global))
		      lineno = 0L - XTRA_TITLE_LINES_IN_OLD_ABOOK_DISP;
		    else{
			print_text("      ");
			print_text(_("ADDRESS BOOK"));
			print_text1(" %s\n\n", as.adrbks[as.cur].abnick);
			lineno = 0L;
		    }
		}
		else{		/* print the list of address books */
		    warp_to_beginning();
		    lineno = 0L;
		}

		for(dl = dlist(lineno);
		    dl->type != End && (!curopen ||
					(anum==adrbk_num_from_lineno(lineno) &&
					 (as.n_serv == 0 ||
					  ((dlc=get_dlc(lineno)) &&
					   dlc->type != DlcDirDelim1))));
		    dl = dlist(++lineno)){

		    switch(dl->type){
		      case Beginning:
		      case ListClickHere:
		      case ClickHereCmb:
			continue;
		      default:
			break;
		    }

		    p = get_abook_display_line(lineno, 0, NULL, NULL, NULL,
					       lbuf, sizeof(lbuf));
		    print_text1("%s\n", p);
		}

		as.zoomed = savezoomed;
	    }
	}

	close_printer();

	/*
	 * jump cache back to where we started so that the next
	 * request won't cause us to page through the whole thing
	 */
	if(!do_entry)
	  warp_to_dlc(match_dlc, save_line);

	ps_global->mangled_screen = 1;
    }

    ps_global->mangled_footer = 1;
    return 1;
}


/*
 * Delete address book entries.
 */
int
ab_agg_delete(struct pine *ps, int agg)
{
    int ret = 0, i, ch, rc = 0;
    PerAddrBook *pab;
    adrbk_cntr_t num, ab_count;
    char prompt[80];

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

    if(agg){
	snprintf(prompt, sizeof(prompt), _("Really delete %d selected entries"), as.selections);
	prompt[sizeof(prompt)-1] = '\0';
	ch = want_to(prompt, 'n', 'n', NO_HELP, WT_NORM);
	if(ch == 'y'){
	    adrbk_cntr_t   newelnum, flushelnum = NO_NEXT;
	    DL_CACHE_S     dlc_save, dlc_restart, *dlc;
	    int            we_cancel = 0;
	    int            top_level_display;
	    
	    /*
	     * We want to try to put the cursor in a reasonable position
	     * on the screen when we're done. If we are in the top-level
	     * display, then we can leave it the same. If we have an
	     * addrbook opened, then we want to see if we can get back to
	     * the same entry we are currently on.
	     */
	    if(!(top_level_display = !any_ab_open())){
		dlc = get_dlc(as.top_ent+as.cur_row);
		dlc_save = *dlc;
		newelnum = dlc_save.dlcelnum;
	    }
	    
	    we_cancel = busy_cue(NULL, NULL, 1);

	    for(i = 0; i < as.n_addrbk && rc != -5; i++){
		int orig_selected, selected;

		pab = &as.adrbks[i];
		if(!pab->address_book)
		  continue;

		ab_count = adrbk_count(pab->address_book);
		rc = 0;
		selected = howmany_selected(pab->address_book->selects);
		orig_selected = selected;
		/*
		 * Because deleting an entry causes the addrbook to be
		 * immediately updated, we need to delete from higher entry
		 * numbers to lower numbers. That way, entry number n is still
		 * entry number n in the updated address book because we've
		 * only deleted entries higher than n.
		 */
		for(num = ab_count-1; selected > 0 && rc == 0; num--){
		    if(entry_is_selected(pab->address_book->selects,
				         (a_c_arg_t)num)){
			rc = adrbk_delete(pab->address_book, (a_c_arg_t)num,
					  1, 0, 0, 0);

			selected--;

			/*
			 * This is just here to help us reposition the cursor.
			 */
			if(!top_level_display && as.cur == i && rc == 0){
			    if(num >= newelnum)
			      flushelnum = num;
			    else
			      newelnum--;
			}
		    }
		}

		if(rc == 0 && orig_selected > 0){
		    int sort_happened = 0;

		    rc = adrbk_write(pab->address_book, 0, NULL, &sort_happened, 1, 0);
		    if(sort_happened)
		      ps_global->mangled_screen = 1;
		}

		if(rc && rc != -5){
		    q_status_message2(SM_ORDER | SM_DING, 3, 5,
			              "Error updating %s: %s",
				      (as.n_addrbk > 1) ? pab->abnick
						        : "address book",
				      error_description(errno));
		    dprint((1, "Error updating %s: %s\n",
			   pab->filename ? pab->filename : "?",
			   error_description(errno)));
		}
	    }

	    if(we_cancel)
	      cancel_busy_cue(-1);
	    
	    if(rc == 0){
		q_status_message(SM_ORDER, 0, 2, _("Deletions completed"));
		ret = 1;
		erase_selections();
	    }

	    if(!top_level_display){
		int lost = 0;
		long new_ent;

		if(flushelnum != dlc_save.dlcelnum){
		    /*
		     * We didn't delete current so restart there. The elnum
		     * may have changed if we deleted entries above it.
		     */
		    dlc_restart = dlc_save;
		    dlc_restart.dlcelnum  = newelnum;
		}
		else{
		    /*
		     * Current was deleted.
		     */
		    dlc_restart.adrbk_num = as.cur;
		    pab = &as.adrbks[as.cur];
		    ab_count = adrbk_count(pab->address_book);
		    if(ab_count == 0)
		      dlc_restart.type = DlcEmpty;
		    else{
			AdrBk_Entry     *abe;

			dlc_restart.dlcelnum  = MIN(newelnum, ab_count-1);
			abe = adrbk_get_ae(pab->address_book,
					   (a_c_arg_t) dlc_restart.dlcelnum);
			if(abe && abe->tag == Single)
			  dlc_restart.type = DlcSimple;
			else if(abe && abe->tag == List)
			  dlc_restart.type = DlcListHead;
			else
			  lost++;
		    }
		}

		if(lost){
		    warp_to_top_of_abook(as.cur);
		    as.top_ent = 0L;
		    new_ent    = first_selectable_line(0L);
		    if(new_ent == NO_LINE)
		      as.cur_row = 0L;
		    else
		      as.cur_row = new_ent;
		    
		    /* if it is off screen */
		    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);
		    }
		}
		else if(dlc_restart.type != DlcEmpty &&
			dlc_restart.dlcelnum == dlc_save.dlcelnum &&
			(F_OFF(F_CMBND_ABOOK_DISP,ps_global) || as.cur == 0)){
		    /*
		     * Didn't delete any before this line.
		     * Leave screen about the same. (May have deleted current.)
		     */
		    warp_to_dlc(&dlc_restart, as.cur_row+as.top_ent);
		}
		else{
		    warp_to_dlc(&dlc_restart, 0L);
		    /* put in middle of screen */
		    as.top_ent = first_line(0L - (long)as.l_p_page/2L);
		    as.cur_row = 0L - as.top_ent;
		}

		ps->mangled_body = 1;
	    }
	}
	else
	  cmd_cancelled("Apply Delete command");
    }

    return(ret);
}


/*
 * Delete an entry from the address book
 *
 * Args: abook        -- The addrbook handle into access library
 *       command_line -- The screen line on which to prompt
 *       cur_line     -- The entry number in the display list
 *       warped       -- We warped to a new part of the addrbook
 *
 * Result: returns 1 if an entry was deleted, 0 if not.
 *
 * The main routine above knows what to repaint because it's always the
 * current entry that's deleted.  Here confirmation is asked of the user
 * and the appropriate adrbklib functions are called.
 */
int
single_entry_delete(AdrBk *abook, long int cur_line, int *warped)
{
    char   ch, *cmd, *dname;
    char   prompt[200];
    int    rc;
    register AddrScrn_Disp *dl;
    AdrBk_Entry     *abe;
    DL_CACHE_S      *dlc_to_flush;

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

    if(warped)
      *warped = 0;

    dl  = dlist(cur_line);
    abe = adrbk_get_ae(abook, (a_c_arg_t) dl->elnum);

    switch(dl->type){
      case Simple:
	dname =	(abe->fullname && abe->fullname[0])
			? (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
							 SIZEOF_20KBUF, abe->fullname)
			: abe->nickname ? abe->nickname : "";
        cmd   = _("Really delete \"%s\"");
        break;

      case ListHead:
	dname =	(abe->fullname && abe->fullname[0])
			? (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
							 SIZEOF_20KBUF, abe->fullname)
			: abe->nickname ? abe->nickname : "";
	cmd   = _("Really delete ENTIRE list \"%s\"");
        break;

      case ListEnt:
        dname = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
					       SIZEOF_20KBUF, listmem_from_dl(abook, dl));
	cmd   = _("Really delete \"%s\" from list");
        break;

      default:
        break;
    } 

    dname = dname ? dname : "";
    cmd   = cmd   ? cmd   : "";

    snprintf(prompt, sizeof(prompt), cmd, dname);
    prompt[sizeof(prompt)-1] = '\0';
    ch = want_to(prompt, 'n', 'n', NO_HELP, WT_NORM);
    if(ch == 'y'){
	dlc_to_flush = get_dlc(cur_line);
	if(dl->type == Simple || dl->type == ListHead){
	    /*--- Kill a single entry or an entire list ---*/
            rc = adrbk_delete(abook, (a_c_arg_t)dl->elnum, 1, 1, 0, 1);
	}
	else if(listmem_count_from_abe(abe) > 2){
            /*---- Kill an entry out of a list ----*/
            rc = adrbk_listdel(abook, (a_c_arg_t)dl->elnum,
		    listmem_from_dl(abook, dl));
	}
	else{
	    char *nick, *full, *addr, *fcc, *comment;
	    adrbk_cntr_t new_entry_num = NO_NEXT;

	    /*---- Convert a List to a Single entry ----*/

	    /* Save old info to be transferred */
	    nick    = cpystr(abe->nickname);
	    full    = cpystr(abe->fullname);
	    fcc     = cpystr(abe->fcc);
	    comment = cpystr(abe->extra);
	    if(listmem_count_from_abe(abe) == 2)
	      addr = cpystr(abe->addr.list[1 - dl->l_offset]);
	    else
	      addr = cpystr("");

            rc = adrbk_delete(abook, (a_c_arg_t)dl->elnum, 0, 1, 0, 0);
	    if(rc == 0)
	      adrbk_add(abook,
			NO_NEXT,
			nick,
			full,
			addr,
			fcc,
			comment,
			Single,
			&new_entry_num,
			(int *)NULL,
			1,
			0,
			1);

	    fs_give((void **)&nick);
	    fs_give((void **)&full);
	    fs_give((void **)&fcc);
	    fs_give((void **)&comment);
	    fs_give((void **)&addr);

	    if(rc == 0){
		DL_CACHE_S dlc_restart;

		dlc_restart.adrbk_num = as.cur;
		dlc_restart.dlcelnum  = new_entry_num;
		dlc_restart.type = DlcSimple;
		warp_to_dlc(&dlc_restart, 0L);
		*warped = 1;
		return 1;
	    }
	}

	if(rc == 0){
	    q_status_message(SM_ORDER, 0, 3,
		_("Entry deleted, address book updated"));
            dprint((5, "abook: Entry %s\n",
		(dl->type == Simple || dl->type == ListHead) ? "deleted"
							     : "modified"));
	    /*
	     * Remove deleted line and everything after it from
	     * the dlc cache.  Next time we try to access those lines they
	     * will get filled in with the right info.
	     */
	    flush_dlc_from_cache(dlc_to_flush);
            return 1;
        }
	else{
	    PerAddrBook     *pab;

	    if(rc != -5)
              q_status_message1(SM_ORDER | SM_DING, 3, 5,
			      _("Error updating address book: %s"),
		    error_description(errno));
	    pab = &as.adrbks[as.cur];
            dprint((1, "Error deleting entry from %s (%s): %s\n",
		   pab->abnick ? pab->abnick : "?",
		   pab->filename ? pab->filename : "?",
		   error_description(errno)));
        }

	return 0;
    }
    else{
	q_status_message(SM_INFO, 0, 2, _("Entry not deleted"));
	return 0;
    }
}


void
free_headents(struct headerentry **head)
{
    struct headerentry  *he;
    PrivateTop          *pt;

    if(head && *head){
	for(he = *head; he->name; he++)
	  if(he->bldr_private){
	      pt = (PrivateTop *)he->bldr_private;
	      free_privatetop(&pt);
	  }

	fs_give((void **)head);
    }
}


#ifdef	ENABLE_LDAP
/*
prompt::name::help::prwid::maxlen::realaddr::
builder::affected_entry::next_affected::selector::key_label::fileedit::
display_it::break_on_comma::is_attach::rich_header::only_file_chars::
single_space::sticky::dirty::start_here::blank::KS_ODATAVAR
*/
static struct headerentry headents_for_query[]={
  {"Normal Search : ",  "NormalSearch",  h_composer_qserv_qq, 16, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Name          : ",  "Name",  h_composer_qserv_cn, 16, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Surname       : ",  "SurName",  h_composer_qserv_sn, 16, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Given Name    : ",  "GivenName",  h_composer_qserv_gn, 16, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Email Address : ",  "EmailAddress",  h_composer_qserv_mail, 16, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Organization  : ",  "Organization",  h_composer_qserv_org, 16, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Org Unit      : ",  "OrganizationalUnit", h_composer_qserv_unit, 16, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Country       : ",  "Country",  h_composer_qserv_country, 16, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"State         : ",  "State",  h_composer_qserv_state, 16, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {"Locality      : ",  "Locality",  h_composer_qserv_locality, 16, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {" ",  "BlankLine",  NO_HELP, 1, 0, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 1, KS_NONE},
  {"Custom Filter : ",  "CustomFilter",  h_composer_qserv_custom, 16, 0, NULL,
   NULL, NULL, NULL, NULL,  NULL, NULL, NULL,
   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
  {NULL, NULL, NO_HELP, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
};
#define QQ_QQ		0
#define QQ_CN		1
#define QQ_SN		2
#define QQ_GN		3
#define QQ_MAIL		4
#define QQ_ORG		5
#define QQ_UNIT		6
#define QQ_COUNTRY	7
#define QQ_STATE	8
#define QQ_LOCALITY	9
#define QQ_BLANK	10
#define QQ_CUSTOM	11
#define QQ_END		12

static SAVED_QUERY_S *saved_params;


/*
 * Formulate a query for LDAP servers and send it and view it.
 * This is called from the Address Book screen, either from ^T from the
 * composer (selecting) or just when browsing in the address book screen
 * (selecting == 0).
 *
 * Args    ps -- Pine struct
 *  selecting -- This is set if we're just selecting an address, as opposed
 *               to browsing on an LDAP server
 *        who -- Tells us which server to query
 *      error -- An error message allocated here and freed by caller.
 *
 * Returns -- Null if not selecting, possibly an address if selecting.
 *            The address is 1522 decoded and should be freed by the caller.
 */
char *
query_server(struct pine *ps, int selecting, int *exit, int who, char **error)
{
    struct headerentry *he = NULL;
    PICO pbf;
    STORE_S *msgso = NULL;
    int i, lret, editor_result;
    int r = 4, flags;
    HelpType help = NO_HELP;
#define FILTSIZE 1000
    char fbuf[FILTSIZE+1];
    char *ret = NULL;
    LDAP_CHOOSE_S *winning_e = NULL;
    LDAP_SERV_RES_S *free_when_done = NULL;
    SAVED_QUERY_S *sq = NULL;
    static ESCKEY_S ekey[] = {
	/* TRANSLATORS: go to more complex search screen */
	{ctrl('T'), 10, "^T", N_("To complex search")},
	{-1, 0, NULL, NULL}
    };

    dprint((2, "- query_server(%s) -\n", selecting?"Selecting":""));

    if(!(ps->VAR_LDAP_SERVERS && ps->VAR_LDAP_SERVERS[0] &&
         ps->VAR_LDAP_SERVERS[0][0])){
	if(error)
	  *error = cpystr(_("No LDAP server available for lookup"));

	return(ret);
    }

    fbuf[0] = '\0';
    flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
    while(r == 4 || r == 3){
	/* TRANSLATORS: we're asking for a character string to search for */
	r = optionally_enter(fbuf, -FOOTER_ROWS(ps), 0, sizeof(fbuf),
			     _("String to search for : "),
			     ekey, help, &flags);
	if(r == 3)
	  help = help == NO_HELP ? h_dir_comp_search : NO_HELP;
    }

    /* strip quotes that user typed by mistake */
    (void)removing_double_quotes(fbuf);

    if(r == 1 || (r != 10 && fbuf[0] == '\0')){
	ps->mangled_footer = 1;
	if(error)
	  *error = cpystr(_("Cancelled"));

	return(ret);
    }

    editor_result = COMP_EXIT;  /* just to get right logic below */

    memset((void *)&pbf, 0, sizeof(pbf));

    if(r == 10){
	standard_picobuf_setup(&pbf);
	pbf.exittest      = pico_simpleexit;
	pbf.exit_label    = _("Search");
	pbf.canceltest    = pico_simplecancel;
	if(saved_params){
	    pbf.expander      = restore_query_parameters;
	    pbf.ctrlr_label   = _("Restore");
	}

	pbf.pine_anchor   = set_titlebar(_("SEARCH DIRECTORY SERVER"),
					  ps_global->mail_stream,
					  ps_global->context_current,
					  ps_global->cur_folder,
					  ps_global->msgmap, 
					  0, FolderName, 0, 0, NULL);
	pbf.pine_flags   |= P_NOBODY;

	/* An informational message */
	if((msgso = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
	    pbf.msgtext = (void *)so_text(msgso);
	    /*
	     * It's nice if we can make it so these lines make sense even if
	     * they don't all make it on the screen, because the user can't
	     * scroll down to see them.  So just make each line a whole sentence
	     * that doesn't need the others below it to make sense.
	     */
	    so_puts(msgso,
_("\n Fill in some of the fields above to create a query."));
	    so_puts(msgso,
_("\n The match will be for the exact string unless you include wildcards (*)."));
	    so_puts(msgso,
_("\n All filled-in fields must match in order to be counted as a match."));
	    so_puts(msgso,
_("\n Press \"^R\" to restore previous query values (if you've queried previously)."));
	    so_puts(msgso,
_("\n \"^G\" for help specific to each item. \"^X\" to make the query, or \"^C\" to cancel."));
	}

	he = (struct headerentry *)fs_get((QQ_END+1) *
						sizeof(struct headerentry));
	memset((void *)he, 0, (QQ_END+1) * sizeof(struct headerentry));
	for(i = QQ_QQ; i <= QQ_END; i++)
	  he[i] = headents_for_query[i];

	pbf.headents = he;

	sq = copy_query_parameters(NULL);
	he[QQ_QQ].realaddr		= &sq->qq;
	he[QQ_CN].realaddr		= &sq->cn;
	he[QQ_SN].realaddr		= &sq->sn;
	he[QQ_GN].realaddr		= &sq->gn;
	he[QQ_MAIL].realaddr		= &sq->mail;
	he[QQ_ORG].realaddr		= &sq->org;
	he[QQ_UNIT].realaddr		= &sq->unit;
	he[QQ_COUNTRY].realaddr		= &sq->country;
	he[QQ_STATE].realaddr		= &sq->state;
	he[QQ_LOCALITY].realaddr	= &sq->locality;
	he[QQ_CUSTOM].realaddr		= &sq->custom;

	/* pass to pico and let user set them */
	editor_result = pico(&pbf);
	ps->mangled_screen = 1;
	standard_picobuf_teardown(&pbf);

	if(editor_result & COMP_GOTHUP)
	  hup_signal();
	else{
	    fix_windsize(ps_global);
	    init_signals();
	}
    }

    if(editor_result & COMP_EXIT &&
       ((r == 0 && *fbuf) ||
        (r == 10 && sq &&
	 (*sq->qq || *sq->cn || *sq->sn || *sq->gn || *sq->mail ||
	  *sq->org || *sq->unit || *sq->country || *sq->state ||
	  *sq->locality || *sq->custom)))){
	LDAPLookupStyle  style;
	WP_ERR_S         wp_err;
	int              need_and, mangled;
	char            *string;
	CUSTOM_FILT_S   *filter;
	SAVED_QUERY_S   *s;

	s = copy_query_parameters(sq);
	save_query_parameters(s);

	if(r == 0){
	    string = fbuf;
	    filter = NULL;
	}
	else{
	    int categories = 0;

	    categories = ((*sq->cn != '\0') ? 1 : 0) +
			 ((*sq->sn != '\0') ? 1 : 0) +
			 ((*sq->gn != '\0') ? 1 : 0) +
			 ((*sq->mail != '\0') ? 1 : 0) +
			 ((*sq->org != '\0') ? 1 : 0) +
			 ((*sq->unit != '\0') ? 1 : 0) +
			 ((*sq->country != '\0') ? 1 : 0) +
			 ((*sq->state != '\0') ? 1 : 0) +
			 ((*sq->locality != '\0') ? 1 : 0);
	    need_and = (categories > 1);

	    if(((sq->cn ? strlen(sq->cn) : 0) +
	        (sq->sn ? strlen(sq->sn) : 0) +
	        (sq->gn ? strlen(sq->gn) : 0) +
	        (sq->mail ? strlen(sq->mail) : 0) +
	        (sq->org ? strlen(sq->org) : 0) +
	        (sq->unit ? strlen(sq->unit) : 0) +
	        (sq->country ? strlen(sq->country) : 0) +
	        (sq->state ? strlen(sq->state) : 0) +
	        (sq->locality ? strlen(sq->locality) : 0)) > FILTSIZE - 100){
		if(error)
		  *error = cpystr(_("Search strings too long"));
		
		goto all_done;
	    }

	    if(categories > 0){

		snprintf(fbuf, sizeof(fbuf),
		   "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
			need_and ? "(&" : "",
			*sq->cn ? "(cn=" : "",
			*sq->cn ? sq->cn : "",
			*sq->cn ? ")" : "",
			*sq->sn ? "(sn=" : "",
			*sq->sn ? sq->sn : "",
			*sq->sn ? ")" : "",
			*sq->gn ? "(givenname=" : "",
			*sq->gn ? sq->gn : "",
			*sq->gn ? ")" : "",
			*sq->mail ? "(mail=" : "",
			*sq->mail ? sq->mail : "",
			*sq->mail ? ")" : "",
			*sq->org ? "(o=" : "",
			*sq->org ? sq->org : "",
			*sq->org ? ")" : "",
			*sq->unit ? "(ou=" : "",
			*sq->unit ? sq->unit : "",
			*sq->unit ? ")" : "",
			*sq->country ? "(c=" : "",
			*sq->country ? sq->country : "",
			*sq->country ? ")" : "",
			*sq->state ? "(st=" : "",
			*sq->state ? sq->state : "",
			*sq->state ? ")" : "",
			*sq->locality ? "(l=" : "",
			*sq->locality ? sq->locality : "",
			*sq->locality ? ")" : "",
			need_and ? ")" : "");
		fbuf[sizeof(fbuf)-1] = '\0';
	    }

	    if(categories > 0 || *sq->custom)
	      filter = (CUSTOM_FILT_S *)fs_get(sizeof(CUSTOM_FILT_S));

	    /* combine the configed filters with this filter */
	    if(*sq->custom){
		string = "";
		filter->filt = sq->custom;
		filter->combine = 0;
	    }
	    else if(*sq->qq && categories > 0){
		string = sq->qq;
		filter->filt = fbuf;
		filter->combine = 1;
	    }
	    else if(categories > 0){
		string = "";
		filter->filt = fbuf;
		filter->combine = 0;
	    }
	    else{
		string = sq->qq;
		filter = NULL;
	    }
	}
	
	mangled = 0;
	memset(&wp_err, 0, sizeof(wp_err));
	wp_err.mangled = &mangled;
	style = selecting ? AlwaysDisplayAndMailRequired : AlwaysDisplay;

	/* maybe coming from composer */
	fix_windsize(ps_global);
	init_sigwinch();
	clear_cursor_pos();

	lret = ldap_lookup_all(string, who, 0, style, filter, &winning_e,
			       &wp_err, &free_when_done);
	
	if(filter)
	  fs_give((void **)&filter);

	if(wp_err.mangled)
	  ps->mangled_screen = 1;

	if(wp_err.error){
	    if(status_message_remaining() && error)
	      *error = wp_err.error;
	    else
	      fs_give((void **)&wp_err.error);
	}

	if(lret == 0 && winning_e && selecting){
	    ADDRESS *addr;

	    addr = address_from_ldap(winning_e);
	    if(addr){
		if(!addr->host){
		    addr->host = cpystr("missing-hostname");
		    if(error){
			if(*error)
			  fs_give((void **)error);

			*error = cpystr(_("Missing hostname in LDAP address"));
		    }
		}

		ret = addr_list_string(addr, NULL, 1);
		if(!ret || !ret[0]){
		    if(ret)
		      fs_give((void **)&ret);
		    
		    if(exit)
		      *exit = 1;
		    
		    if(error && !*error){
			char buf[200];

			snprintf(buf, sizeof(buf), _("No email address available for \"%s\""),
				(addr->personal && *addr->personal)
				  ? addr->personal
				  : "selected entry");
			buf[sizeof(buf)-1] = '\0';
			*error = cpystr(buf);
		    }
		}

		mail_free_address(&addr);
	    }
	}
	else if(lret == -1 && exit)
	  *exit = 1;
    }
    
all_done:
    if(he)
      free_headents(&he);

    if(free_when_done)
      free_ldap_result_list(&free_when_done);

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

    if(msgso)
      so_give(&msgso);

    if(sq)
      free_query_parameters(&sq);

    return(ret);
}


/*
 * View all fields of an LDAP entry while browsing.
 *
 * Args    ps -- Pine struct
 *  winning_e -- The struct containing the information about the entry
 *               to be viewed.
 */
void
view_ldap_entry(struct pine *ps, LDAP_CHOOSE_S *winning_e)
{
    STORE_S    *srcstore = NULL;
    SourceType  srctype = CharStar;
    SCROLL_S	sargs;
    HANDLE_S *handles = NULL;

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

    if((srcstore=prep_ldap_for_viewing(ps,winning_e,srctype,&handles)) != NULL){
	memset(&sargs, 0, sizeof(SCROLL_S));
	sargs.text.text	  = so_text(srcstore);
	sargs.text.src    = srctype;
	sargs.text.desc   = _("expanded entry");
	sargs.text.handles= handles;
	sargs.bar.title   = _("DIRECTORY ENTRY");
	sargs.proc.tool   = process_ldap_cmd;
	sargs.proc.data.p = (void *) winning_e;
	sargs.help.text   = h_ldap_view;
	sargs.help.title  = _("HELP FOR DIRECTORY VIEW");
	sargs.keys.menu   = &ldap_view_keymenu;
	setbitmap(sargs.keys.bitmap);

	if(handles)
	  sargs.keys.menu->how_many = 2;
	else{
	    sargs.keys.menu->how_many = 1;
	    clrbitn(OTHER_KEY, sargs.keys.bitmap);
	}

	scrolltool(&sargs);

	ps->mangled_screen = 1;
	so_give(&srcstore);
	free_handles(&handles);
    }
    else
      q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
}


/*
 * Compose a message to the email addresses contained in an LDAP entry.
 *
 * Args    ps -- Pine struct
 *  winning_e -- The struct containing the information about the entry
 *               to be viewed.
 */
void
compose_to_ldap_entry(struct pine *ps, LDAP_CHOOSE_S *e, int allow_role)
{
    struct berval  **elecmail = NULL,
	  **mail = NULL,
	  **cn = NULL,
	  **sn = NULL,
	  **givenname = NULL;
    int num;
    size_t  len = 0;

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

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

	for(a = ldap_first_attribute(e->ld, e->selected_entry, &ber);
	    a != NULL;
	    a = ldap_next_attribute(e->ld, e->selected_entry, ber)){

	    if(strcmp(a, e->info_used->mailattr) == 0){
		if(!mail)
		  mail = ldap_get_values_len(e->ld, e->selected_entry, a);
	    }
	    else if(strcmp(a, "electronicmail") == 0){
		if(!elecmail)
		  elecmail = ldap_get_values_len(e->ld, e->selected_entry, a);
	    }
	    else if(strcmp(a, e->info_used->cnattr) == 0){
		if(!cn)
		  cn = ldap_get_values_len(e->ld, e->selected_entry, a);
	    }
	    else if(strcmp(a, e->info_used->gnattr) == 0){
		if(!givenname)
		  givenname = ldap_get_values_len(e->ld, e->selected_entry, a);
	    }
	    else if(strcmp(a, e->info_used->snattr) == 0){
		if(!sn)
		  sn = ldap_get_values_len(e->ld, e->selected_entry, a);
	    }

	    our_ldap_memfree(a);
	}
    }

    if(elecmail){
	if(ALPINE_LDAP_can_use(elecmail) && !mail)
	  mail = elecmail;
	else
	  ldap_value_free_len(elecmail);

	elecmail = NULL;
    }

    for(num = 0; ALPINE_LDAP_usable(mail, num); num++)
      len += strlen(mail[num]->bv_val) + 1;
    
    if(len){
	char        *p, *address, *fn = NULL;
	BuildTo      bldto;
	SAVE_STATE_S state;

	address = (char *)fs_get(len * sizeof(char));
	p = address;
	num = 0;
	while(ALPINE_LDAP_usable(mail, num)){
	    sstrncpy(&p, mail[num]->bv_val, len-(p-address));
	    num++;
	    if(mail[num])
	      sstrncpy(&p, ",", len-(p-address));
	}

	address[len-1] = '\0';

	/*
	 * If we have a fullname and there is only a single address and
	 * the address doesn't seem to have a fullname with it, add it.
	 */
	if(ALPINE_LDAP_can_use(mail) && !mail[1]){
	    if(ALPINE_LDAP_can_use(cn))
	      fn = cpystr(cn[0]->bv_val);
	    else if(ALPINE_LDAP_can_use(sn) && ALPINE_LDAP_can_use(givenname)){
		size_t l;

		l = strlen(givenname[0]->bv_val) + strlen(sn[0]->bv_val) + 1;
		fn = (char *) fs_get((l+1) * sizeof(char));
		snprintf(fn, l+1, "%s %s", givenname[0]->bv_val, sn[0]->bv_val);
		fn[l] = '\0';
	    }
	}

	if(ALPINE_LDAP_can_use(mail) && !mail[1] && fn){
	    ADDRESS *adrlist = NULL;
	    char    *tmp_a_string;

	    tmp_a_string = cpystr(address);
	    rfc822_parse_adrlist(&adrlist, tmp_a_string, fakedomain);
	    fs_give((void **)&tmp_a_string);
	    if(adrlist && !adrlist->next && !adrlist->personal){
		char *new_address;
		size_t len;
		RFC822BUFFER rbuf;

		adrlist->personal = cpystr(fn);
		len = est_size(adrlist);
		new_address = (char *) fs_get(len * sizeof(char));
		new_address[0] ='\0';
		rbuf.f   = dummy_soutr;
		rbuf.s   = NULL;
		rbuf.beg = new_address;
		rbuf.cur = new_address;
		rbuf.end = new_address+len-1;
		/* this will quote it if it needs quoting */
		rfc822_output_address_list(&rbuf, adrlist, 0L, NULL);
		*rbuf.cur = '\0';
		fs_give((void **)&address);
		address = new_address;
	    }

	    if(adrlist)
	      mail_free_address(&adrlist);
	}

	bldto.type    = Str;
	bldto.arg.str = address;

	save_state(&state);
	ab_compose_internal(bldto, allow_role);
	restore_state(&state);
	fs_give((void **)&address);
	if(fn)
	  fs_give((void **)&fn);

	/*
	 * Window size may have changed in composer.
         * Pine_send will have reset the window size correctly,
         * but we still have to reset our address book data structures.
	 */
	if(as.initialized)
	  ab_resize();

	ps_global->mangled_screen = 1;
    }
    else
      q_status_message(SM_ORDER, 0, 4, _("No address to compose to"));

    if(mail)
      ldap_value_free_len(mail);
    if(cn)
      ldap_value_free_len(cn);
    if(sn)
      ldap_value_free_len(sn);
    if(givenname)
      ldap_value_free_len(givenname);
}


/*
 * Forward the text of an LDAP entry via email (not a vcard attachment)
 *
 * Args    ps -- Pine struct
 *  winning_e -- The struct containing the information about the entry
 *               to be viewed.
 */
void
forward_ldap_entry(struct pine *ps, LDAP_CHOOSE_S *winning_e)
{
    STORE_S    *srcstore = NULL;
    SourceType  srctype = CharStar;

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

    if((srcstore = prep_ldap_for_viewing(ps,winning_e,srctype,NULL)) != NULL){
	forward_text(ps, so_text(srcstore), srctype);
	ps->mangled_screen = 1;
	so_give(&srcstore);
    }
    else
      q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
}


STORE_S *
prep_ldap_for_viewing(struct pine *ps, LDAP_CHOOSE_S *winning_e, SourceType srctype, HANDLE_S **handlesp)
{
    STORE_S    *store = NULL;
    char       *a, *tmp;
    BerElement *ber;
    int         i, width;
#define W (1000)
#define INDENTHERE (22)
    char        obuf[W+10];
    char        hdr[6*INDENTHERE+1], hdr2[6*INDENTHERE+1];
    struct berval **cn = NULL;
    int         indent = INDENTHERE;

    if(!(store = so_get(srctype, NULL, EDIT_ACCESS)))
      return(store);

    /* for mailto handles so user can select individual email addrs */
    if(handlesp)
      init_handles(handlesp);

    width = MAX(ps->ttyo->screen_cols, 25);

    snprintf(hdr2, sizeof(hdr2), "%-*.*s: ", indent-2,indent-2, "");
    hdr2[sizeof(hdr2)-1] = '\0';

    if(sizeof(obuf) > ps->ttyo->screen_cols+1){
	memset((void *)obuf, '-', ps->ttyo->screen_cols * sizeof(char));
	obuf[ps->ttyo->screen_cols] = '\n';
	obuf[ps->ttyo->screen_cols+1] = '\0';
    }

    a = ldap_get_dn(winning_e->ld, winning_e->selected_entry);

    so_puts(store, obuf);
    if((tmp = fold(a, width, width, "", "   ", FLD_NONE)) != NULL){
	so_puts(store, tmp);
	fs_give((void **)&tmp);
    }

    so_puts(store, obuf);
    so_puts(store, "\n");

    our_ldap_dn_memfree(a);

    for(a = ldap_first_attribute(winning_e->ld, winning_e->selected_entry, &ber);
	a != NULL;
	a = ldap_next_attribute(winning_e->ld, winning_e->selected_entry, ber)){

	if(a && *a){
	    struct berval **vals;
	    char  *fn = NULL;

	    vals = ldap_get_values_len(winning_e->ld, winning_e->selected_entry, a);

	    /* save this for mailto */
	    if(handlesp && !cn && !strcmp(a, winning_e->info_used->cnattr))
	      cn = ldap_get_values_len(winning_e->ld, winning_e->selected_entry, a);

	    if(vals){
		int do_mailto;

		do_mailto = (handlesp &&
			     !strcmp(a, winning_e->info_used->mailattr));

		utf8_snprintf(hdr, sizeof(hdr), "%-*.*w: ", indent-2,indent-2,
			ldap_translate(a, winning_e->info_used));
		hdr[sizeof(hdr)-1] = '\0';
		for(i = 0; ALPINE_LDAP_usable(vals, i); i++){
		    if(do_mailto){
			ADDRESS  *ad = NULL;
			HANDLE_S *h;
			char      buf[20];
			char     *tmp_a_string;
			char     *addr, *new_addr, *enc_addr;
			char     *path = NULL;

			addr = cpystr(vals[i]->bv_val);
			if(ALPINE_LDAP_can_use(cn))
			  fn = cpystr(cn[0]->bv_val);

			if(fn){
			    tmp_a_string = cpystr(addr);
			    rfc822_parse_adrlist(&ad, tmp_a_string, "@");
			    fs_give((void **)&tmp_a_string);
			    if(ad && !ad->next && !ad->personal){
				RFC822BUFFER rbuf;
				size_t len;

				ad->personal = cpystr(fn);
				len = est_size(ad);
				new_addr = (char *) fs_get(len * sizeof(char));
				new_addr[0] = '\0';
				/* this will quote it if it needs quoting */
				rbuf.f   = dummy_soutr;
				rbuf.s   = NULL;
				rbuf.beg = new_addr;
				rbuf.cur = new_addr;
				rbuf.end = new_addr+len-1;
				rfc822_output_address_list(&rbuf, ad, 0L, NULL);
				*rbuf.cur = '\0';
				fs_give((void **) &addr);
				addr = new_addr;
			    }

			    if(ad)
			      mail_free_address(&ad);

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

			if((enc_addr = rfc1738_encode_mailto(addr)) != NULL){
			    size_t l;

			    l = strlen(enc_addr) + 7;
			    path = (char *) fs_get((l+1) * sizeof(char));
			    snprintf(path, l+1, "mailto:%s", enc_addr);
			    path[l] = '\0';
			    fs_give((void **)&enc_addr);
			}
			
			fs_give((void **)&addr);

			if(path){
			    h = new_handle(handlesp);
			    h->type = URL;
			    h->h.url.path = path;
			    snprintf(buf, sizeof(buf), "%d", h->key);
			    buf[sizeof(buf)-1] = '\0';

			    /*
			     * Don't try to fold this address. Just put it on
			     * one line and let scrolltool worry about
			     * cutting it off before it goes past the
			     * right hand edge. Otherwise, we have to figure
			     * out how to handle the wrapped handle.
			     */
			    snprintf(obuf, sizeof(obuf), "%c%c%c%s%s%c%c%c%c",
				    TAG_EMBED, TAG_HANDLE,
				    (int) strlen(buf), buf, vals[i]->bv_val,
				    TAG_EMBED, TAG_BOLDOFF,
				    TAG_EMBED, TAG_INVOFF);
			    obuf[sizeof(obuf)-1] = '\0';

			    so_puts(store, (i==0) ? hdr : hdr2);
			    so_puts(store, obuf);
			    so_puts(store, "\n");
			}
			else{
			    snprintf(obuf, sizeof(obuf), "%s", vals[i]->bv_val);
			    obuf[sizeof(obuf)-1] = '\0';

			    if((tmp = fold(obuf, width, width,
					  (i==0) ? hdr : hdr2,
					  repeat_char(indent+2, SPACE),
					  FLD_NONE)) != NULL){
				so_puts(store, tmp);
				fs_give((void **)&tmp);
			    }
			}
		    }
		    else{
			snprintf(obuf, sizeof(obuf), "%s", vals[i]->bv_val);
			obuf[sizeof(obuf)-1] = '\0';

			if((tmp = fold(obuf, width, width,
				      (i==0) ? hdr : hdr2,
				      repeat_char(indent+2, SPACE),
				      FLD_NONE)) != NULL){
			    so_puts(store, tmp);
			    fs_give((void **)&tmp);
			}
		    }
		}
		
		ldap_value_free_len(vals);
	    }
	    else{
		utf8_snprintf(obuf, sizeof(obuf), "%-*.*w\n", indent-1,indent-1,
			ldap_translate(a, winning_e->info_used));
		obuf[sizeof(obuf)-1] = '\0';
		so_puts(store, obuf);
	    }
	}

	our_ldap_memfree(a);
    }

    if(cn)
      ldap_value_free_len(cn);

    return(store);
}


int
process_ldap_cmd(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
{
    int rv = 1;

    ps_global->next_screen = SCREEN_FUN_NULL;

    switch(cmd){
      case MC_SAVE :
	save_ldap_entry(ps_global, sparms->proc.data.p, 0);
	rv = 0;
	break;

      case MC_COMPOSE :
	compose_to_ldap_entry(ps_global, sparms->proc.data.p, 0);
	rv = 0;
	break;

      case MC_ROLE :
	compose_to_ldap_entry(ps_global, sparms->proc.data.p, 1);
	rv = 0;
	break;

      default:
	alpine_panic("Unexpected command in process_ldap_cmd");
	break;
    }

    return(rv);
}


int
pico_simpleexit(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed,
		char **result)
{
    if(result)
      *result = NULL;

    return(0);
}

char *
pico_simplecancel(void (*redraw_pico)(void))
{
    return("Cancelled");
}


/*
 * Store query parameters so that they can be recalled by user later with ^R.
 */
void
save_query_parameters(SAVED_QUERY_S *params)
{
    free_saved_query_parameters();
    saved_params = params;
}


SAVED_QUERY_S *
copy_query_parameters(SAVED_QUERY_S *params)
{
    SAVED_QUERY_S *sq;

    sq = (SAVED_QUERY_S *)fs_get(sizeof(SAVED_QUERY_S));
    memset((void *)sq, 0, sizeof(SAVED_QUERY_S));

    if(params && params->qq)
      sq->qq = cpystr(params->qq);
    else
      sq->qq = cpystr("");

    if(params && params->cn)
      sq->cn = cpystr(params->cn);
    else
      sq->cn = cpystr("");

    if(params && params->sn)
      sq->sn = cpystr(params->sn);
    else
      sq->sn = cpystr("");

    if(params && params->gn)
      sq->gn = cpystr(params->gn);
    else
      sq->gn = cpystr("");

    if(params && params->mail)
      sq->mail = cpystr(params->mail);
    else
      sq->mail = cpystr("");

    if(params && params->org)
      sq->org = cpystr(params->org);
    else
      sq->org = cpystr("");

    if(params && params->unit)
      sq->unit = cpystr(params->unit);
    else
      sq->unit = cpystr("");

    if(params && params->country)
      sq->country = cpystr(params->country);
    else
      sq->country = cpystr("");

    if(params && params->state)
      sq->state = cpystr(params->state);
    else
      sq->state = cpystr("");

    if(params && params->locality)
      sq->locality = cpystr(params->locality);
    else
      sq->locality = cpystr("");
    
    if(params && params->custom)
      sq->custom = cpystr(params->custom);
    else
      sq->custom = cpystr("");
    
    return(sq);
}


void
free_saved_query_parameters(void)
{
    if(saved_params)
      free_query_parameters(&saved_params);
}


void
free_query_parameters(SAVED_QUERY_S **parm)
{
    if(parm){
	if(*parm){
	    if((*parm)->qq)
	      fs_give((void **)&(*parm)->qq);
	    if((*parm)->cn)
	      fs_give((void **)&(*parm)->cn);
	    if((*parm)->sn)
	      fs_give((void **)&(*parm)->sn);
	    if((*parm)->gn)
	      fs_give((void **)&(*parm)->gn);
	    if((*parm)->mail)
	      fs_give((void **)&(*parm)->mail);
	    if((*parm)->org)
	      fs_give((void **)&(*parm)->org);
	    if((*parm)->unit)
	      fs_give((void **)&(*parm)->unit);
	    if((*parm)->country)
	      fs_give((void **)&(*parm)->country);
	    if((*parm)->state)
	      fs_give((void **)&(*parm)->state);
	    if((*parm)->locality)
	      fs_give((void **)&(*parm)->locality);
	    if((*parm)->custom)
	      fs_give((void **)&(*parm)->custom);
	    
	    fs_give((void **)parm);
	}
    }
}


/*
 * A callback from pico to restore the saved query parameters.
 *
 * Args  he -- Unused.
 *        s -- The place to return the allocated array of values.
 *
 * Returns -- 1 if there are parameters to return, 0 otherwise.
 */
int
restore_query_parameters(struct headerentry *he, char ***s)
{
    int retval = 0, i = 0;

    if(s)
      *s = NULL;

    if(saved_params && s){
	*s = (char **)fs_get((QQ_END + 1) * sizeof(char *));
	(*s)[i++] = cpystr(saved_params->qq ? saved_params->qq : "");
	(*s)[i++] = cpystr(saved_params->cn ? saved_params->cn : "");
	(*s)[i++] = cpystr(saved_params->sn ? saved_params->sn : "");
	(*s)[i++] = cpystr(saved_params->gn ? saved_params->gn : "");
	(*s)[i++] = cpystr(saved_params->mail ? saved_params->mail : "");
	(*s)[i++] = cpystr(saved_params->org ? saved_params->org : "");
	(*s)[i++] = cpystr(saved_params->unit ? saved_params->unit : "");
	(*s)[i++] = cpystr(saved_params->country ? saved_params->country : "");
	(*s)[i++] = cpystr(saved_params->state ? saved_params->state : "");
	(*s)[i++] = cpystr(saved_params->locality ? saved_params->locality :"");
	(*s)[i++] = cpystr(saved_params->custom ? saved_params->custom :"");
	(*s)[i]   = 0;
	retval = 1;
    }

    return(retval);
}


/*
 * Internal handler for viewing an LDAP url.
 */
int
url_local_ldap(char *url)
{
    LDAP            *ld;
    struct timeval   t;
    int              ld_err, mangled = 0, we_cancel, retval = 0, proto = 3;
    int              we_turned_on = 0;
    char             ebuf[300];
    LDAPMessage     *result;
    LDAP_SERV_S     *info;
    LDAP_SERV_RES_S *serv_res = NULL;
    LDAPURLDesc     *ldapurl = NULL;
    WP_ERR_S wp_err;

    dprint((2, "url_local_ldap(%s)\n", url ? url : "?"));

    ld_err = ldap_url_parse(url, &ldapurl);
    if(ld_err || !ldapurl){
      snprintf(ebuf, sizeof(ebuf), "URL parse failed for %s", url);
      ebuf[sizeof(ebuf)-1] = '\0';
      q_status_message(SM_ORDER, 3, 5, ebuf);
      return(retval);
    }

    if(!ldapurl->lud_host){
      /* TRNASLATORS: No host in <url> */
      snprintf(ebuf, sizeof(ebuf), _("No host in %s"), url);
      ebuf[sizeof(ebuf)-1] = '\0';
      q_status_message(SM_ORDER, 3, 5, ebuf);
      ldap_free_urldesc(ldapurl);
      return(retval);
    }
    
    we_turned_on = intr_handling_on();
    we_cancel = busy_cue(_("Searching for LDAP url"), NULL, 0);
    ps_global->mangled_footer = 1;

#if (LDAPAPI >= 11)
#ifdef _WINDOWS
    if((ld = ldap_init(ldapurl->lud_host, ldapurl->lud_port)) == NULL)
#else
    snprintf(tmp_20k_buf, SIZEOF_20KBUF, "ldap://%s:%d", ldapurl->lud_host, ldapurl->lud_port);
    tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
    if(ldap_initialize(&ld, tmp_20k_buf) != LDAP_SUCCESS)
#endif
#else
    if((ld = ldap_open(ldapurl->lud_host, ldapurl->lud_port)) == NULL)
#endif
    {
	if(we_cancel){
	    cancel_busy_cue(-1);
	    we_cancel = 0;
	}

	q_status_message(SM_ORDER,3,5, _("LDAP search failed: can't initialize"));
    }
    else if(!ps_global->intr_pending){
      if(ldap_v3_is_supported(ld) &&
	 our_ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &proto) == 0){
	dprint((5, "ldap: using version 3 protocol\n"));
      }

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

      t.tv_sec = 30; t.tv_usec = 0;
#ifdef _WINDOWS
      ld_err = ldap_search_st(ld, ldapurl->lud_dn, ldapurl->lud_scope,
			      ldapurl->lud_filter, ldapurl->lud_attrs,
			      0, &t, &result);
#else
      ld_err = ldap_search_ext_s(ld, ldapurl->lud_dn, ldapurl->lud_scope,
			      ldapurl->lud_filter, ldapurl->lud_attrs, 0,
			      NULL, NULL, 
			      &t, DEF_LDAP_SIZE, &result);
#endif
      if(ld_err != LDAP_SUCCESS){
	  if(we_cancel){
	      cancel_busy_cue(-1);
	      we_cancel = 0;
	  }

	  snprintf(ebuf, sizeof(ebuf), _("LDAP search failed: %s"), ldap_err2string(ld_err));
	  ebuf[sizeof(ebuf)-1] = '\0';
	  q_status_message(SM_ORDER, 3, 5, ebuf);
#ifdef _WINDOWS
	  ldap_unbind(ld);
#else
	  ldap_unbind_ext(ld, NULL, NULL);
#endif /* _WINDOWS */
      }
      else if(!ps_global->intr_pending){
	if(we_cancel){
	    cancel_busy_cue(-1);
	    we_cancel = 0;
	}

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

	if(ldap_count_entries(ld, result) == 0){
	  q_status_message(SM_ORDER, 3, 5, _("No matches found for url"));
#ifdef _WINDOWS
	  ldap_unbind(ld);
#else
	  ldap_unbind_ext(ld, NULL, NULL);
#endif /* _WINDOWS */
	  if(result)
	    ldap_msgfree(result);
	}
        else{
	  serv_res = (LDAP_SERV_RES_S *)fs_get(sizeof(LDAP_SERV_RES_S));
	  memset((void *)serv_res, 0, sizeof(*serv_res));
	  serv_res->ld  = ld;
	  serv_res->res = result;
	  info = (LDAP_SERV_S *)fs_get(sizeof(LDAP_SERV_S));
	  memset((void *)info, 0, sizeof(*info));
	  info->mailattr = cpystr(DEF_LDAP_MAILATTR);
	  info->snattr   = cpystr(DEF_LDAP_SNATTR);
	  info->gnattr   = cpystr(DEF_LDAP_GNATTR);
	  info->cnattr   = cpystr(DEF_LDAP_CNATTR);
	  serv_res->info_used = info;
	  memset(&wp_err, 0, sizeof(wp_err));
	  wp_err.mangled = &mangled;

	  ask_user_which_entry(serv_res, NULL, NULL, &wp_err, DisplayForURL);
	  if(wp_err.error){
	    q_status_message(SM_ORDER, 3, 5, wp_err.error);
	    fs_give((void **)&wp_err.error);
	  }

	  if(mangled)
	    ps_global->mangled_screen = 1;

	  free_ldap_result_list(&serv_res);
	  retval = 1;
        }
      }
    }

    if(we_cancel)
      cancel_busy_cue(-1);

    if(we_turned_on)
      intr_handling_off();

    if(ldapurl)
      ldap_free_urldesc(ldapurl);

    return(retval);
}
#endif	/* ENABLE_LDAP */
