/* #ident "@(#)smail/src:RELEASE-3_2_0_121:header.c,v 1.59 2005/08/31 16:39:46 woods Exp" */ /* * Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll * Copyright (C) 1992 Ronald S. Karr * * See the file COPYING, distributed with smail, for restriction * and warranty information. */ /* * header.c: * routines to process message headers. * * external functions: process_header, read_header, write_header */ #include "defs.h" #include #include #include #include #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H) # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef __STDC__ # include #else # include #endif #if defined(HAVE_UNISTD_H) # include #endif #include "smail.h" #include "alloc.h" #include "list.h" #include "main.h" #include "parse.h" #include "addr.h" #include "field.h" #include "main.h" #include "spool.h" #include "log.h" #include "exitcodes.h" #include "smailstring.h" #include "dys.h" #include "transport.h" #include "header.h" #include "extern.h" #include "debug.h" #include "smailport.h" /* variables defined in this file */ charplist_t *header; /* list of header fields */ /* variables local to this file */ static int use_resent; /* TRUE if Resent- fields exist */ static int remove_bcc_fields; /* TRUE to remove Bcc: header fields */ static char *to_fn; /* name of To: field */ static char *from_fn; /* name of From: field */ static char *reply_to_fn; /* name of Reply-To: field */ static char *sender_fn; /* name of the Sender: field */ static char *cc_fn; /* name of the Cc: field */ static char *bcc_fn; /* name of the Bcc: field */ static char *date_fn; /* name of the Date: field */ static char *message_id_fn; /* name of the Message-Id: field */ static int found_to; /* TRUE if To: field was found */ static int found_message_id; /* TRUE if Message-Id: field found */ static int found_from; /* TRUE if From: field was found */ static int found_cc; /* TRUE if Cc: field was found */ static int found_bcc; /* TRUE if Bcc: field was found */ static int found_date; /* TRUE if Date: field was found */ static int found_sender; /* TRUE if Sender: field was found */ static int count_received; /* count of Received: fields found */ static struct addr *header_from; /* list of From: addrs */ static charplist_t *remote_header; /* simple remote-form header */ static charplist_t *strict_header; /* strict RFC822 header */ /* functions local to this file */ static void check_for_resent __P((void)); static void scan_header __P((struct addr **, char **)); static void new_field __P((char *)); static char *build_to_field __P((void)); static char *build_date_field __P((void)); static char *build_message_id_field __P((void)); static char *build_from_field __P((char *)); static int write_field __P((char *, FILE *, int)); static char *build_received_field __P((struct addr *)); static char *build_return_path __P((struct addr *)); static void build_remote_header __P((void)); static void build_strict_header __P((void)); /* * process_header - first pass processing of a header. * * Note the existence of certain headers and, if input rq is non-nil * fill it with a list of recipient addresses taken from either the * Resent-To: Resent-Cc: and Resent-Bcc: fields or from the To: Cc: * and Bcc: fields. */ char * process_header(rq) struct addr **rq; /* put list of recipients here */ { char *error; DEBUG(DBG_HEADER_MID, "process_header() called....\n"); /* * initialize the data to be searched for in the header */ use_resent = FALSE; found_to = FALSE; /* nothing found yet */ found_message_id = FALSE; found_from = FALSE; found_date = FALSE; found_sender = FALSE; found_cc = FALSE; found_bcc = FALSE; count_received = 0; header_from = NULL; /* initialize From: addr list */ strict_header = NULL; /* no strict RFC822 header yet */ /* * Bcc: fields are removed only if we are processing the header * for addresses, otherwise we assume that the user agent would * have removed them if it didn't want them passed along */ remove_bcc_fields = (rq != NULL); check_for_resent(); /* setup use_resent */ error = NULL; scan_header(rq, &error); /* scan for fields and recipients */ if (rq && error) { /* got an error message while extracting recipients, done */ return error; } error = NULL; /* * if no recipient fields given, create a To: field containing * the known recipients */ if (! (found_to || found_cc || found_bcc) && rq == NULL) { new_field(build_to_field()); } /* * if no From: field exists, and no Sender: field exists, create * a From: field. * * otherwise, if no Sender: field exists and From: is not verified * to be the actual sender, create a Sender: field. */ if (! (found_from || found_sender)) { if (!islocal) { from_fn = use_resent ? "Resent-From" : "From"; } new_field(build_from_field(from_fn)); } else if (islocal && found_from && !found_sender) { /* * With no, or multiple, addresses in the From: field, always generate * a Sender: field. */ if (! header_from || header_from->succ) { new_field(build_from_field(sender_fn)); } else if (!sender_is_trusted) { char *work_addr = COPY_STRING(header_from->work_addr); char *target = NULL; char *remain; int parseflags = header_from->parseflags; int form = parse_address(work_addr, &target, &remain, &parseflags); struct passwd *pw; int dobuild = FALSE; /* * if the sender is not trusted, allow the From: field only * if it is in a form considered valid for the user. */ switch (form) { case FAIL: dobuild = TRUE; break; /* * catch all of the remote cases here */ default: if (! islocalhost(target) && !EQIC(target, visible_name)) { dobuild = TRUE; break; } form = parse_address(remain, (char **) NULL, &error, &parseflags); if (form != LOCAL) { dobuild = TRUE; break; } /*FALLTHROUGH*/ case LOCAL: pw = getpwbyname(FALSE, remain); if (pw == NULL || pw->pw_uid != real_uid) { dobuild = TRUE; } break; } if (dobuild) { new_field(build_from_field(sender_fn)); } xfree(work_addr); } } /* * if no date field given, create one */ if (!found_date) { new_field(build_date_field()); } /* * if no message-id field given, create one */ if (!found_message_id) { new_field(build_message_id_field()); } /* * note count of received lines if count is not already known */ if (hop_count < 0) { hop_count = count_received; } return error; /* return any error messages */ } /* * set use_resent if there exist any standard Resent- fields in * the header */ static void check_for_resent() { register charplist_t *q; /* index to headers */ /* on the first pass check for Resent- headers */ for (q = header; q; q = q->succ) { /* * some systems (at least pcc for microport/286) can't handle * an expression with four HDREQ macros in one if, so split it * up into two if's. */ if (HDREQ("resent-to", q->text) || HDREQ("resent-from", q->text)) { use_resent = TRUE; break; } if (HDREQ("resent-cc", q->text) || HDREQ("resent-bcc", q->text)) { use_resent = TRUE; break; } } /* set the address field names based on this information */ if (use_resent) { to_fn = "Resent-To"; from_fn = "Resent-From"; reply_to_fn = "Resent-Reply-To"; sender_fn = "Resent-Sender"; cc_fn = "Resent-Cc"; bcc_fn = "Resent-Bcc"; date_fn = "Resent-Date"; message_id_fn = "Resent-Message-Id"; } else { to_fn = "To"; from_fn = "From"; reply_to_fn = "Reply-To"; sender_fn = "Sender"; cc_fn = "Cc"; bcc_fn = "Bcc"; date_fn = "Date"; message_id_fn = "Message-Id"; } } /* * scan_header - pass through searching for required headers. * * If we are extracting recipient addresses from the header, do * so here. */ static void scan_header(rq, error) struct addr **rq; /* put recipients here, if non-NULL */ char **error; /* store error message here */ { register charplist_t *q; /* temp for scanning header */ for (q = header; q; q = q->succ) { if (hop_count < 0 && HDREQ("Received", q->text)) { count_received++; /* count Received: fields */ } else if (HDREQ(to_fn, q->text)) { found_to = TRUE; if (islocal) { /* XXX q->text leaks */ q->text = process_field(q->text, strchr(q->text, ':') + 1, visible_name, uucp_name, rq, (F_LOCAL | F_STRICT), error); } else if (rq) { (void) process_field((char *) NULL, strchr(q->text, ':') + 1, (char *) NULL, (char *) NULL, rq, 0, error); } } else if (HDREQ("apparently-to", q->text) || HDREQ("to", q->text) || HDREQ("resent-to", q->text)) { found_to = TRUE; } else if (HDREQ(message_id_fn, q->text)) { char *id = COPY_STRING(strchr(q->text, ':') + 1); /* * if the field is "empty" (only whitespace, comments, or nothing) * then it is bogus and must be deleted.... */ strip_rfc822_whitespace(id); strip_rfc822_comments(id); if (!*id || EQ(id, " ")) { charplist_t **pq; for (pq = &header; *pq && *pq != q; pq = &(*pq)->succ) { ; } /* XXX q->text leaks */ if (*pq) { *pq = q->succ; } /* XXX maybe we should log the original value??? */ write_log(WRITE_LOG_SYS, "Error in %s field: ID value missing, rewriting with local ID", message_id_fn); } else { found_message_id = TRUE; } xfree(id); } else if (HDREQ(from_fn, q->text)) { found_from = TRUE; if (islocal) { /* XXX q->text leaks */ q->text = process_field(q->text, strchr(q->text, ':') + 1, (char *) NULL, (char *) NULL, &header_from, (islocal ? F_LOCAL : 0), error); } else { (void) process_field((char *) NULL, strchr(q->text, ':') + 1, (char *) NULL, (char *) NULL, &header_from, 0, error); } } else if (HDREQ(date_fn, q->text)) { found_date = TRUE; } else if (HDREQ(sender_fn, q->text)) { if (!sender_is_trusted) { charplist_t **pq; /* * Non-trusted users can't supply their own Sender: fields, * so delete this field. */ for (pq = &header; *pq && *pq != q; pq = &(*pq)->succ) { ; } /* XXX q->text leaks */ if (*pq) { *pq = q->succ; } /* XXX maybe we should log the original value??? */ write_log(WRITE_LOG_SYS, "Notice: deleted %s field supplied by non-trusted user", sender_fn); } else { found_sender = TRUE; if (islocal) { /* XXX q->text leaks */ q->text = process_field(q->text, strchr(q->text, ':') + 1, (char *) NULL, (char *) NULL, &header_from, (islocal ? F_LOCAL : 0), error); } else { (void) process_field((char *) NULL, strchr(q->text, ':') + 1, (char *) NULL, (char *) NULL, &header_from, 0, error); } } } else if (HDREQ(cc_fn, q->text)) { found_cc = TRUE; if (islocal) { /* XXX q->text leaks */ q->text = process_field(q->text, strchr(q->text, ':') + 1, (char *) NULL, (char *) NULL, rq, (islocal ? F_LOCAL : 0), error); } else if (rq) { (void) process_field((char *) NULL, strchr(q->text, ':') + 1, (char *) NULL, (char *) NULL, rq, 0, error); } } else if (HDREQ(bcc_fn, q->text)) { found_bcc = TRUE; if (islocal) { /* XXX q->text leaks */ q->text = process_field(q->text, strchr(q->text, ':') + 1, (char *) NULL, (char *) NULL, rq, (islocal ? F_LOCAL : 0), error); } else if (rq) { (void) process_field((char *) NULL, strchr(q->text, ':') + 1, (char *) NULL, (char *) NULL, rq, 0, error); } } else if (HDREQ("precedence", q->text)) { /* * a precedence field is specified, use it to set the message * priority */ int newgrd = parse_precedence(strchr(q->text, ':')); if (newgrd) { msg_grade = newgrd; } } } } /* * Create a To: header field */ static char * build_to_field() { register struct addr *rq; /* recipients to put in To: field */ struct str str; /* region for string macros */ int col; /* current output line column */ rq = recipients; /* * Sendmail inserted an "Apparently-To:" field because RFC822 people * complained about it being against the rules to add a "To:" line. * * Well, it's not really -- they didn't know their own rules very well. * * The "To:" header is required, and it's fair to use the known recipient * list to re-construct it should it be found to be missing. * * Note that more recently Sendmail has defaulted to creating a "To:" * header containing the empty group: "Undisclosed-recipients:;". They * justify this as a way to avoid the worry that BCC recipients might be * revealed, but if any MTA is downright stupid enough to use BCC but not * include an empty "To:" header itself deserves to be blamed for leaks. */ #ifdef BOGUS_OLD_SENDMAIL_COMPATABILITY if (!islocal) { if (use_resent) { to_fn = "Apparently-Resent-To"; } else { to_fn = "Apparently-To"; } } #endif /* * now we actually form the To: line from the known recipient * addresses */ STR_INIT(&str); /* initialize dynamic string region */ /* form = To: recipient(, recipient)* */ rq = recipients; /* special case, insert the first address */ if (rq) { if (rq->in_addr[0] == '@') { /* route-addr, put it in angle brackets */ str_printf(&str, "%s: <%s>", to_fn, rq->in_addr); } else { /* otherwise just use the address */ str_printf(&str, "%s: %s", to_fn, rq->in_addr); } rq = rq->succ; } col = STR_LEN(&str); while (rq) { int len = strlen(rq->in_addr); STR_NEXT(&str, ','); col++; /* * put at least 27-chars on a line, try to keep down below 72 * NOTE: we aren't counting TAB characters hidden in the * addresses. */ if (col > 27 && col + len > 72) { STR_NEXT(&str, '\n'); STR_NEXT(&str, '\t'); col = 8; } else { STR_NEXT(&str, ' '); col++; } if (rq->in_addr[0] == '@') { str_printf(&str, "<%s>", rq->in_addr); len += 2; /* 2 chars for the angel brackets */ } else { STR_CAT(&str, rq->in_addr); } col += len; rq = rq->succ; } STR_NEXT(&str, '\n'); STR_NEXT(&str, '\0'); STR_DONE(&str); return STR(&str); /* return finished To: field */ } /* * build a Date: field * * XXX could be combined with build_message_id_field() */ static char * build_date_field() { char *s = NULL; /* returned header field */ if (date_field) { s = expand_string(date_field, (struct addr *) NULL, (char *) NULL, (char *) NULL); } if (s == NULL || *s == '\0') { write_log(WRITE_LOG_PANIC, "Error in $date_field: %s", date_field); return ""; } if (use_resent) { return xprintf("Resent-%s", s); } else { return COPY_STRING(s); } } /* * Form a Message-Id: header field. * * XXX could be combined with build_date_field() */ static char * build_message_id_field() { char *s = NULL; if (message_id_field) { s = expand_string(message_id_field, (struct addr *) NULL, (char *) NULL, (char *) NULL); } if (s == NULL || *s == '\0') { write_log(WRITE_LOG_PANIC, "Error in $message_id_field: %s", message_id_field); return ""; } if (use_resent) { return xprintf("Resent-%s", s); } else { return COPY_STRING(s); } } /* * form a From: or Sender: header field */ static char * build_from_field(fn) char *fn; /* field name */ { char *s = NULL; if (from_field) { s = expand_string(from_field, (struct addr *) NULL, (char *) NULL, sender); } if (s == NULL || *s == '\0') { write_log(WRITE_LOG_PANIC, "Error in $from_field: %s", from_field); return ""; } /* * XXX this is really stupid and inviting of errors, but the manual says: * * "The expanded string must begin with ``From:'', which may be replaced * by other strings to form an actual header field." * * However the original code always allowed it to be copied verbatim. * * The right way would have been to specify $from_field as only providing * the field content while always prepending the given field name (fn). */ if (! HDREQ("from", s)) { write_log(WRITE_LOG_PANIC, "Error in $from_field, does not begin with 'From:': %s", from_field); return COPY_STRING(s); } return xprintf("%s%s", fn, s + 4); } /* * link in a brand new field with the given text */ static void new_field(field) char *field; /* new field to add */ { register charplist_t *f; /* header field entry */ f = (charplist_t *) xmalloc(sizeof(charplist_t)); /* create it */ f->text = field; f->succ = header; /* link it in */ header = f; } /* * parse_precedence - parse a Precedence: field and return it's grade value * * use tokenize to parse for an atom (the first atom is used, the rest are * ignored). Then lookup the atom in the grades string and return the * associated grade, if found, 0 otherwise. */ int parse_precedence(field) char *field; { size_t len; /* length of token */ struct token *tok; /* list of tokens from tokenize */ register char *p = grades; /* temp for scanning grades */ char *error; /* error message from tokenize */ DEBUG(DBG_HEADER_HI, "parse_precedence called\n"); error = tokenize(field, &tok, FALSE, TRUE); if (error) { /* tokenize didn't like the field for some reason */ write_log(WRITE_LOG_MLOG, "Xnotice: <> Error in precedence field: %s", error); return '\0'; /* no precedence value */ } if (tok->form != T_TEXT) { return '\0'; /* not a text (atom) token */ } len = strlen(tok->text); DEBUG1(DBG_HEADER_MID, "precedence is %s\n", tok->text); while (p) { if (strncmpic(tok->text, p, len) == 0 && p[len] == ':') { /* we have a match, grab the character */ return p[len+1]; } /* skip to end of precedence string */ p = strchr(p, ':'); if (p) { /* skip past grade character */ p = strchr(p, ':'); if (p) { p++; /* now at next precedence string */ } } } DEBUG(DBG_QUEUE_MID, "unknown precedence\n"); return '\0'; /* unknown precedence */ } /* * read_header - initialize header structures, reading from spool file * * Read a header on the spool file. The header data structure is * initialized to contain an entry for each field of the header. * * returns SUCCEED or FAIL, the actual failure is logged. */ int read_header() { struct str field; /* input field */ register int c; /* current input char */ int last_c = EOF; /* last input char */ charplist_t **link_addr; /* forward link for next field */ off_t line_mark = 0; /* marks beginning of a line */ link_addr = &header; /* * loop until done reading the header */ for (;;) { STR_INIT(&field); /* NOTE: do no re-use string */ /* * field name is some non-space chars followed by * optional white space up to a ':' * * Note the allowance for whitespace before the colon, is from RFC * 2822's "obsolete" header fields syntax: * * 4.5.8. Obsolete optional fields * * obs-optional = field-name *WSP ":" unstructured CRLF * * assuming: * * WSP = SP / HTAB * * Note that RFC 822 §3.2 did _not_ allow for whitespace before the * colon: * * field = field-name ":" [ field-body ] CRLF * * field-name = 1* * * field-body = field-body-contents * [CRLF LWSP-char field-body] * * LWSP-char = SPACE / HTAB ; semantics = SPACE * * The same syntax for "field" is repeated in §B.1. * * So in fact the use of "*WSP" in RFC 2822 §4.5.8 is not a true * requirement for compatability with RFC 822, though it could be * considered part of a requirement for compatability with RFC 733 * which allowed LWSP-chars in the field name, as per §c which says "To * aid in the creation and reading of field-names, the free insertion * of LWSP-chars is allowed in reasonable places." Examples of headers * in RFC 733 §V.D.3 line up the colons with surrounding whitespace. * However since RFC 822 explicitly obsoleted that syntax as per * §C.1.1, so I've no idea why RFC 2822 partially resurrected it again. * In any case Smail has always(?) allowed the 2822 behaviour. */ if (last_c == ':') { /* we had a read ahead character past the last newline */ c = EOF; /* don't allow null fieldnames */ } else { if (last_c != EOF) { STR_NEXT(&field, last_c); line_mark = tell_spool() - 1; last_c = EOF; } else { /* * save the beginning of the line, so we can go back to * here if this is not a header */ line_mark = tell_spool(); } while ((c = GETSPOOL()) != EOF && c != READ_FAIL && !isspace((int) c) && c != ':') { STR_NEXT(&field, c); last_c = c; } while (c != EOF && c != READ_FAIL && isspace((int) c)) { STR_NEXT(&field, c); c = GETSPOOL(); } if (c == READ_FAIL) { write_log(WRITE_LOG_SYS, "read error in spool file <%s/%s>", spool_dir, spool_fn); return FAIL; } } /* not a valid field name, must be past the header */ if (c != ':') { break; } /* * looks like we are in a header field, so add the ':' and then */ STR_NEXT(&field, c); last_c = c; /* * Read the header field contents, terminated by newline which is not * followed by a SPACE or TAB char, or by EOF. */ while ((c = GETSPOOL()) != EOF && c != READ_FAIL && !(last_c == '\n' && c != ' ' && c != '\t')) { STR_NEXT(&field, c); last_c = c; } if (c == READ_FAIL) { write_log(WRITE_LOG_SYS, "read error in spool file <%s/%s>", spool_dir, spool_fn); } last_c = c; /* preserve this for the next field */ STR_NEXT(&field, '\0'); STR_DONE(&field); #if 0 append_charplist(&link_addr, STR(&field)); #else *link_addr = (charplist_t *) xmalloc(sizeof(charplist_t)); (*link_addr)->text = STR(&field); link_addr = &(*link_addr)->succ; #endif /* * msg body starts now if we're at EOF, or if we have a second * consecutive newline.... */ if (c == EOF || c == '\n') { line_mark = -1; /* don't seek back to previous line */ STR_CLEAR(&field); /* don't allow this field to be freed */ break; } } *link_addr = NULL; /* terminate header queue */ /* * all done reading the field, cleanup, seek to beginning of * message body (after last valid field line) */ if (line_mark >= 0 && seek_spool((off_t) line_mark) == FAIL) { write_log(WRITE_LOG_SYS, "seek error on spool file <%s/%s>", spool_dir, spool_fn); return FAIL; } if (STR(&field) && STR_LEN(&field)) { STR_FREE(&field); /* free the field that wasn't */ } return SUCCEED; } /* * write_header - write out the header to a stdio FILE * * inputs: * file - file to write to. * addr - involved address, (note only first in this linked list is used) * * outputs: * SUCCEED or FAIL */ int write_header(f, addr) FILE *f; /* file to send header to */ struct addr *addr; /* for the per-address transport */ { unsigned long flags = addr->transport->flags; /* transport flags */ charplist_t *q, *q2; /* temp for processing header */ int crlf = (flags & PUT_CRLF)?1:0; /* put \r\n at end of each line? */ char *s; /* output transport-specific "insert_header" headers */ for (q2 = addr->transport->hdrinsert; q2; q2 = q2->succ) { s = expand_string(q2->text, addr, (char *) NULL, (char *) NULL); if (s == NULL || *s == '\0') { write_log(WRITE_LOG_PANIC, "transport %v, Error in $insert_header: %s", addr->transport->name, q2->text); return FAIL; } if (write_field(s, f, crlf) == FAIL) { return FAIL; } } /* output a Return-Path: header if required */ if (flags & PUT_RETURNPATH) { if (write_field(build_return_path(addr), f, crlf) == FAIL) { return FAIL; } } /* output a Received: header if required */ if (flags & PUT_RECEIVED) { if (write_field(build_received_field(addr), f, crlf) == FAIL) { return FAIL; } } if (use_resent) { /* * the situation gets complex if resent- headers are involved, * so ignore the issue by not changing anything. */ q = header; } else if (flags & STRICT_TPORT) { /* output a strict header, building one if none exists */ if (strict_header == NULL) { build_strict_header(); } q = strict_header; } else if (flags & LOCAL_XFORM) { /* output header as is */ q = header; } else { /* output a normal remote header */ if (remote_header == NULL) { build_remote_header(); } q = remote_header; } /* * output the appropriate header fields, as selected above */ for (; q; q = q->succ) { /* * Remove the contents of bcc fields if addresses were extracted from * the header. Otherwise, we expect that the MUA would have removed * them if they were not to be delivered. * * Bcc contents are *always* stripped from locally originating mail. * * Leave the empty Bcc header to conform to RFC-822 4.5.3. */ if ((remove_bcc_fields || islocal) && HDREQ(bcc_fn, q->text)) { char bcc_head[sizeof("resent-bcc: \r\n\0")]; /* XXX magic string is max len */ strcpy(bcc_head, bcc_fn); strcat(bcc_head, ":\n"); if (write_field(bcc_head, f, crlf) == FAIL) { return FAIL; } continue; } /* don't output redundant Return-Path headers */ if ((flags & PUT_RETURNPATH) && HDREQ("Return-Path", q->text)) { continue; } /* skip over any transport-specific "remove_header" headers */ for (q2 = addr->transport->hdrremove; q2; q2 = q2->succ) { s = expand_string(q2->text, addr, (char *) NULL, (char *) NULL); if (s == NULL || *s == '\0') { write_log(WRITE_LOG_PANIC, "transport %v: Error in $remove_header: %s", addr->transport->name, q2->text); return FAIL; } if (s && HDREQ(s, q->text)) { break; } } if (q2 == NULL && write_field(q->text, f, crlf) == FAIL) { return FAIL; } } /* output transport-specific "append_header" headers */ for (q2 = addr->transport->hdrappend; q2; q2 = q2->succ) { if (write_field(expand_string(q2->text, addr, (char *) NULL, (char *) NULL), f, crlf) == FAIL) { return FAIL; } } DEBUG(DBG_HEADER_LO, "\n"); return SUCCEED; } /* * write_field - output a header field, possibly changing \n to \r\n. * * return SUCCEED or FAIL. */ static int write_field(field, f, crlf) register char *field; /* field to be written */ FILE *f; /* file to send to */ int crlf; /* TRUE to write \r\n for \n */ { register int c; if (field == NULL || field[0] == '\0') { /* empty field, don't write out anything */ return SUCCEED; } DEBUG1(DBG_HEADER_LO, "%s", field); while ((c = *field++)) { /* write out c, possibly with a '\r' first */ if ((c == '\n' && crlf && putc('\r', f) == EOF) || putc(c, f) == EOF) { return FAIL; } } /* if field did not end with \n, supply it */ if (field[-2] != '\n') { DEBUG(DBG_HEADER_LO, "\n"); if (crlf) { if (putc('\r', f) == EOF) { return FAIL; } } if (putc('\n', f) == EOF) { return FAIL; } } return SUCCEED; } /* * form a Received: header field */ static char * build_received_field(addr) struct addr *addr; { char *s = NULL; /* returned header field */ if (received_field) { s = expand_string(received_field, addr, (char *) NULL, (char *) NULL); } if (s == NULL || *s == '\0') { write_log(WRITE_LOG_PANIC, "Error in $received_field: %s", received_field); return ""; } return s; } /* * build a Return-Path: header field. */ static char * build_return_path(addr) struct addr *addr; { char *return_addr; char *s; return_addr = get_return_addr(addr); /* rewrite the magic internal error sender to be the normal error sender */ if (return_addr && EQ(return_addr, "+")) { *return_addr = '\0'; } s = xprintf("Return-Path: <%s>", return_addr ? return_addr : "MAILER-DAEMON" ); if (return_addr) { xfree(return_addr); } return s; } /* * build_remote_header - build a simple remote-form header * * For From:, Sender:, and Reply-To: fields prepend localhost! on addresses * which are pure !-routes, if mail is originated local then convert user to * user@localdomain and qualify known domain abbreviations. */ static void build_remote_header() { charplist_t **rhq = &remote_header; /* where to put next field */ charplist_t *hq; /* temp for scanning header */ char *error; /* (ignored) error message */ /* * copy the standard header to the remote header, transforming * sender and recipient fields as appropriate. */ for (hq = header; hq; hq = hq->succ) { *rhq = (charplist_t *) xmalloc(sizeof(**rhq)); (*rhq)->succ = NULL; if (HDREQ(from_fn, hq->text) || HDREQ(sender_fn, hq->text) || HDREQ(reply_to_fn, hq->text)) { /* * field containing a sender address: * prepend uucp_host! in !-routes * append @visiblename after local-form addresses * qualify known domain abbreviations, if local */ (*rhq)->text = process_field(hq->text, strchr(hq->text, ':') + 1, visible_name, uucp_name, (struct addr **) NULL, (islocal ? F_LOCAL : 0), &error); } else if (islocal && (HDREQ(to_fn, hq->text) || HDREQ(cc_fn, hq->text) || HDREQ(bcc_fn, hq->text))) { /* * locally generated field containing recipient addresses: * append @visible_name after local-form addresses * qualify known domain abbreviations */ (*rhq)->text = process_field(hq->text, strchr(hq->text, ':') + 1, visible_name, (char *) NULL, (struct addr **) NULL, F_LOCAL, &error); } else { (*rhq)->text = hq->text; } rhq = &(*rhq)->succ; } } /* * build_strict_header - build a somewhat RFC822 conformant header * * For addresses which are in local form, in !-route form or in * remainder%target form, append @visiblename and prepend a !-route * path back to the sender. This essentially qualifies the address * within the context of the current host, which will then be * routed through on replies to handle the !-routes. */ static void build_strict_header() { char *error; /* error messages */ charplist_t **shq = &strict_header; /* where to put next field */ charplist_t *hq; /* temp for scanning header */ char *sender_path; /* path to sender's machine */ /* * build a path back to the sender's machine, which is a !-route * back to the sender with the last component (!username) removed * this will be passed to process_field as the uucp_name parameter * for recipient fields. */ sender_path = build_uucp_route(sender, &error, 0); if (sender_path == NULL) { /* * errors will generate a NULL sender path, so nothing will * be prepended to addresses in recipient fields */ /* XXX paniclog too? */ write_log(WRITE_LOG_MLOG, "Xnotice: <> error building path to sender <%s>: %s", sender, error); } else { char *p = sender_path + strlen(sender_path); /* * back up over the trailing "!token" and terminate the string */ p = back_address_token(sender_path, p); if (p == NULL) { /* XXX paniclog too? */ write_log(WRITE_LOG_MLOG, "Xnotice: <> error building path to sender <%s>: unexpected end of token", sender); sender_path = NULL; } else if (p == sender_path) { sender_path = NULL; } else { p[-1] = '\0'; /* terminate the path */ } } /* * copy the standard header to a new strict header, performing * the necessary transformations on sender and recipient fields */ for (hq = header; hq; hq = hq->succ) { *shq = (charplist_t *) xmalloc(sizeof(**shq)); (*shq)->succ = NULL; if (HDREQ(from_fn, hq->text) || HDREQ(sender_fn, hq->text) || HDREQ(reply_to_fn, hq->text)) { /* * field containing a sender address: * append @visible_name after local-form addresses * qualify known domain abbreviations, if local */ (*shq)->text = process_field(hq->text, (strchr(hq->text, ':') + 1), visible_name, (char *) NULL, (struct addr **) NULL, ((islocal ? F_LOCAL : 0) | F_STRICT), &error); } else if (HDREQ(to_fn, hq->text) || HDREQ(cc_fn, hq->text) || HDREQ(bcc_fn, hq->text)) { /* * field containing a recipient address: * append @visible_name after !-route, local-form or u%d form * prepend sender_path! before the same set of addresses * qualify known domain abbreviations, if local */ (*shq)->text = process_field(hq->text, (strchr(hq->text, ':') + 1), visible_name, EQIC(sender_path, visible_name) ? (char *) NULL : sender_path, (struct addr **) NULL, ((islocal ? F_LOCAL : 0) | F_STRICT), &error); } else { (*shq)->text = hq->text; } shq = &(*shq)->succ; } } /* * Local Variables: * c-file-style: "smail" * End: */