/*
 * ========================================================================
 * Copyright 2013-2022 Eduardo Chappa
 * Copyright 2006-2007 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
 *
 * ========================================================================
 */

/*====================================================================== 
    buildaddr.c
    Support for build_address function and low-level display cache.
 ====*/


#include "../pith/headers.h"
#include "../pith/bldaddr.h"
#include "../pith/ldap.h"
#include "../pith/conf.h"
#include "../pith/adrbklib.h"
#include "../pith/copyaddr.h"
#include "../pith/remote.h"
#include "../pith/status.h"
#include "../pith/search.h"
#include "../pith/addrstring.h"
#include "../pith/util.h"
#include "../pith/ablookup.h"
#include "../pith/news.h"
#include "../pith/stream.h"
#include "../pith/mailcmd.h"
#include "../pith/osdep/pw_stuff.h"


/*
 * Jump back to this location if we discover that one of the open addrbooks
 * has been changed by some other process.
 */
jmp_buf addrbook_changed_unexpectedly;


/*
 * Sometimes the address book code calls something outside of the address
 * book and that calls back to the address book. For example, you could be
 * in the address book mgmt screen, call ab_compose, and then a ^T could
 * call back to an address book select screen. Or moving out of a line
 * calls back to build_address. This keeps track of the nesting level so
 * that we can know when it is safe to update an out of date address book.
 * For example, if we know an address book is open and displayed, it isn't
 * safe to update it because that would pull the cache out from under the
 * displayed lines. If we don't have it displayed, then it is ok to close
 * it and re-open it (adrbk_check_and_fix()).
 */
int         ab_nesting_level = 0;



/*
 * This is like build_address() only it doesn't close
 * everything down when it is done, and it doesn't open addrbooks that
 * are already open.  Other than that, it has the same functionality.
 * It opens addrbooks that haven't been opened and saves and restores the
 * addrbooks open states (if save_and_restore is set).
 *
 * Args: to                    -- the address to attempt expanding (see the
 *				   description in expand_address)
 *       full_to               -- a pointer to result
 *		    This will be allocated here and freed by the caller.
 *       error                 -- a pointer to an error message, if non-null
 *       fcc                   -- a pointer to returned fcc, if non-null
 *		    This will be allocated here and freed by the caller.
 *		    *fcc should be null on entry.
 *       save_and_restore      -- restore addrbook states when finished
 *
 * Results:    0 -- address is ok
 *            -1 -- address is not ok
 * full_to contains the expanded address on success, or a copy of to
 *         on failure
 * *error  will point to an error message on failure it it is non-null
 *
 * Side effect: Can flush addrbook entry cache entries so they need to be
 * re-fetched afterwords.
 */
int
our_build_address(BuildTo to, char **full_to, char **error, char **fcc,
		  void (*save_and_restore_f)(int, SAVE_STATE_S *))
{
    int ret;

    dprint((7, "- our_build_address -  (%s)\n",
	(to.type == Str) ? (to.arg.str ? to.arg.str : "nul")
			 : (to.arg.abe->nickname ? to.arg.abe->nickname
						: "no nick")));

    if((to.type == Str && !to.arg.str) || (to.type == Abe && !to.arg.abe)){
	if(full_to)
	  *full_to = cpystr("");
	ret = 0;
    }
    else
      ret = build_address_internal(to, full_to, error, fcc, NULL, NULL,
				   save_and_restore_f, 0, NULL);

    dprint((8, "   our_build_address says %s address\n",
	ret ? "BAD" : "GOOD"));

    return(ret);
}



#define FCC_SET    1	/* Fcc was set */
#define LCC_SET    2	/* Lcc was set */
#define FCC_NOREPO 4	/* Fcc was set in a non-reproducible way */
#define LCC_NOREPO 8	/* Lcc was set in a non-reproducible way */
/*
 * Given an address, expand it based on address books, local domain, etc.
 * This will open addrbooks if needed before checking (actually one of
 * its children will open them).
 *
 * Args: to       -- The given address to expand (see the description
 *			in expand_address)
 *       full_to  -- Returned value after parsing to.
 *       error    -- This gets pointed at error message, if any
 *       fcc      -- Returned value of fcc for first addr in to
 *       no_repo  -- Returned value, set to 1 if the fcc or lcc we're
 *			returning is not reproducible from the expanded
 *			address.  That is, if we were to run
 *			build_address_internal again on the resulting full_to,
 *			we wouldn't get back the fcc again.  For example,
 *			if we expand a list and use the list fcc from the
 *			addrbook, the full_to no longer contains the
 *			information that this was originally list foo.
 *       save_and_restore -- restore addrbook state when done
 *
 * Result:  0 is returned if address was OK, 
 *         -1 if address wasn't OK.
 * The address is expanded, fully-qualified, and personal name added.
 *
 * Input may have more than one address separated by commas.
 *
 * Side effect: Can flush addrbook entry cache entries so they need to be
 * re-fetched afterwords.
 */
int
build_address_internal(BuildTo to, char **full_to, char **error, char **fcc,
		       int *no_repo, char **lcc,
		       void (*save_and_restore_f)(int, SAVE_STATE_S *),
		       int simple_verify, int *mangled)
{
    ADDRESS *a;
    int      loop, i;
    int      tried_route_addr_hack = 0;
    int      did_set = 0;
    char    *tmp = NULL;
    SAVE_STATE_S  state;
    PerAddrBook  *pab;

    dprint((8, "- build_address_internal -  (%s)\n",
	(to.type == Str) ? (to.arg.str ? to.arg.str : "nul")
			 : (to.arg.abe->nickname ? to.arg.abe->nickname
						: "no nick")));

    init_ab_if_needed();
    if(save_and_restore_f)
      (*save_and_restore_f)(SAR_SAVE, &state);

start:
    loop = 0;
    ps_global->c_client_error[0] = '\0';
    wp_exit = wp_nobail = 0;

    a = expand_address(to, ps_global->maildomain,
		       F_OFF(F_QUELL_LOCAL_LOOKUP, ps_global)
			 ? ps_global->maildomain : NULL,
		       &loop, fcc, &did_set, lcc, error,
		       0, simple_verify, mangled);

    /*
     * If the address is a route-addr, expand_address() will have rejected
     * it unless it was enclosed in brackets, since route-addrs can't stand
     * alone.  Try it again with brackets.  We should really be checking
     * each address in the list of addresses instead of assuming there is
     * only one address, but we don't want to have this function know
     * all about parsing rfc822 addrs.
     */
    if(!tried_route_addr_hack &&
        ps_global->c_client_error[0] != '\0' &&
	((to.type == Str && to.arg.str && to.arg.str[0] == '@') ||
	 (to.type == Abe && to.arg.abe->tag == Single &&
				 to.arg.abe->addr.addr[0] == '@'))){
	BuildTo      bldto;

	tried_route_addr_hack++;

	tmp = (char *)fs_get((size_t)(MAX_ADDR_FIELD + 3));

	/* add brackets to whole thing */
	strncpy(tmp, "<", MAX_ADDR_FIELD+3);
	tmp[MAX_ADDR_FIELD+3-1] = '\0';
	if(to.type == Str)
	  strncat(tmp, to.arg.str, MAX_ADDR_FIELD+3-strlen(tmp)-1);
	else
	  strncat(tmp, to.arg.abe->addr.addr, MAX_ADDR_FIELD+3-strlen(tmp)-1);

	tmp[MAX_ADDR_FIELD+3-1] = '\0';
	strncat(tmp, ">", MAX_ADDR_FIELD+3-strlen(tmp)-1);
	tmp[MAX_ADDR_FIELD+3-1] = '\0';

	loop = 0;
	ps_global->c_client_error[0] = '\0';

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

	if(a)
	  mail_free_address(&a);

	/* try it */
	a = expand_address(bldto, ps_global->maildomain,
			   F_OFF(F_QUELL_LOCAL_LOOKUP, ps_global)
			     ? ps_global->maildomain : NULL,
		       &loop, fcc, &did_set, lcc, error,
		       0, simple_verify, mangled);

	/* if no error this time, use it */
	if(ps_global->c_client_error[0] == '\0'){
	    if(save_and_restore_f)
	      (*save_and_restore_f)(SAR_RESTORE, &state);

	    /*
	     * Clear references so that Addrbook Entry caching in adrbklib.c
	     * is allowed to throw them out of cache.
	     */
	    for(i = 0; i < as.n_addrbk; i++){
		pab = &as.adrbks[i];
		if(pab->ostatus == Open || pab->ostatus == NoDisplay)
		  adrbk_clearrefs(pab->address_book);
	    }

	    goto ok;
	}
	else{  /* go back and use what we had before, so we get the error */
	    if(tmp)
	      fs_give((void **)&tmp);

	    tmp = NULL;
	    goto start;
	}
    }

    if(save_and_restore_f)
      (*save_and_restore_f)(SAR_RESTORE, &state);

    /*
     * Clear references so that Addrbook Entry caching in adrbklib.c
     * is allowed to throw them out of cache.
     */
    for(i = 0; i < as.n_addrbk; i++){
	pab = &as.adrbks[i];
	if(pab->ostatus == Open || pab->ostatus == NoDisplay)
	  adrbk_clearrefs(pab->address_book);
    }

    if(ps_global->c_client_error[0] != '\0'){
        /* Parse error.  Return string as is and error message */
	if(full_to){
	    if(to.type == Str)
	      *full_to = cpystr(to.arg.str);
	    else{
	        if(to.arg.abe->nickname && to.arg.abe->nickname[0])
	          *full_to = cpystr(to.arg.abe->nickname);
	        else if(to.arg.abe->tag == Single)
	          *full_to = cpystr(to.arg.abe->addr.addr);
	        else
	          *full_to = cpystr("");
	    }
	}

	if(error != NULL){
	    /* display previous error and add new one */
	    if(*error){
		q_status_message(SM_ORDER, 3, 5, *error);
		display_message('x');
		fs_give((void **)error);
	    }

            *error = cpystr(ps_global->c_client_error);
	}

        dprint((2,
	    "build_address_internal returning parse error: %s\n",
                  ps_global->c_client_error ? ps_global->c_client_error : "?"));
	if(a)
	  mail_free_address(&a);

	if(tmp)
	  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 -1;
    }

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

    /*
     * If there's a loop in the addressbook, we modify the address and
     * send an error back, but we still return 0.
     */
ok:
    if(loop && error != NULL){
	/* display previous error and add new one */
	if(*error){
	    q_status_message(SM_ORDER, 3, 5, *error);
	    display_message('x');
	    fs_give((void **)error);
	}

	*error = cpystr(_("Loop or Duplicate detected in addressbook!"));
    }


    if(full_to){
	if(simple_verify){
	    if(tmp){
		*full_to = tmp;  /* add the brackets to route addr */
		tmp = NULL;
	    }
	    else{
		/* try to return what they sent us */
		if(to.type == Str)
		  *full_to = cpystr(to.arg.str);
		else{
		    if(to.arg.abe->nickname && to.arg.abe->nickname[0])
		      *full_to = cpystr(to.arg.abe->nickname);
		    else if(to.arg.abe->tag == Single)
		      *full_to = cpystr(to.arg.abe->addr.addr);
		    else
		      *full_to = cpystr("");
		}
	    }
	}
	else{
	    RFC822BUFFER rbuf;
	    size_t len;

	    len = est_size(a);
	    *full_to = (char *) fs_get(len * sizeof(char));
	    (*full_to)[0] = '\0';
	    /*
	     * Assume that quotes surrounding the whole personal name are
	     * not meant to be literal quotes.  That is, the name
	     * "Joe College, PhD." is quoted so that we won't do the
	     * switcheroo of Last, First, not so that the quotes will be
	     * literal.  Rfc822_write_address will put the quotes back if they
	     * are needed, so Joe College would end up looking like
	     * "Joe College, PhD." <joe@somewhere.edu> but not like
	     * "\"Joe College, PhD.\"" <joe@somewhere.edu>.
	     */
	    strip_personal_quotes(a);
	    rbuf.f   = dummy_soutr;
	    rbuf.s   = NULL;
	    rbuf.beg = *full_to;
	    rbuf.cur = *full_to;
	    rbuf.end = (*full_to)+len-1;
	    rfc822_output_address_list(&rbuf, a, 0L, NULL);
	    *rbuf.cur = '\0';
	}
    }

    if(no_repo && (did_set & FCC_NOREPO || did_set & LCC_NOREPO))
      *no_repo = 1;

    /*
     * The condition in the leading if means that addressbook fcc's
     * override the fcc-rule (because did_set will be set).
     */
    if(fcc && !(did_set & FCC_SET)){
	char *fcc_got = NULL;

	if((ps_global->fcc_rule == FCC_RULE_LAST
	    || ps_global->fcc_rule == FCC_RULE_CURRENT)
	   && strcmp(fcc_got = get_fcc(NULL), ps_global->VAR_DEFAULT_FCC)){
	    if(*fcc)
	      fs_give((void **)fcc);
	    
	    *fcc = cpystr(fcc_got);
	}
	else if(a && a->host){ /* not group syntax */
	    if(*fcc)
	      fs_give((void **)fcc);

	    if(!tmp)
	      tmp = (char *)fs_get((size_t)200);

	    if((ps_global->fcc_rule == FCC_RULE_RECIP ||
		ps_global->fcc_rule == FCC_RULE_NICK_RECIP) &&
		  get_uname(a ? a->mailbox : NULL, tmp, 200))
	      *fcc = cpystr(tmp);
	    else
	      *fcc = cpystr(ps_global->VAR_DEFAULT_FCC);
	}
	else{ /* first addr is group syntax */
	    if(!*fcc)
	      *fcc = cpystr(ps_global->VAR_DEFAULT_FCC);
	    /* else, leave it alone */
	}

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

    if(a)
      mail_free_address(&a);

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

    if(wp_exit)
      return -1;

    return 0;
}


/*
 * Expand an address string against the address books, local names, and domain.
 *
 * Args:  to      -- this is either an address string to parse (one or more
 *		      address strings separated by commas) or it is an
 *		      AdrBk_Entry, in which case it refers to a single addrbook
 *		      entry.  If it is an abe, then it is treated the same as
 *		      if the nickname of this entry was passed in and we
 *		      looked it up in the addrbook, except that it doesn't
 *		      actually have to have a non-null nickname.
 *     userdomain -- domain the user is in
 *    localdomain -- domain of the password file (usually same as userdomain)
 *  loop_detected -- pointer to an int we set if we detect a loop in the
 *		     address books (or a duplicate in a list)
 *       fcc      -- Returned value of fcc for first addr in a_string
 *       did_set  -- expand_address set the fcc (need this in case somebody
 *                     sets fcc explicitly to a value equal to default-fcc)
 *  simple_verify -- don't add list full names or expand 2nd level lists
 *
 * Result: An adrlist of expanded addresses is returned
 *
 * If the localdomain is NULL, then no lookup against the password file will
 * be done.
 */
ADDRESS *
expand_address(BuildTo to, char *userdomain, char *localdomain, int *loop_detected,
	       char **fcc, int *did_set, char **lcc, char **error, int recursing,
	       int simple_verify, int *mangled)
{
    size_t       domain_length, length;
    ADDRESS     *adr, *a, *a_tail, *adr2 = NULL, *a2, *a_temp, *wp_a;
    AdrBk_Entry *abe, *abe2;
    char        *list, *l1, **l2;
    char        *tmp_a_string, *q;
    BuildTo      bldto;
    static char *fakedomain;

    dprint((9, "- expand_address -  (%s)\n",
	(to.type == Str) ? (to.arg.str ? to.arg.str : "nul")
			 : (to.arg.abe->nickname ? to.arg.abe->nickname
						: "no nick")));
    /*
     * We use the domain "@" to detect an unqualified address.  If it comes
     * back from rfc822_parse_adrlist with the host part set to "@", then
     * we know it must have been unqualified (so we should look it up in the
     * addressbook).  Later, we also use a c-client hack.  If an ADDRESS has
     * a host part that begins with @ then rfc822_write_address()
     * will write only the local part and leave off the @domain part.
     *
     * We also malloc enough space here so that we can strcpy over host below.
     */
    domain_length = MAX(localdomain!=NULL ? strlen(localdomain) : (size_t)0,
			userdomain!=NULL ? strlen(userdomain) : (size_t)0);
    if(!recursing){
	fakedomain = (char *)fs_get(domain_length + 1);
	memset((void *)fakedomain, '@', domain_length);
	fakedomain[domain_length] = '\0';
    }

    adr = NULL;

    if(to.type == Str){
	/* rfc822_parse_adrlist feels free to destroy input so send copy */
	tmp_a_string = cpystr(to.arg.str);
	/* remove trailing comma */
	for(q = tmp_a_string + strlen(tmp_a_string) - 1;
	    q >= tmp_a_string && (*q == SPACE || *q == ',');
	    q--)
	  *q = '\0';

	if(as.n_impl)
	  mail_parameters(NIL, SET_PARSEPHRASE, (void *)massage_phrase_addr);

	rfc822_parse_adrlist(&adr, tmp_a_string, fakedomain);

	if(as.n_impl)
	  mail_parameters(NIL, SET_PARSEPHRASE, NULL);

	/*
	 * Short circuit the process if there was a parsing error.
	 */
	if(!recursing && ps_global->c_client_error[0] != '\0')
	  mail_free_address(&adr);

	fs_give((void **)&tmp_a_string);
    }
    else{
	if(!to.arg.abe ||
	   (to.arg.abe->tag == Single &&
	     (!to.arg.abe->addr.addr || to.arg.abe->addr.addr[0] == '\0')) ||
	   (to.arg.abe->tag == List &&
	     (!to.arg.abe->addr.list || !to.arg.abe->addr.list[0] ||
	      to.arg.abe->addr.list[0][0] == '\0'))){
	    adr = NULL;
	}
	else{
	    /* if we've already looked it up, fake an adr */
	    adr = mail_newaddr();
	    adr->mailbox = cpystr(to.arg.abe->nickname);
	    adr->host    = cpystr(fakedomain);
	}
    }

    for(a = adr, a_tail = adr; a;){

	/* start or end of c-client group syntax */
	if(!a->host){
            a_tail = a;
            a      = a->next;
	    continue;
	}
        else if(a->host[0] != '@'){
            /* Already fully qualified hostname */
            a_tail = a;
            a      = a->next;
        }
        else{
            /*
             * Hostname is "@" indicating name wasn't qualified.
             * Need to look up in address book, and the password file.
             * If no match then fill in the local domain for host.
             */
	    if(to.type == Str)
              abe = adrbk_lookup_with_opens_by_nick(a->mailbox,
						    !recursing,
						    (int *)NULL, -1);
	    else
              abe = to.arg.abe;

            if(simple_verify && abe == NULL){
                /*--- Move to next address in list -----*/
                a_tail = a;
                a = a->next;
            }
            else if(abe == NULL){
		WP_ERR_S wp_err;

	        if(F_OFF(F_COMPOSE_REJECTS_UNQUAL, ps_global)){
		    if(localdomain != NULL && a->personal == NULL){
			/* lookup in passwd file for local full name */
			a->personal = local_name_lookup(a->mailbox); 
			/* we know a->host is long enough for localdomain */
			if(a->personal){
			    /* we know a->host is long enough for userdomain */
			    strncpy(a->host, localdomain, domain_length+1);
			    a->host[domain_length] = '\0';
			}
		    }
		}

		/*
		 * Didn't find it in address book or password
		 * file, try white pages.
		 */
		memset(&wp_err, 0, sizeof(wp_err));
		wp_err.mangled = mangled;
		if(!wp_exit && a->personal == NULL &&
		   (wp_a = wp_lookups(a->mailbox, &wp_err, recursing))){
		    if(wp_a->mailbox && wp_a->mailbox[0] &&
		       wp_a->host && wp_a->host[0]){
			a->personal = wp_a->personal;
			if(a->adl)fs_give((void **)&a->adl);
			a->adl = wp_a->adl;
			if(a->mailbox)fs_give((void **)&a->mailbox);
			a->mailbox = wp_a->mailbox;
			if(a->host)fs_give((void **)&a->host);
			a->host = wp_a->host;
		    }

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

		if(wp_err.error){
		    /*
		     * If wp_err has already been displayed long enough
		     * just get rid of it. Otherwise, try to fit it in
		     * with any other error messages we have had.
		     * In that case we may display error messages in a
		     * weird order. We'll have to see if this is a problem
		     * in real life.
		     */
		    if(status_message_remaining() && error && !wp_exit){
			if(*error){
			    q_status_message(SM_ORDER, 3, 5, *error);
			    display_message('x');
			    fs_give((void **)error);
			}

			*error = wp_err.error;
		    }
		    else
		      fs_give((void **)&wp_err.error);
		}
		  
		/* still haven't found it */
		if(a->host[0] == '@' && !wp_nobail){
		    int space_phrase;

		    /*
		     * Figure out if there is a space in the mailbox so
		     * that user probably meant it to resolve on the
		     * directory server.
		     */
		    space_phrase = (a->mailbox && strindex(a->mailbox, SPACE));

		    if(!wp_nobail && (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global) ||
		       (space_phrase && as.n_impl))){
			char ebuf[200];

			/* TRANSLATORS: The first %s is a mailbox, the second is either
			   directory or addressbook */
			snprintf(ebuf, sizeof(ebuf), _("Address for \"%s\" not in %s"),
				a->mailbox,
				(space_phrase && as.n_impl) ? _("directory")
							    : _("addressbook"));

			if(error){
			    /* display previous error and add new one */
			    if(*error){
				if(!wp_exit){
				    q_status_message(SM_ORDER, 3, 5, *error);
				    display_message('x');
				}

				fs_give((void **)error);
			    }

			    if(!wp_exit)
			      *error = cpystr(ebuf);
			}

			if(!wp_exit)
			  strncpy(ps_global->c_client_error, ebuf, 200);

			if(!recursing)
			  fs_give((void **)&fakedomain);

			if(adr)
			  mail_free_address(&adr);
			    
			return(adr);
		    }
		    else if(wp_err.wp_err_occurred){
			if(!recursing){
			    if(error && *error && !wp_exit)
			      strncpy(ps_global->c_client_error, *error, sizeof(ps_global->c_client_error));
			    else
			      strncpy(ps_global->c_client_error, " ", sizeof(ps_global->c_client_error));

			    ps_global->c_client_error[sizeof(ps_global->c_client_error)-1] = '\0';

			    fs_give((void **)&fakedomain);
			    if(adr)
			      mail_free_address(&adr);
			    
			    return(adr);
			}
		    }
		    else{
			/* we know a->host is long enough for userdomain */
			strncpy(a->host, userdomain, domain_length+1);
			a->host[domain_length] = '\0';
		    }
		}

                /*--- Move to next address in list -----*/
                a_tail = a;
                a = a->next;

            }
	    /* expand first list, but not others if simple_verify */
	    else if(abe->tag == List && simple_verify && recursing){
                /*--- Move to next address in list -----*/
                a_tail = a;
                a = a->next;
	    }
	    else{
                /*
                 * There was a match in the address book.  We have to do a lot
                 * here because the item from the address book might be a 
                 * distribution list.  Treat the string just like an address
                 * passed in to parse and recurse on it.  Then combine
                 * the personal names from address book.  Lastly splice
                 * result into address list being processed
                 */

		/* first addr in list and fcc needs to be filled in */
		if(!recursing && a == adr && fcc && !(*did_set & FCC_SET)){
		    /*
		     * Easy case for fcc.  This is a nickname that has
		     * an fcc associated with it.
		     */
		    if(abe->fcc && abe->fcc[0]){
			if(*fcc)
			  fs_give((void **)fcc);

			if(!strcmp(abe->fcc, "\"\""))
			  *fcc = cpystr("");
			else
			  *fcc = cpystr(abe->fcc);

			/*
			 * After we expand the list, we no longer remember
			 * that it came from this address book entry, so
			 * we wouldn't be able to set the fcc again based
			 * on the result.  This tells our caller to remember
			 * that for us.
			 */
			*did_set |= (FCC_SET | FCC_NOREPO);
		    }
		    /*
		     * else if fcc-rule=fcc-by-nickname, use that
		     */
		    else if(abe->nickname && abe->nickname[0] &&
			    (ps_global->fcc_rule == FCC_RULE_NICK ||
			     ps_global->fcc_rule == FCC_RULE_NICK_RECIP)){
			if(*fcc)
			  fs_give((void **)fcc);

			*fcc = cpystr(abe->nickname);
			/*
			 * After we expand the list, we no longer remember
			 * that it came from this address book entry, so
			 * we wouldn't be able to set the fcc again based
			 * on the result.  This tells our caller to remember
			 * that for us.
			 */
			*did_set |= (FCC_SET | FCC_NOREPO);
		    }
		}

		/* lcc needs to be filled in */
		if(a == adr  &&
		    lcc      &&
		   (!*lcc || !**lcc)){
		    ADDRESS *atmp;
		    char    *tmp;

		    /* return fullname for To line */
		    if(abe->fullname && *abe->fullname){
			RFC822BUFFER rbuf;
			size_t l, len;

			if(*lcc)
			  fs_give((void **)lcc);

			atmp = mail_newaddr();
			atmp->mailbox = cpystr(abe->fullname);
			len = est_size(atmp);
			tmp = (char *) fs_get(len * sizeof(char));
			tmp[0] = '\0';
			/* write the phrase with quoting */
			rbuf.f   = dummy_soutr;
			rbuf.s   = NULL;
			rbuf.beg = tmp;
			rbuf.cur = tmp;
			rbuf.end = tmp+len-1;
			rfc822_output_address_list(&rbuf, atmp, 0L, NULL);
			*rbuf.cur = '\0';
			l = strlen(tmp)+1;
			*lcc = (char *) fs_get((l+1) * sizeof(char));
			strncpy(*lcc, tmp, l);
			(*lcc)[l] = '\0';
			strncat(*lcc, ";", l+1-strlen(*lcc)-1);
			(*lcc)[l] = '\0';
			mail_free_address(&atmp);
			fs_give((void **)&tmp);
			*did_set |= (LCC_SET | LCC_NOREPO);
		    }
		}

                if(recursing && abe->referenced){
                     /*---- caught an address loop! ----*/
                    fs_give(((void **)&a->host));
		    (*loop_detected)++;
                    a->host = cpystr("***address-loop-in-addressbooks***");
                    continue;
                }

                abe->referenced++;   /* For address loop detection */
                if(abe->tag == List){
                    length = 0;
                    for(l2 = abe->addr.list; *l2; l2++)
                        length += (strlen(*l2) + 1);

                    list = (char *)fs_get(length + 1);
		    list[0] = '\0';
                    l1 = list;
                    for(l2 = abe->addr.list; *l2; l2++){
                        if(l1 != list && length+1-(l1-list) > 0)
                          *l1++ = ',';

                        strncpy(l1, *l2, length+1-(l1-list));
			if(l1 > &list[length])
			  l1 = &list[length];

			list[length] = '\0';

                        l1 += strlen(l1);
                    }

		    bldto.type    = Str;
		    bldto.arg.str = list;
                    adr2 = expand_address(bldto, userdomain, localdomain,
					  loop_detected, fcc, did_set,
					  lcc, error, 1, simple_verify,
					  mangled);
                    fs_give((void **)&list);
                }
                else if(abe->tag == Single){
                    if(strucmp(abe->addr.addr, a->mailbox)){
			bldto.type    = Str;
			bldto.arg.str = abe->addr.addr;
                        adr2 = expand_address(bldto, userdomain,
					      localdomain, loop_detected,
					      fcc, did_set, lcc,
					      error, 1, simple_verify,
					      mangled);
                    }
		    else{
                        /*
			 * A loop within plain single entry is ignored.
			 * Set up so later code thinks we expanded.
			 */
                        adr2          = mail_newaddr();
                        adr2->mailbox = cpystr(abe->addr.addr);
                        adr2->host    = cpystr(userdomain);
                        adr2->adl     = cpystr(a->adl);
                    }
                }

                abe->referenced--;  /* Janet Jackson <janet@dialix.oz.au> */
                if(adr2 == NULL){
                    /* expanded to nothing, hack out of list */
                    a_temp = a;
                    if(a == adr){
                        adr    = a->next;
                        a      = adr;
                        a_tail = adr;
                    }
		    else{
                        a_tail->next = a->next;
                        a            = a->next;
                    }

		    a_temp->next = NULL;  /* So free won't do whole list */
                    mail_free_address(&a_temp);
                    continue;
                }

                /*
                 * Personal names:  If the expanded address has a personal
                 * name and the address book entry is a list with a fullname,
		 * tack the full name from the address book on in front.
                 * This mainly occurs with a distribution list where the
                 * list has a full name, and the first person in the list also
                 * has a full name.
		 *
		 * This algorithm doesn't work very well if lists are
		 * included within lists, but it's not clear what would
		 * be better.
                 */
		if(abe->fullname && abe->fullname[0]){
		    if(adr2->personal && adr2->personal[0]){
			if(abe->tag == List){
			    /* combine list name and existing name */
			    char *name;

			    if(!simple_verify){
				size_t l;

				l = strlen(adr2->personal) + strlen(abe->fullname) + 4;
				name = (char *) fs_get((l+1) * sizeof(char));
				snprintf(name, l+1, "%s -- %s", abe->fullname,
					adr2->personal);
				fs_give((void **)&adr2->personal);
				adr2->personal = name;
			    }
			}
			else{
			    /* replace with nickname fullname */
			    fs_give((void **)&adr2->personal);
			    adr2->personal = adrbk_formatname(abe->fullname,
							      NULL, NULL);
			}
		    }
		    else{
			if(abe->tag != List || !simple_verify){
			    if(adr2->personal)
			      fs_give((void **)&adr2->personal);

			    adr2->personal = adrbk_formatname(abe->fullname,
							      NULL, NULL);
			}
		    }
		}

                /* splice new list into old list and remove replaced addr */
                for(a2 = adr2; a2->next != NULL; a2 = a2->next)
		  ;/* do nothing */

                a2->next = a->next;
                if(a == adr)
                  adr    = adr2;
		else
                  a_tail->next = adr2;

                /* advance to next item, and free replaced ADDRESS */
                a_tail       = a2;
                a_temp       = a;
                a            = a->next;
                a_temp->next = NULL;  /* So free won't do whole list */
                mail_free_address(&a_temp);
            }
        }

	if((a_tail == adr && fcc && !(*did_set & FCC_SET))
	   || !a_tail->personal)
	  /*
	   * This looks for the addressbook entry that matches the given
	   * address.  It looks through all the addressbooks
	   * looking for an exact match and then returns that entry.
	   */
	  abe2 = address_to_abe(a_tail);
	else
	  abe2 = NULL;

	/*
	 * If there is no personal name yet but we found the address in
	 * an address book, then we take the fullname from that address
	 * book entry and use it.  One consequence of this is that if I
	 * have an address book entry with address hubert@cac.washington.edu
	 * and a fullname of Steve Hubert, then there is no way I can
	 * send mail to hubert@cac.washington.edu without having the
	 * personal name filled in for me.
	 */
	if(!a_tail->personal && abe2 && abe2->fullname && abe2->fullname[0])
	  a_tail->personal = adrbk_formatname(abe2->fullname, NULL, NULL);

	/* if it's first addr in list and fcc hasn't been set yet */
	if(!recursing && a_tail == adr && fcc && !(*did_set & FCC_SET)){
	    if(abe2 && abe2->fcc && abe2->fcc[0]){
		if(*fcc)
		  fs_give((void **)fcc);

		if(!strcmp(abe2->fcc, "\"\""))
		  *fcc = cpystr("");
		else
		  *fcc = cpystr(abe2->fcc);

		*did_set |= FCC_SET;
	    }
	    else if(abe2 && abe2->nickname && abe2->nickname[0] &&
		    (ps_global->fcc_rule == FCC_RULE_NICK ||
		     ps_global->fcc_rule == FCC_RULE_NICK_RECIP)){
		if(*fcc)
		  fs_give((void **)fcc);

		*fcc = cpystr(abe2->nickname);
		*did_set |= FCC_SET;
	    }
	}

	/*
	 * Lcc needs to be filled in.
	 * Bug: if ^T select was used to put the list in the lcc field, then
	 * the list will have been expanded already and the fullname for
	 * the list will be mixed with the initial fullname in the list,
	 * and we don't have anyway to tell them apart.  We could look for
	 * the --.  We could change expand_address so it doesn't combine
	 * those two addresses.
	 */
	if(adr &&
	    a_tail == adr  &&
	    lcc      &&
	   (!*lcc || !**lcc)){
	    if(adr->personal){
		ADDRESS *atmp;
		char    *tmp;
		RFC822BUFFER rbuf;
		size_t l, len;

		if(*lcc)
		  fs_give((void **)lcc);

		atmp = mail_newaddr();
		atmp->mailbox = cpystr(adr->personal);
		len = est_size(atmp);
		tmp = (char *) fs_get(len * sizeof(char));
		tmp[0] = '\0';
		/* write the phrase with quoting */
		rbuf.f   = dummy_soutr;
		rbuf.s   = NULL;
		rbuf.beg = tmp;
		rbuf.cur = tmp;
		rbuf.end = tmp+len-1;
		rfc822_output_address_list(&rbuf, atmp, 0L, NULL);
		*rbuf.cur = '\0';
		l = strlen(tmp)+1;
		*lcc = (char *) fs_get((l+1) * sizeof(char));
		strncpy(*lcc, tmp, l);
		(*lcc)[l] = '\0';
		strncat(*lcc, ";", l+1-strlen(*lcc)-1);
		(*lcc)[l] = '\0';
		mail_free_address(&atmp);
		fs_give((void **)&tmp);
		*did_set |= (LCC_SET | LCC_NOREPO);
	    }
	}
    }

    if(!recursing)
      fs_give((void **)&fakedomain);

    return(adr);
}


/*
 * This is a call back from rfc822_parse_adrlist. If we return NULL then
 * it does its regular parsing. However, if we return and ADDRESS then it
 * returns that address to us.
 *
 * We want a phrase with no address to be parsed just like a nickname would
 * be in expand_address. That is, the phrase gets put in the mailbox name
 * and the host is set to the fakedomain. Then expand_address will try to
 * look that up using wp_lookup().
 *
 * Args     phrase -- The start of the phrase of the address
 *             end -- The first character after the phrase
 *            host -- The defaulthost that was passed to rfc822_parse_adrlist
 *
 * Returns  An allocated address, or NULL.
 */
ADDRESS *
massage_phrase_addr(char *phrase, char *end, char *host)
{
    ADDRESS *adr = NULL;
    size_t   size;

    if((size = end - phrase) > 0){
	char *mycopy;

	mycopy = (char *)fs_get((size+1) * sizeof(char));
	strncpy(mycopy, phrase, size);
	mycopy[size] = '\0';
	removing_trailing_white_space(mycopy);

	/*
	 * If it is quoted we want to leave it alone. It will be treated
	 * like an atom in parse_adrlist anyway, which is what we want to
	 * have happen, and we'd have to remove the quotes ourselves here
	 * if we did it. The problem then is that we don't know if we
	 * removed the quotes here or they just weren't there in the
	 * first place.
	 */
	if(*mycopy == '"' && mycopy[strlen(mycopy)-1] == '"')
	  fs_give((void **)&mycopy);
	else{
	    adr = mail_newaddr();
	    adr->mailbox = mycopy;
	    adr->host    = cpystr(host);
	}
    }

    return(adr);
}


/*
 * Run through the adrlist "adr" and strip off any enclosing quotes
 * around personal names.  That is, change "Joe L. Normal" to
 * Joe L. Normal.
 */
void
strip_personal_quotes(struct mail_address *adr)
{
    int   len;
    register char *p, *q;

    while(adr){
	if(adr->personal){
	    len = strlen(adr->personal);
	    if(len > 1
	       && adr->personal[0] == '"'
	       && adr->personal[len-1] == '"'){
		adr->personal[len-1] = '\0';
		p = adr->personal;
		q = p + 1;
		while((*p++ = *q++) != '\0')
		  ;
	    }
	}

	adr = adr->next;
    }
}


static char *last_fcc_used;
/*
 * Returns alloc'd fcc.
 */
char *
get_fcc(char *fcc_arg)
{
    char *fcc;

    /*
     * Use passed in arg unless it is the same as default (and then
     * may use that anyway below).
     */
    if(fcc_arg && strcmp(fcc_arg, ps_global->VAR_DEFAULT_FCC))
      fcc = cpystr(fcc_arg);
    else{
	if(ps_global->fcc_rule == FCC_RULE_LAST && last_fcc_used)
	  fcc = cpystr(last_fcc_used);
	else if(ps_global->fcc_rule == FCC_RULE_CURRENT
		&& ps_global->mail_stream
		&& !sp_flagged(ps_global->mail_stream, SP_INBOX)
		&& !IS_NEWS(ps_global->mail_stream)
		&& ps_global->cur_folder
		&& ps_global->cur_folder[0]){
	    CONTEXT_S *cntxt = ps_global->context_current;
	    char *rs = NULL;

	    if(((cntxt->use) & CNTXT_SAVEDFLT))
	      rs = ps_global->cur_folder;
	    else
	      rs = ps_global->mail_stream->mailbox;

	    fcc = cpystr((rs&&*rs) ? rs : ps_global->VAR_DEFAULT_FCC);
	}
	else
	  fcc = cpystr(ps_global->VAR_DEFAULT_FCC);
    }
    
    return(fcc);
}


/*
 * Save the fcc for use with next composition.
 */
void
set_last_fcc(char *fcc)
{
    size_t l;

    if(fcc){
	if(!last_fcc_used)
	  last_fcc_used = cpystr(fcc);
	else if(strcmp(last_fcc_used, fcc)){
	    if((l=strlen(last_fcc_used)) >= strlen(fcc)){
		strncpy(last_fcc_used, fcc, l+1);
		last_fcc_used[l] = '\0';
	    }
	    else{
		fs_give((void **)&last_fcc_used);
		last_fcc_used = cpystr(fcc);
	    }
	}
    }
}


/*
 * Figure out what the fcc is based on the given to address.
 *
 * Returns an allocated copy of the fcc, to be freed by the caller, or NULL.
 */
char *
get_fcc_based_on_to(struct mail_address *to)
{
    ADDRESS    *next_addr;
    char       *bufp, *fcc;
    BuildTo	bldto;
    size_t      len;

    if(!to || !to->host || to->host[0] == '.')
      return(NULL);

    /* pick off first address */
    next_addr = to->next;
    to->next = NULL;
    len = est_size(to);
    bufp = (char *) fs_get(len * sizeof(char));
    bufp[0] = '\0';

    bldto.type    = Str;
    bldto.arg.str = cpystr(addr_string(to, bufp, len));

    fs_give((void **)&bufp);
    to->next = next_addr;

    fcc = NULL;

    (void) build_address_internal(bldto, NULL, NULL, &fcc, NULL, NULL, NULL, 0, NULL);

    if(bldto.arg.str)
      fs_give((void **) &bldto.arg.str);
    
    return(fcc);
}


/*
 * Free storage in headerentry.bldr_private.
 */
void
free_privatetop(PrivateTop **pt)
{
    if(pt && *pt){
	if((*pt)->affector)
	  fs_give((void **)&(*pt)->affector);
	
	fs_give((void **)pt);
    }
}

void
free_bldaddr_module_globals(void)
{
  if(last_fcc_used) fs_give((void **)&last_fcc_used);
}
