source: trunk/third/sendmail/src/collect.c @ 12554

Revision 12554, 16.3 KB checked in by danw, 26 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r12553, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2 * Copyright (c) 1998 Sendmail, Inc.  All rights reserved.
3 * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
4 * Copyright (c) 1988, 1993
5 *      The Regents of the University of California.  All rights reserved.
6 *
7 * By using this file, you agree to the terms and conditions set
8 * forth in the LICENSE file which can be found at the top level of
9 * the sendmail distribution.
10 *
11 */
12
13#ifndef lint
14static char sccsid[] = "@(#)collect.c   8.93 (Berkeley) 1/26/1999";
15#endif /* not lint */
16
17# include <errno.h>
18# include "sendmail.h"
19
20/*
21**  COLLECT -- read & parse message header & make temp file.
22**
23**      Creates a temporary file name and copies the standard
24**      input to that file.  Leading UNIX-style "From" lines are
25**      stripped off (after important information is extracted).
26**
27**      Parameters:
28**              fp -- file to read.
29**              smtpmode -- if set, we are running SMTP: give an RFC821
30**                      style message to say we are ready to collect
31**                      input, and never ignore a single dot to mean
32**                      end of message.
33**              hdrp -- the location to stash the header.
34**              e -- the current envelope.
35**
36**      Returns:
37**              none.
38**
39**      Side Effects:
40**              Temp file is created and filled.
41**              The from person may be set.
42*/
43
44static jmp_buf  CtxCollectTimeout;
45static void     collecttimeout __P((time_t));
46static bool     CollectProgress;
47static EVENT    *CollectTimeout;
48
49/* values for input state machine */
50#define IS_NORM         0       /* middle of line */
51#define IS_BOL          1       /* beginning of line */
52#define IS_DOT          2       /* read a dot at beginning of line */
53#define IS_DOTCR        3       /* read ".\r" at beginning of line */
54#define IS_CR           4       /* read a carriage return */
55
56/* values for message state machine */
57#define MS_UFROM        0       /* reading Unix from line */
58#define MS_HEADER       1       /* reading message header */
59#define MS_BODY         2       /* reading message body */
60#define MS_DISCARD      3       /* discarding rest of message */
61
62void
63collect(fp, smtpmode, hdrp, e)
64        FILE *fp;
65        bool smtpmode;
66        HDR **hdrp;
67        register ENVELOPE *e;
68{
69        register FILE *volatile tf;
70        volatile bool ignrdot = smtpmode ? FALSE : IgnrDot;
71        volatile time_t dbto = smtpmode ? TimeOuts.to_datablock : 0;
72        register char *volatile bp;
73        volatile int c = EOF;
74        volatile bool inputerr = FALSE;
75        bool headeronly;
76        char *volatile buf;
77        volatile int buflen;
78        volatile int istate;
79        volatile int mstate;
80        u_char *volatile pbp;
81        int hdrslen = 0;
82        u_char peekbuf[8];
83        char dfname[MAXQFNAME];
84        char bufbuf[MAXLINE];
85        extern bool isheader __P((char *));
86        extern void tferror __P((FILE *volatile, ENVELOPE *));
87
88        headeronly = hdrp != NULL;
89
90        /*
91        **  Create the temp file name and create the file.
92        */
93
94        if (!headeronly)
95        {
96                int tfd;
97                struct stat stbuf;
98
99                strcpy(dfname, queuename(e, 'd'));
100                tfd = dfopen(dfname, O_WRONLY|O_CREAT|O_TRUNC, FileMode, SFF_ANYFILE);
101                if (tfd < 0 || (tf = fdopen(tfd, "w")) == NULL)
102                {
103                        syserr("Cannot create %s", dfname);
104                        e->e_flags |= EF_NO_BODY_RETN;
105                        finis(TRUE, ExitStat);
106                }
107                if (fstat(fileno(tf), &stbuf) < 0)
108                        e->e_dfino = -1;
109                else
110                {
111                        e->e_dfdev = stbuf.st_dev;
112                        e->e_dfino = stbuf.st_ino;
113                }
114                HasEightBits = FALSE;
115                e->e_msgsize = 0;
116                e->e_flags |= EF_HAS_DF;
117        }
118
119        /*
120        **  Tell ARPANET to go ahead.
121        */
122
123        if (smtpmode)
124                message("354 Enter mail, end with \".\" on a line by itself");
125
126        if (tTd(30, 2))
127                printf("collect\n");
128
129        /*
130        **  Read the message.
131        **
132        **      This is done using two interleaved state machines.
133        **      The input state machine is looking for things like
134        **      hidden dots; the message state machine is handling
135        **      the larger picture (e.g., header versus body).
136        */
137
138        buf = bp = bufbuf;
139        buflen = sizeof bufbuf;
140        pbp = peekbuf;
141        istate = IS_BOL;
142        mstate = SaveFrom ? MS_HEADER : MS_UFROM;
143        CollectProgress = FALSE;
144
145        if (dbto != 0)
146        {
147                /* handle possible input timeout */
148                if (setjmp(CtxCollectTimeout) != 0)
149                {
150                        if (LogLevel > 2)
151                                sm_syslog(LOG_NOTICE, e->e_id,
152                                    "timeout waiting for input from %s during message collect",
153                                    CurHostName ? CurHostName : "<local machine>");
154                        errno = 0;
155                        usrerr("451 timeout waiting for input during message collect");
156                        goto readerr;
157                }
158                CollectTimeout = setevent(dbto, collecttimeout, dbto);
159        }
160
161        for (;;)
162        {
163                if (tTd(30, 35))
164                        printf("top, istate=%d, mstate=%d\n", istate, mstate);
165                for (;;)
166                {
167                        if (pbp > peekbuf)
168                                c = *--pbp;
169                        else
170                        {
171                                while (!feof(fp) && !ferror(fp))
172                                {
173                                        errno = 0;
174                                        c = getc(fp);
175                                        if (errno != EINTR)
176                                                break;
177                                        clearerr(fp);
178                                }
179                                CollectProgress = TRUE;
180                                if (TrafficLogFile != NULL && !headeronly)
181                                {
182                                        if (istate == IS_BOL)
183                                                fprintf(TrafficLogFile, "%05d <<< ",
184                                                        (int) getpid());
185                                        if (c == EOF)
186                                                fprintf(TrafficLogFile, "[EOF]\n");
187                                        else
188                                                putc(c, TrafficLogFile);
189                                }
190                                if (c == EOF)
191                                        goto readerr;
192                                if (SevenBitInput)
193                                        c &= 0x7f;
194                                else
195                                        HasEightBits |= bitset(0x80, c);
196                        }
197                        if (tTd(30, 94))
198                                printf("istate=%d, c=%c (0x%x)\n",
199                                        istate, c, c);
200                        switch (istate)
201                        {
202                          case IS_BOL:
203                                if (c == '.')
204                                {
205                                        istate = IS_DOT;
206                                        continue;
207                                }
208                                break;
209
210                          case IS_DOT:
211                                if (c == '\n' && !ignrdot &&
212                                    !bitset(EF_NL_NOT_EOL, e->e_flags))
213                                        goto readerr;
214                                else if (c == '\r' &&
215                                         !bitset(EF_CRLF_NOT_EOL, e->e_flags))
216                                {
217                                        istate = IS_DOTCR;
218                                        continue;
219                                }
220                                else if (c != '.' ||
221                                         (OpMode != MD_SMTP &&
222                                          OpMode != MD_DAEMON &&
223                                          OpMode != MD_ARPAFTP))
224                                {
225                                        *pbp++ = c;
226                                        c = '.';
227                                }
228                                break;
229
230                          case IS_DOTCR:
231                                if (c == '\n' && !ignrdot)
232                                        goto readerr;
233                                else
234                                {
235                                        /* push back the ".\rx" */
236                                        *pbp++ = c;
237                                        *pbp++ = '\r';
238                                        c = '.';
239                                }
240                                break;
241
242                          case IS_CR:
243                                if (c == '\n')
244                                        istate = IS_BOL;
245                                else
246                                {
247                                        ungetc(c, fp);
248                                        c = '\r';
249                                        istate = IS_NORM;
250                                }
251                                goto bufferchar;
252                        }
253
254                        if (c == '\r' && !bitset(EF_CRLF_NOT_EOL, e->e_flags))
255                        {
256                                istate = IS_CR;
257                                continue;
258                        }
259                        else if (c == '\n' && !bitset(EF_NL_NOT_EOL, e->e_flags))
260                                istate = IS_BOL;
261                        else
262                                istate = IS_NORM;
263
264bufferchar:
265                        if (!headeronly)
266                                e->e_msgsize++;
267                        switch (mstate)
268                        {
269                          case MS_BODY:
270                                /* just put the character out */
271                                if (MaxMessageSize <= 0 ||
272                                    e->e_msgsize <= MaxMessageSize)
273                                        putc(c, tf);
274
275                                /* fall through */
276
277                          case MS_DISCARD:
278                                continue;
279                        }
280
281                        /* header -- buffer up */
282                        if (bp >= &buf[buflen - 2])
283                        {
284                                char *obuf;
285
286                                if (mstate != MS_HEADER)
287                                        break;
288
289                                /* out of space for header */
290                                obuf = buf;
291                                if (buflen < MEMCHUNKSIZE)
292                                        buflen *= 2;
293                                else
294                                        buflen += MEMCHUNKSIZE;
295                                buf = xalloc(buflen);
296                                bcopy(obuf, buf, bp - obuf);
297                                bp = &buf[bp - obuf];
298                                if (obuf != bufbuf)
299                                        free(obuf);
300                        }
301                        if (c >= 0200 && c <= 0237)
302                        {
303#if 0   /* causes complaints -- figure out something for 8.9 */
304                                usrerr("Illegal character 0x%x in header", c);
305#endif
306                        }
307                        else if (c != '\0')
308                        {
309                                *bp++ = c;
310                                if (MaxHeadersLength > 0 &&
311                                    ++hdrslen > MaxHeadersLength)
312                                {
313                                        sm_syslog(LOG_NOTICE, e->e_id,
314                                                  "headers too large (%d max) from %s during message collect",
315                                                  MaxHeadersLength,
316                                                  CurHostName != NULL ? CurHostName : "localhost");
317                                        errno = 0;
318                                        e->e_flags |= EF_CLRQUEUE;
319                                        e->e_status = "5.6.0";
320                                        usrerr("552 Headers too large (%d max)",
321                                                MaxHeadersLength);
322                                        mstate = MS_DISCARD;
323                                }
324                        }
325                        if (istate == IS_BOL)
326                                break;
327                }
328                *bp = '\0';
329
330nextstate:
331                if (tTd(30, 35))
332                        printf("nextstate, istate=%d, mstate=%d, line = \"%s\"\n",
333                                istate, mstate, buf);
334                switch (mstate)
335                {
336                  case MS_UFROM:
337                        mstate = MS_HEADER;
338#ifndef NOTUNIX
339                        if (strncmp(buf, "From ", 5) == 0)
340                        {
341                                extern void eatfrom __P((char *volatile, ENVELOPE *));
342
343                                bp = buf;
344                                eatfrom(buf, e);
345                                continue;
346                        }
347#endif
348                        /* fall through */
349
350                  case MS_HEADER:
351                        if (!isheader(buf))
352                        {
353                                mstate = MS_BODY;
354                                goto nextstate;
355                        }
356
357                        /* check for possible continuation line */
358                        do
359                        {
360                                clearerr(fp);
361                                errno = 0;
362                                c = getc(fp);
363                        } while (errno == EINTR);
364                        if (c != EOF)
365                                ungetc(c, fp);
366                        if (c == ' ' || c == '\t')
367                        {
368                                /* yep -- defer this */
369                                continue;
370                        }
371
372                        /* trim off trailing CRLF or NL */
373                        if (*--bp != '\n' || *--bp != '\r')
374                                bp++;
375                        *bp = '\0';
376
377                        if (bitset(H_EOH, chompheader(buf, FALSE, hdrp, e)))
378                        {
379                                mstate = MS_BODY;
380                                goto nextstate;
381                        }
382                        break;
383
384                  case MS_BODY:
385                        if (tTd(30, 1))
386                                printf("EOH\n");
387                        if (headeronly)
388                                goto readerr;
389                        bp = buf;
390
391                        /* toss blank line */
392                        if ((!bitset(EF_CRLF_NOT_EOL, e->e_flags) &&
393                                bp[0] == '\r' && bp[1] == '\n') ||
394                            (!bitset(EF_NL_NOT_EOL, e->e_flags) &&
395                                bp[0] == '\n'))
396                        {
397                                break;
398                        }
399
400                        /* if not a blank separator, write it out */
401                        if (MaxMessageSize <= 0 ||
402                            e->e_msgsize <= MaxMessageSize)
403                        {
404                                while (*bp != '\0')
405                                        putc(*bp++, tf);
406                        }
407                        break;
408                }
409                bp = buf;
410        }
411
412readerr:
413        if ((feof(fp) && smtpmode) || ferror(fp))
414        {
415                const char *errmsg = errstring(errno);
416
417                if (tTd(30, 1))
418                        printf("collect: premature EOM: %s\n", errmsg);
419                if (LogLevel >= 2)
420                        sm_syslog(LOG_WARNING, e->e_id,
421                                "collect: premature EOM: %s", errmsg);
422                inputerr = TRUE;
423        }
424
425        /* reset global timer */
426        clrevent(CollectTimeout);
427
428        if (headeronly)
429                return;
430
431        if (tf != NULL &&
432            (fflush(tf) != 0 || ferror(tf) ||
433             (SuperSafe && fsync(fileno(tf)) < 0) ||
434             fclose(tf) < 0))
435        {
436                tferror(tf, e);
437                flush_errors(TRUE);
438                finis(TRUE, ExitStat);
439        }
440
441        /* An EOF when running SMTP is an error */
442        if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
443        {
444                char *host;
445                char *problem;
446
447                host = RealHostName;
448                if (host == NULL)
449                        host = "localhost";
450
451                if (feof(fp))
452                        problem = "unexpected close";
453                else if (ferror(fp))
454                        problem = "I/O error";
455                else
456                        problem = "read timeout";
457                if (LogLevel > 0 && feof(fp))
458                        sm_syslog(LOG_NOTICE, e->e_id,
459                            "collect: %s on connection from %.100s, sender=%s: %s",
460                            problem, host,
461                            shortenstring(e->e_from.q_paddr, MAXSHORTSTR),
462                            errstring(errno));
463                if (feof(fp))
464                        usrerr("451 collect: %s on connection from %s, from=%s",
465                                problem, host,
466                                shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
467                else
468                        syserr("451 collect: %s on connection from %s, from=%s",
469                                problem, host,
470                                shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
471
472                /* don't return an error indication */
473                e->e_to = NULL;
474                e->e_flags &= ~EF_FATALERRS;
475                e->e_flags |= EF_CLRQUEUE;
476
477                /* and don't try to deliver the partial message either */
478                if (InChild)
479                        ExitStat = EX_QUIT;
480                finis(TRUE, ExitStat);
481        }
482
483        /*
484        **  Find out some information from the headers.
485        **      Examples are who is the from person & the date.
486        */
487
488        eatheader(e, TRUE);
489
490        if (GrabTo && e->e_sendqueue == NULL)
491                usrerr("No recipient addresses found in header");
492
493        /* collect statistics */
494        if (OpMode != MD_VERIFY)
495                markstats(e, (ADDRESS *) NULL, FALSE);
496
497#if _FFR_DSN_RRT_OPTION
498        /*
499        **  If we have a Return-Receipt-To:, turn it into a DSN.
500        */
501
502        if (RrtImpliesDsn && hvalue("return-receipt-to", e->e_header) != NULL)
503        {
504                ADDRESS *q;
505
506                for (q = e->e_sendqueue; q != NULL; q = q->q_next)
507                        if (!bitset(QHASNOTIFY, q->q_flags))
508                                q->q_flags |= QHASNOTIFY|QPINGONSUCCESS;
509        }
510#endif
511
512        /*
513        **  Add an Apparently-To: line if we have no recipient lines.
514        */
515
516        if (hvalue("to", e->e_header) != NULL ||
517            hvalue("cc", e->e_header) != NULL ||
518            hvalue("apparently-to", e->e_header) != NULL)
519        {
520                /* have a valid recipient header -- delete Bcc: headers */
521                e->e_flags |= EF_DELETE_BCC;
522        }
523        else if (hvalue("bcc", e->e_header) == NULL)
524        {
525                /* no valid recipient headers */
526                register ADDRESS *q;
527                char *hdr = NULL;
528
529                /* create an Apparently-To: field */
530                /*    that or reject the message.... */
531                switch (NoRecipientAction)
532                {
533                  case NRA_ADD_APPARENTLY_TO:
534                        hdr = "Apparently-To";
535                        break;
536
537                  case NRA_ADD_TO:
538                        hdr = "To";
539                        break;
540
541                  case NRA_ADD_BCC:
542                        addheader("Bcc", " ", &e->e_header);
543                        break;
544
545                  case NRA_ADD_TO_UNDISCLOSED:
546                        addheader("To", "undisclosed-recipients:;", &e->e_header);
547                        break;
548                }
549
550                if (hdr != NULL)
551                {
552                        for (q = e->e_sendqueue; q != NULL; q = q->q_next)
553                        {
554                                if (q->q_alias != NULL)
555                                        continue;
556                                if (tTd(30, 3))
557                                        printf("Adding %s: %s\n",
558                                                hdr, q->q_paddr);
559                                addheader(hdr, q->q_paddr, &e->e_header);
560                        }
561                }
562        }
563
564        /* check for message too large */
565        if (MaxMessageSize > 0 && e->e_msgsize > MaxMessageSize)
566        {
567                e->e_flags |= EF_NO_BODY_RETN|EF_CLRQUEUE;
568                e->e_status = "5.2.3";
569                usrerr("552 Message exceeds maximum fixed size (%ld)",
570                        MaxMessageSize);
571                if (LogLevel > 6)
572                        sm_syslog(LOG_NOTICE, e->e_id,
573                                "message size (%ld) exceeds maximum (%ld)",
574                                e->e_msgsize, MaxMessageSize);
575        }
576
577        /* check for illegal 8-bit data */
578        if (HasEightBits)
579        {
580                e->e_flags |= EF_HAS8BIT;
581                if (!bitset(MM_PASS8BIT|MM_MIME8BIT, MimeMode) &&
582                    !bitset(EF_IS_MIME, e->e_flags))
583                {
584                        e->e_status = "5.6.1";
585                        usrerr("554 Eight bit data not allowed");
586                }
587        }
588        else
589        {
590                /* if it claimed to be 8 bits, well, it lied.... */
591                if (e->e_bodytype != NULL &&
592                    strcasecmp(e->e_bodytype, "8BITMIME") == 0)
593                        e->e_bodytype = "7BIT";
594        }
595
596        if ((e->e_dfp = fopen(dfname, "r")) == NULL)
597        {
598                /* we haven't acked receipt yet, so just chuck this */
599                syserr("Cannot reopen %s", dfname);
600                finis(TRUE, ExitStat);
601        }
602}
603
604
605static void
606collecttimeout(timeout)
607        time_t timeout;
608{
609        /* if no progress was made, die now */
610        if (!CollectProgress)
611                longjmp(CtxCollectTimeout, 1);
612
613        /* otherwise reset the timeout */
614        CollectTimeout = setevent(timeout, collecttimeout, timeout);
615        CollectProgress = FALSE;
616}
617/*
618**  TFERROR -- signal error on writing the temporary file.
619**
620**      Parameters:
621**              tf -- the file pointer for the temporary file.
622**              e -- the current envelope.
623**
624**      Returns:
625**              none.
626**
627**      Side Effects:
628**              Gives an error message.
629**              Arranges for following output to go elsewhere.
630*/
631
632void
633tferror(tf, e)
634        FILE *volatile tf;
635        register ENVELOPE *e;
636{
637        setstat(EX_IOERR);
638        if (errno == ENOSPC)
639        {
640#if STAT64 > 0
641                struct stat64 st;
642#else
643                struct stat st;
644#endif
645                long avail;
646                long bsize;
647                extern long freediskspace __P((char *, long *));
648
649                e->e_flags |= EF_NO_BODY_RETN;
650
651                if (
652#if STAT64 > 0
653                    fstat64(fileno(tf), &st)
654#else
655                    fstat(fileno(tf), &st)
656#endif
657                    < 0)
658                  st.st_size = 0;
659                (void) freopen(queuename(e, 'd'), "w", tf);
660                if (st.st_size <= 0)
661                        fprintf(tf, "\n*** Mail could not be accepted");
662                else if (sizeof st.st_size > sizeof (long))
663                        fprintf(tf, "\n*** Mail of at least %s bytes could not be accepted\n",
664                                quad_to_string(st.st_size));
665                else
666                        fprintf(tf, "\n*** Mail of at least %lu bytes could not be accepted\n",
667                                (unsigned long) st.st_size);
668                fprintf(tf, "*** at %s due to lack of disk space for temp file.\n",
669                        MyHostName);
670                avail = freediskspace(QueueDir, &bsize);
671                if (avail > 0)
672                {
673                        if (bsize > 1024)
674                                avail *= bsize / 1024;
675                        else if (bsize < 1024)
676                                avail /= 1024 / bsize;
677                        fprintf(tf, "*** Currently, %ld kilobytes are available for mail temp files.\n",
678                                avail);
679                }
680                e->e_status = "4.3.1";
681                usrerr("452 Out of disk space for temp file");
682        }
683        else
684                syserr("collect: Cannot write tf%s", e->e_id);
685        if (freopen("/dev/null", "w", tf) == NULL)
686                sm_syslog(LOG_ERR, e->e_id,
687                          "tferror: freopen(\"/dev/null\") failed: %s",
688                          errstring(errno));
689}
690/*
691**  EATFROM -- chew up a UNIX style from line and process
692**
693**      This does indeed make some assumptions about the format
694**      of UNIX messages.
695**
696**      Parameters:
697**              fm -- the from line.
698**
699**      Returns:
700**              none.
701**
702**      Side Effects:
703**              extracts what information it can from the header,
704**              such as the date.
705*/
706
707# ifndef NOTUNIX
708
709char    *DowList[] =
710{
711        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
712};
713
714char    *MonthList[] =
715{
716        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
717        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
718        NULL
719};
720
721void
722eatfrom(fm, e)
723        char *volatile fm;
724        register ENVELOPE *e;
725{
726        register char *p;
727        register char **dt;
728
729        if (tTd(30, 2))
730                printf("eatfrom(%s)\n", fm);
731
732        /* find the date part */
733        p = fm;
734        while (*p != '\0')
735        {
736                /* skip a word */
737                while (*p != '\0' && *p != ' ')
738                        p++;
739                while (*p == ' ')
740                        p++;
741                if (!(isascii(*p) && isupper(*p)) ||
742                    p[3] != ' ' || p[13] != ':' || p[16] != ':')
743                        continue;
744
745                /* we have a possible date */
746                for (dt = DowList; *dt != NULL; dt++)
747                        if (strncmp(*dt, p, 3) == 0)
748                                break;
749                if (*dt == NULL)
750                        continue;
751
752                for (dt = MonthList; *dt != NULL; dt++)
753                        if (strncmp(*dt, &p[4], 3) == 0)
754                                break;
755                if (*dt != NULL)
756                        break;
757        }
758
759        if (*p != '\0')
760        {
761                char *q;
762
763                /* we have found a date */
764                q = xalloc(25);
765                (void) strncpy(q, p, 25);
766                q[24] = '\0';
767                q = arpadate(q);
768                define('a', newstr(q), e);
769        }
770}
771
772# endif /* NOTUNIX */
Note: See TracBrowser for help on using the repository browser.