Logo Search packages:      
Sourcecode: af version File versions  Download package

address.c

/* Address.c - Address checking and translation for af.
   Copyright (C) 1991 - 2002 Malc Arnold.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */


#include <stdio.h>
#include "af.h"
#include "atom.h"
#include "address.h"
#include "ttable.h"
#include "keyseq.h"
#include "functions.h"
#include "variable.h"
#include "mime.h"
#include STRING_HDR

/****************************************************************************/
/* RCS info */

#ifndef lint
static char *RcsId = "$Id: address.c,v 2.2 2002/08/21 23:54:48 malc Exp $";
static char *AddressId = ADDRESSID;
#endif /* ! lint */

/****************************************************************************/
/* Global function declarations */

extern char *xmalloc(), *xrealloc();
extern char *xstrdup(), *vstrcat();
extern char *atext(), *avalue();
extern int strcasecmp();
extern void free(), afree();
extern ATOM *tokenise(), *wtokenise();
extern ATOM *atoken(), *asearch();
extern ATOM *afind(), *adelete();
extern ATOM *acut(), *adiscard();
extern ATOM *amerge(), *aappend();
extern ATOM *acopy(), *asplit();
extern ATOM *aquote(), *acomment();

#ifdef AFACK
extern char *get_host();
#else /* ! AFACK */
extern char *get_vtext();
#endif /* ! AFACK */

/* Local function declarations */

char *addr_text();
void free_glist(), free_alist();
GROUP *remove_address();
static char *addr_canon(), *name_canon();
static int contains_encoded_words();
static int equivalent_addr();
static void add_group(), add_address();
static void handle_error(), remove_if_null();
static void set_checksums(), uucp_to_rfc();
static void comment_to_phrase();
static void rfc_to_percent(), set_domain();
static ATOM *pop_addresses();

/****************************************************************************/
/* Import the error flag and text for parsing and address translation */

extern int a_errno;
extern char *a_errtext;

/****************************************************************************/
/* The parsing variables, stored as statics */

static int state = ST_INITIAL;                  /* Current state */
static int in_group = FALSE;              /* In a group */
static int in_bracket = FALSE;                  /* In a <..> */
static int need_new_group = TRUE;         /* Group has ended */

static ATOM *start = NULL;                      /* First unparsed token */
static ATOM *lookahead = NULL;                  /* Lookahead token */

/****************************************************************************/
/* The variables in which to store the list we are building */

static GROUP *grp_list = NULL;                  /* Overall list */
static GROUP *grp = NULL;                 /* Current group */
static ADDRESS *addr = NULL;              /* Current address */

/****************************************************************************/
GROUP *parse_addrs(alist)
ATOM *alist;
{
      /* Actually parse a previously-tokenised list of atoms */

      ATOM *token;

      /* Start the parse in the initial state */

      state = ST_INITIAL;
      in_group = in_bracket = FALSE;
      need_new_group = TRUE;
      grp_list = grp = NULL;
      addr = NULL;

      /* Set up the parse variables */

      start = alist;
      if ((token = atoken(alist)) == NULL) {
            afree(alist);
            return(NULL);
      }

      /* Scan through the list, handling tokens as we can */

      while (token != NULL) {
            /* Find the lookahead token */

            if ((lookahead = atoken(token->next)) == NULL) {
                  /* Check for error at end of string */

                  if (ttable[state][token->type] == tt_error) {
                        handle_error(start, NULL);
                        return(NULL);
                  }
                  break;
            }

            /*
             * Call the function determined by the current state,
             * the type of the current token, and the type of the
             * lookahead token, and collect the next unparsed token.
             */

            token = ttable[state][token->type][lookahead->type](token);
      }

      /* Free any trailing tokens in the list */

      afree(start);

      /* Delete null addresses in the addresses */

      remove_if_null();

      /* Build the checksum for each address */

      set_checksums();

      /* And return the group list */

      return(grp_list);
}
/****************************************************************************/
static ATOM *group(token)
ATOM *token;
{
      /* We have encountered the start of a group */

      /* Set the state */

      state = ST_GROUP;
      in_group = TRUE;
      need_new_group = FALSE;

      /* Add the new group */

      add_group();

      /* Set the group name and the parse positions */

      if (token->type != AT_COLON) {
            /* Extract the group name from the list */

            grp->name = acut(start, lookahead, NULL);
            start = adiscard(lookahead, lookahead);
            token = atoken(start);
      } else {
            /* Simply discard the colon */

            start = adiscard(start, token);
            token = lookahead;
      }

      return(token);
}
/****************************************************************************/
static ATOM *bracket(token)
ATOM *token;
{
      /* Encountered the start of a bracket address */

      int badname;
      ATOM *qname;

      /* If we are in state LOCAL, then this may be an error */

      badname = (state == ST_LOCAL);

      /* Check for nested brackets */

      if (badname && in_bracket) {
            /* Handle the error and fail */

            handle_error(start, lookahead);
            return(NULL);
      }

      /* Set the state */

      state = ST_BRACKET;
      in_bracket = TRUE;

      /* Add the address if  required */

      if (addr == NULL || addr->local != NULL) {
            add_address();
      }

      /* Set the address name and the parse positions */

      if (lookahead->type == AT_LANGLEB) {
            /* Extract the name from the atoms */

            addr->name = acut(start, lookahead, NULL);

            /* And handle quoting names containing dots */

            if (badname) {
                  qname = aquote(addr->name);
                  afree(addr->name);
                  addr->name = qname;
            }

            /* Move on to the next token in the list */

            start = adiscard(lookahead, lookahead);
            token = atoken(start);
      } else {
            /* Move on to the next token in the list */

            start = adiscard(start, token);
            token = lookahead;
      }

      /* Return the next token in the list */

      return(token);
}
/****************************************************************************/
/*ARGSUSED*/
static ATOM *route(token)
ATOM *token;
{
      /* Encountered the start of a route */

      /* Add the address if  required */

      if (addr == NULL || addr->local != NULL) {
            add_address();
      }

      /* And set the state */

      state = ST_ROUTE;
      return(lookahead);
}
/****************************************************************************/
static ATOM *local(token)
ATOM *token;
{
      /* Encountered the start of a local-part */

      /* Push any stacked tokens as addresses or a route */

      if (lookahead->type == AT_COLON) {
            /* The stacked atoms contain a route */

            addr->route = acut(start, lookahead, NULL);
            start = adiscard(lookahead, lookahead);
            token = atoken(start);
      } else if (!in_bracket) {
            /* The stacked atoms contain addresses */ 

            if ((start = pop_addresses(token)) == NULL) {
                  /* Invalid stacked address */

                  return(NULL);
            }

            /* Add a new address if required */

            if (addr == NULL || addr->local != NULL) {
                  add_address();
            }
      }

      /* And set the state */

      state = ST_LOCAL;
      return(token);
}
/****************************************************************************/
static ATOM *proute(token)
ATOM *token;
{
      /* Encountered an RFC 733 route */

      /* Push any stacked tokens as a local-part */

      addr->local = acut(start, lookahead, NULL);
      start = lookahead;
      token = atoken(start);

      /* And set the state */

      state = ST_PROUTE;
      return(token);
}
/****************************************************************************/
static ATOM *domain(token)
ATOM *token;
{
      /* Encountered the start of a domain */

      /* Push any stacked tokens as a local-part or route */

      if (state == ST_PROUTE) {
            addr->proute = acut(start, lookahead, NULL);
      } else {
            addr->local = acut(start, lookahead, NULL);
      }

      /* Find the next token in the list */

      start = adiscard(lookahead, lookahead);
      token = atoken(start);

      /* And set the state */

      state = ST_DOMAIN;
      return(token);
}
/****************************************************************************/
static ATOM *addresses(token)
ATOM *token;
{
      /*
       * Encountered a comma or semicolon;
       * add any stacked tokens as addresses
       */

      /* Process the addresses */

      if ((start = token = pop_addresses(lookahead)) == NULL) {
            /* Error in stacked address */

            return(NULL);
      }

      /*
       * If the lookahead token is a semi-colon we must preserve it
       * so that the state pop to ST_GROUP will work OK.  Otherwise
       * the lookahead token can be discarded.
       */

      if (lookahead->type != AT_SEMI) {
            start = adiscard(start, start);
            token = atoken(start);
      }

      /* Return the current token */

      return(token);
}
/****************************************************************************/
static ATOM *pops(token)
ATOM *token;
{
      /* Restore the previous state, or clean up at end-of-list */

      /* Handle incomplete tokens */

      switch (state) {
      case ST_LOCAL:
            /* Terminate the address */

            addr->local = acut(start, lookahead, NULL);
            start = token = lookahead;
            break;
      case ST_DOMAIN:
            /* Terminate the address */

            addr->domain = acut(start, lookahead, NULL);
            start = token = lookahead;
            break;
      case ST_BRACKET:
            /* Discard any right angle bracket */

            if (token->type == AT_RANGLEB) {
                  start = adiscard(start, token);
                  token = lookahead;
            }

            /* Check that there is an address; if not discard */

            if (addr->local == NULL) {
                  afree(addr->name);
                  addr->name = NULL;
            }
            break;
      case ST_GROUP:
            /* We'll be needing a new group now */

            need_new_group = TRUE;

            /* Discard any semicolon */

            if (token->type == AT_SEMI) {
                  start = adiscard(start, token);
                  token = lookahead;
            }
            break;
      }

      /* Restore the previous state */

      if (in_bracket && state != ST_BRACKET) {
            state = ST_BRACKET;
      } else if (in_group && state != ST_GROUP) {
            state = ST_GROUP;
            in_bracket = FALSE;
      } else {
            state = ST_INITIAL;
            in_group = in_bracket = FALSE;
      }

      return(token);
}
/****************************************************************************/
/*ARGSUSED*/
static ATOM *queue(token)
ATOM *token;
{
      /* Queue a token until we find out what to do with it */

      return(lookahead);
}
/****************************************************************************/
static ATOM *ignore(token)
ATOM *token;
{
      /* Discard the current token */

      start = adiscard(start, token);
      return(lookahead);
}
/****************************************************************************/
/*ARGSUSED*/
static ATOM *error(token)
ATOM *token;
{
      /* Handle an error while parsing an address */

      handle_error(start, NULL);
      return(NULL);
}
/****************************************************************************/
static void handle_error(from, to)
ATOM *from, *to;
{
      /* Error in parsing address */

      char *buf = NULL;

      /* Set the error flag */

      switch (state) {
      case ST_BRACKET:
            a_errno = AERR_BRACKET;
            break;
      case ST_ROUTE:
      case ST_PROUTE:
            a_errno = AERR_ROUTE;
            break;
      case ST_LOCAL:
            a_errno = AERR_LOCAL;
            break;
      case ST_DOMAIN:
            a_errno = AERR_DOMAIN;
            break;
      default:
            a_errno = AERR_ADDRESS;
            break;
      }

      /* Discard trailing commas in the list */

      if (to != NULL && to->type == AT_COMMA
          && to->next == NULL) {
            /* Discard the comma */

            from = adiscard(from, to);
            to = NULL;
      }

      /* Cut the error and build the text */

      if (from != NULL && start != NULL) {
            start = acut(start, from, to);
            buf = atext(NULL, from, AC_NONE);
      }

      /* If the address text wasn't set then we're at end of string */

      if (a_errtext != NULL) {
            free(a_errtext);
      }
      a_errtext = (buf != NULL) ? buf : xstrdup(END_ERRTEXT);

      /* Finally, clean up the group and atom lists */

      free_glist(grp_list);
      grp_list = grp = NULL;
      addr = NULL;
      afree(start);
      start = NULL;

      return;
}
/****************************************************************************/
static ATOM *pop_addresses(token)
ATOM *token;
{
      /*
       * Build addresses from tokens from start up to token.
       * Rebuild foo.bar.baz sequences into a single address.
       * If the sequence ends with foo. then we leave that
       * section as part of the current address.
       */

      ATOM *a, *next, *end;

      /* Add each address in the token list */

      while ((a = atoken(start)) != NULL && a != token) {
            /* Is there another address in the list? */

            next = atoken(a->next);
            end = (next != NULL && next != token) ? a->next : token;

            /* Handle "foo.bar.baz" as one address */

            while (end != NULL && end != token && end->type == AT_DOT
                   && (next = atoken(end->next)) != NULL) {
                  /* Check if this is part of the current address */

                  if (next == token) {
                        return(start);
                  }

                  /* Make this the end of the address */

                  end = next->next;
            }

            /* Add this address to the list */

            add_address();
            addr->local = acut(start, end, NULL);
            start = a = end;
      }

      /* And return the current token */

      return(start);
}
/****************************************************************************/
static void add_group()
{
      /* Add a new group to the list of groups */

      /* Allocate the space for the new group */

      if (grp_list == NULL) {
            grp_list = grp = (GROUP *) xmalloc(sizeof(GROUP));
      } else {
            grp->next = (GROUP *) xmalloc(sizeof(GROUP));
            grp = grp->next;
      }

      /* Initialise the group */

      grp->name = grp->comment = NULL;
      grp->addresses = addr = NULL;
      grp->next = NULL;

      return;
}
/****************************************************************************/
static void add_address()
{
      /* Add a new address to the list of addresses */

      /* If there is no group then we need to create one */

      if (grp_list == NULL || need_new_group) {
            need_new_group = FALSE;
            add_group();
      }

      /* Allocate the space for the new address */

      if (addr == NULL) {
            grp->addresses = addr = (ADDRESS *) xmalloc(sizeof(ADDRESS));
      } else {
            addr->next = (ADDRESS *) xmalloc(sizeof(ADDRESS));
            addr = addr->next;
      }

      /* Initialise the address */

      addr->name = addr->route = NULL;
      addr->local = addr->proute = addr->domain = NULL;
      addr->checksum = 0L;
      addr->next = NULL;

      return;
}
/****************************************************************************/
static void remove_if_null()
{
      /* Remove the current address and/or group if empty */

      GROUP *g, *lastgrp = NULL;
      ADDRESS *a, *last = NULL;

      /* Remove the address if it doesn't have a local-part */

      if (addr != NULL && addr->local == NULL) {
            /* Find the address in the list */

            for (a = grp->addresses; a != addr; a = a->next) {
                  /* Is this the address to remove? */

                  last = (a->next == addr) ? a : last;
            }

            /* Now update the address list as required */

            if (last == NULL) {
                  /* Free the group addresses entirely */

                  free_alist(addr);
                  grp->addresses = NULL;
            } else {
                  /* Remove addresses after last */

                  free_alist(last->next);
                  last->next = NULL;
            }
      }

      /* Now check the group similarly */

      if (grp != NULL && grp->name == NULL && grp->addresses == NULL) {
            /* Find the group in the list */

            for (g = grp_list; g != grp; g = g->next) {
                  /* Is this the group to remove? */

                  lastgrp = (g->next == grp) ? g : lastgrp;
            }

            /* Now update the group list as required */

            if (lastgrp == NULL) {
                  /* Free the group entirely */

                  free_glist(grp);
                  grp_list = NULL;
            } else {
                  /* Remove groups after lastgrp */

                  free_glist(lastgrp->next);
                  lastgrp->next = NULL;
            }
      }

      return;
}
/****************************************************************************/
void set_checksums()
{
      /*
       * Create a simple checksum for each address in glist.  This
       * checksum can then be used later when we try to remove any
       * duplicate addresses for the list; this speeds up scanning
       * long address lists considerably.
       *
       * The checksum is generated from the canonical form of the
       * address.
       */

      char *buf, *p;
      unsigned long sum;
      GROUP *g;
      ADDRESS *a;

      /* Loop through each available group */

      for (g = grp; g != NULL; g = g->next) {
            /* Loop through each address in the group */

            for (a = g->addresses; a != NULL; a = a->next) {
                  /* Build the canonical address */

                  buf = addr_canon(NULL, a);

                  /* Build the checksum for this address */

                  sum = 0L;
                  for (p = buf; *p != '\0'; p++) {
                        /* Rotate the checksum for better accuracy */
                  
                        sum = (sum & 01) ? (sum >> 1)
                              + 0x8000 : (sum >> 1);

                        /* Add the checksum and check bounds */

                        sum = (sum + *p) & 0xffff;
                  }
      
                  /* Now free the buffer */

                  free(buf);

                  /* And set the checksum for this address */

                  a->checksum = sum;
            }
      }

      /* That's that */

      return;
}
/****************************************************************************/
GROUP *translate(glist)
GROUP *glist;
{
      /*
       * Convert the addresses and groups in the list to strict
       * RFC 822 form, as modified by compile options (MTAs often
       * don't handle RFC 822 groups or routes, and hence we must
       * use RFC 733 ones).
       */

      GROUP *g;
      ADDRESS *a;

      /* Loop through each available group */

      for (g = glist; g != NULL; g = g->next) {
#ifdef NO_MTA_GROUPS
            /* Turn any group name into a comment */

            if (g->name != NULL) {
                  g->comment = acomment(g->name);
                  afree(g->name);
                  g->name = NULL;
            }
#endif /* NO_MTA_GROUPS */

            /* Loop through each address in the group */

            for (a = g->addresses; a != NULL; a = a->next) {
                  /* Convert UUCP addresses to RFC 822 */

                  if (a->domain == NULL) {
                        uucp_to_rfc(a);
                  }

                  /* Handle malc@thing (Malc Arnold) format */

                  if (a->name == NULL) {
                        comment_to_phrase(a);
                  }

                  /* Convert RFC 822 routes to RFC 733 */

                  if (a->route != NULL) {
                        rfc_to_percent(a);
                  }

                  /* Fix any local mail with no domain specified */

                  if (a->domain == NULL) {
                        set_domain(a);
                  }
            }
      }

      /* Now remove duplicate addresses within the list */

      for (g = glist; g != NULL; g = g->next) {
            for (a = g->addresses; a != NULL; a = a->next) {
                  glist = remove_address(glist, g, a->next, a);
            }
      }

      return(glist);
}
/****************************************************************************/
GROUP *remove_address(glist, first_group, first_address, address)
GROUP *glist, *first_group;
ADDRESS *first_address, *address;
{
      /*
       * Loop through the addresses in glist, deleting any 
       * which are equivalent to address.  Return the
       * updated group list.
       */

      char *a_canon;
      GROUP *g, *prev_grp = NULL;
      ADDRESS *a, *p, *prev_addr = NULL;

      /* Get the canonical form of the original address */

      a_canon = addr_canon(NULL, address);

      /* Initialise the loop variables */

      g = (first_address != NULL) ? first_group : first_group->next;
      a = (first_address != NULL) ? first_address
            : (g != NULL) ? g->addresses : NULL;

      /* Find the previous address in the group */

      for (p = (g != NULL) ? g->addresses : NULL;
           p != NULL && p != a; p = p->next) {
            /* Is this the previous address? */

            prev_addr = (p->next == a) ? p : NULL;
      }

      /* Loop through each address checking for duplicates */

      while (a != NULL) {
            /* Check if this is an equivalent address */
  
            if (a != address && equivalent_addr(address, a_canon, a)) {
                  /* Preserve any real name if that's useful */

                  if (address->name == NULL && a->name != NULL) {
                        address->name = a->name;
                        a->name = NULL;
                  }

                  /* Remove the duplicate address */

                  if (prev_addr != NULL) {
                        /* Remove the address from the list */

                        prev_addr->next = a->next;
                        a->next = NULL;
                        free_alist(a);
                  } else if ((g->addresses = a->next) == NULL) {
                        /* Removed all addresses in group */

                        if (prev_grp != NULL) {
                              /* Remove the group from the list */

                              prev_grp->next = g->next;
                              g->next = NULL;
                              free_glist(g);
                              g = prev_grp->next;
                        } else {
                              /* Update glist if we can */

                              if ((glist = g->next) == NULL) {
                                    /* Removed last address */

                                    return(NULL);
                              }
                              g->next = NULL;
                              free_glist(g);
                              g = glist;
                        }
                  }

                  /* Move a on to the next address */

                  a = (prev_addr != NULL) ? prev_addr->next :
                        (g != NULL) ? g->addresses : NULL;
            } else {
                  /* Check the next address */

                  prev_addr = a;
                  a = a->next;
            }

            /* May need to check next group */

            if (a == NULL && g->next != NULL) {
                  prev_grp = g;
                  prev_addr = NULL;
                  g = g->next;
                  a = g->addresses;
            }
      }

      /* Free the canonical name and return the modified list */

      free(a_canon);
      return(glist);
}
/****************************************************************************/
static void uucp_to_rfc(address)
ADDRESS *address;
{
      /*
       * Convert UUCP addresses to RFC 822 format.
       * Return TRUE on success, FALSE if the address is erroneous.
       */

      char *pling = NULL;
      ATOM *a, *alist, *next = NULL;
      ATOM *lcl = NULL, *dmn = NULL;
      ATOM *rt = NULL;

      /* Initialise for the translation */

      a = alist = address->local;
      address->local = NULL;

      /* Loop through the local-part handling each pling found */

      while (alist != NULL) {
            /* Do we need to handle this atom? */

            if (a == NULL || a->type == AT_ATOM &&
                (pling = strchr(a->text, '!')) != NULL &&
                (next = asplit(alist, a, pling)) != NULL) {
                  /* Update the elements of the list */

                  if (dmn != NULL) {
                        /* Append '@domain' to route */

                        rt = aappend(rt, "@", AT_AT);
                        rt = amerge(rt, NULL, dmn);
                  }

                  /* Move local to domain and alist to local */

                  dmn = lcl;
                  lcl = alist;

                  /* And move on to the next atom */

                  a = alist = (a == NULL) ? NULL : next;
            } else {
                  /* Just skip this atom */

                  a = a->next;
            }
      }

      /* Now set the address and return */

      address->route = rt;
      address->local = lcl;
      address->domain = dmn;

      return;
}
/****************************************************************************/
static void comment_to_phrase(address)
ADDRESS *address;
{
      /*
       * Convert a trailing comment into a phrase.  This handles
       * old style "malc@thing (Malc Arnold)" addresses, converting
       * them into RFC 822 phrase-addresses "Malc Arnold <malc@thing>"
       */

      char *phrase;
      int quote = FALSE;
      ATOM *rhs, *name;
      ATOM *qname, *a;

      /* Which section of the address is the rightmost? */

      rhs = (address->domain != NULL) ? address->domain : address->local;

      /* Do we have a single trailing comment? */

      if ((name = asearch(rhs, AT_COMMENT)) != NULL
          && afind(name->next) == NULL) {
            /* Extract the comment from the address */

            if (address->domain != NULL) {
                  address->domain = acut(address->domain, name, NULL);
            } else {
                  address->local = acut(address->local, name, NULL);
            }

            /* Convert the comment into a text phrase */

            phrase = atext(NULL, name, AC_UNCOMMENT);
            name = adiscard(name, name);

            /* Tokenise the new phrase */

            if ((address->name = tokenise(phrase)) == NULL) {
                  /* Tokenise the list by words instead */

                  address->name = wtokenise(phrase);

                  /* This phrase will need quoting */

                  quote = TRUE;
            }

            /* Check if we need to quote the phrase */

            for (a = address->name; !quote && a != NULL; a = a->next) {
                  /* Does this atom require quoting? */

                  quote = (!IS_WS(a) && !IS_PHRASE(a));
            }

            /* Quote the atom list if required */

            if (quote) {
                  /* Replace any name with the quoted one */

                  qname = aquote(address->name);
                  afree(address->name);
                  address->name = qname;
            }

            /* Now append any tail to the phrase */

            address->name = amerge(address->name, NULL, name);

            /* Free the phrase now */

            free(phrase);
      }

      return;
}
/****************************************************************************/
static void rfc_to_percent(address)
ADDRESS *address;
{
      /* Convert deprecated RFC 822 routes to percent form */

      ATOM *new_route = NULL;
      ATOM *first, *dmn, *comma, *at;

      /* Find the domain (ie the first route entry) */

      at = asearch(address->route, AT_AT);
      first = adiscard(address->route, at);
      comma = asearch(first, AT_COMMA);
      at = (comma != NULL) ? asearch(comma, AT_AT) : NULL;
      dmn = acut(first, comma, NULL);

      /* Set the start of the main route */

      first = (comma != NULL) ? adiscard(comma, comma) : NULL;
      first = (at != NULL) ? adiscard(first, at) : NULL;

      /* Loop through route domains, adding them as we go */

      while (first != NULL) {
            /* Find the first comma in the route */

            comma = asearch(first, AT_COMMA);
            at = (comma != NULL) ? asearch(comma, AT_AT) : NULL;

            /* Prepend the domain to the route list */

            first = acut(first, comma, NULL);

            /* Append the prior route */

            first = amerge(first, NULL, new_route);

            /* Prepend the percent and update the new route */

            new_route = aappend(NULL, "%", AT_PERCENT);
            new_route = amerge(new_route, NULL, first);

            /* Move the start pointer on */

            first = (comma != NULL) ? adiscard(comma, comma) : NULL;
            first = (at != NULL) ? adiscard(first, at) : NULL;
      }

      /* Move the domain to the start of the new route */

      first = aappend(NULL, "%", AT_PERCENT);
      first = amerge(first, NULL, address->domain);

      /* Append the new route and form the new address */

      address->route = NULL;
      address->proute = amerge(first, NULL, new_route);
      address->domain = dmn;

      return;
}
/****************************************************************************/
static void set_domain(address)
ADDRESS *address;
{
      /* Set the domain for an address that has none */

      ATOM *a, *last = NULL;

      /* Set the domain itself */
#ifdef AFACK
      address->domain = tokenise(get_host());
#else /* ! AFACK */
      address->domain = tokenise(get_vtext(V_DOMAIN));
#endif /* ! AFACK */
      
      /* Find the last non-whitespace atom in the local-part */

      for (a = address->local; a != NULL; a = a->next) {
            last = (!IS_WS(a)) ? a : last;
      }

      /* Move any trailing white space to after the domain */

      address->domain = amerge(address->domain, NULL, last->next);
      last->next = NULL;
      return;
}
/****************************************************************************/
static int equivalent_addr(address, canonical, equivalent)
ADDRESS *address, *equivalent;
char *canonical;
{
      /* Return TRUE if equivalent is equivalent to address */

      char *a_canon;
      int equiv;

      /* First check if the checksums match */

      if (address->checksum != equivalent->checksum) {
            return(FALSE);
      }

      /* So far so good; check the addresses themselves */

      a_canon = addr_canon(NULL, equivalent);
      equiv = !strcmp(canonical, a_canon);
      free(a_canon);

      /* Now return whether the addresses match */

      return(equiv);
}
/****************************************************************************/
void free_glist(glist)
GROUP *glist;
{
      /* Free the space taken up by the group list */

      if (glist != NULL) {
            /* Free the next list entry */

            free_glist(glist->next);

            /* And free the parts of this entry */

            afree(glist->name);
            afree(glist->comment);
            free_alist(glist->addresses);

            /* Finally, free the entry itself */

            free(glist);
      }

      return;
}
/****************************************************************************/
void free_alist(alist)
ADDRESS *alist;
{
      /* Free the space taken up by the list */

      if (alist != NULL) {
            /* Free the next list entry */

            free_alist(alist->next);

            /* Free the parts of this address */

            afree(alist->name);
            afree(alist->route);
            afree(alist->local);
            afree(alist->proute);
            afree(alist->domain);

            /* And finally, free the address itself */

            free(alist);
      }

      return;
}
/****************************************************************************/
char *grp_text(glist, canon)
GROUP *glist;
int canon;
{
      /*
       * Form an allocated string containing glist in a textual form.
       * Canon specifies the canonicalisation level required, which
       * may be AC_TERSE for no real names, or AC_FIRST for only the
       * first address in the list.
       */

      char *addrs = NULL;
      GROUP *g;
      ADDRESS *a;

      /* If required, simply return the first local-part */

      if (canon == AC_FIRST_LOCAL) {
            /* Find the first non-empty group */

            for (g = glist; g != NULL; g = g->next) {
                  /* If this group isn't empty... */

                  if (g->addresses != NULL) {
                        /* ...return the first local part */

                        return(atext(NULL, g->addresses->local,
                                   AC_FULL));
                  }
            }

            /* No addresses to be found */

            return(NULL);
      }

      /* Loop through each group and address */

      for (g = glist; g != NULL; g = g->next) {
            /* Handle the group comment, if any */

            if (canon != AC_TERSE && canon != AC_MAILBOXES
                && (canon != AC_GROUPS || g->addresses != NULL)
                && g->comment != NULL) {
                  addrs = atext(addrs, g->comment, AC_NONE);
                  addrs = xrealloc(addrs, strlen(addrs) + 2);
                  (void) strcat(addrs, " ");
            }

            /* Or add the group name, if any */

            if (canon != AC_TERSE && canon != AC_MAILBOXES
                && (canon != AC_GROUPS || g->addresses != NULL)
                && g->name != NULL) {
                  addrs = atext(addrs, g->name, AC_TRIM);
                  addrs = xrealloc(addrs, strlen(addrs) + 3);
                  (void) strcat(addrs, ": ");
            }

            /* Add each address in the group */

            for (a = g->addresses; a != NULL; a = a->next) {
                  /* Initialise the text if required */

                  addrs = (addrs == NULL) ? xstrdup("") : addrs;

                  /* Handle the address name */

                  if (canon != AC_TERSE && a->name != NULL) {
                        addrs = atext(addrs, a->name, AC_TRIM);
                        addrs = xrealloc(addrs, strlen(addrs) + 3);
                        (void) strcat(addrs, " <");
                  }

                  /* Add the address itself */

                  addrs = (canon == AC_TERSE) ? addr_canon(addrs, a)
                        : addr_text(addrs, a, canon);

                  /* End angle brackets around an address */

                  if (canon != AC_TERSE && a->name != NULL) {
                        addrs = xrealloc(addrs, strlen(addrs) + 2);
                        (void) strcat(addrs, ">");
                  }

                  /* Put a terminating comma if required */

                  if (a->next != NULL) {
                        addrs = xrealloc(addrs, strlen(addrs) + 3);
                        (void) strcat(addrs, ", ");
                  }
            }

            /* Put a semicolon after the group if required */

            if (canon != AC_TERSE && canon != AC_MAILBOXES
                && (canon != AC_GROUPS || g->addresses != NULL)
                && g->name != NULL) {
                  /* Terminate the group with a semicolon */

                  addrs = xrealloc(addrs, strlen(addrs) +
                               ((g->next != NULL) ? 3 : 2));
                  (void) strcat(addrs, (g->next != NULL) ? "; " : ";");
            }

            /* Put a comma after the group if required */

            if ((canon != AC_TERSE && canon != AC_MAILBOXES
                 && canon != AC_GROUPS || g->addresses != NULL)
                && g->next != NULL) {
                  /* Add a comma after the address */

                  addrs = xrealloc(addrs, strlen(addrs) + 3);
                  (void) strcat(addrs, ", ");
            }
      }

      /* Now return the address text */

      return(addrs);
}
/****************************************************************************/
char *grp_names(glist)
GROUP *glist;
{
      /*
       * Form an allocated string containing the names associated
       * with the addresses in glist.
       */

      char *names = NULL;
      GROUP *g;
      ADDRESS *a;

      /* Loop through each group and address */

      for (g = glist; g != NULL; g = g->next) {
            /* See if the group has a name */

            if (g->name != NULL) {
                  names = name_canon(names, g->name);
            } else if (g->comment != NULL) {
                  names = name_canon(names, g->comment);
            } else {
                  /* No group name, set names for each address */

                  for (a = g->addresses; a != NULL; a = a->next) {
                        names = (a->name != NULL) ?
                              name_canon(names, a->name)
                              : addr_canon(names, a);

                        /* Put a terminating comma if required */

                        if (a->next != NULL) {
                              names = xrealloc(names, strlen(names)
                                           + 3);
                              (void) strcat(names, ", ");
                        }
                  }
            }

            /* Put a comma or semicolon after the group as required */

            if (g->next != NULL) {
                  names = xrealloc(names, strlen(names) + 3);
                  (void) strcat(names, ", ");
            }
      }

      return(names);
}
/****************************************************************************/
char *addr_text(buf, address, canon)
char *buf;
ADDRESS *address;
int canon;
{
      /*
       * Append address in a textual form to buf.
       * Canon specifies the canonicalisation level required.
       */

      /* Prepend any route */

      if (address->route != NULL) {
            buf = atext(buf, address->route, canon);
            buf = xrealloc(buf, strlen(buf) + 2);
            (void) strcat(buf, ":");
      }

      /* Append the local-part */

      buf = atext(buf, address->local, canon);

      /* Append any percent route */

      buf = atext(buf, address->proute, canon);

      /* Finally, append @domain */

      buf = xrealloc(buf, strlen(buf) + 2);
      (void) strcat(buf, "@");
      buf = atext(buf, address->domain, canon);

      /* Return the modified buffer */

      return(buf);
}
/****************************************************************************/
static char *addr_canon(buf, address)
char *buf;
ADDRESS *address;
{
      /* Append address in canonical form (local@domain) to buf */

      ATOM *dmn, *pct;

      /* Form the local part and append the @ */

      buf = atext(buf, address->local, AC_FULL);
      buf = xrealloc(buf, strlen(buf) + 2);
      (void) strcat(buf, "@");

      /* Extract domain from domain or percent route */

      if (address->proute != NULL) {
            /* Find the first token after the initial '%' */

            dmn = asearch(address->proute, AT_PERCENT);
            dmn = atoken(dmn->next);

            /* Make a copy of the route */

            dmn = acopy(dmn);

            /* Cut out the domain */

            if ((pct = asearch(dmn, AT_PERCENT)) != NULL) {
                  dmn = adelete(dmn, pct, NULL);
            }

            /* Append the domain to the address */

            buf = atext(buf, dmn, AC_FULL);
            afree(dmn);
      } else {
            /* No percent route, simply add the domain */

            buf = atext(buf, address->domain, AC_FULL);
      }

      /* Return the modified buffer */

      return(buf);
}
/****************************************************************************/
static char *name_canon(buf, name)
char *buf;
ATOM *name;
{
      /*
       * Append name in canonical form to buf.  If the name is a
       * single quoted string or comment, then unquote or uncomment
       * it; otherwise leave the name as is.  This seems to be the
       * best balance between a convenient display for the user, and
       * preserving the sender's text.
       */

      int unquote;
      int uncomment;
      ATOM *atom;

      /* Is the name a single quoted string we can unquote? */

      unquote = ((atom = atoken(name)) != NULL &&
               atom->type == AT_QSTRING &&
               atoken(atom->next) == NULL
               && !contains_encoded_words(atom));

      /* Is the name a single comment we can uncomment? */

      uncomment = ((atom = afind(name)) != NULL &&
                 atom->type == AT_COMMENT &&
                 atoken(atom->next) == NULL);

      /* Now add the name to buf */

      return(atext(buf, name, (unquote) ? AC_UNQUOTE :
                 (uncomment) ? AC_UNCOMMENT : AC_TRIM));
}
/****************************************************************************/
static int contains_encoded_words(atom)
ATOM *atom;
{
      /*
       * Return TRUE if the quoted-string atom contains any
       * sequences which might be taken to be an encoded-word.
       */
 
      int eword = FALSE;
      size_t len;
      ATOM *alist, *a;

      /* First tokenise the text of the atom as words */

      alist = wtokenise(avalue(atom));

      /* Look for any encoded-words in the list */

      for (a = alist; !eword && a != NULL; a = a->next) {
            /* Get the length of the atom's text */

            len = strlen(a->text);

            /* Does this atom look like an encoded-word? */

            eword = (len >= 4 && *(a->text) == EW_TERMINATOR &&
                   *(a->text + 1) == EW_DELIMITER &&
                   *(a->text + len - 2) == EW_DELIMITER &&
                   *(a->text + len - 1) == EW_TERMINATOR);
      }

      /* Clean up and return status */

      afree(alist);
      return(eword);
}
/****************************************************************************/

Generated by  Doxygen 1.6.0   Back to index