/* * Copyright (c) 1983, 1995-1997 Eric P. Allman * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static char sccsid[] = "@(#)collect.c 8.72 (Berkeley) 10/6/97"; #endif /* not lint */ # include # include "sendmail.h" /* ** COLLECT -- read & parse message header & make temp file. ** ** Creates a temporary file name and copies the standard ** input to that file. Leading UNIX-style "From" lines are ** stripped off (after important information is extracted). ** ** Parameters: ** fp -- file to read. ** smtpmode -- if set, we are running SMTP: give an RFC821 ** style message to say we are ready to collect ** input, and never ignore a single dot to mean ** end of message. ** hdrp -- the location to stash the header. ** e -- the current envelope. ** ** Returns: ** none. ** ** Side Effects: ** Temp file is created and filled. ** The from person may be set. */ static jmp_buf CtxCollectTimeout; static void collecttimeout(); static bool CollectProgress; static EVENT *CollectTimeout; /* values for input state machine */ #define IS_NORM 0 /* middle of line */ #define IS_BOL 1 /* beginning of line */ #define IS_DOT 2 /* read a dot at beginning of line */ #define IS_DOTCR 3 /* read ".\r" at beginning of line */ #define IS_CR 4 /* read a carriage return */ /* values for message state machine */ #define MS_UFROM 0 /* reading Unix from line */ #define MS_HEADER 1 /* reading message header */ #define MS_BODY 2 /* reading message body */ void collect(fp, smtpmode, hdrp, e) FILE *fp; bool smtpmode; HDR **hdrp; register ENVELOPE *e; { register FILE *volatile tf; volatile bool ignrdot = smtpmode ? FALSE : IgnrDot; volatile time_t dbto = smtpmode ? TimeOuts.to_datablock : 0; register char *volatile bp; volatile int c = EOF; volatile bool inputerr = FALSE; bool headeronly; char *volatile buf; volatile int buflen; volatile int istate; volatile int mstate; u_char *volatile pbp; u_char peekbuf[8]; char dfname[MAXQFNAME]; char bufbuf[MAXLINE]; extern bool isheader(); extern void eatheader(); extern void tferror(); headeronly = hdrp != NULL; /* ** Create the temp file name and create the file. */ if (!headeronly) { int tfd; struct stat stbuf; strcpy(dfname, queuename(e, 'd')); tfd = dfopen(dfname, O_WRONLY|O_CREAT|O_TRUNC, FileMode, SFF_ANYFILE); if (tfd < 0 || (tf = fdopen(tfd, "w")) == NULL) { syserr("Cannot create %s", dfname); e->e_flags |= EF_NO_BODY_RETN; finis(); } if (fstat(fileno(tf), &stbuf) < 0) e->e_dfino = -1; else { e->e_dfdev = stbuf.st_dev; e->e_dfino = stbuf.st_ino; } HasEightBits = FALSE; e->e_msgsize = 0; e->e_flags |= EF_HAS_DF; } /* ** Tell ARPANET to go ahead. */ if (smtpmode) message("354 Enter mail, end with \".\" on a line by itself"); if (tTd(30, 2)) printf("collect\n"); /* ** Read the message. ** ** This is done using two interleaved state machines. ** The input state machine is looking for things like ** hidden dots; the message state machine is handling ** the larger picture (e.g., header versus body). */ buf = bp = bufbuf; buflen = sizeof bufbuf; pbp = peekbuf; istate = IS_BOL; mstate = SaveFrom ? MS_HEADER : MS_UFROM; CollectProgress = FALSE; if (dbto != 0) { /* handle possible input timeout */ if (setjmp(CtxCollectTimeout) != 0) { if (LogLevel > 2) sm_syslog(LOG_NOTICE, e->e_id, "timeout waiting for input from %s during message collect", CurHostName ? CurHostName : ""); errno = 0; usrerr("451 timeout waiting for input during message collect"); goto readerr; } CollectTimeout = setevent(dbto, collecttimeout, dbto); } for (;;) { if (tTd(30, 35)) printf("top, istate=%d, mstate=%d\n", istate, mstate); for (;;) { if (pbp > peekbuf) c = *--pbp; else { while (!feof(fp) && !ferror(fp)) { errno = 0; c = getc(fp); if (errno != EINTR) break; clearerr(fp); } CollectProgress = TRUE; if (TrafficLogFile != NULL && !headeronly) { if (istate == IS_BOL) fprintf(TrafficLogFile, "%05d <<< ", (int) getpid()); if (c == EOF) fprintf(TrafficLogFile, "[EOF]\n"); else putc(c, TrafficLogFile); } if (c == EOF) goto readerr; if (SevenBitInput) c &= 0x7f; else HasEightBits |= bitset(0x80, c); } if (tTd(30, 94)) printf("istate=%d, c=%c (0x%x)\n", istate, c, c); switch (istate) { case IS_BOL: if (c == '.') { istate = IS_DOT; continue; } break; case IS_DOT: if (c == '\n' && !ignrdot && !bitset(EF_NL_NOT_EOL, e->e_flags)) goto readerr; else if (c == '\r' && !bitset(EF_CRLF_NOT_EOL, e->e_flags)) { istate = IS_DOTCR; continue; } else if (c != '.' || (OpMode != MD_SMTP && OpMode != MD_DAEMON && OpMode != MD_ARPAFTP)) { *pbp++ = c; c = '.'; } break; case IS_DOTCR: if (c == '\n' && !ignrdot) goto readerr; else { /* push back the ".\rx" */ *pbp++ = c; *pbp++ = '\r'; c = '.'; } break; case IS_CR: if (c == '\n') istate = IS_BOL; else { ungetc(c, fp); c = '\r'; istate = IS_NORM; } goto bufferchar; } if (c == '\r' && !bitset(EF_CRLF_NOT_EOL, e->e_flags)) { istate = IS_CR; continue; } else if (c == '\n' && !bitset(EF_NL_NOT_EOL, e->e_flags)) istate = IS_BOL; else istate = IS_NORM; bufferchar: if (!headeronly) e->e_msgsize++; if (mstate == MS_BODY) { /* just put the character out */ if (MaxMessageSize <= 0 || e->e_msgsize <= MaxMessageSize) putc(c, tf); continue; } /* header -- buffer up */ if (bp >= &buf[buflen - 2]) { char *obuf; if (mstate != MS_HEADER) break; /* out of space for header */ obuf = buf; if (buflen < MEMCHUNKSIZE) buflen *= 2; else buflen += MEMCHUNKSIZE; buf = xalloc(buflen); bcopy(obuf, buf, bp - obuf); bp = &buf[bp - obuf]; if (obuf != bufbuf) free(obuf); } if (c >= 0200 && c <= 0237) { #if 0 /* causes complaints -- figure out something for 8.9 */ usrerr("Illegal character 0x%x in header", c); #endif } else if (c != '\0') *bp++ = c; if (istate == IS_BOL) break; } *bp = '\0'; nextstate: if (tTd(30, 35)) printf("nextstate, istate=%d, mstate=%d, line = \"%s\"\n", istate, mstate, buf); switch (mstate) { extern int chompheader(); case MS_UFROM: mstate = MS_HEADER; #ifndef NOTUNIX if (strncmp(buf, "From ", 5) == 0) { extern void eatfrom(); bp = buf; eatfrom(buf, e); continue; } #endif /* fall through */ case MS_HEADER: if (!isheader(buf)) { mstate = MS_BODY; goto nextstate; } /* check for possible continuation line */ do { clearerr(fp); errno = 0; c = getc(fp); } while (errno == EINTR); if (c != EOF) ungetc(c, fp); if (c == ' ' || c == '\t') { /* yep -- defer this */ continue; } /* trim off trailing CRLF or NL */ if (*--bp != '\n' || *--bp != '\r') bp++; *bp = '\0'; if (bitset(H_EOH, chompheader(buf, FALSE, hdrp, e))) mstate = MS_BODY; break; case MS_BODY: if (tTd(30, 1)) printf("EOH\n"); if (headeronly) goto readerr; bp = buf; /* toss blank line */ if ((!bitset(EF_CRLF_NOT_EOL, e->e_flags) && bp[0] == '\r' && bp[1] == '\n') || (!bitset(EF_NL_NOT_EOL, e->e_flags) && bp[0] == '\n')) { break; } /* if not a blank separator, write it out */ if (MaxMessageSize <= 0 || e->e_msgsize <= MaxMessageSize) { while (*bp != '\0') putc(*bp++, tf); } break; } bp = buf; } readerr: if ((feof(fp) && smtpmode) || ferror(fp)) { const char *errmsg = errstring(errno); if (tTd(30, 1)) printf("collect: premature EOM: %s\n", errmsg); if (LogLevel >= 2) sm_syslog(LOG_WARNING, e->e_id, "collect: premature EOM: %s", errmsg); inputerr = TRUE; } /* reset global timer */ clrevent(CollectTimeout); if (headeronly) return; if (tf != NULL && (fflush(tf) != 0 || ferror(tf) || (SuperSafe && fsync(fileno(tf)) < 0) || fclose(tf) < 0)) { tferror(tf, e); flush_errors(TRUE); finis(); } /* An EOF when running SMTP is an error */ if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON)) { char *host; char *problem; host = RealHostName; if (host == NULL) host = "localhost"; if (feof(fp)) problem = "unexpected close"; else if (ferror(fp)) problem = "I/O error"; else problem = "read timeout"; if (LogLevel > 0 && feof(fp)) sm_syslog(LOG_NOTICE, e->e_id, "collect: %s on connection from %.100s, sender=%s: %s", problem, host, shortenstring(e->e_from.q_paddr, 203), errstring(errno)); if (feof(fp)) usrerr("451 collect: %s on connection from %s, from=%s", problem, host, shortenstring(e->e_from.q_paddr, 203)); else syserr("451 collect: %s on connection from %s, from=%s", problem, host, shortenstring(e->e_from.q_paddr, 203)); /* don't return an error indication */ e->e_to = NULL; e->e_flags &= ~EF_FATALERRS; e->e_flags |= EF_CLRQUEUE; /* and don't try to deliver the partial message either */ if (InChild) ExitStat = EX_QUIT; finis(); } /* ** Find out some information from the headers. ** Examples are who is the from person & the date. */ eatheader(e, TRUE); if (GrabTo && e->e_sendqueue == NULL) usrerr("No recipient addresses found in header"); /* collect statistics */ if (OpMode != MD_VERIFY) { extern void markstats(); markstats(e, (ADDRESS *) NULL); } #if _FFR_DSN_RRT_OPTION /* ** If we have a Return-Receipt-To:, turn it into a DSN. */ if (RrtImpliesDsn && hvalue("return-receipt-to", e->e_header) != NULL) { ADDRESS *q; for (q = e->e_sendqueue; q != NULL; q = q->q_next) if (!bitset(QHASNOTIFY, q->q_flags)) q->q_flags |= QHASNOTIFY|QPINGONSUCCESS; } #endif /* ** Add an Apparently-To: line if we have no recipient lines. */ if (hvalue("to", e->e_header) != NULL || hvalue("cc", e->e_header) != NULL || hvalue("apparently-to", e->e_header) != NULL) { /* have a valid recipient header -- delete Bcc: headers */ e->e_flags |= EF_DELETE_BCC; } else if (hvalue("bcc", e->e_header) == NULL) { /* no valid recipient headers */ register ADDRESS *q; char *hdr = NULL; extern void addheader(); /* create an Apparently-To: field */ /* that or reject the message.... */ switch (NoRecipientAction) { case NRA_ADD_APPARENTLY_TO: hdr = "Apparently-To"; break; case NRA_ADD_TO: hdr = "To"; break; case NRA_ADD_BCC: addheader("Bcc", " ", &e->e_header); break; case NRA_ADD_TO_UNDISCLOSED: addheader("To", "undisclosed-recipients:;", &e->e_header); break; } if (hdr != NULL) { for (q = e->e_sendqueue; q != NULL; q = q->q_next) { if (q->q_alias != NULL) continue; if (tTd(30, 3)) printf("Adding %s: %s\n", hdr, q->q_paddr); addheader(hdr, q->q_paddr, &e->e_header); } } } /* check for message too large */ if (MaxMessageSize > 0 && e->e_msgsize > MaxMessageSize) { e->e_flags |= EF_NO_BODY_RETN|EF_CLRQUEUE; e->e_status = "5.2.3"; usrerr("552 Message exceeds maximum fixed size (%ld)", MaxMessageSize); if (LogLevel > 6) sm_syslog(LOG_NOTICE, e->e_id, "message size (%ld) exceeds maximum (%ld)", e->e_msgsize, MaxMessageSize); } /* check for illegal 8-bit data */ if (HasEightBits) { e->e_flags |= EF_HAS8BIT; if (!bitset(MM_PASS8BIT|MM_MIME8BIT, MimeMode) && !bitset(EF_IS_MIME, e->e_flags)) { e->e_status = "5.6.1"; usrerr("554 Eight bit data not allowed"); } } else { /* if it claimed to be 8 bits, well, it lied.... */ if (e->e_bodytype != NULL && strcasecmp(e->e_bodytype, "8BITMIME") == 0) e->e_bodytype = "7BIT"; } if ((e->e_dfp = fopen(dfname, "r")) == NULL) { /* we haven't acked receipt yet, so just chuck this */ syserr("Cannot reopen %s", dfname); finis(); } } static void collecttimeout(timeout) time_t timeout; { /* if no progress was made, die now */ if (!CollectProgress) longjmp(CtxCollectTimeout, 1); /* otherwise reset the timeout */ CollectTimeout = setevent(timeout, collecttimeout, timeout); CollectProgress = FALSE; } /* ** TFERROR -- signal error on writing the temporary file. ** ** Parameters: ** tf -- the file pointer for the temporary file. ** e -- the current envelope. ** ** Returns: ** none. ** ** Side Effects: ** Gives an error message. ** Arranges for following output to go elsewhere. */ void tferror(tf, e) FILE *tf; register ENVELOPE *e; { setstat(EX_IOERR); if (errno == ENOSPC) { struct stat st; long avail; long bsize; extern long freediskspace __P((char *, long *)); e->e_flags |= EF_NO_BODY_RETN; if (fstat(fileno(tf), &st) < 0) st.st_size = 0; (void) freopen(queuename(e, 'd'), "w", tf); if (st.st_size <= 0) fprintf(tf, "\n*** Mail could not be accepted"); else if (sizeof st.st_size > sizeof (long)) fprintf(tf, "\n*** Mail of at least %qd bytes could not be accepted\n", st.st_size); else fprintf(tf, "\n*** Mail of at least %ld bytes could not be accepted\n", (long) st.st_size); fprintf(tf, "*** at %s due to lack of disk space for temp file.\n", MyHostName); avail = freediskspace(QueueDir, &bsize); if (avail > 0) { if (bsize > 1024) avail *= bsize / 1024; else if (bsize < 1024) avail /= 1024 / bsize; fprintf(tf, "*** Currently, %ld kilobytes are available for mail temp files.\n", avail); } e->e_status = "4.3.1"; usrerr("452 Out of disk space for temp file"); } else syserr("collect: Cannot write tf%s", e->e_id); (void) freopen("/dev/null", "w", tf); } /* ** EATFROM -- chew up a UNIX style from line and process ** ** This does indeed make some assumptions about the format ** of UNIX messages. ** ** Parameters: ** fm -- the from line. ** ** Returns: ** none. ** ** Side Effects: ** extracts what information it can from the header, ** such as the date. */ # ifndef NOTUNIX char *DowList[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL }; char *MonthList[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; void eatfrom(fm, e) char *fm; register ENVELOPE *e; { register char *p; register char **dt; if (tTd(30, 2)) printf("eatfrom(%s)\n", fm); /* find the date part */ p = fm; while (*p != '\0') { /* skip a word */ while (*p != '\0' && *p != ' ') p++; while (*p == ' ') p++; if (!(isascii(*p) && isupper(*p)) || p[3] != ' ' || p[13] != ':' || p[16] != ':') continue; /* we have a possible date */ for (dt = DowList; *dt != NULL; dt++) if (strncmp(*dt, p, 3) == 0) break; if (*dt == NULL) continue; for (dt = MonthList; *dt != NULL; dt++) if (strncmp(*dt, &p[4], 3) == 0) break; if (*dt != NULL) break; } if (*p != '\0') { char *q; /* we have found a date */ q = xalloc(25); (void) strncpy(q, p, 25); q[24] = '\0'; q = arpadate(q); define('a', newstr(q), e); } } # endif /* NOTUNIX */