From: Eric Allman Date: Mon, 8 Aug 1994 01:02:37 +0000 (-0800) Subject: first cut at full 8-bit data capability X-Git-Url: https://git.subgeniuskitty.com/unix-history/.git/commitdiff_plain/0d721ffb7a5095c10c541239421cbd4decb5281d?hp=68a0f832e4e2d3148d2eca842958d1cd142a3368 first cut at full 8-bit data capability SCCS-vsn: usr.sbin/sendmail/src/collect.c 8.19 SCCS-vsn: usr.sbin/sendmail/src/util.c 8.43 SCCS-vsn: usr.sbin/sendmail/src/deliver.c 8.90 --- diff --git a/usr/src/usr.sbin/sendmail/src/collect.c b/usr/src/usr.sbin/sendmail/src/collect.c index 397760ea99..c22b5b9eba 100644 --- a/usr/src/usr.sbin/sendmail/src/collect.c +++ b/usr/src/usr.sbin/sendmail/src/collect.c @@ -7,7 +7,7 @@ */ #ifndef lint -static char sccsid[] = "@(#)collect.c 8.18 (Berkeley) %G%"; +static char sccsid[] = "@(#)collect.c 8.19 (Berkeley) %G%"; #endif /* not lint */ # include @@ -39,18 +39,43 @@ static char sccsid[] = "@(#)collect.c 8.18 (Berkeley) %G%"; char *CollectErrorMessage; bool CollectErrno; +static jmp_buf CtxCollectTimeout; +static int 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 */ + + maketemp(from) char *from; { register FILE *tf; bool ignrdot = smtpmode ? FALSE : IgnrDot; time_t dbto = smtpmode ? TimeOuts.to_datablock : 0; - register char *workbuf, *freebuf; + register char *bp; + register int c; bool inputerr = FALSE; bool headeronly = FALSE; - char buf[MAXLINE], buf2[MAXLINE]; + char *buf; + int buflen; + int istate; + int mstate; + char *pbp; + char peekbuf[8]; + char bufbuf[MAXLINE]; extern char *hvalue(); - extern bool isheader(), flusheol(); + extern bool isheader(); extern char *index(); CollectErrorMessage = NULL; @@ -84,194 +109,246 @@ maketemp(from) if (smtpmode) message("354 Enter mail, end with \".\" on a line by itself"); - /* set global timer to monitor progress */ - sfgetset(dbto); - /* - ** Try to read a UNIX-style From line + ** 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). */ - if (sfgets(buf, MAXLINE, fp, dbto, "initial message read") == NULL) - goto readerr; - fixcrlf(buf, FALSE); -# ifndef NOTUNIX - if (!headeronly && !SaveFrom && strncmp(buf, "From ", 5) == 0) - { - if (!flusheol(buf, fp, dbto)) - goto readerr; - eatfrom(buf, e); - if (sfgets(buf, MAXLINE, fp, dbto, - "message header read") == NULL) - goto readerr; - fixcrlf(buf, FALSE); - } -# endif /* NOTUNIX */ + buf = bp = bufbuf; + buflen = sizeof bufbuf; + pbp = peekbuf; + istate = IS_BOL; + mstate = SaveFrom ? MS_HEADER : MS_UFROM; + CollectProgress = FALSE; - /* - ** Copy fp to temp file & do message editing. - ** To keep certain mailers from getting confused, - ** and to keep the output clean, lines that look - ** like UNIX "From" lines are deleted in the header. - */ + /* if transmitting binary, don't map NL to EOL */ + if (e->e_bodytype != NULL && strcasecmp(e->e_bodytype, "8BITMIME") == 0) + e->e_flags |= EF_NL_NOT_EOL; - workbuf = buf; /* `workbuf' contains a header field */ - freebuf = buf2; /* `freebuf' can be used for read-ahead */ - for (;;) + if (dbto != 0) { - char *curbuf; - int curbuffree; - register int curbuflen; - char *p; - - /* first, see if the header is over */ - if (!isheader(workbuf)) + /* handle possible input timeout */ + if (setjmp(CtxCollectTimeout) != 0) { - fixcrlf(workbuf, TRUE); - break; - } - - /* if the line is too long, throw the rest away */ - if (!flusheol(workbuf, fp, dbto)) +#ifdef LOG + syslog(LOG_NOTICE, + "timeout waiting for input from %s during message collect", + CurHostName ? CurHostName : ""); +#endif + errno = 0; + usrerr("451 timeout waiting for input during message collect"); goto readerr; + } + CollectTimeout = setevent(dbto, collecttimeout, dbto); + } - /* it's okay to toss '\n' now (flusheol() needed it) */ - fixcrlf(workbuf, TRUE); - - curbuf = workbuf; - curbuflen = strlen(curbuf); - curbuffree = MAXLINE - curbuflen; - p = curbuf + curbuflen; - - /* get the rest of this field */ + for (;;) + { + if (tTd(30, 35)) + printf("top, istate=%d, mstate=%d\n", istate, mstate); for (;;) { - int clen; - - if (sfgets(freebuf, MAXLINE, fp, dbto, - "message header read") == NULL) + if (pbp > peekbuf) + c = *--pbp; + else { - freebuf[0] = '\0'; - break; + while (!feof(InChannel) && !ferror(InChannel)) + { + errno = 0; + c = fgetc(InChannel); + if (errno != EINTR) + break; + clearerr(InChannel); + } + CollectProgress = TRUE; + if (TrafficLogFile != NULL) + { + if (istate == IS_BOL) + fprintf(TrafficLogFile, "%05d <<< ", + getpid()); + if (c == EOF) + fprintf(TrafficLogFile, "[EOF]\n"); + else + fputc(c, TrafficLogFile); + } + if (c == EOF) + goto readerr; + if (SevenBitInput) + c &= 0x7f; + else + HasEightBits |= bitset(0x80, c); + e->e_msgsize++; } + 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; - /* is this a continuation line? */ - if (*freebuf != ' ' && *freebuf != '\t') + 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; - if (!flusheol(freebuf, fp, dbto)) - goto readerr; + case IS_DOTCR: + if (c == '\n') + goto readerr; + else + { + /* push back the ".\rx" */ + *pbp++ = c; + *pbp++ = '\r'; + c = '.'; + } + break; - fixcrlf(freebuf, TRUE); - clen = strlen(freebuf) + 1; + case IS_CR: + if (c != '\n') + { + ungetc(c, InChannel); + c = '\r'; + } + else if (!bitset(EF_CRLF_NOT_EOL, e->e_flags)) + istate = IS_BOL; + break; + } - /* if insufficient room, dynamically allocate buffer */ - if (clen >= curbuffree) + if (c == '\r') { - /* reallocate buffer */ - int nbuflen = ((p - curbuf) + clen) * 2; - char *nbuf = xalloc(nbuflen); - - p = nbuf + curbuflen; - curbuffree = nbuflen - curbuflen; - bcopy(curbuf, nbuf, curbuflen); - if (curbuf != buf && curbuf != buf2) - free(curbuf); - curbuf = nbuf; + istate = IS_CR; + continue; } - *p++ = '\n'; - bcopy(freebuf, p, clen - 1); - p += clen - 1; - curbuffree -= clen; - curbuflen += clen; - } - *p++ = '\0'; - - e->e_msgsize += curbuflen; - - /* - ** The working buffer now becomes the free buffer, since - ** the free buffer contains a new header field. - ** - ** This is premature, since we still havent called - ** chompheader() to process the field we just created - ** (so the call to chompheader() will use `freebuf'). - ** This convolution is necessary so that if we break out - ** of the loop due to H_EOH, `workbuf' will always be - ** the next unprocessed buffer. - */ + else if (c == '\n' && !bitset(EF_NL_NOT_EOL, e->e_flags)) + istate = IS_BOL; + else + istate = IS_NORM; - { - register char *tmp = workbuf; - workbuf = freebuf; - freebuf = tmp; - } - - /* - ** Snarf header away. - */ - - if (bitset(H_EOH, chompheader(curbuf, FALSE, e))) - break; - - /* - ** If the buffer was dynamically allocated, free it. - */ - - if (curbuf != buf && curbuf != buf2) - free(curbuf); - } - - if (tTd(30, 1)) - printf("EOH\n"); - - if (headeronly) - { - if (*workbuf != '\0') - syserr("collect: lost first line of message"); - goto readerr; - } + if (mstate == MS_BODY) + { + /* just put the character out */ + fputc(c, tf); + continue; + } - if (*workbuf == '\0') - { - /* throw away a blank line */ - if (sfgets(buf, MAXLINE, fp, dbto, - "message separator read") == NULL) - goto readerr; - } - else if (workbuf == buf2) /* guarantee `buf' contains data */ - (void) strcpy(buf, buf2); + /* 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); + } + *bp++ = c; + if (istate == IS_BOL) + break; + } + *bp = '\0'; - /* - ** Collect the body of the message. - */ +nextstate: + if (tTd(30, 35)) + printf("nextstate, istate=%d, mstate=%d, line = \"%s\"\n", + istate, mstate, buf); + switch (mstate) + { + case MS_UFROM: + mstate = MS_HEADER; + if (strncmp(buf, "From ", 5) == 0) + { + eatfrom(buf, e); + continue; + } + /* fall through */ - for (;;) - { - register char *bp = buf; + case MS_HEADER: + if (!isheader(buf)) + { + mstate = MS_BODY; + goto nextstate; + } - fixcrlf(buf, TRUE); + /* check for possible continuation line */ + do + { + clearerr(InChannel); + errno = 0; + c = fgetc(InChannel); + } while (errno == EINTR); + if (c != EOF) + ungetc(c, InChannel); + if (c == ' ' || c == '\t') + { + /* yep -- defer this */ + continue; + } - /* check for end-of-message */ - if (!ignrdot && buf[0] == '.' && (buf[1] == '\n' || buf[1] == '\0')) + /* trim off trailing CRLF or NL */ + if (*--bp != '\n' || *--bp != '\r') + bp++; + *bp = '\0'; + if (bitset(H_EOH, chompheader(buf, FALSE, e))) + mstate = MS_BODY; break; - /* check for transparent dot */ - if ((OpMode == MD_SMTP || OpMode == MD_DAEMON) && - bp[0] == '.' && bp[1] == '.') - bp++; + case MS_BODY: + if (tTd(30, 1)) + printf("EOH\n"); + if (headeronly) + goto readerr; + bp = buf; - /* - ** Figure message length, output the line to the temp - ** file, and insert a newline if missing. - */ + /* 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; + } - e->e_msgsize += strlen(bp) + 1; - fputs(bp, tf); - fputs("\n", tf); - if (ferror(tf)) - tferror(tf, e); - if (sfgets(buf, MAXLINE, fp, dbto, "message body read") == NULL) - goto readerr; + /* if not a blank separator, write it out */ + while (*bp != '\0') + fputc(*bp++, tf); + break; + } + bp = buf; } readerr: @@ -283,7 +360,7 @@ readerr: } /* reset global timer */ - sfgetset((time_t) 0); + clrevent(CollectTimeout); if (headeronly) return; @@ -418,40 +495,19 @@ readerr: finis(); } } - /* -** FLUSHEOL -- if not at EOL, throw away rest of input line. -** -** Parameters: -** buf -- last line read in (checked for '\n'), -** fp -- file to be read from. -** -** Returns: -** FALSE on error from sfgets(), TRUE otherwise. -** -** Side Effects: -** none. -*/ -bool -flusheol(buf, fp, dbto) - char *buf; - FILE *fp; - time_t dbto; -{ - register char *p = buf; - char junkbuf[MAXLINE]; - while (strchr(p, '\n') == NULL) - { - CollectErrorMessage = "553 header line too long"; - CollectErrno = 0; - if (sfgets(junkbuf, MAXLINE, fp, dbto, - "long line flush") == NULL) - return (FALSE); - p = junkbuf; - } +static +collecttimeout(timeout) + time_t timeout; +{ + /* if no progress was made, die now */ + if (!CollectProgress) + longjmp(CtxCollectTimeout, 1); - return (TRUE); + /* otherwise reset the timeout */ + CollectTimeout = setevent(timeout, collecttimeout, timeout); + CollectProgress = FALSE; } /* ** TFERROR -- signal error on writing the temporary file. diff --git a/usr/src/usr.sbin/sendmail/src/deliver.c b/usr/src/usr.sbin/sendmail/src/deliver.c index ac55424f11..4e5e026b37 100644 --- a/usr/src/usr.sbin/sendmail/src/deliver.c +++ b/usr/src/usr.sbin/sendmail/src/deliver.c @@ -7,7 +7,7 @@ */ #ifndef lint -static char sccsid[] = "@(#)deliver.c 8.89 (Berkeley) %G%"; +static char sccsid[] = "@(#)deliver.c 8.90 (Berkeley) %G%"; #endif /* not lint */ #include "sendmail.h" @@ -2048,6 +2048,11 @@ putfromline(mci, e) ** The message is written onto fp. */ +/* values for output state variable */ +#define OS_HEAD 0 /* at beginning of line */ +#define OS_CR 1 /* read a carriage return */ +#define OS_INLINE 2 /* putting rest of line */ + putbody(mci, e, separator) register MCI *mci; register ENVELOPE *e; @@ -2076,34 +2081,74 @@ putbody(mci, e, separator) mci->mci_flags &= ~MCIF_INHEADER; } putline("<<< No Message Collected >>>", mci); + goto endofmessage; } } - if (e->e_dfp != NULL) + rewind(e->e_dfp); + + if (bitset(MCIF_CVT8TO7, mci->mci_flags)) + { + /* do 8 to 7 bit MIME conversion */ + if (hvalue("MIME-Version", e->e_header) == NULL) + putline("MIME-Version: 1.0", mci); + mime8to7(mci, e->e_header, e, NULL); + } + else { - rewind(e->e_dfp); + int ostate; + register char *bp; + register char *pbp; + register int c; + int padc; + char *buflim; + int pos; + char peekbuf[10]; - if (bitset(MCIF_CVT8TO7, mci->mci_flags)) + /* we can pass it through unmodified */ + if (bitset(MCIF_INHEADER, mci->mci_flags)) { - /* do 8 to 7 bit MIME conversion */ - if (hvalue("MIME-Version", e->e_header) == NULL) - putline("MIME-Version: 1.0", mci); - mime8to7(mci, e->e_header, e, NULL); + putline("", mci); + mci->mci_flags &= ~MCIF_INHEADER; } - else + + /* determine end of buffer; allow for short mailer lines */ + buflim = &buf[sizeof buf - 1]; + if (mci->mci_mailer->m_linelimit < sizeof buf - 1) + buflim = &buf[mci->mci_mailer->m_linelimit - 1]; + + /* copy temp file to output with mapping */ + ostate = OS_HEAD; + bp = buf; + pbp = peekbuf; + while (!ferror(mci->mci_out)) { - /* we can pass it through unmodified */ - if (bitset(MCIF_INHEADER, mci->mci_flags)) - { - putline("", mci); - mci->mci_flags &= ~MCIF_INHEADER; - } - while (!ferror(mci->mci_out) && - fgets(buf, sizeof buf, e->e_dfp) != NULL) + register char *xp; + + if (pbp > peekbuf) + c = *--pbp; + else if ((c = fgetc(e->e_dfp)) == EOF) + break; + if (bitset(MCIF_7BIT, mci->mci_flags)) + c &= 0x7f; + switch (ostate) { + case OS_HEAD: + if (c != '\r' && c != '\n' && bp < buflim) + { + *bp++ = c; + break; + } + + /* check beginning of line for special cases */ + *bp = '\0'; + pos = 0; + padc = EOF; if (buf[0] == 'F' && bitnset(M_ESCFROM, mci->mci_mailer->m_flags) && strncmp(buf, "From ", 5) == 0) - (void) putc('>', mci->mci_out); + { + padc = '>'; + } if (buf[0] == '-' && buf[1] == '-' && separator != NULL) { @@ -2111,19 +2156,113 @@ putbody(mci, e, separator) int sl = strlen(separator); if (strncmp(&buf[2], separator, sl) == 0) - (void) putc(' ', mci->mci_out); + padc = ' '; + } + if (buf[0] == '.' && + bitnset(M_XDOT, mci->mci_mailer->m_flags)) + { + padc = '.'; + } + + /* now copy out saved line */ + if (TrafficLogFile != NULL) + { + fprintf(TrafficLogFile, "%05d >>> ", getpid()); + if (padc != EOF) + fputc(padc, TrafficLogFile); + for (xp = buf; xp < bp; xp++) + fputc(*xp, TrafficLogFile); + if (c == '\n') + fputs(mci->mci_mailer->m_eol, + TrafficLogFile); + } + if (padc != EOF) + { + fputc(padc, mci->mci_out); + pos++; + } + for (xp = buf; xp < bp; xp++) + fputc(*xp, mci->mci_out); + if (c == '\n') + { + fputs(mci->mci_mailer->m_eol, + mci->mci_out); + pos = 0; + } + else + { + pos += bp - buf; + *pbp++ = c; } - putline(buf, mci); + bp = buf; + + /* determine next state */ + if (c == '\n') + ostate = OS_HEAD; + else if (c == '\r') + ostate = OS_CR; + else + ostate = OS_INLINE; + continue; + + case OS_CR: + if (c == '\n') + { + /* got CRLF */ + fputs(mci->mci_mailer->m_eol, mci->mci_out); + if (TrafficLogFile != NULL) + { + fputs(mci->mci_mailer->m_eol, + TrafficLogFile); + } + ostate = OS_HEAD; + continue; + } + + /* had a naked carriage return */ + *pbp++ = c; + c = '\r'; + goto putchar; + + case OS_INLINE: + if (c == '\r') + { + ostate = OS_CR; + continue; + } +putchar: + if (pos > mci->mci_mailer->m_linelimit && + c != '\n') + { + putc('!', mci->mci_out); + fputs(mci->mci_mailer->m_eol, mci->mci_out); + if (TrafficLogFile != NULL) + { + fprintf(TrafficLogFile, "!%s", + mci->mci_mailer->m_eol); + } + ostate = OS_HEAD; + *pbp++ = c; + continue; + } + if (TrafficLogFile != NULL) + fputc(c, TrafficLogFile); + putc(c, mci->mci_out); + pos++; + if (c == '\n') + ostate = OS_HEAD; + break; } } + } - if (ferror(e->e_dfp)) - { - syserr("putbody: %s: read error", e->e_df); - ExitStat = EX_IOERR; - } + if (ferror(e->e_dfp)) + { + syserr("putbody: %s: read error", e->e_df); + ExitStat = EX_IOERR; } +endofmessage: /* some mailers want extra blank line at end of message */ if (bitnset(M_BLANKEND, mci->mci_mailer->m_flags) && buf[0] != '\0' && buf[0] != '\n') diff --git a/usr/src/usr.sbin/sendmail/src/util.c b/usr/src/usr.sbin/sendmail/src/util.c index ccb0c7994d..2c984146c7 100644 --- a/usr/src/usr.sbin/sendmail/src/util.c +++ b/usr/src/usr.sbin/sendmail/src/util.c @@ -7,7 +7,7 @@ */ #ifndef lint -static char sccsid[] = "@(#)util.c 8.42 (Berkeley) %G%"; +static char sccsid[] = "@(#)util.c 8.43 (Berkeley) %G%"; #endif /* not lint */ # include "sendmail.h" @@ -871,9 +871,6 @@ xfclose(fp, a, b) static jmp_buf CtxReadTimeout; static int readtimeout(); -static EVENT *GlobalTimeout = NULL; -static bool EnableTimeout = FALSE; -static int ReadProgress; char * sfgets(buf, siz, fp, timeout, during) @@ -911,10 +908,7 @@ sfgets(buf, siz, fp, timeout, during) #endif return (NULL); } - if (GlobalTimeout == NULL) - ev = setevent(timeout, readtimeout, 0); - else - EnableTimeout = TRUE; + ev = setevent(timeout, readtimeout, 0); } /* try to read */ @@ -929,10 +923,7 @@ sfgets(buf, siz, fp, timeout, during) } /* clear the event if it has not sprung */ - if (GlobalTimeout == NULL) - clrevent(ev); - else - EnableTimeout = FALSE; + clrevent(ev); /* clean up the books and exit */ LineNumber++; @@ -964,44 +955,11 @@ sfgets(buf, siz, fp, timeout, during) return (buf); } -void -sfgetset(timeout) - time_t timeout; -{ - /* cancel pending timer */ - if (GlobalTimeout != NULL) - { - clrevent(GlobalTimeout); - GlobalTimeout = NULL; - } - - /* schedule fresh one if so requested */ - if (timeout != 0) - { - ReadProgress = LineNumber; - GlobalTimeout = setevent(timeout, readtimeout, timeout); - } -} - static readtimeout(timeout) time_t timeout; { - /* terminate if ordinary timeout */ - if (GlobalTimeout == NULL) - longjmp(CtxReadTimeout, 1); - - /* terminate if no progress was made -- reset state */ - if (EnableTimeout && (LineNumber <= ReadProgress)) - { - EnableTimeout = FALSE; - GlobalTimeout = NULL; - longjmp(CtxReadTimeout, 2); - } - - /* schedule a new timeout */ - GlobalTimeout = NULL; - sfgetset(timeout); + longjmp(CtxReadTimeout, 1); } /* ** FGETFOLDED -- like fgets, but know about folded lines.