/* ** message spooing, header and address parsing and completion ** functions for smail/rmail */ /* ** Modified by Chip Salzenberg (chip@ateng.UUCP), originally for SCO XENIX. ** Released to Usenet on 01 Dec 1987. ** Further modified 23 Dec 1987. ** Modifications added to MS-DOS version by Stephen Trier, 3/27/90 ** ** Additions: ** ** Understand "a%b" to mean "a@b". (This is a judgement call. ** I prefer to accept any mail that I can understand, not just ** that which is "correct.") */ /* ** Patched for MS-DOS compatibility by Stephen Trier March, April and ** May, 1990. This file is in the public domain. ** ** I added the a%b patch by Chip Salzenburg not knowing that one of my ** test feeds was going to start giving me mail in that misformed notation. ** This is the only one of Chip's patches that I used. */ #ifndef lint static char *sccsid="@(#)headers.c 2.5 (smail) 9/15/87"; #endif # include # include # include # include # include # include "defs.h" #ifdef MSDOS # include #endif extern enum edebug debug; /* how verbose we are */ extern char hostname[]; /* */ extern char hostdomain[]; /* */ extern char *spoolfile; /* file name of spooled message */ extern FILE *spoolfp; /* file ptr to spooled message */ extern int spoolmaster; /* set if creator of spoolfile */ extern time_t now; /* time */ extern char nows[], arpanows[]; /* time strings */ extern struct tm *gmt, *loc; /* time structs */ extern char *from_addr; /* replacement fromaddr with -F */ static char toline[SMLBUF]; static char fromline[SMLBUF]; static char dateline[SMLBUF]; static char midline[SMLBUF]; static char *ieof = "NOTNULL"; struct reqheaders { char *name; char *field; char have; }; static struct reqheaders reqtab[] = { "Message-Id:" , midline , 'N' , "Date:" , dateline , 'N' , "From:" , fromline , 'N' , "To:" , toline , 'N' , NULL , NULL , 'N' }; /* ** ** parse(): parse
into . ** ** input form ** ----- ---- ** user LOCAL ** domain!user DOMAIN ** user@domain DOMAIN ** @domain,address LOCAL (just for sendmail) ** host!address UUCP ** */ enum eform parse(address, domain, user) char *address; /* input address */ char *domain; /* output domain */ char *user; /* output user */ { int parts; char *partv[MAXPATH]; /* to crack address */ /* ** If this is route address form @domain_a,@domain_b:user@domain_c, ... */ if(*address == '@') #ifdef SENDMAIL /* ** hand it to sendmail */ { goto local; } #else /* ** no sendmail, convert it into a bang path: domain_a!domain_b!domain_c!user */ { char buf[SMLBUF], *p; char t_dom[SMLBUF], t_user[SMLBUF]; (void) strcpy(buf, address+1); /* elide leading '@' */ for(p=buf; *p != '\0' ; p++) { /* search for ',' or ':' */ if(*p == ':') { /* reached end of route */ break; } if(*p == ',') { /* elide ','s */ (void) strcpy(p, p+1); } if(*p == '@') { /* convert '@' to '!' */ *p = '!'; } } if(*p != ':') { /* bad syntax - punt */ goto local; } *p = '\0'; if(parse(p+1, t_dom, t_user) != LOCAL) { (void) strcat(buf, "!"); (void) strcat(buf, t_dom); } (void) strcat(buf, "!"); (void) strcat(buf, t_user); /* munge the address (yuk) ** it's OK to copy into 'address', because the machinations ** above don't increase the string length of the address. */ (void) strcpy(address, buf); /* re-parse the address */ return(parse(address, domain, user)); } #endif /* ** Try splitting at @. If it works, this is user@domain, form DOMAIN. ** Prefer the righthand @ in a@b@c. */ if ((parts = ssplit(address, '@', partv)) >= 2) { (void) strcpy(domain, partv[parts-1]); (void) strncpy(user, partv[0], partv[parts-1]-partv[0]-1); user[partv[parts-1]-partv[0]-1] = '\0'; return (DOMAIN); } /* ** Try splitting at !. If it works, see if the piece before the ! has ** a . in it (domain!user, form DOMAIN) or not (host!user, form UUCP). */ if (ssplit(address, '!', partv) > 1) { (void) strcpy(user, partv[1]); (void) strncpy(domain, partv[0], partv[1]-partv[0]-1); domain[partv[1]-partv[0]-1] = '\0'; if((parts = ssplit(domain, '.', partv)) < 2) { return(UUCP); } if(partv[parts-1][0] == '\0') { partv[parts-1][-1] = '\0'; /* strip trailing . */ } return (DOMAIN); } /* ** Try splitting at %. If it works, this is user%domain, which we choose ** to understand as user@domain. Prefer the righthand % in a%b%c. ** (This code allows 'user%foo@mydom' to mean '@mydom,user@foo'.) ** [Chip Salzenburg] */ if ((parts = ssplit(address, '%', partv)) >= 2) { (void) strcpy(domain, partv[parts-1]); (void) strncpy(user, partv[0], partv[parts-1]-partv[0]-1); user[partv[parts-1]-partv[0]-1] = '\0'; return (DOMAIN); } /* ** Done trying. This must be just a user name, form LOCAL. */ local: (void) strcpy(user, address); (void) strcpy(domain, ""); return(LOCAL); /* user */ } build(domain, user, form, result) char *domain; char *user; enum eform form; char *result; { switch((int) form) { case LOCAL: (void) sprintf(result, "%s", user); break; case UUCP: (void) sprintf(result, "%s!%s", domain, user); break; case DOMAIN: (void) sprintf(result, "%s@%s", user, domain); break; } } /* ** ssplit(): split a line into array pointers. ** ** Each pointer wordv[i] points to the first character after the i'th ** occurence of c in buf. Note that each wordv[i] includes wordv[i+1]. ** */ ssplit(buf, c, ptr) register char *buf; /* line to split up */ char c; /* character to split on */ char **ptr; /* the resultant vector */ { int count = 0; int wasword = 0; for(; *buf; buf++) { if (!wasword) { count++; *ptr++ = buf; } wasword = (c != *buf); } if (!wasword) { count++; *ptr++ = buf; } *ptr = NULL; return(count); } /* ** Determine whether an address is a local address */ islocal(addr, domain, user) char *addr, *domain, *user; { enum eform form, parse(); extern char hostuucp[]; /* ** parse the address */ form = parse(addr, domain, user); if((form == LOCAL) /* user */ ||(strcmpic(domain, hostdomain) == 0) /* user@hostdomain */ ||(strcmpic(domain, hostname) == 0) /* user@hostname */ #ifdef DOMGATE ||(strcmpic(domain, &MYDOM[0]) == 0) /* user@MYDOM w/ dot */ ||(strcmpic(domain, &MYDOM[1]) == 0) /* user@MYDOM no dot */ #endif ||(strcmpic(domain, hostuucp) == 0)) {/* user@hostuucp */ return(1); } return(0); } /* ** spool - message spooling module ** ** (1) get dates for headers, etc. ** (2) if the message is on the standard input (no '-f') ** (a) create a temp file for spooling the message. ** (b) collapse the From_ headers into a path. ** (c) if the mail originated locally, then ** (i) establish default headers ** (ii) scan the message headers for required header fields ** (iii) add any required message headers that are absent ** (d) copy rest of the message to the spool file ** (e) close the spool file ** (3) open the spool file for reading */ void spool(argc, argv) int argc; char **argv; { #ifndef MSDOS static char *tmpf = "/tmp/rmXXXXXX"; /* temp file name */ #else /* MSDOS */ static char tmpf[80] = ""; #endif /* !MSDOS */ char *mktemp(); char buf[SMLBUF]; static char splbuf[SMLBUF]; char from[SMLBUF], domain[SMLBUF], user[SMLBUF]; void rline(), scanheaders(), compheaders(); #ifdef MSDOS /* * If we haven't figured out a name for the * temporary file yet, do so now. */ if (*tmpf == '\0') sprintf(tmpf, "%s/rmXXXXXX", ms_tmpdir); #endif /* MSDOS */ /* ** if the mail has already been spooled by ** a previous invocation of smail don't respool. ** check the file name to prevent things like ** rmail -f /etc/passwd badguy@dreadfuldomain */ if((spoolfile != NULL) && (strncmp(spoolfile, tmpf, strlen(tmpf) - 6) != 0)) { error(EX_TEMPFAIL, "spool: bad file name '%s'\n", spoolfile); } /* ** set dates in local, arpa, and gmt forms */ setdates(); /* ** If necessary, copy stdin to a temp file. */ if(spoolfile == NULL) { spoolfile = strcpy(splbuf, tmpf); (void) mktemp(spoolfile); if((spoolfp = fopen(spoolfile, "w")) == NULL) { error(EX_CANTCREAT, "can't create %s.\n", spoolfile); } spoolmaster = 1; /* ** rline reads the standard input, ** collapsing the From_ and >From_ ** lines into a single uucp path. ** first non-from_ line is in buf[]; */ rline(from, buf); /* ** if the mail originated here, we parse the header ** and add any required headers that are missing. */ if(islocal(from, domain, user) || (from_addr != NULL)) { /* ** initialize default headers */ def_headers(argc, argv, from); /* ** buf has first, non-from_ line */ scanheaders(buf); /* ** buf has first, non-header line, */ compheaders(); if(buf[0] != '\n') { (void) fputs("\n", spoolfp); } } /* ** now, copy the rest of the letter into the spool file ** terminate on either EOF or '^.$' */ while(ieof != NULL) { (void) fputs(buf, spoolfp); if((fgets(buf, SMLBUF, stdin) == NULL) || (buf[0] == '.' && buf[1] == '\n')) { ieof = NULL; } } /* ** close the spool file, and the standard input. */ (void) fclose(spoolfp); (void) fclose(stdin); /* you don't see this too often! */ } if((spoolfp = fopen(spoolfile, "r")) == NULL) { error(EX_TEMPFAIL, "can't open %s.\n", spoolfile); } } /* ** ** rline(): collapse From_ and >From_ lines. ** ** Same idea as the old rmail, but also turns user@domain to domain!user. ** */ void rline(from, retbuf) char *from; char *retbuf; { int parts; /* for cracking From_ lines ... */ char *partv[16]; /* ... apart using ssplit() */ char user[SMLBUF]; /* for rewriting user@host */ char domain[SMLBUF]; /* " " " */ char addr[SMLBUF]; /* " " " */ enum eform form, parse(); /* " " " */ extern build(); /* " " " */ char *c; int nhops, i; char buf[SMLBUF], tmp[SMLBUF], *hop[128], *e, *b; char *pwuid(); if(spoolmaster == 0) return; buf[0] = from[0] = addr[0] = '\0'; /* ** Read each line until we hit EOF or a line not beginning with "From " ** or ">From " (called From_ lines), accumulating the new path in from ** and stuffing the actual sending user (the user name on the last From_ ** line) in addr. */ for(;;) { (void) strcpy(retbuf, buf); if(ieof == NULL) { break; } if((fgets(buf, sizeof(buf), stdin) == NULL) || (buf[0] == '.' && buf[1] == '\n')) { ieof = NULL; break; } if (strncmp("From ", buf, 5) && strncmp(">From ", buf, 6)) { break; } /* ** Crack the line apart using ssplit. */ if(c = index(buf, '\n')) { *c = '\0'; } parts = ssplit(buf, ' ', partv); /* ** Tack host! onto the from argument if "remote from host" is present. */ if((parts > 3) && (strncmp("remote from ", partv[parts-3], 12) == 0)) { (void) strcat(from, partv[parts-1]); (void) strcat(from, "!"); } /* ** Stuff user name into addr, overwriting the user name from previous ** From_ lines, since only the last one counts. Then rewrite user@host ** into host!user, since @'s don't belong in the From_ argument. */ if(parts < 2) { break; } else { char *x = partv[1]; char *q = index(x, ' '); if(q != NULL) { *q = '\0'; } (void) strcpy(addr, x); } (void) parse(addr, domain, user); if(*domain == '\0') { form = LOCAL; } else { form = UUCP; } build(domain, user, form, addr); } /* ** Now tack the user name onto the from argument. */ (void) strcat(from, addr); /* ** If we still have no from argument, we have junk headers, but we try ** to get the user's name using /etc/passwd. */ if (from[0] == '\0') { char *login; if ((login = pwuid(getuid())) == NULL) { (void) strcpy(from, "nobody"); /* bad news */ } else { (void) strcpy(from, login); } } /* split the from line on '!'s */ nhops = ssplit(from, '!', hop); for(i = 0; i < (nhops - 1); i++) { b = hop[i]; if(*b == '\0') { continue; } e = hop[i+1]; e-- ; *e = '\0'; /* null terminate each path segment */ e++; #ifdef HIDDENHOSTS /* ** Strip hidden hosts: anything.hostname.MYDOM -> hostname.MYDOM */ for(p = b;(p = index(p, '.')) != NULL; p++) { if(strcmpic(hostdomain, p+1) == 0) { (void) strcpy(b, hostdomain); break; } } #endif /* ** Strip useless MYDOM: hostname.MYDOM -> hostname */ if(strcmpic(hop[i], hostdomain) == 0) { (void) strcpy(hop[i], hostname); } } /* ** Now strip out any redundant information in the From_ line ** a!b!c!c!d => a!b!c!d */ for(i = 0; i < (nhops - 2); i++) { b = hop[i]; e = hop[i+1]; if(strcmpic(b, e) == 0) { *b = '\0'; } } /* ** Reconstruct the From_ line */ tmp[0] = '\0'; /* empty the tmp buffer */ for(i = 0; i < (nhops - 1); i++) { if((hop[i][0] == '\0') /* deleted this hop */ ||((tmp[0] == '\0') /* first hop == hostname */ &&(strcmpic(hop[i], hostname) == 0))) { continue; } (void) strcat(tmp, hop[i]); (void) strcat(tmp, "!"); } (void) strcat(tmp, hop[i]); (void) strcpy(from, tmp); (void) strcpy(retbuf, buf); (void) fprintf(spoolfp, "%s\n", from); } void scanheaders(buf) char *buf; { int inheader = 0; while(ieof != NULL) { if(buf[0] == '\n') { break; /* end of headers */ } /* ** header lines which begin with whitespace ** are continuation lines */ if((inheader == 0) || ((buf[0] != ' ' && buf[0] != '\t'))) { /* not a continuation line ** check for header */ if(isheader(buf) == 0) { /* ** not a header */ break; } inheader = 1; haveheaders(buf); } (void) fputs(buf, spoolfp); if((fgets(buf, SMLBUF, stdin) == NULL) || (buf[0] == '.' && buf[1] == '\n')) { ieof = NULL; } } if(isheader(buf)) { buf[0] = '\0'; } } /* ** complete headers - add any required headers that are not in the message */ void compheaders() { struct reqheaders *i; /* ** look at the table of required headers and ** add those that are missing to the spooled message. */ for(i = reqtab; i->name != NULL; i++) { if(i->have != 'Y') { (void) fprintf(spoolfp, "%s\n", i->field); } } } /* ** look at a string and determine ** whether or not it is a valid header. */ isheader(s) char *s; { char *p; /* ** header field names must terminate with a colon ** and may not be null. */ if(((p = index(s, ':')) == NULL) || (s == p)) { return(0); } /* ** header field names must consist entirely of ** printable ascii characters. */ while(s != p) { if((*s < '!') || (*s > '~')) { return(0); } s++; } /* ** we hit the ':', so the field may be a header */ return(1); } /* ** compare the header field to those in the required header table. ** if it matches, then mark the required header as being present ** in the message. */ haveheaders(s) char *s; { struct reqheaders *i; for(i = reqtab; i->name != NULL; i++) { if(strncmpic(i->name, s, strlen(i->name)) == 0) { if((strncmpic("From:", s, 5) == 0) && (from_addr != NULL)) { (void) sprintf(s, "From: %s\n", from_addr); } i->have = 'Y'; break; } } } /* ** create default headers for the message. */ def_headers(argc, argv, from) int argc; char **argv; char *from; { def_to(argc, argv); /* default To: */ def_date(); /* default Date: */ def_from(from); /* default From: */ def_mid(); /* default Message-Id: */ } /* ** default Date: in arpa format */ def_date() { (void) strcpy(dateline, "Date: "); (void) strcat(dateline, arpanows); } /* ** default Message-Id ** Message-Id: ** ** yy year ** mm month ** dd day ** hh hour ** mm minute ** ppppp process-id ** ** date and time are set by GMT */ def_mid() { (void) sprintf(midline, "Message-Id: <%02d%02d%02d%02d%02d.AA%05d@%s>", gmt->tm_year, gmt->tm_mon+1, gmt->tm_mday, gmt->tm_hour, gmt->tm_min, getpid(), hostdomain); } /* ** default From: ** From: user@hostdomain (Full Name) */ def_from(from) char *from; { char *nameptr; char name[SMLBUF]; char *getenv(), *login; char *pwfnam(), *pwuid(); if (from_addr != NULL) { (void) sprintf(fromline, "From: %s", from_addr); return; } name[0] = '\0'; if((nameptr = getenv("NAME")) != NULL) { (void) strcpy(name, nameptr); } else if((login = pwuid(getuid())) != NULL) { if((nameptr = pwfnam(login)) != NULL) { (void) strcpy(name, nameptr); } } if(name[0] != '\0') { (void) sprintf(fromline, "From: %s@%s (%s)", from, hostdomain, name); } else { (void) sprintf(fromline, "From: %s@%s", from, hostdomain); } } /* ** default To: ** To: recip1, recip2, ... ** ** lines longer than 50 chars are continued on another line. */ def_to(argc, argv) int argc; char **argv; { int i, n; char *bol; bol = toline; (void) strcpy(bol, "To: "); for(n = i = 0; i < argc; i++) { (void) strcat(bol, argv[i]); if((index(argv[i], '!') == NULL) && (index(argv[i], '@') == NULL)) { (void) strcat(bol, "@"); (void) strcat(bol, hostdomain); } if(i+1 < argc) { n = strlen(bol); if(n > 50) { (void) strcat(bol, ",\n\t"); bol = bol + strlen(bol); *bol = '\0'; n = 8; } else { (void) strcat(bol, ", "); } } } }