This is field.c in view mode; [Download] [Up]
/* @(#)src/field.c 1.2 24 Oct 1990 05:22:58 */ /* * Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll * * See the file COPYING, distributed with smail, for restriction * and warranty information. */ /* * field.c: * routines to process header fields and alias/.forward files * * The routines defined in this file are not complicated * conceptually. The basic algorithm is tokenize a string, * match patterns of tokens to specific addressing forms, * and insert a comma separator between addresses, if * necessary. * * The pattern matching is made somewhat more complicated * in that when an address is found, it may be modified * to make it somewhat more conforming to standards than * it may have been to begin with. Also, an address may * be extracted and added to a list. * * external functions: process_field, tokenize, detokenize, dump_tokens */ #include <stdio.h> #include <ctype.h> #include "defs.h" #include "smail.h" #include "field.h" #include "addr.h" #include "log.h" #include "dys.h" #include "exitcodes.h" #ifndef DEPEND # include "debug.h" # include "extern.h" #endif /* functions local to this file */ static char *finish_mod_clean(); static void insert_comma(); static int match_route_or_group(); static int match_group_term(); static int match_general(); static char *queue_qualify_domain(); static int enqueue_address(); /* macros local to this file */ #define DUMP_TOKENS(d,t) {if (d <= debug) dump_tokens(t);} /* * tokenize - turn a string into a queue of tokens. * * Given a string, parse the string into a list of tokens. The list * is to be terminated by either an ERRORTOK or an ENDTOK token which * do not have a successor. * * This routine is somewhat long, however, it is basically a state * machine with some initialization at the beginning and cleanup at * the end. * * inputs: * field - string to tokenize. * ret_q - address of a struct token variable in which to * return the head of the queue of tokens. * alias - TRUE if # is a comment character and ':include:' * is allowed at the beginning of text tokens. For * use in parsing alias, forward and mailing list * files. * space - TRUE if white space is to be put in space. * output: * an error message is returned on error, or NULL if no error. * Also, the value pointed to by ret_q is filled with the header * of the queue of tokens. * * called by: process_field, external functions */ char * tokenize(field, ret_q, alias, space) char *field; /* string to be tokenized */ struct token **ret_q; /* return start of token queue here */ int alias; /* TRUE if scanning alias file */ int space; /* if TRUE put space in space */ { struct token *tq; /* member pointer for building queue */ register char *fp; /* pointer to chars in field */ struct str str; register struct str *sp = &str; /* pointer to string building region */ enum e_state { /* state machine definitions */ s_domlit, /* inside a domain literal */ s_text, /* inside a text literal token */ s_quote, /* inside a quoted literal */ s_comment, /* inside a comment */ s_cquote, /* previous character was a \ */ s_space, /* skipping through white space */ s_newtok, /* finished a token, start a new one */ s_hash_comment, /* comment from '#' to a newline */ } state; enum e_state save_state; /* save state from before a \ */ int comment_level; /* embeddedness level in comments */ char *non_text_tokens; /* chars not in text literal tokens */ int text_offs; /* offset to text area in p */ /* * initialize state */ if (alias) { /* if parsing alias file, # is white space */ non_text_tokens = ":;<>][\",.!%@ \t\n#"; } else { /* otherwise, # is a token char */ non_text_tokens = ":;<>][\",.!%@ \t\n"; } /* initialize the dynamic string variables */ STR_INIT(sp); /* allocate space for initial token */ *ret_q = tq = (struct token *)xmalloc(sizeof(*tq)); /* begin by reading through white space */ state = s_space; /* * loop until we have reached the end of the string, * going through the state machine to build up tokens. */ for (fp = field; *fp != '\0'; fp++) { switch(state) { /* * initial state * * scan for the end of white space and when found set * state as appropriate to the next character. If the * next state is to be anything other than s_comment * or s_hash_comment, finish off the white-space associated * with the current token and begin the text associated * with the current token. * * entry state: s_newtok, s_comment * exit state: s_comment, s_quote, s_text, s_comment, s_domain, * s_hash_comment, s_newtok */ case s_space: if (alias && *fp == '#') { /* found a '#' comment, skip through to the end of the line */ state = s_hash_comment; } else { if (*fp == '(') { /* found a comment, scan through it next */ state = s_comment; comment_level = 1; /* comment finished when this is 0 */ if (space) { STR_NEXT(sp, *fp); } break; } else if (isspace(*fp)) { if (space) { STR_NEXT(sp, *fp); } break; /* continue with space token */ } else { if (space) { /* end of white space and comments preceding token */ STR_NEXT(sp, '\0'); /* end white space */ } /* * leave room for possible comma in the white space * also, if we are not putting white space in space, * this will at least make space valid */ STR_NEXT(sp, '\0'); } text_offs = sp->i; /* mark offset for token text in p */ /* determine what form this token will be */ switch (*fp) { case '[': /* a domain literal comes next */ state = s_domlit; tq->form = T_DOMLIT; break; case '"': /* a quoted literal comes next */ state = s_quote; tq->form = T_QUOTE; break; case '\\': /* text token with first char quoted */ state = s_cquote; save_state = s_text; /* state after \ */ tq->form = T_TEXT; break; default: if (alias && *fp == ':' && strncmpic(fp, ":include:", sizeof(":include:")-1) == 0) { fp[sizeof(":include:")-2] = '\0'; STR_CAT(sp, fp); fp += sizeof(":include:")-2; *fp = ':'; state = s_text; tq->form = T_TEXT; } else { if (index(non_text_tokens, *fp)) { state = s_newtok; tq->form = T_OPER; } else { state = s_text; tq->form = T_TEXT; } } break; } STR_NEXT(sp, *fp); /* copy character into token */ } break; /* * a comment was begun with a '#' character and a newline * terminates it. This state is entered only when parsing * an alias file. * * entry state: s_space * exit state: s_space */ case s_hash_comment: if (*fp == '\n') { state = s_space; } break; /* * a domain literal was begun with a '[' character * and a ']' terminates it, however, a "\]" sequence * does not terminate a domain literal. * * entry state: s_space * exit state: s_newtok */ case s_domlit: STR_NEXT(sp, *fp); if (*fp == '\\') { /* \ quotes next character, save s_domlit state */ save_state = s_domlit; state = s_cquote; } else if (*fp == ']') { /* ] terminates a domain literal */ state = s_newtok; } break; /* * a text token was begun by a non white space character * which is not in the set "[!@%.\"" and ends with a white * space character or a character that is in that set. * a special character can be prefixed with \ to be included * in the text literal. * * entry state: s_space * exit state: s_newtok */ case s_text: if (*fp == '\\') { /* \ quotes next character, save s_text state */ STR_NEXT(sp, *fp); /* copy char into token */ save_state = s_text; state = s_cquote; } else if (index(non_text_tokens, *fp)) { /* space or an operator follows a text literal */ fp--; /* re-scan character */ state = s_newtok; } else { STR_NEXT(sp, *fp); /* copy char into token */ } break; /* * a quoted literal was begun by a " character and ends with * a " character. A \" sequence does not end a quoted literal. * * entry state: s_space * exit state: s_newtok */ case s_quote: STR_NEXT(sp, *fp); /* copy char into token */ if (*fp == '\\') { /* \ quotes next character, save s_quote state */ save_state = s_quote; state = s_cquote; } else if (*fp == '"') { /* " terminates a quoted literal */ state = s_newtok; } break; /* * a comment begins with a ( and ends when a balancing ) is * found. A \( or \) sequence does not count in determining * balancing of parentheses. * * entry state: s_space * exit state: s_space */ case s_comment: if (space) { STR_NEXT(sp, *fp); /* copy char into token */ } if (*fp == '\\') { /* \ quotes next character, save s_comment state */ save_state = s_comment; state = s_cquote; } else if (*fp == ')') { comment_level--; if (comment_level == 0) { /* balanced parentheses--done with comment */ state = s_space; } } else if (*fp == '(') { comment_level++; } break; /* * \ escape in quote, text literal, comment or domain * include the character following a \ in the token and * retain the previous state. * * entry state: s_quote, s_text, s_comment or s_domain * exit state: the entry state */ case s_cquote: STR_NEXT(sp, *fp); /* copy character into token */ /* restore previous state */ state = save_state; break; /* * finished up a complete token--set up for the next one. * this involes ending the dynamic string region * creating a new token and linking the previous token * before the new one. * * entry state: s_quote, s_text, s_comment, s_domain * exit state: s_space */ case s_newtok: /* finished a token, setup for next */ /* finish up dynamic string region */ STR_NEXT(sp, '\0'); STR_DONE(sp); /* create new token which is the current token's successor */ tq->succ = (struct token *)xmalloc(sizeof(*tq)); tq->space = sp->p; /* mark pointer to white space */ tq->text = sp->p + text_offs; /* mark pointer to token text */ tq = tq->succ; /* scan through white space next */ state = s_space; /* create a new dynamic string region */ STR_INIT(sp); fp--; /* re-read current character */ break; } } /* * we reached the end of the string. This is either okay, if we * are scanning white space or a text literal, or it is not okay. * if we are scanning white space or a comment, we need to close * off the white-space for the token, properly, otherwise we need * to close off the text associated with the token. */ if (state == s_hash_comment) { state = s_space; } if (state == s_space || state == s_comment) { /* no token text exists for last token, fill in with empty text */ if (space) { STR_NEXT(sp, '\0'); /* terminate space */ } STR_NEXT(sp, '\0'); /* leave room for a possible comma */ tq->text = ""; /* empty text */ STR_DONE(sp); } else { /* last token does contain some text */ STR_NEXT(sp, '\0'); STR_DONE(sp); tq->text = sp->p + text_offs; } tq->space = sp->p; /* first part of p is the white space and comments */ /* * if the current token is white space, then make it the ending * token in the generated list. Otherwise, allocate a new token * with no text or white-space and make that the ending token. * In the second case, check for errors as well. */ if (state == s_space) { tq->form = T_END; tq->succ = NULL; } else { struct token *end_q; end_q = tq->succ = (struct token *)xmalloc(sizeof(*end_q)); end_q->text = end_q->space = ""; end_q->form = T_END; end_q->succ = NULL; /* is it an error? */ if (state == s_cquote) { return end_q->text = "no character after \\"; } /* what was the specific state for the error */ switch (state) { case s_domlit: /* unterminated domain literal */ tq->form = T_ERROR; return end_q->text = "unterminated domain literal"; case s_comment: tq->form = T_ERROR; return end_q->text = "unterminated comment"; case s_quote: tq->form = T_ERROR; return end_q->text = "unterminated quoted literal"; } } /* * everything went fine. ret_q is computed queue of tokens. * don't return an error message. */ return NULL; } /* * detokenize - convert a list of tokens into its string representation * * given a queue of tokens, such as produced by tokenize, return * a string corresponding to the space and text of the tokens. * * inputs: * buf - buffer in which to store result. NULL if we should * use the dynamic string region facility. If buf is * non-NULL it is assumed to be large enough to store * the result * tq_head - head of queue of tokens * tq_end - end of tokens to tokenize, or NULL to tokenize up * to an ENDTOK token * * output: * string representing list of tokens * * called by: finish_modified_clean, enqueue_address, external functions */ char * detokenize(space, buf, tq_head, tq_end) int space; /* TRUE if space should be copied */ char *buf; /* store result here, if non-NULL */ struct token *tq_head; /* list of tokens to detokenize */ struct token *tq_end; /* end of tokens to, or NULL */ { register struct token *tq; /* temp for scanning through tokens */ if (buf) { register char *bp= buf; /* point to buf */ bp[0] = '\0'; /* loop through contatenating space and text from tokens */ for (tq = tq_head; !ENDTOK(tq->form); tq = tq->succ) { if (space) { (void)strcat(bp, tq->space); } (void)strcat(bp, tq->text); if (tq == tq_end) { return bp; } } /* get the white space from the ending token */ (void)strcat(bp, tq->space); return bp; /* return the buffer */ } else { struct str str; register struct str *sp = &str; /* dynamic string region */ STR_INIT(sp); /* initialize dynamic string region */ for (tq = tq_head; !ENDTOK(tq->form); tq = tq->succ) { if (space) { STR_CAT(sp, tq->space); } STR_CAT(sp, tq->text); if (tq == tq_end) { STR_NEXT(sp, '\0'); /* null terminate */ STR_DONE(sp); /* finish dynamic string */ return sp->p; /* return string */ } } STR_CAT(sp, tq->space); /* add space from last token */ STR_NEXT(sp, '\0'); /* null terminate */ STR_DONE(sp); /* finish dynamic string */ return sp->p; /* return it */ } } /* * process_field - cleanly separate addresses in a header field, and extract * and cleanup those addresses * * given a header field which contains addresses, cleanly separate * each address with a comma, if it is not separated already. * Optionally clean local addresses by appending an RFC822 '@domain' form. * * Recognized addressing forms are: * * ANY*<ANY*> - route, can be recursive. * ANY*: - beginning of a group. * ;[@WORD] - end of a group. * WORD [op WORD [op ... WORD]] * - op is from the list ".!%@" * the simple form "WORD" is a local address. * * inputs: * field - a header field which contains addresses. If NULL * no header is returned. * fp - start of region to tokenize and clean. * domain - if non-NULL, a domain which is to be appended in * RFC822 '@domain' form to local addresses. * uucp_host - if non-NULL, a string to prepend to ! routes. * The purpose of this field is to keep ! routes in * From: or Sender: fields in ! route notation and to * ensure that the ! route will correctly return to * the sender, assuming software on other machines * doing something else. * extract_q - Address queue in which to insert extracted addresses. * NULL if we are not extracting addresses. * flags - A bitwise or of the following flags from field.h: * F_LOCAL - set if message originated on the local host. * This causes domains to be fully qualified. * F_STRICT - set to adhere more closely to RFC822. When * this is set, then all local addressing forms, * bang routes and tokens%domain forms are appended * with @domain, if domain is given, and prepended * with uucp_host, if uucp_host is given. This is * for use in gatewaying to stricter networks. * F_ALIAS - set to parse an aliases-style file. In these * cases, '#' introduces a comment and a the * string ":include:" is allowed at the start of * a text token, and does not introduce a group. * error - if an error occurs, an error message is stored here, * otherwise error is left alone. * * output: * a header cleaned according to the rules stated above, or NULL * if `field' was NULL. * * called by: external functions * calls: tokenize, match_route_or_group, match_group_term, match_general */ char * process_field(field, fp, domain, uucp_host, extract_q, flags, error) char *field; /* header field to be cleaned */ char *fp; /* pointer to field contents */ char *domain; /* domain to add to local addresses */ char *uucp_host; /* uucp host to prepend to ! routes */ struct addr **extract_q; /* queue in which to put addresses */ int flags; /* miscellaneous flags */ char **error; /* store error message here */ { int modified = FALSE; /* set to TRUE if field is modified */ char *error_message; /* error returned by tokenize */ struct token *tq_head; /* list of tokens to return */ struct token *tq_anchor; /* anchor point for pattern scan */ struct token *tq_new; /* new anchor found by pattern scan */ int new_group = FALSE; /* set if group: pattern newly found */ int group = FALSE; /* set when inside of a group */ unsigned len = 0; /* length of cleaned header */ int need_comma = FALSE; /* TRUE if we may need a , at anchor */ int check_route = TRUE; /* TRUE if we must scan for routes */ int i; /* temp */ if (field) { len = strlen(field) + 1; } DEBUG(DBG_FIELD_HI, "process_field: entry\n"); /* tokenize the contents to make parsing easy */ error_message = tokenize(fp, &tq_head, flags&F_ALIAS, field != NULL); DUMP_TOKENS(DBG_FIELD_HI, tq_head); /* * If tokenize found an error, then there is a syntax error * which would make processing this header of dubious value. * If we are not depending on the correctness of the header * for extracting addresses, this is not enough to warrant return * of mail. */ if (error_message) { *error = error_message; return field; /* return field unmodified */ } /* * scan through until no more tokens are left * * starting at anchor points, find an addressing form that * matches a set of tokens starting at that anchor point. * If the addressing form needs to be separated from the previous * by a comma, and it is not currently so separated then * insert a comma in the white space before the anchor point token. */ tq_anchor = tq_head; while (!ENDTOK(tq_anchor->form)) { tq_new = NULL; /* set when address pattern found */ if (check_route) { /* scan for: phrase <route-addr> or phrase : */ i = match_route_or_group(tq_anchor, &tq_new, extract_q, group, &len, domain, uucp_host, flags, error); switch (i) { case T_NOMATCH: /* didn't match route or group */ tq_new = NULL; break; case T_ROUTE: /* matched a route */ /* tq_new points to end of complete route form */ break; case T_GROUP: /* matched a group */ /* tq_new points to : at end of group */ group = TRUE; /* NOTE: next address does not need comma separator */ new_group = TRUE; break; case T_MODIFIED: /* matched and something modified */ modified = TRUE; break; default: /* error occured */ return field; /* return the field unchanged */ } } if (!tq_new) { /* scan for group terminator: ;[@WORD] */ i = match_group_term(tq_anchor, &tq_new, extract_q, group, error); switch (i) { case T_NOMATCH: /* didn't match group terminator */ tq_new = NULL; break; case T_GROUPTERM: /* matched a group terminator */ /* tq_new points to end of complete group terminator */ need_comma = FALSE; /* never need a comma before this */ group = FALSE; /* not in a group anymore */ break; default: /* error occured */ return field; /* return the field unchanged */ } } if (!tq_new) { /* * scan for: WORD [op WORD [op ... WORD]] * where op is from the set [.!%@] and the sequence * ends in a WORD. */ i = match_general(tq_anchor, &tq_new, &len, extract_q, domain, uucp_host, flags, error); DEBUG1(DBG_FIELD_MID, "match_general returned %d\n", i); switch (i) { case T_NOMATCH: /* didn't match general address form */ tq_new = NULL; break; case T_GENERAL: /* matched a general address */ /* tq_new points to end of address */ DEBUG(DBG_FIELD_MID, "just match, no mods\n"); break; case T_MODIFIED: /* matched and changed in some way */ /* tq_new points to end of address */ modified = TRUE; /* modified in match_general */ break; case T_MUTANT_FORM: /* not allowed outside of a route */ *error = "mutant addressing form outside of route"; return field; /* return the field unchanged */ default: /* error occured */ return field; /* return the field unchanged */ } } if (!tq_new) { /* we didn't find an addressing form that matched */ *error = "unknown addressing form"; return field; } if (need_comma && field != NULL) { /* there is an address and previous address needs a comma */ insert_comma(tq_anchor->space); modified = TRUE; /* field has been modified */ len++; /* 1 character inserted */ } /* * set state for next pass through the loop */ if (new_group) { /* * if a group was found, the next address should not be * preceded by a comma and the next token is the token * immediately following the : */ need_comma = FALSE; new_group = FALSE; tq_anchor = tq_new->succ; } else { if (tq_new->succ->text[0] == ',') { /* * if the next token is a comma, then we will not need to * insert one before the next address. * The next token is the one after the ',' */ need_comma = FALSE; /* skip the comma */ tq_anchor = tq_new->succ->succ; } else { /* * not a new group, and next token not a comma, we * may need to insert a comma before the next address * The next token is the one after the end of the previous * match. */ need_comma = TRUE; tq_anchor = tq_new->succ; } } } if (modified && field != NULL) { /* copy finished results into buffer for returning to caller */ return finish_mod_clean(field, (unsigned)(fp-field), tq_head, len); } return field; /* all done, return the header */ } /* * finish_mod_clean - return string for field name token list * * Finish process_field for the case that the header field was modified * by copying the field name and the tokens into a string area and * returning a pointer to the string. * * inputs: * field_name - string to copy to beginning of buffer * name_len - number of chars to copy from field_name * tq_head - token queue to copy into buffer * len - computed total length of result * * output: * pointer to string representing completed header field * * called by: process_field * calls: detokenize */ static char * finish_mod_clean(field_name, name_len, tq_head, len) char *field_name; /* field name string */ unsigned name_len; /* length of field name */ struct token *tq_head; /* head of list to convert to string */ unsigned len; /* computed length of result */ { register char *p = xmalloc(len); /* where to store result */ DEBUG(DBG_FIELD_HI, "field was modified--build string for return\n"); DEBUG1(DBG_FIELD_HI, "field = %s\n", field_name); /* copy field name up to colon */ (void)memcpy(p, field_name, name_len); /* * copy space and text from queued tokens */ (void)detokenize(TRUE, p+name_len, tq_head, (struct token *)NULL); DEBUG1(DBG_FIELD_HI, "completed string: %s\n", p); return p; } /* * insert_comma - insert a comma after a comment or at beginning of string * * Given the space field from a token, insert a ',' character either * after the last comment (if one exists) or at the beginning of the * string, if no comment exists in the string. * * input: * s - string in which to insert a comma * * outputs: * none * * called by: process_field */ static void insert_comma(s) char *s; { register char *p; /* end point of copy */ register char *q; /* temp pointer */ /* put comma at beginning of white space or after last comment */ p = rindex(s, ')'); if (!p) { p = s; } else { p++; /* advance beyond ) */ } /* copy text up one byte to allow space for comma */ for (q = p+strlen(p)+1; q != p; --q) { q[0] = q[-1]; } *q = ','; /* insert the comma */ } /* * match_route_or_group - reduce on a route or group form if possible * * This function is called by process_field to determine if the current * anchor point is the beginning of a route or a group. If so the * route or group is processed and the end of the route or group is * returned. * * inputs: * tq_anchor - the anchor point from process_field. * tq_new - pointer to variable in which to return the end * of the matched form. * extract_q - Address queue in which to insert extracted addresses. * NULL if we are not extracting addresses. * group - TRUE if a matched group would be recursive, * this is specifically an RFC822 no-no and is likely * to mean that an unsupported addressing form has * been used. * domain - if non-NULL, a domain which is to be appended in * RFC822 '@domain' form to local addresses. * uucp_host - if non-NULL, a string to prepend to ! routes. * The purpose of this field is to keep ! routes in * From: or Sender: fields in ! route notation and to * ensure that the ! route will correctly return to * the sender, assuming software on other machines * doing something else. * flags - A bitwise or of the following flags from field.h: * F_LOCAL - set if message originated on the local host. * This causes domains to be fully qualified. * F_STRICT - set to adhere more closely to RFC822. When * this is set, then all local addressing forms, * bang routes and tokens%domain forms are appended * with @domain, if domain is given, and prepended * with uucp_host, if uucp_host is given. This is * for use in gatewaying to stricter networks. * F_ALIAS - set to parse an aliases-style file. In these * cases, '#' introduces a comment and a the * string ":include:" is allowed at the start of * a text token, and does not introduce a group. * error - store an error message here if an error occurs. * * output: * T_NOMATCH if not matched, T_ROUTE if matched a route, * T_GROUP if matched a group, * T_MODIFIED if general addressing form which modified field, * FAIL if error. * * called by: process_field * calls: match_general, enqueue_address */ static int match_route_or_group(tq_anchor, tq_new, extract_q, group, len, domain, uucp_host, flags, error) struct token *tq_anchor; /* anchor point from process_field */ struct token **tq_new; /* return last matched token here */ struct addr **extract_q; /* queue in which to put addresses */ unsigned *len; /* len variable from process_field */ int group; /* TRUE if group would be recursive */ char *domain; /* domain to add to local addresses */ char *uucp_host; /* uucp host to prepend to ! routes */ int flags; /* miscellaneous flags */ char **error; /* store error message here */ { register struct token *tq; /* temp for scanning tokens */ int recursion_level = 1; /* embeddedness of route */ struct token *tq_start; /* start of innermost route */ struct token *tq_end; /* end of innermost route */ struct token *tq_temp; /* temp */ int seek_end; /* we are scanning for end of route */ int i; /* temp */ *tq_new = NULL; /* nothing yet */ /* * scan through tokens until we know what we have: * a route, a group or something else. */ tq = tq_anchor; for (;;) { if (tq->text[0] == ',' || ENDTOK(tq->form)) { /* we have something else nothing left to do here */ return T_NOMATCH; /* we didn't match anything */ } if (tq->text[0] == '<') { /* we have a route */ DEBUG(DBG_FIELD_MID, "We have a route\n"); *tq_new = tq; break; } if (tq->text[0] == ':' && tq->form == T_OPER) { /* we have a group */ DEBUG(DBG_FIELD_MID, "We have a group\n"); *tq_new = tq; if (group) { /* catch recursive groups */ *error = "recursive address group"; return FAIL; } return T_GROUP; /* signal that we have a group */ } tq = tq->succ; /* get next token */ } /* * we have a route, search for end point of route * and note the tokens in the innermost recursion * level so that we can extract them as an address. */ tq_start = (*tq_new)->succ; seek_end = 1; /* assume we are innermost for now */ /* allow recursion because it happens sometimes */ for (tq = (*tq_new)->succ; !ENDTOK(tq->form); tq = tq->succ) { if (tq->text[0] == '<') { recursion_level++; DEBUG(DBG_FIELD_HI, "bump up recursion level on route\n"); /* at more deeply nested address, forget what we had before */ tq_start = tq->succ; tq_end = NULL; seek_end = 1; } else if (tq->text[0] == '>') { recursion_level--; DEBUG(DBG_FIELD_HI, "bump down recursion level on route\n"); seek_end = 0; /* the end, if no more < tokens */ if (recursion_level == 0) { break; } } else if (seek_end) { tq_end = tq; /* could be the end of the address */ } } *tq_new = tq; /* end of matched route */ if (recursion_level) { *error = "unterminated route"; return FAIL; /* signal an error */ } if (tq_end == NULL) { *error = "null route"; return FAIL; /* signal an error */ } /* * route may match a general WORD op WORD op ... WORD form */ i = match_general(tq_start, &tq_temp, len, extract_q, domain, uucp_host, flags, error); switch (i) { case T_NOMATCH: break; /* didn't match */ case T_MUTANT_FORM: break; /* mutant form allowed in route */ case T_GENERAL: /* match, didn't modify anything */ if (tq_temp != tq_end) { /* not a complete match--this is a problem */ *error = "syntax error in address"; return FAIL; } return T_ROUTE; /* matched route, nothing modified */ case T_MODIFIED: /* match and something was modified */ return T_MODIFIED; default: /* an error occured */ return FAIL; /* propogate the error */ } if (extract_q) { if (enqueue_address(extract_q, tq_start, tq_end, error) == FAIL) { /* enqueue_address returned error, specific error already logged */ return FAIL; /* signal an error */ } } return T_ROUTE; /* signal a route */ } /* * match_group_term - match a group terminator pattern (;[@TOKEN]). * * Called from check_field to determine if the tokens after the * anchor point match a group terminator pattern (a semicolon optionally * followed by the pattern @WORD. * * inputs: * tq_anchor - the anchor point from process_field. * tq_new - pointer to variable in which to return the end * of the matched form. * extract_q - Address queue in which to insert extracted addresses. * NULL if we are not extracting addresses. * group - TRUE if we are now in a group. If this is not * the case then a match on a group terminator would * be an error. * error - store an error message here, on errors. * * output: * T_NOMATCH if not matched, T_GROUPTERM if match, FAIL on error. */ /*ARGSUSED*/ static int match_group_term(tq_anchor, tq_new, extract_q, group, error) struct token *tq_anchor; /* anchor point from process_field */ struct token **tq_new; /* return last matched token here */ struct addr **extract_q; /* queue in which to add addresses */ int group; /* TRUE if we are processing a group */ char **error; /* store error message here */ { register struct token *tq; /* temp for scanning list of tokens */ tq = tq_anchor; /* copy this into a register */ /* * if first token is a ; then we have a terminator and it * just remains to see if an optional, correct @WORD pattern * follows it, or if matching a group terminator is an error. */ if (tq->text[0] == ';') { if (!group) { /* no matching group : form exists, this is not correct */ *error = "\";\" does not terminate a group"; return FAIL; /* signal an error */ } if (tq->succ->text[0] == '@') { /* optional @WORD given, make sure the WORD exists */ *tq_new = tq = tq->succ->succ; if (!WORDTOK(tq->form)) { *error = "syntax error in address"; return FAIL; /* signal an error */ } DEBUG(DBG_FIELD_MID, "group terminator of form ;@WORD\n"); return T_GROUPTERM; /* match */ } else { /* no optional @WORD */ DEBUG(DBG_FIELD_MID, "simple group terminator\n"); *tq_new = tq; return T_GROUPTERM; /* match */ } } return T_NOMATCH; /* no match */ } /* * match_general - match a general address form WORD [op WORD [op ... WORD]] * * Called from check_field to determine if the tokens after the * anchor point match a general address form, which is a sequence * of WORD tokens separated by operators from the set ".!%@". * * If domain is given and we have an address which is just WORD, then * append @domain to the address. * * If uucp_host is given and we have a bang route, then prepend * uucp_host! to the address. * * If local is TRUE and address is WORD*@WORD1 or WORD*%WORD1 then * have the domain WORD1 fully qualfied if possible. * * inputs: * tq_anchor - the anchor point from process_field. * tq_new - pointer to variable in which to return the end * of the matched form. * len - len variable from check_form. This routine may modify * an address. If so, the len variable is modified to * taken into account the change in length of the * header field. * extract_q - Address queue in which to insert extracted addresses. * NULL if we are not extracting addresses. * domain - if non-NULL, a domain which is to be appended in * RFC822 '@domain' form to local addresses. * uucp_host - if non-NULL, a string to prepend to ! routes. * The purpose of this field is to keep ! routes in * From: or Sender: fields in ! route notation and to * ensure that the ! route will correctly return to * the sender, assuming software on other machines * doing something else. * flags - A bitwise or of the following flags from field.h: * F_LOCAL - set if message originated on the local host. * This causes domains to be fully qualified. * F_STRICT - set to adhere more closely to RFC822. When * this is set, then all local addressing forms, * bang routes and tokens%domain forms are appended * with @domain, if domain is given, and prepended * with uucp_host, if uucp_host is given. This is * for use in gatewaying to stricter networks. * F_ALIAS - set to parse an aliases-style file. In these * cases, '#' introduces a comment and a the * string ":include:" is allowed at the start of * a text token, and does not introduce a group. * error - store any error message here. * * output: * T_NOMATCH if no match found, T_GENERAL if match and unmodified, * T_MODIFIED if @domain appended, uucp_host! prepended, or domain * qualified, FAIL on error. * * called by: process_field * calls: queue_qualify_domain, enqueue_address */ static int match_general(tq_anchor, tq_new, len, extract_q, domain, uucp_host, flags, error) struct token *tq_anchor; /* anchor point from process_field */ struct token **tq_new; /* return last matched token here */ unsigned *len; /* len variable from process_field */ struct addr **extract_q; /* queue in which to add addresses */ char *domain; /* domain to add to local addresses */ char *uucp_host; /* uucp host to prepend to ! routes */ int flags; /* miscellaneous flags */ char **error; /* store error message here */ { register struct token *tq; /* temp for scanning token list */ register struct token *tq_temp; /* temp */ int bang_route = FALSE; /* TRUE if bang route */ int pure_bang_route = TRUE; /* TRUE if pure bang route */ int domain_address = FALSE; /* TRUE if domain address */ int at_found = FALSE; /* TRUE if @ token found */ int ret_val = T_GENERAL; /* value to be returned */ struct token *tq_mark = NULL; /* mark primary domain */ tq = tq_anchor; /* load anchor into a register */ if (!WORDTOK(tq->form) && tq->text[0] != '.') { /* it doesn't begin with WORD token */ return T_NOMATCH; /* signal no match */ } /* some part of the remaining tokens matches the form */ /* skip initial collection of zero or more WORD tokens delimited * by one or more "." tokens */ for (;;) { tq_temp = tq->succ; if (tq->text[0] == '.' && tq_temp->text[0] == '.') { tq = tq_temp; continue; } if ((WORDTOK(tq->form) || WORDTOK(tq_temp->form)) && (tq->text[0] == '.' || tq_temp->text[0] == '.')) { tq = tq_temp; continue; } break; } while (!ENDTOK(tq->succ->form) && index("!%@", tq->succ->text[0]) && (WORDTOK(tq->succ->succ->form) || tq->succ->succ->text[0] == '.')) { switch(tq->succ->text[0]) { case '!': /* take first host in ! route */ bang_route = TRUE; break; case '%': /* alternately, last % host */ if (!bang_route) { tq_mark = tq->succ->succ; domain_address = TRUE; } pure_bang_route = FALSE; break; case '@': /* always take last @ host */ tq_mark = tq->succ->succ; domain_address = TRUE; at_found = TRUE; pure_bang_route = FALSE; break; } tq = tq->succ->succ; /* * skip initial collection of zero or more WORD tokens delimited * by one or more "." tokens */ for (;;) { tq_temp = tq->succ; if (tq->text[0] == '.' && tq_temp->text[0] == '.') { tq = tq_temp; continue; } if ((WORDTOK(tq->form) || WORDTOK(tq_temp->form)) && (tq->text[0] == '.' || tq_temp->text[0] == '.')) { tq = tq_temp; continue; } break; } } /* do we match host!(host!)*@route ? */ if (pure_bang_route && tq->succ->text[0] == '!' && tq->succ->succ->text[0] == '@') { return T_MUTANT_FORM; /* mutant form allowed for route */ } DEBUG(DBG_FIELD_HI, "found a WORD op WORD op ... WORD sequence\n"); *tq_new = tq; /* at end of sequence */ /* * qualify domain by appending qualifier to it, if needed * * If we have a WORD*@WORD1 or a WORD*%WORD1 form, qualify * the domain WORD1, if necessary by appending a qualifier * to the domain. len is updated to reflect length change. */ if (domain_address && (flags&F_LOCAL)) { char *s = queue_qualify_domain(tq_mark, tq); if (s) { /* append .s */ (*tq_new)->text = xprintf("%s.%s", (*tq_new)->text, s); /* field length increased */ *len += 1 + strlen(s); ret_val = T_MODIFIED; } } if (!tq_mark && uucp_host && (bang_route || (flags&F_STRICT))) { /* * we have a bang route, prepend uucp_host! to the route. * Also prepend uucp_host! to the route if we are doing * strict RFC822. In this case an address will be * prepended with the route back to the sender and * appended with the current domain. */ tq_anchor->text = xprintf("%s!%s", uucp_host, tq_anchor->text); /* field length increased */ *len += 1 + strlen(uucp_host); ret_val = T_MODIFIED; } if ((flags & F_LOCAL) && domain && ! (domain_address || bang_route || at_found)) { /* * we have an unqualified local address. put address * in @domain */ (*tq_new)->text = xprintf("%s@%s", (*tq_new)->text, domain); /* field length increased */ *len += 1 + strlen(domain); ret_val = T_MODIFIED; } if (extract_q) { /* * have the address added to the extraction queue */ if (enqueue_address(extract_q, tq_anchor, *tq_new, error) < FAIL) { /* enqueue_address returned error, specific error already logged */ return FAIL; /* signal an error */ } DEBUG(DBG_FIELD_MID, "address enqueued\n"); } return ret_val; } /* * queue_qualify_domain - untokenize a domain and call qualify_domain * * Called from match_general, this routine takes a token list * representing a domain, converts it back to a string and calls * qualify_domain() to determine if any text needs to be appended * in order to make the domain fully qualified. * * inputs: * tq_start - first token in domain * tq_end - last token in domain * * output: * NULL if nothing should be appended to the domain, * otherwise a string which represents the complete super * domain that the given domain should be qualified in. * * called by: match_general * calls: qualify_domain(external), detokenize */ static char * queue_qualify_domain(tq_start, tq_end) struct token *tq_start; /* beginning of domain reference */ struct token *tq_end; /* end of domain reference */ { struct str str; register struct str *sp = &str; /* dynamic string region */ register struct token *tq; /* temp for scanning through tokens */ char *ret; /* return value from qualify_domain */ STR_INIT(sp); /* initialize dynamic string region */ /* get string represented by domain tokens */ tq = tq_start; do { STR_CAT(sp, tq->text); } while (tq != tq_end && (tq = tq->succ)); STR_NEXT(sp, '\0'); /* send out for the actual qualification */ ret = qualify_domain(sp->p); DEBUG2(200, "qualify_domain(%s) returns %s\n", sp->p, ret? ret: "(null)"); STR_FREE(sp); /* free region */ return ret; /* return the value from qualify_domain() */ } /* * enqueue_address - insert a new address into a queue * * Given a token list representing an address, detokenize the list * and add it to the given address queue. * * inputs: * q - pointer to queue of addresses * tq_start - first token in the address * tq_end - ending token of the address * errro - store any error message here * * outputs: * SUCCEED if everything went okay, FAIL on error * * called by: match_or_route_group, match_general * calls: detokenize */ static int enqueue_address(q, tq_start, tq_end, error) struct addr **q; /* queue in which to insert */ struct token *tq_start; /* first token in the address */ struct token *tq_end; /* ending token in the address */ char **error; /* store error message here */ { register char *s; /* string representing the address */ register struct addr *temp_q; /* temp */ char *parse_error; /* error from parse_address() */ /* grab the string corresponding to the tokens */ s = detokenize(FALSE, (char *)NULL, tq_start, tq_end); DEBUG1(DBG_FIELD_LO, "enqueue_address(%s)\n", s); /* insert it into the queue */ temp_q = alloc_addr(); /* get an address queue entry */ temp_q->succ = *q; temp_q->in_addr = s; /* work_addr gets a mungeable copy */ if ((temp_q->work_addr = preparse_address(s, &parse_error)) == NULL) { *error = xprintf("%s: %s", s, error); return FAIL; } *q = temp_q; /* insert at beginning of list */ return SUCCEED; /* added to the list */ } /* * dump_tokens - list tokens to standard error for debugging purposes * * called from the DUMP_TOKENS macro, this function generates * a verbose description of what is going on with a list of tokens. * * input: * tq - head of a queue of tokens * * outputs: * none * * called by: DUMP_TOKENS(local macro) */ void dump_tokens(tq) register struct token *tq; /* dump these tokens on errfile */ { (void)fprintf(errfile, "token list:\n"); while (tq) { register char *s; char buf[100+1]; switch(tq->form) { case T_QUOTE: s = "T_QUOTE"; break; case T_DOMLIT: s = "T_DOMLIT"; break; case T_OPER: s = "T_OPER"; break; case T_TEXT: s = "T_TEXT"; break; case T_END: s = "T_END"; break; case T_ERROR: s = "T_ERROR"; break; default: (void)sprintf(s = buf, "form=%d", tq->form); break; } (void)fprintf(errfile, "\t|%s|%s|%s|\n", tq->space, tq->text, s); tq = tq->succ; } (void)fprintf(errfile, "end of list\n"); } #ifdef STANDALONE #include "varargs.h" int send_to_postmaster = FALSE; /* see if this gets set */ int return_to_sender = FALSE; /* see if this gets set */ struct addr *recipients = NULL; /* initial list here is zero */ char **args_recipients = {0}; /* nothing in this list */ int exitvalue = 0; FILE *errfile = stderr; #ifdef DEBUG_LEVEL int debug = DEBUG_LEVEL; #else /* DEBUG_LEVEL */ int debug = 0; #endif /* DEBUG_LEVEL */ /* * test the above functions by calling process_field for each * argument given to the program. */ void main(argc, argv) int argc; /* count of arguments */ char **argv; /* vector of arguments */ { char *s; /* return value from process_field */ struct addr *q; /* temp for scanning hdr_recipients */ char *error; /* * if first argument is a number, change the debug level */ if (isdigit(argv[1][0])) { debug = atoi(*++argv); argc--; } /* * loop over all arguments */ if (argc > 1) { while (*++argv) { (void)fprintf(stderr, "input: %s\n", *argv); s = index(*argv, ':'); if (s) { s++; } else { s = *argv; } /* * non-strict RFC822, from local machine */ error = NULL; #ifdef NEWALIASES s = process_field((char *)NULL, s, (char *)NULL, (char *)NULL, &recipients, F_ALIAS, &error); #else /* NEWALIASES */ s = process_field(*argv, s, "e-.uucp", "e-", &recipients, F_LOCAL, &error); #endif /* NEWALIASES */ if (error) { (void) fprintf(stderr, "error: %s\n", error); } else { (void)fprintf(stderr, "output: %s\n", s? s: "(null)"); } } } else { char line[4096]; while (gets(line) != NULL) { (void)fprintf(stderr, "input: %s\n", line); s = index(line, ':'); if (s) { s++; } else { s = line; } /* * non-strict RFC822, from local machine */ #ifdef NEWALIASES s = process_field((char *)NULL, s, (char *)NULL, (char *)NULL, &recipients, F_ALIAS, &error); #else /* NEWALIASES */ s = process_field(line, s, "e-.uucp", "e-", &recipients, F_LOCAL, &error); #endif /* NEWALIASES */ if (error) { (void) fprintf(stderr, "error: %s\n", error); } else { (void)fprintf(stderr, "output: %s\n", s? s: "(null)"); } } } for (q = recipients; q; q = q->succ) { (void)printf("%s\n", q->in_addr); } exit(exitvalue); } /* * define panic, fatal and write_log here, rather than * using the external routines. We are testing and just want * the information displayed, not logged. */ /*VARARGS2*/ void panic(exitcode, fmt, va_alist) int exitcode; /* call exit(exitcode) */ char *fmt; /* printf(3) format */ va_dcl /* arguments for printf */ { va_list ap; va_start(ap); (void)fprintf(stderr, "PANIC(%s): ", exitcode); (void)vfprintf(stderr, fmt, ap); putc('\n', stderr); /* fatal messages not \n terminated */ va_end(ap); return_to_sender = TRUE; exit(exitcode); } /*VARARGS2*/ void write_log(log, fmt, va_alist) int log; /* TRUE if to write global log file */ char *fmt; /* printf(3) format */ va_dcl /* arguments for printf */ { va_list ap; va_start(ap); (void)fprintf(stderr, log? "PUBLIC: ": "PRIVATE: "); (void)vfprintf(stderr, fmt, ap); putc('\n', stderr); va_end(ap); } #endif /* STANDALONE */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.