home *** CD-ROM | disk | FTP | other *** search
/ PC-Online 1996 May / PCOnline_05_1996.bin / linux / source / contrib / smail / smail-3.1 / smail-3 / smail-3.1.28 / src / field.c < prev    next >
C/C++ Source or Header  |  1992-07-11  |  50KB  |  1,654 lines

  1. /* @(#)src/field.c    1.6 7/11/92 11:49:10 */
  2.  
  3. /*
  4.  *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
  5.  *    Copyright (C) 1992  Ronald S. Karr
  6.  * 
  7.  * See the file COPYING, distributed with smail, for restriction
  8.  * and warranty information.
  9.  */
  10.  
  11. /*
  12.  * field.c:
  13.  *    routines to process header fields and alias/.forward files
  14.  *
  15.  *    The routines defined in this file are not complicated
  16.  *    conceptually.  The basic algorithm is tokenize a string,
  17.  *    match patterns of tokens to specific addressing forms,
  18.  *    and insert a comma separator between addresses, if
  19.  *    necessary.
  20.  *
  21.  *    The pattern matching is made somewhat more complicated
  22.  *    in that when an address is found, it may be modified
  23.  *    to make it somewhat more conforming to standards than
  24.  *    it may have been to begin with.  Also, an address may
  25.  *    be extracted and added to a list.
  26.  *
  27.  *    external functions:  process_field, tokenize, detokenize, dump_tokens
  28.  */
  29. #include <stdio.h>
  30. #include <ctype.h>
  31. #include "defs.h"
  32. #include "smail.h"
  33. #include "field.h"
  34. #include "addr.h"
  35. #include "log.h"
  36. #include "dys.h"
  37. #include "exitcodes.h"
  38. #ifndef DEPEND
  39. # include "debug.h"
  40. # include "extern.h"
  41. #endif
  42.  
  43. /* functions local to this file */
  44. static char *finish_mod_clean();
  45. static void insert_comma();
  46. static int match_route_or_group();
  47. static int match_group_term();
  48. static int match_general();
  49. static char *queue_qualify_domain();
  50. static int enqueue_address();
  51.  
  52. /* macros local to this file */
  53. #define DUMP_TOKENS(d,t)  {if (d <= debug) dump_tokens(t);}
  54.  
  55.  
  56. /*
  57.  * tokenize - turn a string into a queue of tokens.
  58.  *
  59.  * Given a string, parse the string into a list of tokens.  The list
  60.  * is to be terminated by either an ERRORTOK or an ENDTOK token which
  61.  * do not have a successor.
  62.  *
  63.  * This routine is somewhat long, however, it is basically a state
  64.  * machine with some initialization at the beginning and cleanup at
  65.  * the end.
  66.  *
  67.  * inputs:
  68.  *    field    - string to tokenize.
  69.  *    ret_q    - address of a struct token variable in which to
  70.  *          return the head of the queue of tokens.
  71.  *    alias    - TRUE if # is a comment character and ':include:'
  72.  *          is allowed at the beginning of text tokens.  For
  73.  *          use in parsing alias, forward and mailing list
  74.  *          files.
  75.  *    space    - TRUE if white space is to be put in space.
  76.  * output:
  77.  *    an error message is returned on error, or NULL if no error.
  78.  *    Also, the value pointed to by ret_q is filled with the header
  79.  *    of the queue of tokens.
  80.  *
  81.  * called by: process_field, external functions
  82.  */
  83. char *
  84. tokenize(field, ret_q, alias, space)
  85.     char *field;            /* string to be tokenized */
  86.     struct token **ret_q;        /* return start of token queue here */
  87.     int alias;                /* TRUE if scanning alias file */
  88.     int space;                /* if TRUE put space in space */
  89. {
  90.     struct token *tq;            /* member pointer for building queue */
  91.     register char *fp;            /* pointer to chars in field */
  92.     char *p;
  93.     struct str str;
  94.     register struct str *sp = &str;    /* pointer to string building region */
  95.     enum e_state {            /* state machine definitions */
  96.     s_domlit,            /* inside a domain literal */
  97.     s_text,                /* inside a text literal token */
  98.     s_quote,            /* inside a quoted literal */
  99.     s_comment,            /* inside a comment */
  100.     s_cquote,            /* previous character was a \ */
  101.     s_space,            /* skipping through white space */
  102.     s_newtok,            /* finished a token, start a new one */
  103.     s_hash_comment,            /* comment from '#' to a newline */
  104.     } state;
  105.     enum e_state save_state;        /* save state from before a \ */
  106.     int comment_level;            /* embeddedness level in comments */
  107.     char *non_text_tokens;        /* chars not in text literal tokens */
  108.     int text_offs;            /* offset to text area in p */
  109.  
  110.     /*
  111.      * initialize state
  112.      */
  113.     if (alias) {
  114.     /* if parsing alias file, # is white space */
  115.     non_text_tokens = ":;<>][\",.!%@ \t\n#";
  116.     } else {
  117.     /* otherwise, # is a token char */
  118.     non_text_tokens = ":;<>][\",.!%@ \t\n";
  119.     }
  120.  
  121.     /* initialize the dynamic string variables */
  122.     STR_INIT(sp);
  123.  
  124.     /* allocate space for initial token */
  125.     *ret_q = tq = (struct token *)xmalloc(sizeof(*tq));
  126.  
  127.     /* begin by reading through white space */
  128.     state = s_space;
  129.  
  130.     /*
  131.      * loop until we have reached the end of the string,
  132.      * going through the state machine to build up tokens.
  133.      */
  134.     for (fp = field; *fp != '\0'; fp++) {
  135.     switch(state) {
  136.  
  137.     /*
  138.      * initial state
  139.      *
  140.      * scan for the end of white space and when found set
  141.      * state as appropriate to the next character.  If the
  142.      * next state is to be anything other than s_comment
  143.      * or s_hash_comment, finish off the white-space associated
  144.      * with the current token and begin the text associated
  145.      * with the current token.
  146.      *
  147.      * entry state:  s_newtok, s_comment
  148.      * exit state:  s_comment, s_quote, s_text, s_comment, s_domain,
  149.      *        s_hash_comment, s_newtok
  150.      */
  151.     case s_space:
  152.         if (alias && *fp == '#') {
  153.         /* found a '#' comment, skip through to the end of the line */
  154.         state = s_hash_comment;
  155.         } else {
  156.         if (*fp == '(') {
  157.             /* found a comment, scan through it next */
  158.             state = s_comment;
  159.             comment_level = 1;    /* comment finished when this is 0 */
  160.             if (space) {
  161.             STR_NEXT(sp, *fp);
  162.             }
  163.             break;
  164.         } else if (isspace(*fp)) {
  165.             if (space) {
  166.             STR_NEXT(sp, *fp);
  167.             }
  168.             break;        /* continue with space token */
  169.         } else {
  170.             if (space) {
  171.             /* end of white space and comments preceding token */
  172.             STR_NEXT(sp, '\0'); /* end white space */
  173.             }
  174.  
  175.             /*
  176.              * leave room for possible comma in the white space
  177.              * also, if we are not putting white space in space,
  178.              * this will at least make space valid
  179.              */
  180.             STR_NEXT(sp, '\0');
  181.             }
  182.         text_offs = sp->i;    /* mark offset for token text in p */
  183.  
  184.         /* determine what form this token will be */
  185.         switch (*fp) {
  186.         case '[':        /* a domain literal comes next */
  187.             state = s_domlit;
  188.             tq->form = T_DOMLIT;
  189.             break;
  190.         case '"':        /* a quoted literal comes next */
  191.             state = s_quote;
  192.             tq->form = T_QUOTE;
  193.             break;
  194.         case '\\':        /* text token with first char quoted */
  195.             state = s_cquote;
  196.             save_state = s_text; /* state after \ */
  197.             tq->form = T_TEXT;
  198.             break;
  199.         default:
  200.             if (alias && *fp == ':' &&
  201.             strncmpic(fp, ":include:", sizeof(":include:")-1) == 0)
  202.             {
  203.             p = fp + sizeof(":include:") - 1;
  204.             while (isspace(*p) && *p != '\n')
  205.                 p++;
  206.             if (*p && index(non_text_tokens, *p) == NULL) {
  207.                 str_ncat(sp, fp, p - fp);
  208.                 fp = p;
  209.                 state = s_text;
  210.                 tq->form = T_TEXT;
  211.                 break;
  212.             }
  213.             }
  214.             if (index(non_text_tokens, *fp)) {
  215.             state = s_newtok;
  216.             tq->form = T_OPER;
  217.             } else {
  218.             state = s_text;
  219.             tq->form = T_TEXT;
  220.             }
  221.             break;
  222.         }
  223.         STR_NEXT(sp, *fp);    /* copy character into token */
  224.         }
  225.         break;
  226.  
  227.     /*
  228.      * a comment was begun with a '#' character and a newline
  229.      * terminates it.  This state is entered only when parsing
  230.      * an alias file.
  231.      *
  232.      * entry state:  s_space
  233.      * exit state:  s_space
  234.      */
  235.     case s_hash_comment:
  236.         if (*fp == '\n') {
  237.         state = s_space;
  238.         }
  239.         break;
  240.  
  241.     /*
  242.      * a domain literal was begun with a '[' character
  243.      * and a ']' terminates it, however, a "\]" sequence
  244.      * does not terminate a domain literal.
  245.      *
  246.      * entry state:  s_space
  247.      * exit state:  s_newtok
  248.      */
  249.     case s_domlit:
  250.         STR_NEXT(sp, *fp);
  251.         if (*fp == '\\') {
  252.         /* \ quotes next character, save s_domlit state */
  253.         save_state = s_domlit;
  254.         state = s_cquote;
  255.         } else if (*fp == ']') {
  256.         /* ] terminates a domain literal */
  257.         state = s_newtok;
  258.         }
  259.         break;
  260.  
  261.     /*
  262.      * a text token was begun by a non white space character
  263.      * which is not in the set "[!@%.\"" and ends with a white
  264.      * space character or a character that is in that set.
  265.      * a special character can be prefixed with \ to be included
  266.      * in the text literal.
  267.      *
  268.      * entry state:  s_space
  269.      * exit state:  s_newtok
  270.      */
  271.     case s_text:
  272.         if (*fp == '\\') {
  273.             /* \ quotes next character, save s_text state */
  274.             STR_NEXT(sp, *fp);    /* copy char into token */
  275.         save_state = s_text;
  276.         state = s_cquote;
  277.         } else if (index(non_text_tokens, *fp)) {
  278.         /* space or an operator follows a text literal */
  279.         fp--;        /* re-scan character */
  280.         state = s_newtok;
  281.         } else {
  282.             STR_NEXT(sp, *fp);    /* copy char into token */
  283.         }
  284.         break;
  285.  
  286.     /*
  287.      * a quoted literal was begun by a " character and ends with
  288.      * a " character.  A \" sequence does not end a quoted literal.
  289.      *
  290.      * entry state:  s_space
  291.      * exit state:  s_newtok
  292.      */
  293.     case s_quote:
  294.         STR_NEXT(sp, *fp);        /* copy char into token */
  295.         if (*fp == '\\') {
  296.         /* \ quotes next character, save s_quote state */
  297.         save_state = s_quote;
  298.         state = s_cquote;
  299.         } else if (*fp == '"') {
  300.         /* " terminates a quoted literal */
  301.         state = s_newtok;
  302.         }
  303.         break;
  304.  
  305.     /*
  306.      * a comment begins with a ( and ends when a balancing ) is
  307.      * found.  A \( or \) sequence does not count in determining
  308.      * balancing of parentheses.
  309.      *
  310.      * entry state:  s_space
  311.      * exit state:  s_space
  312.      */
  313.     case s_comment:
  314.         if (space) {
  315.         STR_NEXT(sp, *fp);    /* copy char into token */
  316.         }
  317.         if (*fp == '\\') {
  318.         /* \ quotes next character, save s_comment state */
  319.         save_state = s_comment;
  320.         state = s_cquote;
  321.         } else if (*fp == ')') {
  322.         comment_level--;
  323.         if (comment_level == 0) {
  324.             /* balanced parentheses--done with comment */
  325.             state = s_space;
  326.         }
  327.         } else if (*fp == '(') {
  328.         comment_level++;
  329.         }
  330.         break;
  331.  
  332.     /*
  333.      * \ escape in quote, text literal, comment or domain
  334.      * include the character following a \ in the token and
  335.      * retain the previous state.
  336.      *
  337.      * entry state:  s_quote, s_text, s_comment or s_domain
  338.      * exit state:  the entry state
  339.      */
  340.     case s_cquote:
  341.         STR_NEXT(sp, *fp);        /* copy character into token */
  342.         /* restore previous state */
  343.         state = save_state;
  344.         break;
  345.  
  346.     /*
  347.      * finished up a complete token--set up for the next one.
  348.      * this involes ending the dynamic string region
  349.      * creating a new token and linking the previous token
  350.      * before the new one.
  351.      *
  352.      * entry state:  s_quote, s_text, s_comment, s_domain
  353.      * exit state:  s_space
  354.      */
  355.     case s_newtok:            /* finished a token, setup for next */
  356.         /* finish up dynamic string region */
  357.         STR_NEXT(sp, '\0');
  358.         STR_DONE(sp);
  359.         /* create new token which is the current token's successor */
  360.         tq->succ = (struct token *)xmalloc(sizeof(*tq));
  361.         tq->space = sp->p;        /* mark pointer to white space */
  362.         tq->text = sp->p + text_offs; /* mark pointer to token text */
  363.         tq = tq->succ;
  364.         /* scan through white space next */
  365.         state = s_space;
  366.         /* create a new dynamic string region */
  367.         STR_INIT(sp);
  368.         fp--;            /* re-read current character */
  369.         break;
  370.     }
  371.     }
  372.  
  373.     /*
  374.      * we reached the end of the string.  This is either okay, if we
  375.      * are scanning white space or a text literal, or it is not okay.
  376.      * if we are scanning white space or a comment, we need to close
  377.      * off the white-space for the token, properly, otherwise we need
  378.      * to close off the text associated with the token.
  379.      */
  380.     if (state == s_hash_comment) {
  381.     state = s_space;
  382.     }
  383.     if (state == s_space || state == s_comment) {
  384.     /* no token text exists for last token, fill in with empty text */
  385.     if (space) {
  386.         STR_NEXT(sp, '\0');        /* terminate space */
  387.     }
  388.     STR_NEXT(sp, '\0');        /* leave room for a possible comma */
  389.     tq->text = "";            /* empty text */
  390.     STR_DONE(sp);
  391.     } else {
  392.     /* last token does contain some text */
  393.     STR_NEXT(sp, '\0');
  394.     STR_DONE(sp);
  395.     tq->text = sp->p + text_offs;
  396.     }
  397.  
  398.     tq->space = sp->p;    /* first part of p is the white space and comments */
  399.  
  400.     /*
  401.      * if the current token is white space, then make it the ending
  402.      * token in the generated list.  Otherwise, allocate a new token
  403.      * with no text or white-space and make that the ending token.
  404.      * In the second case, check for errors as well.
  405.      */
  406.     if (state == s_space) {
  407.     tq->form = T_END;
  408.     tq->succ = NULL;
  409.     } else {
  410.     struct token *end_q;
  411.     end_q = tq->succ = (struct token *)xmalloc(sizeof(*end_q));
  412.     end_q->text = end_q->space = "";
  413.     end_q->form = T_END;
  414.     end_q->succ = NULL;
  415.  
  416.     /* is it an error? */
  417.     if (state == s_cquote) {
  418.         return end_q->text = "no character after \\";
  419.     }
  420.  
  421.     /* what was the specific state for the error */
  422.     switch (state) {
  423.  
  424.     case s_domlit:        /* unterminated domain literal */
  425.         tq->form = T_ERROR;
  426.         return end_q->text = "unterminated domain literal";
  427.  
  428.     case s_comment:
  429.         tq->form = T_ERROR;
  430.         return end_q->text = "unterminated comment";
  431.  
  432.     case s_quote:
  433.         tq->form = T_ERROR;
  434.         return end_q->text = "unterminated quoted literal";
  435.     }
  436.     }
  437.  
  438.     /*
  439.      * everything went fine.  ret_q is computed queue of tokens.
  440.      * don't return an error message.
  441.      */
  442.     return NULL;
  443. }
  444.  
  445.  
  446. /*
  447.  * detokenize - convert a list of tokens into its string representation
  448.  *
  449.  * given a queue of tokens, such as produced by tokenize, return
  450.  * a string corresponding to the space and text of the tokens.
  451.  *
  452.  * inputs:
  453.  *    buf    - buffer in which to store result.  NULL if we should
  454.  *          use the dynamic string region facility.  If buf is
  455.  *          non-NULL it is assumed to be large enough to store
  456.  *          the result
  457.  *    tq_head    - head of queue of tokens
  458.  *    tq_end    - end of tokens to tokenize, or NULL to tokenize up
  459.  *          to an ENDTOK token
  460.  *
  461.  * output:
  462.  *    string representing list of tokens
  463.  *
  464.  * called by:  finish_modified_clean, enqueue_address, external functions
  465.  */
  466. char *
  467. detokenize(space, buf, tq_head, tq_end)
  468.     int space;                /* TRUE if space should be copied */
  469.     char *buf;                /* store result here, if non-NULL */
  470.     struct token *tq_head;        /* list of tokens to detokenize */
  471.     struct token *tq_end;        /* end of tokens to, or NULL */
  472. {
  473.     register struct token *tq;        /* temp for scanning through tokens */
  474.  
  475.     if (buf) {
  476.     register char *bp= buf;        /* point to buf */
  477.  
  478.     bp[0] = '\0';
  479.     /* loop through contatenating space and text from tokens */
  480.     for (tq = tq_head; !ENDTOK(tq->form); tq = tq->succ) {
  481.         if (space) {
  482.         (void)strcat(bp, tq->space);
  483.         }
  484.         (void)strcat(bp, tq->text);
  485.         if (tq == tq_end) {
  486.         return bp;
  487.         }
  488.     }
  489.     /* get the white space from the ending token */
  490.     (void)strcat(bp, tq->space);
  491.     return bp;            /* return the buffer */
  492.     } else {
  493.     struct str str;
  494.     register struct str *sp = &str;    /* dynamic string region */
  495.  
  496.     STR_INIT(sp);            /* initialize dynamic string region */
  497.  
  498.     for (tq = tq_head; !ENDTOK(tq->form); tq = tq->succ) {
  499.         if (space) {
  500.         STR_CAT(sp, tq->space);
  501.         }
  502.         STR_CAT(sp, tq->text);
  503.         if (tq == tq_end) {
  504.         STR_NEXT(sp, '\0');    /* null terminate */
  505.         STR_DONE(sp);        /* finish dynamic string */
  506.  
  507.         return sp->p;        /* return string */
  508.         }
  509.     }
  510.  
  511.     STR_CAT(sp, tq->space);        /* add space from last token */
  512.     STR_NEXT(sp, '\0');        /* null terminate */
  513.     STR_DONE(sp);            /* finish dynamic string */
  514.  
  515.     return sp->p;            /* return it */
  516.     }
  517. }
  518.  
  519.  
  520. /*
  521.  * process_field - cleanly separate addresses in a header field, and extract
  522.  *           and cleanup those addresses
  523.  *
  524.  * given a header field which contains addresses, cleanly separate
  525.  * each address with a comma, if it is not separated already.
  526.  * Optionally clean local addresses by appending an RFC822 '@domain' form.
  527.  *
  528.  * Recognized addressing forms are:
  529.  *
  530.  *    ANY*<ANY*>        - route, can be recursive.
  531.  *    ANY*:            - beginning of a group.
  532.  *    ;[@WORD]        - end of a group.
  533.  *    WORD [op WORD [op ... WORD]]
  534.  *                - op is from the list ".!%@"
  535.  *                  the simple form "WORD" is a local address.
  536.  *
  537.  * inputs:
  538.  *    field    - a header field which contains addresses.  If NULL
  539.  *          no header is returned.
  540.  *    fp    - start of region to tokenize and clean.
  541.  *    domain    - if non-NULL, a domain which is to be appended in
  542.  *          RFC822 '@domain' form to local addresses.
  543.  *    uucp_host - if non-NULL, a string to prepend to ! routes.
  544.  *          The purpose of this field is to keep ! routes in
  545.  *          From: or Sender: fields in ! route notation and to
  546.  *          ensure that the ! route will correctly return to
  547.  *          the sender, assuming software on other machines
  548.  *          doing something else.
  549.  *    extract_q - Address queue in which to insert extracted addresses.
  550.  *          NULL if we are not extracting addresses.
  551.  *    flags    - A bitwise or of the following flags from field.h:
  552.  *          F_LOCAL  - set if message originated on the local host.
  553.  *                 This causes domains to be fully qualified.
  554.  *          F_STRICT - set to adhere more closely to RFC822.  When
  555.  *                 this is set, then all local addressing forms,
  556.  *                 bang routes and tokens%domain forms are appended
  557.  *                 with @domain, if domain is given, and prepended
  558.  *                 with uucp_host, if uucp_host is given.  This is
  559.  *                 for use in gatewaying to stricter networks.
  560.  *          F_ALIAS  - set to parse an aliases-style file.  In these
  561.  *                 cases, '#' introduces a comment and a the
  562.  *                 string ":include:" is allowed at the start of
  563.  *                 a text token, and does not introduce a group.
  564.  *    error    - if an error occurs, an error message is stored here,
  565.  *          otherwise error is left alone.
  566.  *
  567.  * output:
  568.  *    a header cleaned according to the rules stated above, or NULL
  569.  *    if `field' was NULL.
  570.  *
  571.  * called by: external functions
  572.  * calls: tokenize, match_route_or_group, match_group_term, match_general
  573.  */
  574. char *
  575. process_field(field, fp, domain, uucp_host, extract_q, flags, error)
  576.     char *field;            /* header field to be cleaned */
  577.     char *fp;                /* pointer to field contents */
  578.     char *domain;            /* domain to add to local addresses */
  579.     char *uucp_host;            /* uucp host to prepend to ! routes */
  580.     struct addr **extract_q;        /* queue in which to put addresses */
  581.     int flags;                /* miscellaneous flags */
  582.     char **error;            /* store error message here */
  583. {
  584.     int modified = FALSE;        /* set to TRUE if field is modified */
  585.     char *error_message;        /* error returned by tokenize */
  586.     struct token *tq_head;        /* list of tokens to return */
  587.     struct token *tq_anchor;        /* anchor point for pattern scan */
  588.     struct token *tq_new;        /* new anchor found by pattern scan */
  589.     int new_group = FALSE;        /* set if group: pattern newly found */
  590.     int group = FALSE;            /* set when inside of a group */
  591.     unsigned len = 0;            /* length of cleaned header */
  592.     int need_comma = FALSE;        /* TRUE if we may need a , at anchor */
  593.     int check_route = TRUE;        /* TRUE if we must scan for routes */
  594.     int i;                /* temp */
  595.  
  596.     if (field) {
  597.     len = strlen(field) + 1;
  598.     }
  599.     DEBUG(DBG_FIELD_LO, "process_field: entry\n");
  600.     /* tokenize the contents to make parsing easy */
  601.     error_message = tokenize(fp, &tq_head, flags&F_ALIAS, field != NULL);
  602.  
  603.     DUMP_TOKENS(DBG_FIELD_HI, tq_head);
  604.  
  605.     /*
  606.      * If tokenize found an error, then there is a syntax error
  607.      * which would make processing this header of dubious value.
  608.      * If we are not depending on the correctness of the header
  609.      * for extracting addresses, this is not enough to warrant return
  610.      * of mail.
  611.      */
  612.     if (error_message) {
  613.     *error = error_message;
  614.     DEBUG1(DBG_FIELD_LO, "process_field: error: %s\n", error_message);
  615.         return field;            /* return field unmodified */
  616.     }
  617.  
  618.     /*
  619.      * scan through until no more tokens are left
  620.      *
  621.      * starting at anchor points, find an addressing form that
  622.      * matches a set of tokens starting at that anchor point.
  623.      * If the addressing form needs to be separated from the previous
  624.      * by a comma, and it is not currently so separated then
  625.      * insert a comma in the white space before the anchor point token.
  626.      */
  627.     tq_anchor = tq_head;
  628.     while (!ENDTOK(tq_anchor->form)) {
  629.     tq_new = NULL;            /* set when address pattern found */
  630.     if (check_route) {
  631.         /* scan for:  phrase <route-addr> or phrase : */
  632.         i = match_route_or_group(tq_anchor, &tq_new, extract_q, group,
  633.                      &len, domain, uucp_host, flags, error);
  634.         switch (i) {
  635.  
  636.         case T_NOMATCH:        /* didn't match route or group */
  637.         tq_new = NULL;
  638.         break;
  639.         case T_ROUTE:        /* matched a route */
  640.         /* tq_new points to end of complete route form */
  641.         break;
  642.         case T_GROUP:        /* matched a group */
  643.         /* tq_new points to : at end of group */
  644.         group = TRUE;
  645.         /* NOTE:  next address does not need comma separator */
  646.         new_group = TRUE;
  647.         break;
  648.         case T_MODIFIED:        /* matched and something modified */
  649.         modified = TRUE;
  650.         break;
  651.         default:            /* error occured */
  652.         DEBUG1(DBG_FIELD_LO, "process_field: error: %s\n", *error);
  653.         return field;        /* return the field unchanged */
  654.         }
  655.     }
  656.  
  657.     if (!tq_new) {
  658.         /* scan for group terminator: ;[@WORD] */
  659.         i = match_group_term(tq_anchor, &tq_new, extract_q, group, error);
  660.         switch (i) {
  661.  
  662.         case T_NOMATCH:        /* didn't match group terminator */
  663.         tq_new = NULL;
  664.         break;
  665.         case T_GROUPTERM:        /* matched a group terminator */
  666.         /* tq_new points to end of complete group terminator */
  667.         need_comma = FALSE;    /* never need a comma before this */
  668.         group = FALSE;        /* not in a group anymore */
  669.         break;
  670.         default:            /* error occured */
  671.         DEBUG1(DBG_FIELD_LO, "process_field: error: %s\n", *error);
  672.         return field;        /* return the field unchanged */
  673.         }
  674.     }
  675.  
  676.     if (!tq_new) {
  677.         /*
  678.          * scan for:  WORD [op WORD [op ... WORD]]
  679.          *    where op is from the set [.!%@] and the sequence
  680.          *    ends in a WORD.
  681.          */
  682.         i = match_general(tq_anchor, &tq_new, &len, extract_q,
  683.                   domain, uucp_host, flags, error);
  684.         DEBUG1(DBG_FIELD_MID, "match_general returned %d\n", i);
  685.         switch (i) {
  686.  
  687.         case T_NOMATCH:        /* didn't match general address form */
  688.         tq_new = NULL;
  689.         break;
  690.         case T_GENERAL:        /* matched a general address */
  691.         /* tq_new points to end of address */
  692.         DEBUG(DBG_FIELD_MID, "just match, no mods\n");
  693.         break;
  694.         case T_MODIFIED:        /* matched and changed in some way */
  695.         /* tq_new points to end of address */
  696.         modified = TRUE;    /* modified in match_general */
  697.         break;
  698.         case T_MUTANT_FORM:        /* not allowed outside of a route */
  699.         *error = "mutant addressing form outside of route";
  700.         DEBUG1(DBG_FIELD_LO, "process_field: error: %s\n", *error);
  701.         return field;        /* return the field unchanged */
  702.  
  703.         default:            /* error occured */
  704.         DEBUG1(DBG_FIELD_LO, "process_field: error: %s\n", *error);
  705.         return field;        /* return the field unchanged */
  706.         }
  707.     }
  708.  
  709.     if (!tq_new) {
  710.         /* we didn't find an addressing form that matched */
  711.         *error = "unknown addressing form";
  712.         DEBUG1(DBG_FIELD_LO, "process_field: error: %s\n", *error);
  713.         return field;
  714.     }
  715.  
  716.     if (need_comma && field != NULL) {
  717.         /* there is an address and previous address needs a comma */
  718.         insert_comma(tq_anchor->space);
  719.         modified = TRUE;        /* field has been modified */
  720.         len++;            /* 1 character inserted */
  721.     }
  722.  
  723.     /*
  724.      * set state for next pass through the loop
  725.      */
  726.     if (new_group) {
  727.         /*
  728.          * if a group was found, the next address should not be
  729.          * preceded by a comma and the next token is the token
  730.          * immediately following the :
  731.          */
  732.         need_comma = FALSE;
  733.         new_group = FALSE;
  734.         tq_anchor = tq_new->succ;
  735.     } else {
  736.         if (tq_new->succ->text[0] == ',') {
  737.         /*
  738.          * if the next token is a comma, then we will not need to
  739.          * insert one before the next address.
  740.          * The next token is the one after the ','
  741.          */
  742.         need_comma = FALSE;
  743.         /* skip the comma */
  744.         tq_anchor = tq_new->succ->succ;
  745.         } else {
  746.         /*
  747.          * not a new group, and next token not a comma, we
  748.          * may need to insert a comma before the next address
  749.          * The next token is the one after the end of the previous
  750.          * match.
  751.          */
  752.         need_comma = TRUE;
  753.         tq_anchor = tq_new->succ;
  754.         }
  755.     }
  756.     }
  757.  
  758.     if (modified && field != NULL) {
  759.     /* copy finished results into buffer for returning to caller */
  760.     field = finish_mod_clean(field, (unsigned)(fp-field), tq_head, len);
  761.     }
  762.  
  763.     DEBUG1(DBG_FIELD_LO, "process_field: return %s\n", field);
  764.     return field;            /* all done, return the header */
  765. }
  766.  
  767.  
  768. /*
  769.  * finish_mod_clean - return string for field name token list
  770.  *
  771.  * Finish process_field for the case that the header field was modified
  772.  * by copying the field name and the tokens into a string area and
  773.  * returning a pointer to the string.
  774.  *
  775.  * inputs:
  776.  *    field_name - string to copy to beginning of buffer
  777.  *    name_len - number of chars to copy from field_name
  778.  *    tq_head    - token queue to copy into buffer
  779.  *    len    - computed total length of result
  780.  *
  781.  * output:
  782.  *    pointer to string representing completed header field
  783.  *
  784.  * called by: process_field
  785.  * calls: detokenize
  786.  */
  787. static char *
  788. finish_mod_clean(field_name, name_len, tq_head, len)
  789.     char *field_name;            /* field name string */
  790.     unsigned name_len;            /* length of field name */
  791.     struct token *tq_head;        /* head of list to convert to string */
  792.     unsigned len;            /* computed length of result */
  793. {
  794.     register char *p = xmalloc(len);    /* where to store result */
  795.  
  796.     DEBUG(DBG_FIELD_HI, "field was modified--build string for return\n");
  797.     DEBUG1(DBG_FIELD_HI, "field = %s\n", field_name);
  798.     /* copy field name up to colon */
  799.     (void)memcpy(p, field_name, name_len);
  800.  
  801.     /*
  802.      * copy space and text from queued tokens
  803.      */
  804.     (void)detokenize(TRUE, p+name_len, tq_head, (struct token *)NULL);
  805.  
  806.     DEBUG1(DBG_FIELD_HI, "completed string: %s\n", p);
  807.  
  808.     return p;
  809. }
  810.  
  811.  
  812. /*
  813.  * insert_comma - insert a comma after a comment or at beginning of string
  814.  *
  815.  * Given the space field from a token, insert a ',' character either
  816.  * after the last comment (if one exists) or at the beginning of the
  817.  * string, if no comment exists in the string.
  818.  *
  819.  * input:
  820.  *    s    - string in which to insert a comma
  821.  *
  822.  * outputs:
  823.  *    none
  824.  *
  825.  * called by: process_field
  826.  */
  827. static void
  828. insert_comma(s)
  829.     char *s;
  830. {
  831.     register char *p;            /* end point of copy */
  832.     register char *q;            /* temp pointer */
  833.  
  834.     /* put comma at beginning of white space or after last comment */
  835.     p = rindex(s, ')');
  836.     if (!p) {
  837.     p = s;
  838.     } else {
  839.     p++;                /* advance beyond ) */
  840.     }
  841.  
  842.     /* copy text up one byte to allow space for comma */
  843.     for (q = p+strlen(p)+1; q != p; --q) {
  844.     q[0] = q[-1];
  845.     }
  846.  
  847.     *q = ',';                /* insert the comma */
  848. }
  849.  
  850.  
  851. /*
  852.  * match_route_or_group - reduce on a route or group form if possible
  853.  *
  854.  * This function is called by process_field to determine if the current
  855.  * anchor point is the beginning of a route or a group.  If so the
  856.  * route or group is processed and the end of the route or group is
  857.  * returned.
  858.  *
  859.  * inputs:
  860.  *    tq_anchor - the anchor point from process_field.
  861.  *    tq_new    - pointer to variable in which to return the end
  862.  *          of the matched form.
  863.  *    extract_q - Address queue in which to insert extracted addresses.
  864.  *          NULL if we are not extracting addresses.
  865.  *    group    - TRUE if a matched group would be recursive,
  866.  *          this is specifically an RFC822 no-no and is likely
  867.  *          to mean that an unsupported addressing form has
  868.  *          been used.
  869.  *    domain    - if non-NULL, a domain which is to be appended in
  870.  *          RFC822 '@domain' form to local addresses.
  871.  *    uucp_host - if non-NULL, a string to prepend to ! routes.
  872.  *          The purpose of this field is to keep ! routes in
  873.  *          From: or Sender: fields in ! route notation and to
  874.  *          ensure that the ! route will correctly return to
  875.  *          the sender, assuming software on other machines
  876.  *          doing something else.
  877.  *    flags    - A bitwise or of the following flags from field.h:
  878.  *          F_LOCAL  - set if message originated on the local host.
  879.  *                 This causes domains to be fully qualified.
  880.  *          F_STRICT - set to adhere more closely to RFC822.  When
  881.  *                 this is set, then all local addressing forms,
  882.  *                 bang routes and tokens%domain forms are appended
  883.  *                 with @domain, if domain is given, and prepended
  884.  *                 with uucp_host, if uucp_host is given.  This is
  885.  *                 for use in gatewaying to stricter networks.
  886.  *          F_ALIAS  - set to parse an aliases-style file.  In these
  887.  *                 cases, '#' introduces a comment and a the
  888.  *                 string ":include:" is allowed at the start of
  889.  *                 a text token, and does not introduce a group.
  890.  *    error    - store an error message here if an error occurs.
  891.  *
  892.  * output:
  893.  *    T_NOMATCH if not matched, T_ROUTE if matched a route,
  894.  *    T_GROUP if matched a group,
  895.  *    T_MODIFIED if general addressing form which modified field,
  896.  *    FAIL if error.
  897.  *
  898.  * called by: process_field
  899.  * calls: match_general, enqueue_address
  900.  */
  901. static int
  902. match_route_or_group(tq_anchor, tq_new, extract_q, group,
  903.              len, domain, uucp_host, flags, error)
  904.     struct token *tq_anchor;        /* anchor point from process_field */
  905.     struct token **tq_new;        /* return last matched token here */
  906.     struct addr **extract_q;        /* queue in which to put addresses */
  907.     unsigned *len;            /* len variable from process_field */
  908.     int group;                /* TRUE if group would be recursive */
  909.     char *domain;            /* domain to add to local addresses */
  910.     char *uucp_host;            /* uucp host to prepend to ! routes */
  911.     int flags;                /* miscellaneous flags */
  912.     char **error;            /* store error message here */
  913. {
  914.     register struct token *tq;        /* temp for scanning tokens */
  915.     int recursion_level = 1;        /* embeddedness of route */
  916.     struct token *tq_start;        /* start of innermost route */
  917.     struct token *tq_end;        /* end of innermost route */
  918.     struct token *tq_temp;        /* temp */
  919.     int seek_end;            /* we are scanning for end of route */
  920.     int i;                /* temp */
  921.  
  922.     *tq_new = NULL;            /* nothing yet */
  923.  
  924.     /*
  925.      * scan through tokens until we know what we have:
  926.      * a route, a group or something else.
  927.      */
  928.     tq = tq_anchor;
  929.     for (;;) {
  930.         if (tq->text[0] == ',' || ENDTOK(tq->form)) {
  931.         /* we have something else nothing left to do here */
  932.             return T_NOMATCH;        /* we didn't match anything */
  933.         }
  934.         if (tq->text[0] == '<') {
  935.             /* we have a route */
  936.             DEBUG(DBG_FIELD_MID, "We have a route\n");
  937.             *tq_new = tq;
  938.         break;
  939.         }
  940.         if (tq->text[0] == ':' && tq->form == T_OPER) {
  941.             /* we have a group */
  942.             DEBUG(DBG_FIELD_MID, "We have a group\n");
  943.             *tq_new = tq;
  944.             if (group) {
  945.                 /* catch recursive groups */
  946.         *error = "recursive address group";
  947.         return FAIL;
  948.             }
  949.             return T_GROUP;        /* signal that we have a group */
  950.         }
  951.     tq = tq->succ;        /* get next token */
  952.     }
  953.  
  954.     /*
  955.      * we have a route, search for end point of route
  956.      * and note the tokens in the innermost recursion
  957.      * level so that we can extract them as an address.
  958.      */
  959.     tq_start = (*tq_new)->succ;
  960.     seek_end = 1;            /* assume we are innermost for now */
  961.  
  962.     /* allow recursion because it happens sometimes */
  963.     for (tq = (*tq_new)->succ;
  964.      !ENDTOK(tq->form);
  965.      tq = tq->succ)
  966.     {
  967.     if (tq->text[0] == '<') {
  968.         recursion_level++;
  969.         DEBUG(DBG_FIELD_HI, "bump up recursion level on route\n");
  970.  
  971.         /* at more deeply nested address, forget what we had before */
  972.         tq_start = tq->succ;
  973.         tq_end = NULL;
  974.         seek_end = 1;
  975.     } else if (tq->text[0] == '>') {
  976.         recursion_level--;
  977.         DEBUG(DBG_FIELD_HI, "bump down recursion level on route\n");
  978.         seek_end = 0;        /* the end, if no more < tokens */
  979.  
  980.         if (recursion_level == 0) {
  981.         break;
  982.         }
  983.     } else if (seek_end) {
  984.         tq_end = tq;        /* could be the end of the address */
  985.     }
  986.     }
  987.  
  988.     *tq_new = tq;        /* end of matched route */
  989.     if (recursion_level) {
  990.     *error = "unterminated route";
  991.     return FAIL;            /* signal an error */
  992.     }
  993.     if (tq_end == NULL) {
  994.     *error = "null route";
  995.     return FAIL;            /* signal an error */
  996.     }
  997.  
  998.     /*
  999.      * route may match a general WORD op WORD op ... WORD form
  1000.      */
  1001.     i = match_general(tq_start, &tq_temp, len, extract_q,
  1002.               domain, uucp_host, flags, error);
  1003.     switch (i) {
  1004.  
  1005.     case T_NOMATCH:
  1006.     break;                /* didn't match */
  1007.  
  1008.     case T_MUTANT_FORM:
  1009.     break;                /* mutant form allowed in route */
  1010.  
  1011.     case T_GENERAL:
  1012.     /* match, didn't modify anything */
  1013.     if (tq_temp != tq_end) {
  1014.         /* not a complete match--this is a problem */
  1015.         *error = "syntax error in address";
  1016.         return FAIL;
  1017.     }
  1018.     return T_ROUTE;            /* matched route, nothing modified */
  1019.  
  1020.     case T_MODIFIED:            /* match and something was modified */
  1021.     return T_MODIFIED;
  1022.  
  1023.     default:                /* an error occured */
  1024.     return FAIL;            /* propogate the error */
  1025.     }
  1026.  
  1027.     if (extract_q) {
  1028.     if (enqueue_address(extract_q, tq_start, tq_end, error) == FAIL) {
  1029.         /* enqueue_address returned error, specific error already logged */
  1030.         return FAIL;        /* signal an error */
  1031.     }
  1032.     }
  1033.     return T_ROUTE;            /* signal a route */
  1034. }
  1035.  
  1036.  
  1037. /*
  1038.  * match_group_term - match a group terminator pattern (;[@TOKEN]).
  1039.  *
  1040.  * Called from check_field to determine if the tokens after the
  1041.  * anchor point match a group terminator pattern (a semicolon optionally
  1042.  * followed by the pattern @WORD.
  1043.  *
  1044.  * inputs:
  1045.  *    tq_anchor - the anchor point from process_field.
  1046.  *    tq_new    - pointer to variable in which to return the end
  1047.  *          of the matched form.
  1048.  *    extract_q - Address queue in which to insert extracted addresses.
  1049.  *          NULL if we are not extracting addresses.
  1050.  *    group    - TRUE if we are now in a group.  If this is not
  1051.  *          the case then a match on a group terminator would
  1052.  *          be an error.
  1053.  *    error    - store an error message here, on errors.
  1054.  *
  1055.  * output:
  1056.  *    T_NOMATCH if not matched, T_GROUPTERM if match, FAIL on error.
  1057.  */
  1058. /*ARGSUSED*/
  1059. static int
  1060. match_group_term(tq_anchor, tq_new, extract_q, group, error)
  1061.     struct token *tq_anchor;        /* anchor point from process_field */
  1062.     struct token **tq_new;        /* return last matched token here */
  1063.     struct addr **extract_q;        /* queue in which to add addresses */
  1064.     int group;                /* TRUE if we are processing a group */
  1065.     char **error;            /* store error message here */
  1066. {
  1067.     register struct token *tq;        /* temp for scanning list of tokens */
  1068.  
  1069.     tq = tq_anchor;            /* copy this into a register */
  1070.  
  1071.     /*
  1072.      * if first token is a ; then we have a terminator and it
  1073.      * just remains to see if an optional, correct @WORD pattern
  1074.      * follows it, or if matching a group terminator is an error.
  1075.      */
  1076.     if (tq->text[0] == ';') {
  1077.     if (!group) {
  1078.         /* no matching group : form exists, this is not correct */
  1079.         *error = "\";\" does not terminate a group";
  1080.         return FAIL;        /* signal an error */
  1081.     }
  1082.     if (tq->succ->text[0] == '@') {
  1083.         /* optional @WORD given, make sure the WORD exists */
  1084.         *tq_new = tq = tq->succ->succ;
  1085.         if (!WORDTOK(tq->form)) {
  1086.         *error = "syntax error in address";
  1087.         return FAIL;        /* signal an error */
  1088.         }
  1089.         DEBUG(DBG_FIELD_MID, "group terminator of form ;@WORD\n");
  1090.         return T_GROUPTERM;        /* match */
  1091.     } else {
  1092.         /* no optional @WORD */
  1093.         DEBUG(DBG_FIELD_MID, "simple group terminator\n");
  1094.         *tq_new = tq;
  1095.         return T_GROUPTERM;        /* match */
  1096.     }
  1097.     }
  1098.     return T_NOMATCH;            /* no match */
  1099. }
  1100.  
  1101.  
  1102. /*
  1103.  * match_general - match a general address form WORD [op WORD [op ... WORD]]
  1104.  *
  1105.  * Called from check_field to determine if the tokens after the
  1106.  * anchor point match a general address form, which is a sequence
  1107.  * of WORD tokens separated by operators from the set ".!%@".
  1108.  *
  1109.  * If domain is given and we have an address which is just WORD, then
  1110.  * append @domain to the address.
  1111.  *
  1112.  * If uucp_host is given and we have a bang route, then prepend
  1113.  * uucp_host! to the address.
  1114.  *
  1115.  * If local is TRUE and address is WORD*@WORD1 or WORD*%WORD1 then
  1116.  * have the domain WORD1 fully qualfied if possible.
  1117.  *
  1118.  * inputs:
  1119.  *    tq_anchor - the anchor point from process_field.
  1120.  *    tq_new    - pointer to variable in which to return the end
  1121.  *          of the matched form.
  1122.  *    len    - len variable from check_form.  This routine may modify
  1123.  *          an address.  If so, the len variable is modified to
  1124.  *          taken into account the change in length of the
  1125.  *          header field.
  1126.  *    extract_q - Address queue in which to insert extracted addresses.
  1127.  *          NULL if we are not extracting addresses.
  1128.  *    domain    - if non-NULL, a domain which is to be appended in
  1129.  *          RFC822 '@domain' form to local addresses.
  1130.  *    uucp_host - if non-NULL, a string to prepend to ! routes.
  1131.  *          The purpose of this field is to keep ! routes in
  1132.  *          From: or Sender: fields in ! route notation and to
  1133.  *          ensure that the ! route will correctly return to
  1134.  *          the sender, assuming software on other machines
  1135.  *          doing something else.
  1136.  *    flags    - A bitwise or of the following flags from field.h:
  1137.  *          F_LOCAL  - set if message originated on the local host.
  1138.  *                 This causes domains to be fully qualified.
  1139.  *          F_STRICT - set to adhere more closely to RFC822.  When
  1140.  *                 this is set, then all local addressing forms,
  1141.  *                 bang routes and tokens%domain forms are appended
  1142.  *                 with @domain, if domain is given, and prepended
  1143.  *                 with uucp_host, if uucp_host is given.  This is
  1144.  *                 for use in gatewaying to stricter networks.
  1145.  *          F_ALIAS  - set to parse an aliases-style file.  In these
  1146.  *                 cases, '#' introduces a comment and a the
  1147.  *                 string ":include:" is allowed at the start of
  1148.  *                 a text token, and does not introduce a group.
  1149.  *    error    - store any error message here.
  1150.  *
  1151.  * output:
  1152.  *    T_NOMATCH if no match found, T_GENERAL if match and unmodified,
  1153.  *    T_MODIFIED if @domain appended, uucp_host! prepended, or domain
  1154.  *    qualified, FAIL on error.
  1155.  *
  1156.  * called by: process_field
  1157.  * calls: queue_qualify_domain, enqueue_address
  1158.  */
  1159. static int
  1160. match_general(tq_anchor, tq_new, len, extract_q, domain,
  1161.           uucp_host, flags, error)
  1162.     struct token *tq_anchor;        /* anchor point from process_field */
  1163.     struct token **tq_new;        /* return last matched token here */
  1164.     unsigned *len;            /* len variable from process_field */
  1165.     struct addr **extract_q;        /* queue in which to add addresses */
  1166.     char *domain;            /* domain to add to local addresses */
  1167.     char *uucp_host;            /* uucp host to prepend to ! routes */
  1168.     int flags;                /* miscellaneous flags */
  1169.     char **error;            /* store error message here */
  1170. {
  1171.     register struct token *tq;        /* temp for scanning token list */
  1172.     register struct token *tq_temp;    /* temp */
  1173.     int bang_route = FALSE;        /* TRUE if bang route */
  1174.     int pure_bang_route = TRUE;        /* TRUE if pure bang route */
  1175.     int domain_address = FALSE;        /* TRUE if domain address */
  1176.     int at_found = FALSE;        /* TRUE if @ token found */
  1177.     int ret_val = T_GENERAL;        /* value to be returned */
  1178.     struct token *tq_mark = NULL;    /* mark primary domain */
  1179.  
  1180.     tq = tq_anchor;            /* load anchor into a register */
  1181.     if (!WORDTOK(tq->form) && tq->text[0] != '.') {
  1182.     /* it doesn't begin with WORD token */
  1183.     return T_NOMATCH;        /* signal no match */
  1184.     }
  1185.  
  1186.     /* some part of the remaining tokens matches the form */
  1187.  
  1188.     /* skip initial collection of zero or more WORD tokens delimited
  1189.      * by one or more "." tokens */
  1190.     for (;;) {
  1191.     tq_temp = tq->succ;
  1192.     if (tq->text[0] == '.' && tq_temp->text[0] == '.') {
  1193.         tq = tq_temp;
  1194.         continue;
  1195.     }
  1196.     if ((WORDTOK(tq->form) || WORDTOK(tq_temp->form)) &&
  1197.         (tq->text[0] == '.' || tq_temp->text[0] == '.')) {
  1198.         tq = tq_temp;
  1199.         continue;
  1200.     }
  1201.     break;
  1202.     }
  1203.  
  1204.     while (!ENDTOK(tq->succ->form) && index("!%@", tq->succ->text[0]) &&
  1205.        (WORDTOK(tq->succ->succ->form) ||
  1206.         tq->succ->succ->text[0] == '.'))
  1207.     {
  1208.     switch(tq->succ->text[0]) {
  1209.  
  1210.     case '!':            /* take first host in ! route */
  1211.         bang_route = TRUE;
  1212.         break;
  1213.     case '%':            /* alternately, last % host */
  1214.         if (!bang_route) {
  1215.         tq_mark = tq->succ->succ;
  1216.         domain_address = TRUE;
  1217.         }
  1218.         pure_bang_route = FALSE;
  1219.         break;
  1220.     case '@':            /* always take last @ host */
  1221.         tq_mark = tq->succ->succ;
  1222.         domain_address = TRUE;
  1223.         at_found = TRUE;
  1224.         pure_bang_route = FALSE;
  1225.         break;
  1226.     }
  1227.     tq = tq->succ->succ;
  1228.  
  1229.     /*
  1230.      * skip initial collection of zero or more WORD tokens delimited
  1231.      * by one or more "." tokens
  1232.      */
  1233.     for (;;) {
  1234.         tq_temp = tq->succ;
  1235.         if (tq->text[0] == '.' && tq_temp->text[0] == '.') {
  1236.         tq = tq_temp;
  1237.         continue;
  1238.         }
  1239.         if ((WORDTOK(tq->form) || WORDTOK(tq_temp->form)) &&
  1240.         (tq->text[0] == '.' || tq_temp->text[0] == '.')) {
  1241.         tq = tq_temp;
  1242.         continue;
  1243.         }
  1244.         break;
  1245.     }
  1246.     }
  1247.     /* do we match host!(host!)*@route ? */
  1248.     if (pure_bang_route && tq->succ->text[0] == '!' &&
  1249.     tq->succ->succ->text[0] == '@')
  1250.     {
  1251.     return T_MUTANT_FORM;        /* mutant form allowed for route */
  1252.     }
  1253.  
  1254.     DEBUG(DBG_FIELD_HI, "found a WORD op WORD op ... WORD sequence\n");
  1255.     *tq_new = tq;        /* at end of sequence */
  1256.  
  1257.     /*
  1258.      * qualify domain by appending qualifier to it, if needed
  1259.      *
  1260.      * If we have a WORD*@WORD1 or a WORD*%WORD1 form, qualify
  1261.      * the domain WORD1, if necessary by appending a qualifier
  1262.      * to the domain.  len is updated to reflect length change.
  1263.      */
  1264.     if (domain_address && (flags&F_LOCAL)) {
  1265.     char *s = queue_qualify_domain(tq_mark, tq);
  1266.  
  1267.     if (s) {
  1268.         /* append .s */
  1269.         (*tq_new)->text = xprintf("%s.%s", (*tq_new)->text, s);
  1270.  
  1271.         /* field length increased */
  1272.         *len += 1 + strlen(s);
  1273.         ret_val = T_MODIFIED;
  1274.     }
  1275.     }
  1276.  
  1277.     if (!tq_mark && uucp_host && (bang_route || (flags&F_STRICT))) {
  1278.     /*
  1279.      * we have a bang route, prepend uucp_host! to the route.
  1280.      * Also prepend uucp_host! to the route if we are doing
  1281.      * strict RFC822.  In this case an address will be
  1282.      * prepended with the route back to the sender and
  1283.      * appended with the current domain.
  1284.      */
  1285.     tq_anchor->text = xprintf("%s!%s", uucp_host, tq_anchor->text);
  1286.     /* field length increased */
  1287.     *len += 1 + strlen(uucp_host);
  1288.     ret_val = T_MODIFIED;
  1289.     }
  1290.  
  1291.     /*
  1292.      * if we want strict addresses and we don't have one, or if
  1293.      * we have a local address but we don't want one (for a local
  1294.      * message only), then append @domain
  1295.      */
  1296.  
  1297.     if ((flags & F_STRICT && ! at_found) ||
  1298.     ((flags & F_LOCAL) && domain &&
  1299.      ! (domain_address || bang_route || at_found)))
  1300.     {
  1301.     (*tq_new)->text = xprintf("%s@%s", (*tq_new)->text, domain);
  1302.  
  1303.     /* field length increased */
  1304.     *len += 1 + strlen(domain);
  1305.     ret_val = T_MODIFIED;
  1306.     }
  1307.  
  1308.     if (extract_q) {
  1309.         /*
  1310.          * have the address added to the extraction queue
  1311.          */
  1312.     if (enqueue_address(extract_q, tq_anchor, *tq_new, error) < FAIL) {
  1313.         /* enqueue_address returned error, specific error already logged */
  1314.         return FAIL;        /* signal an error */
  1315.     }
  1316.     DEBUG(DBG_FIELD_MID, "address enqueued\n");
  1317.     }
  1318.  
  1319.     return ret_val;
  1320. }
  1321.  
  1322.  
  1323. /*
  1324.  * queue_qualify_domain - untokenize a domain and call qualify_domain
  1325.  *
  1326.  * Called from match_general, this routine takes a token list
  1327.  * representing a domain, converts it back to a string and calls
  1328.  * qualify_domain() to determine if any text needs to be appended
  1329.  * in order to make the domain fully qualified.
  1330.  *
  1331.  * inputs:
  1332.  *    tq_start - first token in domain
  1333.  *    tq_end    - last token in domain
  1334.  *
  1335.  * output:
  1336.  *    NULL if nothing should be appended to the domain,
  1337.  *    otherwise a string which represents the complete super
  1338.  *    domain that the given domain should be qualified in.
  1339.  *
  1340.  * called by:  match_general
  1341.  * calls:  qualify_domain(external), detokenize
  1342.  */
  1343. static char *
  1344. queue_qualify_domain(tq_start, tq_end)
  1345.     struct token *tq_start;        /* beginning of domain reference */
  1346.     struct token *tq_end;        /* end of domain reference */
  1347. {
  1348.     struct str str;
  1349.     register struct str *sp = &str;    /* dynamic string region */
  1350.     register struct token *tq;        /* temp for scanning through tokens */
  1351.     char *ret;                /* return value from qualify_domain */
  1352.  
  1353.     STR_INIT(sp);            /* initialize dynamic string region */
  1354.  
  1355.     /* get string represented by domain tokens */
  1356.     tq = tq_start;
  1357.     do {
  1358.     STR_CAT(sp, tq->text);
  1359.     } while (tq != tq_end && (tq = tq->succ));
  1360.     STR_NEXT(sp, '\0');
  1361.  
  1362.     /* send out for the actual qualification */
  1363.     ret = qualify_domain(sp->p);
  1364.     DEBUG2(200, "qualify_domain(%s) returns %s\n", sp->p, ret? ret: "(null)");
  1365.  
  1366.     STR_FREE(sp);        /* free region */
  1367.  
  1368.     return ret;            /* return the value from qualify_domain() */
  1369. }
  1370.  
  1371.  
  1372. /*
  1373.  * enqueue_address - insert a new address into a queue
  1374.  *
  1375.  * Given a token list representing an address, detokenize the list
  1376.  * and add it to the given address queue.
  1377.  *
  1378.  * inputs:
  1379.  *    q    - pointer to queue of addresses
  1380.  *    tq_start - first token in the address
  1381.  *    tq_end    - ending token of the address
  1382.  *    errro    - store any error message here
  1383.  *
  1384.  * outputs:
  1385.  *    SUCCEED if everything went okay, FAIL on error
  1386.  *
  1387.  * called by: match_or_route_group, match_general
  1388.  * calls: detokenize
  1389.  */
  1390. static int
  1391. enqueue_address(q, tq_start, tq_end, error)
  1392.     struct addr **q;            /* queue in which to insert */
  1393.     struct token *tq_start;        /* first token in the address */
  1394.     struct token *tq_end;        /* ending token in the address */
  1395.     char **error;            /* store error message here */
  1396. {
  1397.     register char *s;            /* string representing the address */
  1398.     register struct addr *temp_q;    /* temp */
  1399.     char *parse_error;            /* error from parse_address() */
  1400.  
  1401.     /* grab the string corresponding to the tokens */
  1402.     s = detokenize(FALSE, (char *)NULL, tq_start, tq_end);
  1403.  
  1404.     DEBUG1(DBG_FIELD_LO, "enqueue_address(%s)\n", s);
  1405.     /* insert it into the queue */
  1406.     temp_q = alloc_addr();        /* get an address queue entry */
  1407.     temp_q->succ = *q;
  1408.     temp_q->in_addr = s;
  1409.     /* work_addr gets a mungeable copy */
  1410.     if ((temp_q->work_addr = preparse_address(s, &parse_error)) == NULL) {
  1411.     *error = xprintf("%s: %s", s, error);
  1412.     return FAIL;
  1413.     }
  1414.     *q = temp_q;            /* insert at beginning of list */
  1415.  
  1416.     return SUCCEED;            /* added to the list */
  1417. }
  1418.  
  1419.  
  1420. /*
  1421.  * dump_tokens - list tokens to standard error for debugging purposes
  1422.  *
  1423.  * called from the DUMP_TOKENS macro, this function generates
  1424.  * a verbose description of what is going on with a list of tokens.
  1425.  *
  1426.  * input:
  1427.  *    tq    - head of a queue of tokens
  1428.  *
  1429.  * outputs:
  1430.  *    none
  1431.  *
  1432.  * called by:  DUMP_TOKENS(local macro)
  1433.  */
  1434. void
  1435. dump_tokens(tq)
  1436.     register struct token *tq;        /* dump these tokens on errfile */
  1437. {
  1438.     (void)fprintf(errfile, "token list:\n");
  1439.     while (tq) {
  1440.     register char *s;
  1441.     char buf[100+1];
  1442.  
  1443.     switch(tq->form) {
  1444.  
  1445.     case T_QUOTE:
  1446.         s = "T_QUOTE";
  1447.         break;
  1448.     case T_DOMLIT:
  1449.         s = "T_DOMLIT";
  1450.         break;
  1451.     case T_OPER:
  1452.         s = "T_OPER";
  1453.         break;
  1454.     case T_TEXT:
  1455.         s = "T_TEXT";
  1456.         break;
  1457.     case T_END:
  1458.         s = "T_END";
  1459.         break;
  1460.     case T_ERROR:
  1461.         s = "T_ERROR";
  1462.         break;
  1463.     default:
  1464.         (void)sprintf(s = buf, "form=%d", tq->form);
  1465.         break;
  1466.     }
  1467.     (void)fprintf(errfile, "\t|%s|%s|%s|\n",
  1468.               tq->space, tq->text, s);
  1469.     tq = tq->succ;
  1470.     }
  1471.     (void)fprintf(errfile, "end of list\n");
  1472. }
  1473.  
  1474.  
  1475. #ifdef STANDALONE
  1476.  
  1477. #include "varargs.h"
  1478.  
  1479. int send_to_postmaster = FALSE;        /* see if this gets set */
  1480. int return_to_sender = FALSE;        /* see if this gets set */
  1481. struct addr *recipients = NULL;        /* initial list here is zero */
  1482. char **args_recipients = {0};        /* nothing in this list */
  1483. int exitvalue = 0;
  1484. FILE *errfile = stderr;
  1485. char *primary_name = NULL;
  1486. char *program = "field";
  1487. int compile_num = 999;
  1488.  
  1489. extern int getopt();
  1490. extern char *optarg;
  1491. extern int optind;
  1492.  
  1493. #ifdef DEBUG_LEVEL
  1494.  int debug = DEBUG_LEVEL;
  1495. #else    /* DEBUG_LEVEL */
  1496.  int debug = 0;
  1497. #endif    /* DEBUG_LEVEL */
  1498.  
  1499. /*
  1500.  * test the above functions by calling process_field for each
  1501.  * argument given to the program.
  1502.  */
  1503. void
  1504. main(argc, argv)
  1505.     int argc;                /* count of arguments */
  1506.     char **argv;            /* vector of arguments */
  1507. {
  1508.     char *s;                /* return value from process_field */
  1509.     struct addr *q;            /* temp for scanning hdr_recipients */
  1510.     char *error;
  1511.     char *domain = NULL;
  1512.     char *uucp_host = NULL;
  1513.     int flags = 0;
  1514.     int c;
  1515.  
  1516.     while ((c = getopt(argc, argv, "v:p:d:u:lsaD:")) != EOF) {
  1517.         switch (c) {
  1518.         case 'v':
  1519.             visible_name = optarg;
  1520.             break;
  1521.  
  1522.         case 'p':
  1523.             primary_name = optarg;
  1524.             break;
  1525.  
  1526.         case 'd':
  1527.             domain = optarg;
  1528.             break;
  1529.  
  1530.         case 'u':
  1531.             uucp_host = optarg;
  1532.             break;
  1533.  
  1534.         case 'l':
  1535.             flags |= F_LOCAL;
  1536.             break;
  1537.  
  1538.         case 's':
  1539.             flags |= F_STRICT;
  1540.             break;
  1541.  
  1542.         case 'a':
  1543.             flags |= F_ALIAS;
  1544.             break;
  1545.  
  1546.         case 'D':
  1547.             debug = atoi(optarg);
  1548.             break;
  1549.         }
  1550.     }
  1551.  
  1552.     argc -= optind;
  1553.     argv += optind;
  1554.  
  1555.     /*
  1556.      * loop over all arguments
  1557.      */
  1558.     if (argc > 0) {
  1559.     while (*argv) {
  1560.         (void)fprintf(stderr, "input:  %s\n", *argv);
  1561.         s = index(*argv, ':');
  1562.         if (s) {
  1563.         s++;
  1564.         } else {
  1565.         s = *argv;
  1566.         }
  1567.  
  1568.         /*
  1569.          * non-strict RFC822, from local machine
  1570.          */
  1571.         error = NULL;
  1572.         s = process_field(*argv, s, domain, uucp_host,
  1573.                   &recipients, flags, &error);
  1574.         if (error) {
  1575.         (void) fprintf(stderr, "error: %s\n", error);
  1576.         } else {
  1577.         (void)fprintf(stderr, "output: %s\n", s? s: "(null)");
  1578.         }
  1579.         argv++;
  1580.     }
  1581.     } else {
  1582.     char line[4096];
  1583.  
  1584.     while (gets(line) != NULL) {
  1585.         (void)fprintf(stderr, "input:  %s\n", line);
  1586.         s = index(line, ':');
  1587.         if (s) {
  1588.         s++;
  1589.         } else {
  1590.         s = line;
  1591.         }
  1592.  
  1593.         /*
  1594.          * non-strict RFC822, from local machine
  1595.          */
  1596.         error = NULL;
  1597.         s = process_field(line, s, domain, uucp_host,
  1598.                   &recipients, flags, &error);
  1599.         if (error) {
  1600.         (void) fprintf(stderr, "error: %s\n", error);
  1601.         } else {
  1602.         (void)fprintf(stderr, "output: %s\n", s? s: "(null)");
  1603.         }
  1604.     }
  1605.     }
  1606.  
  1607.     for (q = recipients; q; q = q->succ) {
  1608.     (void)printf("%s\n", q->in_addr);
  1609.     }
  1610.     exit(exitvalue);
  1611. }
  1612.  
  1613. /*
  1614.  * define panic, fatal and write_log here, rather than
  1615.  * using the external routines.  We are testing and just want
  1616.  * the information displayed, not logged.
  1617.  */
  1618. /*VARARGS2*/
  1619. void
  1620. panic(exitcode, fmt, va_alist)
  1621.     int exitcode;            /* call exit(exitcode) */
  1622.     char *fmt;                /* printf(3) format */
  1623.     va_dcl                              /* arguments for printf */
  1624. {
  1625.     va_list ap;
  1626.  
  1627.     va_start(ap);
  1628.     (void)fprintf(stderr, "PANIC(%s): ", exitcode);
  1629.     (void)vfprintf(stderr, fmt, ap);
  1630.     putc('\n', stderr);            /* fatal messages not \n terminated */
  1631.     va_end(ap);
  1632.  
  1633.     return_to_sender = TRUE;
  1634.     exit(exitcode);
  1635. }
  1636.  
  1637. /*VARARGS2*/
  1638. void
  1639. write_log(log, fmt, va_alist)
  1640.     int log;                /* TRUE if to write global log file */
  1641.     char *fmt;                /* printf(3) format */
  1642.     va_dcl                              /* arguments for printf */
  1643. {
  1644.     va_list ap;
  1645.  
  1646.     va_start(ap);
  1647.     (void)fprintf(stderr, log? "PUBLIC: ": "PRIVATE: ");
  1648.     (void)vfprintf(stderr, fmt, ap);
  1649.     putc('\n', stderr);
  1650.     va_end(ap);
  1651. }
  1652.  
  1653. #endif    /* STANDALONE */
  1654.