source: trunk/third/nmh/uip/slocal.c @ 14162

Revision 14162, 32.4 KB checked in by rbasch, 25 years ago (diff)
Handle error on close() of mailbox files, so we detect a quota exceeded failure.
Line 
1
2/*
3 * slocal.c -- asynchronously filter and deliver new mail
4 *
5 * $Id: slocal.c,v 1.3 2000-01-07 04:43:58 rbasch Exp $
6 */
7
8/*
9 *  Under sendmail, users should add the line
10 *
11 *      "| /usr/local/nmh/lib/slocal"
12 *
13 *  to their $HOME/.forward file.
14 *
15 *  Under MMDF-I, users should (symbolically) link
16 *  /usr/local/nmh/lib/slocal to $HOME/bin/rcvmail.
17 *
18 */
19
20#include <h/mh.h>
21#include <h/dropsbr.h>
22#include <h/rcvmail.h>
23#include <h/signals.h>
24#include <zotnet/tws/tws.h>
25#include <zotnet/mts/mts.h>
26
27#include <pwd.h>
28#include <signal.h>
29#include <sys/ioctl.h>
30#include <fcntl.h>
31
32#if defined (HAVE_DB_H) && !defined (HAVE_NDBM_H)
33#define DB_DBM_HSEARCH 1
34#include <db.h>
35#elif defined (HAVE_NDBM_H)
36#include <ndbm.h>
37#else
38#error Cannot find a suitable database header
39#endif
40
41#include <utmp.h>
42
43#ifndef UTMP_FILE
44# ifdef _PATH_UTMP
45#  define UTMP_FILE _PATH_UTMP
46# else
47#  define UTMP_FILE "/etc/utmp"
48# endif
49#endif
50
51static struct swit switches[] = {
52#define ADDRSW         0
53    { "addr address", 0 },
54#define USERSW         1
55    { "user name", 0 },
56#define FILESW         2
57    { "file file", 0 },
58#define SENDERSW       3
59    { "sender address", 0 },
60#define MAILBOXSW      4
61    { "mailbox file", 0 },
62#define HOMESW         5
63    { "home directory", -4 },
64#define INFOSW         6
65    { "info data", 0 },
66#define MAILSW         7
67    { "maildelivery file", 0 },
68#define VERBSW         8
69    { "verbose", 0 },
70#define NVERBSW        9
71    { "noverbose", 0 },
72#define SUPPRESSDUP   10
73    { "suppressdup", 0 },
74#define NSUPPRESSDUP 11
75    { "nosuppressdup", 0 },
76#define DEBUGSW       12
77    { "debug", 0 },
78#define VERSIONSW     13
79    { "version", 0 },
80#define HELPSW        14
81    { "help", 4 },
82    { NULL, 0 }
83};
84
85static int globbed = 0;         /* have we built "vars" table yet?        */
86static int parsed = 0;          /* have we built header field table yet   */
87static int utmped = 0;          /* have we scanned umtp(x) file yet       */
88static int suppressdup = 0;     /* are we suppressing duplicate messages? */
89
90static int verbose = 0;
91static int debug = 0;
92
93static char *addr = NULL;
94static char *user = NULL;
95static char *info = NULL;
96static char *file = NULL;
97static char *sender = NULL;
98static char *envelope = NULL;   /* envelope information ("From " line)  */
99static char *mbox = NULL;
100static char *home = NULL;
101
102static struct passwd *pw;       /* passwd file entry */
103
104static char ddate[BUFSIZ];      /* record the delivery date */
105struct tws *now;
106
107static jmp_buf myctx;
108
109/* flags for pair->p_flags */
110#define P_NIL  0x00
111#define P_ADR  0x01     /* field is address     */
112#define P_HID  0x02     /* special (fake) field */
113#define P_CHK  0x04
114
115struct pair {
116    char *p_name;
117    char *p_value;
118    char  p_flags;
119};
120
121#define NVEC 100
122
123/*
124 * Lookup table for matching fields and patterns
125 * in messages.  The rest of the table is added
126 * when the message is parsed.
127 */
128static struct pair hdrs[NVEC + 1] = {
129    { "source",          NULL, P_HID },
130    { "addr",            NULL, P_HID },
131    { "Return-Path",     NULL, P_ADR },
132    { "Reply-To",        NULL, P_ADR },
133    { "From",            NULL, P_ADR },
134    { "Sender",          NULL, P_ADR },
135    { "To",              NULL, P_ADR },
136    { "cc",              NULL, P_ADR },
137    { "Resent-Reply-To", NULL, P_ADR },
138    { "Resent-From",     NULL, P_ADR },
139    { "Resent-Sender",   NULL, P_ADR },
140    { "Resent-To",       NULL, P_ADR },
141    { "Resent-cc",       NULL, P_ADR },
142    { NULL, NULL, 0 }
143};
144
145/*
146 * The list of builtin variables to expand in a string
147 * before it is executed by the "pipe" or "qpipe" action.
148 */
149static struct pair vars[] = {
150    { "sender",   NULL, P_NIL },
151    { "address",  NULL, P_NIL },
152    { "size",     NULL, P_NIL },
153    { "reply-to", NULL, P_CHK },
154    { "info",     NULL, P_NIL },
155    { NULL, NULL, 0 }
156};
157
158extern char **environ;
159
160/*
161 * static prototypes
162 */
163static int localmail (int, char *);
164static int usr_delivery (int, char *, int);
165static int split (char *, char **);
166static int parse (int);
167static void expand (char *, char *, int);
168static void glob (int);
169static struct pair *lookup (struct pair *, char *);
170static int logged_in (void);
171static int timely (char *, char *);
172static int usr_file (int, char *, int);
173static int usr_pipe (int, char *, char *, char **, int);
174static int usr_folder (int, char *);
175static RETSIGTYPE alrmser (int);
176static void get_sender (char *, char **);
177static int copy_message (int, char *, int);
178static void verbose_printf (char *fmt, ...);
179static void adorn (char *, char *, ...);
180static void debug_printf (char *fmt, ...);
181static int suppress_duplicates (int, char *);
182static char *trim (char *);
183
184
185int
186main (int argc, char **argv)
187{
188    int fd, status;
189    FILE *fp = stdin;
190    char *cp, *mdlvr = NULL, buf[BUFSIZ];
191    char mailbox[BUFSIZ], tmpfil[BUFSIZ];
192    char **argp, **arguments;
193
194#ifdef LOCALE
195    setlocale(LC_ALL, "");
196#endif
197    invo_name = r1bindex (*argv, '/');
198
199    /* foil search of user profile/context */
200    if (context_foil (NULL) == -1)
201        done (1);
202
203    mts_init (invo_name);
204    arguments = getarguments (invo_name, argc, argv, 0);
205    argp = arguments;
206
207    /* Parse arguments */
208    while ((cp = *argp++)) {
209        if (*cp == '-') {
210            switch (smatch (++cp, switches)) {
211                case AMBIGSW:
212                    ambigsw (cp, switches);
213                    done (1);
214                case UNKWNSW:
215                    adios (NULL, "-%s unknown", cp);
216
217                case HELPSW:
218                    snprintf (buf, sizeof(buf),
219                        "%s [switches] [address info sender]", invo_name);
220                    print_help (buf, switches, 0);
221                    done (1);
222                case VERSIONSW:
223                    print_version(invo_name);
224                    done (1);
225
226                case ADDRSW:
227                    if (!(addr = *argp++))/* allow -xyz arguments */
228                        adios (NULL, "missing argument to %s", argp[-2]);
229                    continue;
230                case INFOSW:
231                    if (!(info = *argp++))/* allow -xyz arguments */
232                        adios (NULL, "missing argument to %s", argp[-2]);
233                    continue;
234                case USERSW:
235                    if (!(user = *argp++))/* allow -xyz arguments */
236                        adios (NULL, "missing argument to %s", argp[-2]);
237                    continue;
238                case FILESW:
239                    if (!(file = *argp++) || *file == '-')
240                        adios (NULL, "missing argument to %s", argp[-2]);
241                    continue;
242                case SENDERSW:
243                    if (!(sender = *argp++))/* allow -xyz arguments */
244                        adios (NULL, "missing argument to %s", argp[-2]);
245                    continue;
246                case MAILBOXSW:
247                    if (!(mbox = *argp++) || *mbox == '-')
248                        adios (NULL, "missing argument to %s", argp[-2]);
249                    continue;
250                case HOMESW:
251                    if (!(home = *argp++) || *home == '-')
252                        adios (NULL, "missing argument to %s", argp[-2]);
253                    continue;
254
255                case MAILSW:
256                    if (!(cp = *argp++) || *cp == '-')
257                        adios (NULL, "missing argument to %s", argp[-2]);
258                    if (mdlvr)
259                        adios (NULL, "only one maildelivery file at a time!");
260                    mdlvr = cp;
261                    continue;
262
263                case VERBSW:
264                    verbose++;
265                    continue;
266                case NVERBSW:
267                    verbose = 0;
268                    continue;
269
270                case SUPPRESSDUP:
271                    suppressdup++;
272                    continue;
273                case NSUPPRESSDUP:
274                    suppressdup = 0;
275                    continue;
276                case DEBUGSW:
277                    debug++;
278                    continue;
279            }
280        }
281
282        switch (argp - (argv + 1)) {
283            case 1:
284                addr = cp;
285                break;
286
287            case 2:
288                info = cp;
289                break;
290
291            case 3:
292                sender = cp;
293                break;
294        }
295    }
296
297    if (addr == NULL)
298        addr = getusername ();
299    if (user == NULL)
300        user = (cp = strchr(addr, '.')) ? ++cp : addr;
301    if ((pw = getpwnam (user)) == NULL)
302        adios (NULL, "no such local user as %s", user);
303
304    if (chdir (pw->pw_dir) == -1)
305        chdir ("/");
306    umask (0077);
307
308    if (geteuid() == 0) {
309        setgid (pw->pw_gid);
310        initgroups (pw->pw_name, pw->pw_gid);
311        setuid (pw->pw_uid);
312    }
313   
314    if (info == NULL)
315        info = "";
316
317    setbuf (stdin, NULL);
318
319    /* Record the delivery time */
320    if ((now = dlocaltimenow ()) == NULL)
321        adios (NULL, "unable to ascertain local time");
322    snprintf (ddate, sizeof(ddate), "Delivery-Date: %s\n", dtimenow (0));
323
324    /*
325     * Copy the message to a temporary file
326     */
327    if (file) {
328        int tempfd;
329
330        /* getting message from file */
331        if ((tempfd = open (file, O_RDONLY)) == -1)
332            adios (file, "unable to open");
333        if (debug)
334            debug_printf ("retrieving message from file \"%s\"\n", file);
335        if ((fd = copy_message (tempfd, tmpfil, 1)) == -1)
336            adios (NULL, "unable to create temporary file");
337        close (tempfd);
338    } else {
339        /* getting message from stdin */
340        if (debug)
341            debug_printf ("retrieving message from stdin\n");
342        if ((fd = copy_message (fileno (stdin), tmpfil, 1)) == -1)
343            adios (NULL, "unable to create temporary file");
344    }
345    if (debug)
346        debug_printf ("temporary file=\"%s\"\n", tmpfil);
347    else
348        unlink (tmpfil);
349
350    if (!(fp = fdopen (fd, "r+")))
351        adios (NULL, "unable to access temporary file");
352
353    /*
354     * If no sender given, extract it
355     * from envelope information.
356     */
357    if (sender == NULL)
358        get_sender (envelope, &sender);
359
360    if (mbox == NULL) {
361        snprintf (mailbox, sizeof(mailbox), "%s/%s",
362                mmdfldir[0] ? mmdfldir : pw->pw_dir,
363                mmdflfil[0] ? mmdflfil : pw->pw_name);
364        mbox = mailbox;
365    }
366    if (home == NULL)
367        home = pw->pw_dir;
368
369    if (debug) {
370        debug_printf ("addr=\"%s\"\n", trim(addr));
371        debug_printf ("user=\"%s\"\n", trim(user));
372        debug_printf ("info=\"%s\"\n", trim(info));
373        debug_printf ("sender=\"%s\"\n", trim(sender));
374        debug_printf ("envelope=\"%s\"\n", envelope ? trim(envelope) : "");
375        debug_printf ("mbox=\"%s\"\n", trim(mbox));
376        debug_printf ("home=\"%s\"\n", trim(home));
377        debug_printf ("ddate=\"%s\"\n", trim(ddate));
378        debug_printf ("now=%02d:%02d\n\n", now->tw_hour, now->tw_min);
379    }
380
381    /* deliver the message */
382    status = localmail (fd, mdlvr);
383
384    done (status != -1 ? RCV_MOK : RCV_MBX);
385}
386
387
388/*
389 * Main routine for delivering message.
390 */
391
392static int
393localmail (int fd, char *mdlvr)
394{
395    /* check if this message is a duplicate */
396    if (suppressdup &&
397        suppress_duplicates(fd, mdlvr ? mdlvr : ".maildelivery") == DONE)
398        return 0;
399
400    /* delivery according to personal Maildelivery file */
401    if (usr_delivery (fd, mdlvr ? mdlvr : ".maildelivery", 0) != -1)
402        return 0;
403
404    /* delivery according to global Maildelivery file */
405    if (usr_delivery (fd, maildelivery, 1) != -1)
406        return 0;
407
408    if (verbose)
409        verbose_printf ("(delivering to standard mail spool)\n");
410
411    /* last resort - deliver to standard mail spool */
412#ifdef SLOCAL_MBOX
413    return usr_file (fd, mbox, MBOX_FORMAT);
414#else
415    return usr_file (fd, mbox, MMDF_FORMAT);
416#endif
417}
418
419
420#define matches(a,b) (stringdex (b, a) >= 0)
421
422/*
423 * Parse the delivery file, and process incoming message.
424 */
425
426static int
427usr_delivery (int fd, char *delivery, int su)
428{
429    int i, accept, status, won, vecp, next;
430    char *field, *pattern, *action, *result, *string;
431    char buffer[BUFSIZ], tmpbuf[BUFSIZ];
432    char *cp, *vec[NVEC];
433    struct stat st;
434    struct pair *p;
435    FILE *fp;
436
437    /* open the delivery file */
438    if ((fp = fopen (delivery, "r")) == NULL)
439        return -1;
440
441    /* check if delivery file has bad ownership or permissions */
442    if (fstat (fileno (fp), &st) == -1
443            || (st.st_uid != 0 && (su || st.st_uid != pw->pw_uid))
444            || st.st_mode & (S_IWGRP|S_IWOTH)) {
445        if (verbose) {
446            verbose_printf ("WARNING: %s has bad ownership/modes (su=%d,uid=%d,owner=%d,mode=0%o)\n",
447                    delivery, su, (int) pw->pw_uid, (int) st.st_uid, (int) st.st_mode);
448        }
449        return -1;
450    }
451
452    won = 0;
453    next = 1;
454
455    /* read and process delivery file */
456    while (fgets (buffer, sizeof(buffer), fp)) {
457        /* skip comments and empty lines */
458        if (*buffer == '#' || *buffer == '\n')
459            continue;
460
461        /* zap trailing newline */
462        if ((cp = strchr(buffer, '\n')))
463            *cp = 0;
464
465        /* split buffer into fields */
466        vecp = split (buffer, vec);
467
468        /* check for too few fields */
469        if (vecp < 5) {
470            if (debug)
471                debug_printf ("WARNING: entry with only %d fields, skipping.\n", vecp);
472            continue;
473        }
474
475        if (debug) {
476            for (i = 0; vec[i]; i++)
477                debug_printf ("vec[%d]: \"%s\"\n", i, trim(vec[i]));
478        }
479
480        field   = vec[0];
481        pattern = vec[1];
482        action  = vec[2];
483        result  = vec[3];
484        string  = vec[4];
485
486        /* find out how to perform the action */
487        switch (result[0]) {
488            case 'N':
489            case 'n':
490                /*
491                 * If previous condition failed, don't
492                 * do this - else fall through
493                 */
494                if (!next)
495                    continue;   /* else fall */
496
497            case '?':
498                /*
499                 * If already delivered, skip this action.  Else
500                 * consider delivered if action is successful.
501                 */
502                if (won)
503                    continue;   /* else fall */
504
505            case 'A':
506            case 'a':
507                /*
508                 * Take action, and consider delivered if
509                 * action is successful.
510                 */
511                accept = 1;
512                break;
513
514            case 'R':
515            case 'r':
516            default:
517                /*
518                 * Take action, but don't consider delivered, even
519                 * if action is successful
520                 */
521                accept = 0;
522                break;
523        }
524
525        if (vecp > 5) {
526            if (!strcasecmp (vec[5], "select")) {
527                if (logged_in () != -1)
528                    continue;
529                if (vecp > 7 && timely (vec[6], vec[7]) == -1)
530                    continue;
531            }
532        }
533
534        /* check if the field matches */
535        switch (*field) {
536            case '*':
537            /* always matches */
538                break;
539
540            case 'd':
541            /*
542             * "default" matches only if the message hasn't
543             * been delivered yet.
544             */
545                if (!strcasecmp (field, "default")) {
546                    if (won)
547                        continue;
548                    break;
549                }               /* else fall */
550
551            default:
552                /* parse message and build lookup table */
553                if (!parsed && parse (fd) == -1) {
554                    fclose (fp);
555                    return -1;
556                }
557                /*
558                 * find header field in lookup table, and
559                 * see if the pattern matches.
560                 */
561                if ((p = lookup (hdrs, field)) && (p->p_value != NULL)
562                        && matches (p->p_value, pattern)) {
563                    next = 1;
564                } else {
565                    next = 0;
566                    continue;
567                }
568                break;
569        }
570
571        /* find out the action to perform */
572        switch (*action) {
573            case 'q':
574                /* deliver to quoted pipe */
575                if (strcasecmp (action, "qpipe"))
576                    continue;   /* else fall */
577            case '^':
578                expand (tmpbuf, string, fd);
579                if (split (tmpbuf, vec) < 1)
580                    continue;
581                status = usr_pipe (fd, tmpbuf, vec[0], vec, 0);
582                break;
583
584            case 'p':
585                /* deliver to pipe */
586                if (strcasecmp (action, "pipe"))
587                    continue;   /* else fall */
588            case '|':
589                vec[2] = "sh";
590                vec[3] = "-c";
591                expand (tmpbuf, string, fd);
592                vec[4] = tmpbuf;
593                vec[5] = NULL;
594                status = usr_pipe (fd, tmpbuf, "/bin/sh", vec + 2, 0);
595                break;
596
597            case 'f':
598                /* mbox format */
599                if (!strcasecmp (action, "file")) {
600                    status = usr_file (fd, string, MBOX_FORMAT);
601                    break;
602                }
603                /* deliver to nmh folder */
604                else if (strcasecmp (action, "folder"))
605                    continue;   /* else fall */
606            case '+':
607                status = usr_folder (fd, string);
608                break;
609
610            case 'm':
611                /* mmdf format */
612                if (!strcasecmp (action, "mmdf")) {
613                    status = usr_file (fd, string, MMDF_FORMAT);
614                    break;
615                }
616                /* mbox format */
617                else if (strcasecmp (action, "mbox"))
618                    continue;   /* else fall */
619
620            case '>':
621                /* mbox format */
622                status = usr_file (fd, string, MBOX_FORMAT);
623                break;
624
625            case 'd':
626                /* ignore message */
627                if (strcasecmp (action, "destroy"))
628                    continue;
629                status = 0;
630                break;
631        }
632
633        if (accept && status == 0)
634            won++;
635    }
636
637    fclose (fp);
638    return (won ? 0 : -1);
639}
640
641
642#define QUOTE   '\\'
643
644/*
645 * Split buffer into fields (delimited by whitespace or
646 * comma's).  Return the number of fields found.
647 */
648
649static int
650split (char *cp, char **vec)
651{
652    int i;
653    char *s;
654
655    s = cp;
656
657    /* split into a maximum of NVEC fields */
658    for (i = 0; i <= NVEC;) {
659        vec[i] = NULL;
660
661        /* zap any whitespace and comma's */
662        while (isspace (*s) || *s == ',')
663            *s++ = 0;
664
665        /* end of buffer, time to leave */
666        if (*s == 0)
667            break;
668
669        /* get double quote text as a single field */
670        if (*s == '"') {
671            for (vec[i++] = ++s; *s && *s != '"'; s++) {
672                /*
673                 * Check for escaped double quote.  We need
674                 * to shift the string to remove slash.
675                 */
676                if (*s == QUOTE) {
677                    if (*++s == '"')
678                        strcpy (s - 1, s);
679                    s--;
680                }
681            }
682            if (*s == '"')      /* zap trailing double quote */
683                *s++ = 0;
684            continue;
685        }
686
687        if (*s == QUOTE && *++s != '"')
688            s--;
689        vec[i++] = s++;
690
691        /* move forward to next field delimiter */
692        while (*s && !isspace (*s) && *s != ',')
693            s++;
694    }
695    vec[i] = NULL;
696
697    return i;
698}
699
700
701/*
702 * Parse the headers of a message, and build the
703 * lookup table for matching fields and patterns.
704 */
705
706static int
707parse (int fd)
708{
709    int i, state;
710    int fd1;
711    char *cp, *dp, *lp;
712    char name[NAMESZ], field[BUFSIZ];
713    struct pair *p, *q;
714    FILE  *in;
715
716    if (parsed++)
717        return 0;
718
719    /* get a new FILE pointer to message */
720    if ((fd1 = dup (fd)) == -1)
721        return -1;
722    if ((in = fdopen (fd1, "r")) == NULL) {
723        close (fd1);
724        return -1;
725    }
726    rewind (in);
727
728    /* add special entries to lookup table */
729    if ((p = lookup (hdrs, "source")))
730        p->p_value = getcpy (sender);
731    if ((p = lookup (hdrs, "addr")))
732        p->p_value = getcpy (addr);
733
734    /*
735     * Scan the headers of the message and build
736     * a lookup table.
737     */
738    for (i = 0, state = FLD;;) {
739        switch (state = m_getfld (state, name, field, sizeof(field), in)) {
740            case FLD:
741            case FLDEOF:
742            case FLDPLUS:
743                lp = add (field, NULL);
744                while (state == FLDPLUS) {
745                    state = m_getfld (state, name, field, sizeof(field), in);
746                    lp = add (field, lp);
747                }
748                for (p = hdrs; p->p_name; p++) {
749                    if (!strcasecmp (p->p_name, name)) {
750                        if (!(p->p_flags & P_HID)) {
751                            if ((cp = p->p_value))
752                                if (p->p_flags & P_ADR) {
753                                    dp = cp + strlen (cp) - 1;
754                                    if (*dp == '\n')
755                                        *dp = 0;
756                                    cp = add (",\n\t", cp);
757                                } else {
758                                    cp = add ("\t", cp);
759                                }
760                            p->p_value = add (lp, cp);
761                        }
762                        free (lp);
763                        break;
764                    }
765                }
766                if (p->p_name == NULL && i < NVEC) {
767                    p->p_name = getcpy (name);
768                    p->p_value = lp;
769                    p->p_flags = P_NIL;
770                    p++, i++;
771                    p->p_name = NULL;
772                }
773                if (state != FLDEOF)
774                    continue;
775                break;
776
777            case BODY:
778            case BODYEOF:
779            case FILEEOF:
780                break;
781
782            case LENERR:
783            case FMTERR:
784                advise (NULL, "format error in message");
785                break;
786
787            default:
788                advise (NULL, "internal error in m_getfld");
789                fclose (in);
790                return -1;
791        }
792        break;
793    }
794    fclose (in);
795
796    if ((p = lookup (vars, "reply-to"))) {
797        if ((q = lookup (hdrs, "reply-to")) == NULL || q->p_value == NULL)
798            q = lookup (hdrs, "from");
799        p->p_value = getcpy (q ? q->p_value : "");
800        p->p_flags &= ~P_CHK;
801        if (debug)
802            debug_printf ("vars[%d]: name=\"%s\" value=\"%s\"\n",
803                    p - vars, p->p_name, trim(p->p_value));
804    }
805    if (debug) {
806        for (p = hdrs; p->p_name; p++)
807            debug_printf ("hdrs[%d]: name=\"%s\" value=\"%s\"\n",
808                p - hdrs, p->p_name, p->p_value ? trim(p->p_value) : "");
809    }
810
811    return 0;
812}
813
814
815#define LPAREN  '('
816#define RPAREN  ')'
817
818/*
819 * Expand any builtin variables such as $(sender),
820 * $(address), etc., in a string.
821 */
822
823static void
824expand (char *s1, char *s2, int fd)
825{
826    char c, *cp;
827    struct pair *p;
828
829    if (!globbed)
830        glob (fd);
831
832    while ((c = *s2++)) {
833        if (c != '$' || *s2 != LPAREN) {
834            *s1++ = c;
835        } else {
836            for (cp = ++s2; *s2 && *s2 != RPAREN; s2++)
837                continue;
838            if (*s2 != RPAREN) {
839                s2 = --cp;
840                continue;
841            }
842            *s2++ = 0;
843            if ((p = lookup (vars, cp))) {
844                if (!parsed && (p->p_flags & P_CHK))
845                    parse (fd);
846
847                strcpy (s1, p->p_value);
848                s1 += strlen (s1);
849            }
850        }
851    }
852    *s1 = 0;
853}
854
855
856/*
857 * Fill in the information missing from the "vars"
858 * table, which is necessary to expand any builtin
859 * variables in the string for a "pipe" or "qpipe"
860 * action.
861 */
862
863static void
864glob (int fd)
865{
866    char buffer[BUFSIZ];
867    struct stat st;
868    struct pair *p;
869
870    if (globbed++)
871        return;
872
873    if ((p = lookup (vars, "sender")))
874        p->p_value = getcpy (sender);
875    if ((p = lookup (vars, "address")))
876        p->p_value = getcpy (addr);
877    if ((p = lookup (vars, "size"))) {
878        snprintf (buffer, sizeof(buffer), "%d",
879                fstat (fd, &st) != -1 ? (int) st.st_size : 0);
880        p->p_value = getcpy (buffer);
881    }
882    if ((p = lookup (vars, "info")))
883        p->p_value = getcpy (info);
884
885    if (debug) {
886        for (p = vars; p->p_name; p++)
887            debug_printf ("vars[%d]: name=\"%s\" value=\"%s\"\n",
888                    p - vars, p->p_name, trim(p->p_value));
889    }
890}
891
892
893/*
894 * Find a matching name in a lookup table.  If found,
895 * return the "pairs" entry, else return NULL.
896 */
897
898static struct pair *
899lookup (struct pair *pairs, char *key)
900{
901    for (; pairs->p_name; pairs++)
902        if (!strcasecmp (pairs->p_name, key))
903            return pairs;
904
905    return NULL;
906}
907
908
909/*
910 * Check utmp(x) file to see if user is currently
911 * logged in.
912 */
913
914static int
915logged_in (void)
916{
917    struct utmp ut;
918    FILE *uf;
919
920    if (utmped)
921        return utmped;
922
923    if ((uf = fopen (UTMP_FILE, "r")) == NULL)
924        return NOTOK;
925
926    while (fread ((char *) &ut, sizeof(ut), 1, uf) == 1) {
927        if (ut.ut_name[0] != 0
928            && strncmp (user, ut.ut_name, sizeof(ut.ut_name)) == 0) {
929            if (debug)
930                continue;
931            fclose (uf);
932            return (utmped = DONE);
933        }
934    }
935
936    fclose (uf);
937    return (utmped = NOTOK);
938}
939
940
941#define check(t,a,b)            if (t < a || t > b) return -1
942#define cmpar(h1,m1,h2,m2)      if (h1 < h2 || (h1 == h2 && m1 < m2)) return 0
943
944static int
945timely (char *t1, char *t2)
946{
947    int t1hours, t1mins, t2hours, t2mins;
948
949    if (sscanf (t1, "%d:%d", &t1hours, &t1mins) != 2)
950        return -1;
951    check (t1hours, 0, 23);
952    check (t1mins, 0, 59);
953
954    if (sscanf (t2, "%d:%d", &t2hours, &t2mins) != 2)
955        return -1;
956    check (t2hours, 0, 23);
957    check (t2mins, 0, 59);
958
959    cmpar (now->tw_hour, now->tw_min, t1hours, t1mins);
960    cmpar (t2hours, t2mins, now->tw_hour, now->tw_min);
961
962    return -1;
963}
964
965
966/*
967 * Deliver message by appending to a file.
968 */
969
970static int
971usr_file (int fd, char *mailbox, int mbx_style)
972{
973    int md, mapping;
974
975    if (verbose)
976        verbose_printf ("delivering to file \"%s\"", mailbox);
977
978    if (mbx_style == MBOX_FORMAT) {
979        if (verbose)
980            verbose_printf (" (mbox style)");
981        mapping = 0;
982    } else {
983        if (verbose)
984            verbose_printf (" (mmdf style)");
985        mapping = 1;
986    }
987
988    /* open and lock the file */
989    if ((md = mbx_open (mailbox, mbx_style, pw->pw_uid, pw->pw_gid, m_gmprot())) == -1) {
990        if (verbose)
991            adorn ("", "unable to open:");
992        return -1;
993    }
994
995    lseek (fd, (off_t) 0, SEEK_SET);
996
997    /* append message to file */
998    if (mbx_copy (mailbox, mbx_style, md, fd, mapping, NULL, verbose) == -1) {
999        if (verbose)
1000            adorn ("", "error writing to:");
1001        return -1;
1002    }
1003
1004    /* close and unlock file */
1005    if (mbx_close (mailbox, md) == NOTOK) {
1006        if (verbose)
1007            adorn ("", "error writing to:");
1008        return -1;
1009    }
1010
1011    if (verbose)
1012        verbose_printf (", success.\n");
1013    return 0;
1014}
1015
1016
1017/*
1018 * Deliver message to a nmh folder.
1019 */
1020
1021static int
1022usr_folder (int fd, char *string)
1023{
1024    int status;
1025    char folder[BUFSIZ], *vec[3];
1026
1027    /* get folder name ready */
1028    if (*string == '+')
1029        strncpy(folder, string, sizeof(folder));
1030    else
1031        snprintf(folder, sizeof(folder), "+%s", string);
1032
1033    if (verbose)
1034        verbose_printf ("delivering to folder \"%s\"", folder + 1);
1035
1036    vec[0] = "rcvstore";
1037    vec[1] = folder;
1038    vec[2] = NULL;
1039
1040    /* use rcvstore to put message in folder */
1041    status = usr_pipe (fd, "rcvstore", rcvstoreproc, vec, 1);
1042
1043#if 0
1044    /*
1045     * Currently, verbose status messages are handled by usr_pipe().
1046     */
1047    if (verbose) {
1048        if (status == 0)
1049            verbose_printf (", success.\n");
1050        else
1051            verbose_printf (", failed.\n");
1052    }
1053#endif
1054
1055    return status;
1056}
1057
1058/*
1059 * Deliver message to a process.
1060 */
1061
1062static int
1063usr_pipe (int fd, char *cmd, char *pgm, char **vec, int suppress)
1064{
1065    pid_t child_id;
1066    int i, bytes, seconds, status;
1067    struct stat st;
1068
1069    if (verbose && !suppress)
1070        verbose_printf ("delivering to pipe \"%s\"", cmd);
1071
1072    lseek (fd, (off_t) 0, SEEK_SET);
1073
1074    for (i = 0; (child_id = fork()) == -1 && i < 5; i++)
1075        sleep (5);
1076
1077    switch (child_id) {
1078        case -1:
1079            /* fork error */
1080            if (verbose)
1081                adorn ("fork", "unable to");
1082            return -1;
1083
1084        case 0:
1085            /* child process */
1086            if (fd != 0)
1087                dup2 (fd, 0);
1088            freopen ("/dev/null", "w", stdout);
1089            freopen ("/dev/null", "w", stderr);
1090            if (fd != 3)
1091                dup2 (fd, 3);
1092            closefds (4);
1093
1094#ifdef TIOCNOTTY
1095            if ((fd = open ("/dev/tty", O_RDWR)) != -1) {
1096                ioctl (fd, TIOCNOTTY, NULL);
1097                close (fd);
1098            }
1099#endif /* TIOCNOTTY */
1100
1101            setpgid ((pid_t) 0, getpid ());     /* put in own process group */
1102
1103            *environ = NULL;
1104            m_putenv ("USER", pw->pw_name);
1105            m_putenv ("HOME", pw->pw_dir);
1106            m_putenv ("SHELL", pw->pw_shell);
1107
1108            execvp (pgm, vec);
1109            _exit (-1);
1110
1111        default:
1112            /* parent process */
1113            if (!setjmp (myctx)) {
1114                SIGNAL (SIGALRM, alrmser);
1115                bytes = fstat (fd, &st) != -1 ? (int) st.st_size : 100;
1116
1117                /* amount of time to wait depends on message size */
1118                if (bytes <= 100) {
1119                    /* give at least 5 minutes */
1120                    seconds = 300;
1121                } else if (bytes >= 90000) {
1122                    /* a half hour is long enough */
1123                    seconds = 1800;
1124                } else {
1125                    seconds = (bytes / 60) + 300;
1126                }
1127                alarm ((unsigned int) seconds);
1128                status = pidwait (child_id, 0);
1129                alarm (0);
1130
1131#ifdef MMDFI
1132                if (status == RP_MOK || status == RP_OK)
1133                    status = 0;
1134#endif
1135                if (verbose) {
1136                    if (status == 0)
1137                        verbose_printf (", success.\n");
1138                    else
1139                        if ((status & 0xff00) == 0xff00)
1140                            verbose_printf (", system error\n");
1141                        else
1142                            pidstatus (status, stdout, ", failed");
1143                }
1144                return (status == 0 ? 0 : -1);
1145            } else {
1146                /*
1147                 * Ruthlessly kill the child and anything
1148                 * else in its process group.
1149                 */
1150                KILLPG(child_id, SIGKILL);
1151                if (verbose)
1152                    verbose_printf (", timed-out; terminated\n");
1153                return -1;
1154            }
1155    }
1156}
1157
1158
1159static RETSIGTYPE
1160alrmser (int i)
1161{
1162#ifndef RELIABLE_SIGNALS
1163    SIGNAL (SIGALRM, alrmser);
1164#endif
1165
1166    longjmp (myctx, DONE);
1167}
1168
1169
1170/*
1171 * Get the `sender' from the envelope
1172 * information ("From " line).
1173 */
1174
1175static void
1176get_sender (char *envelope, char **sender)
1177{
1178    int i;
1179    char *cp;
1180    char buffer[BUFSIZ];
1181
1182    if (envelope == NULL) {
1183        *sender = getcpy ("");
1184        return;
1185    }
1186
1187    i = strlen ("From ");
1188    strncpy (buffer, envelope + i, sizeof(buffer));
1189    if ((cp = strchr(buffer, '\n'))) {
1190        *cp = 0;
1191        cp -= 24;
1192        if (cp < buffer)
1193            cp = buffer;
1194    } else {
1195        cp = buffer;
1196    }
1197    *cp = 0;
1198
1199    for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1200        if (isspace (*cp))
1201            *cp = 0;
1202        else
1203            break;
1204    *sender = getcpy (buffer);
1205}
1206
1207
1208/*
1209 * Copy message into a temporary file.
1210 * While copying, it will do some header processing
1211 * including the extraction of the envelope information.
1212 */
1213
1214static int
1215copy_message (int qd, char *tmpfil, int fold)
1216{
1217    int i, first = 1, fd1, fd2;
1218    char buffer[BUFSIZ];
1219    FILE *qfp, *ffp;
1220
1221    strcpy (tmpfil, m_tmpfil (invo_name));
1222
1223    /* open temporary file to put message in */
1224    if ((fd1 = open (tmpfil, O_RDWR | O_CREAT | O_TRUNC, 0600)) == -1)
1225        return -1;
1226
1227    if (!fold) {
1228        while ((i = read (qd, buffer, sizeof(buffer))) > 0)
1229            if (write (fd1, buffer, i) != i) {
1230you_lose:
1231                close (fd1);
1232                unlink (tmpfil);
1233                return -1;
1234            }
1235        if (i == -1)
1236            goto you_lose;
1237        lseek (fd1, (off_t) 0, SEEK_SET);
1238        return fd1;
1239    }
1240
1241    /* dup the fd for incoming message */
1242    if ((fd2 = dup (qd)) == -1) {
1243        close (fd1);
1244        return -1;
1245    }
1246
1247    /* now create a FILE pointer for it */
1248    if ((qfp = fdopen (fd2, "r")) == NULL) {
1249        close (fd1);
1250        close (fd2);
1251        return -1;
1252    }
1253
1254    /* dup the fd for temporary file */
1255    if ((fd2 = dup (fd1)) == -1) {
1256        close (fd1);
1257        fclose (qfp);
1258        return -1;
1259    }
1260
1261    /* now create a FILE pointer for it */
1262    if ((ffp = fdopen (fd2, "r+")) == NULL) {
1263        close (fd1);
1264        close (fd2);
1265        fclose (qfp);
1266        return -1;
1267    }
1268
1269    /*
1270     * copy message into temporary file
1271     * and massage the headers.  Save
1272     * a copy of the "From " line for later.
1273     */
1274    i = strlen ("From ");
1275    while (fgets (buffer, sizeof(buffer), qfp)) {
1276        if (first) {
1277            first = 0;
1278            if (!strncmp (buffer, "From ", i)) {
1279#ifdef RPATHS
1280                char *fp, *cp, *hp, *ep;
1281#endif
1282                /* get copy of envelope information ("From " line) */
1283                envelope = getcpy (buffer);
1284
1285#if 0
1286                /* First go ahead and put "From " line in message */
1287                fputs (buffer, ffp);
1288                if (ferror (ffp))
1289                    goto fputs_error;
1290#endif
1291
1292#ifdef RPATHS
1293                /*
1294                 * Now create a "Return-Path:" line
1295                 * from the "From " line.
1296                 */
1297                hp = cp = strchr(fp = envelope + i, ' ');
1298                while ((hp = strchr(++hp, 'r')))
1299                    if (uprf (hp, "remote from")) {
1300                        hp = strrchr(hp, ' ');
1301                        break;
1302                    }
1303                if (hp) {
1304                    /* return path for UUCP style addressing */
1305                    ep = strchr(++hp, '\n');
1306                    snprintf (buffer, sizeof(buffer), "Return-Path: %.*s!%.*s\n",
1307                        ep - hp, hp, cp - fp, fp);
1308                } else {
1309                    /* return path for standard domain addressing */
1310                    snprintf (buffer, sizeof(buffer), "Return-Path: %.*s\n",
1311                        cp - fp, fp);
1312                }
1313
1314                /* Add Return-Path header to message */
1315                fputs (buffer, ffp);
1316                if (ferror (ffp))
1317                    goto fputs_error;
1318#endif
1319                /* Put the delivery date in message */
1320                fputs (ddate, ffp);
1321                if (ferror (ffp))
1322                    goto fputs_error;
1323
1324                continue;
1325            }
1326        }
1327
1328        fputs (buffer, ffp);
1329        if (ferror (ffp))
1330            goto fputs_error;
1331    }
1332
1333    fclose (ffp);
1334    if (ferror (qfp)) {
1335        close (fd1);
1336        fclose (qfp);
1337        return -1;
1338    }
1339    fclose (qfp);
1340    lseek (fd1, (off_t) 0, SEEK_SET);
1341    return fd1;
1342
1343
1344fputs_error:
1345    close (fd1);
1346    fclose (ffp);
1347    fclose (qfp);
1348    return -1;
1349}
1350
1351/*
1352 * Trim strings for pretty printing of debugging output
1353 */
1354
1355static char *
1356trim (char *cp)
1357{
1358    char buffer[BUFSIZ*4];
1359    char *bp, *sp;
1360
1361    if (cp == NULL)
1362        return NULL;
1363
1364    /* copy string into temp buffer */
1365    strncpy (buffer, cp, sizeof(buffer));
1366    bp = buffer;
1367
1368    /* skip over leading whitespace */
1369    while (isspace(*bp))
1370        bp++;
1371
1372    /* start at the end and zap trailing whitespace */
1373    for (sp = bp + strlen(bp) - 1; sp >= bp; sp--) {
1374        if (isspace(*sp))
1375            *sp = 0;
1376        else
1377            break;
1378    }
1379
1380    /* replace remaining whitespace with spaces */
1381    for (sp = bp; *sp; sp++)
1382        if (isspace(*sp))
1383            *sp = ' ';
1384
1385    /* now return a copy */
1386    return getcpy(bp);
1387}
1388
1389/*
1390 * Function for printing `verbose' messages.
1391 */
1392
1393static void
1394verbose_printf (char *fmt, ...)
1395{
1396    va_list ap;
1397
1398    va_start(ap, fmt);
1399    vfprintf (stdout, fmt, ap);
1400    va_end(ap);
1401
1402    fflush (stdout);    /* now flush output */
1403}
1404
1405
1406/*
1407 * Function for printing `verbose' delivery
1408 * error messages.
1409 */
1410
1411static void
1412adorn (char *what, char *fmt, ...)
1413{
1414    va_list ap;
1415    int eindex;
1416    char *s;
1417
1418    eindex = errno;     /* save the errno */
1419    fprintf (stdout, ", ");
1420
1421    va_start(ap, fmt);
1422    vfprintf (stdout, fmt, ap);
1423    va_end(ap);
1424
1425    if (what) {
1426        if (*what)
1427            fprintf (stdout, " %s: ", what);
1428        if ((s = strerror (eindex)))
1429            fprintf (stdout, "%s", s);
1430        else
1431            fprintf (stdout, "Error %d", eindex);
1432    }
1433
1434    fputc ('\n', stdout);
1435    fflush (stdout);
1436}
1437
1438
1439/*
1440 * Function for printing `debug' messages.
1441 */
1442
1443static void
1444debug_printf (char *fmt, ...)
1445{
1446    va_list ap;
1447
1448    va_start(ap, fmt);
1449    vfprintf (stderr, fmt, ap);
1450    va_end(ap);
1451}
1452
1453
1454/*
1455 * Check ndbm/db file(s) to see if the Message-Id of this
1456 * message matches the Message-Id of a previous message,
1457 * so we can discard it.  If it doesn't match, we add the
1458 * Message-Id of this message to the ndbm/db file.
1459 */
1460static int
1461suppress_duplicates (int fd, char *file)
1462{
1463    int fd1, lockfd, state, result;
1464    char *cp, buf[BUFSIZ], name[NAMESZ];
1465    datum key, value;
1466    DBM *db;
1467    FILE *in;
1468
1469    if ((fd1 = dup (fd)) == -1)
1470        return -1;
1471    if (!(in = fdopen (fd1, "r"))) {
1472        close (fd1);
1473        return -1;
1474    }
1475    rewind (in);
1476
1477    for (state = FLD;;) {
1478        state = m_getfld (state, name, buf, sizeof(buf), in);
1479        switch (state) {
1480            case FLD:
1481            case FLDPLUS:
1482            case FLDEOF:
1483                /* Search for the message ID */
1484                if (strcasecmp (name, "Message-ID")) {
1485                    while (state == FLDPLUS)
1486                        state = m_getfld (state, name, buf, sizeof(buf), in);
1487                    continue;
1488                }
1489
1490                cp = add (buf, NULL);
1491                while (state == FLDPLUS) {
1492                    state = m_getfld (state, name, buf, sizeof(buf), in);
1493                    cp = add (buf, cp);
1494                }
1495                key.dptr = trimcpy (cp);
1496                key.dsize = strlen (key.dptr) + 1;
1497                free (cp);
1498                cp = key.dptr;
1499
1500                if (!(db = dbm_open (file, O_RDWR | O_CREAT, 0600))) {
1501                    advise (file, "unable to perform dbm_open on");
1502                    free (cp);
1503                    fclose (in);
1504                    return -1;
1505                }
1506                /*
1507                 * Since it is difficult to portable lock a ndbm file,
1508                 * we will open and lock the Maildelivery file instead.
1509                 * This will fail if your Maildelivery file doesn't
1510                 * exist.
1511                 */
1512                if ((lockfd = lkopen(file, O_RDWR, 0)) == -1) {
1513                    advise (file, "unable to perform file locking on");
1514                    free (cp);
1515                    fclose (in);
1516                    return -1;
1517                }
1518                value = dbm_fetch (db, key);
1519                if (value.dptr) {
1520                    if (verbose)
1521                        verbose_printf ("Message-ID: %s\n            already received on %s",
1522                                 cp, value.dptr);
1523                    result = DONE;
1524                } else {
1525                    value.dptr  = ddate + sizeof("Delivery-Date:");
1526                    value.dsize = strlen(value.dptr) + 1;
1527                    if (dbm_store (db, key, value, DBM_INSERT))
1528                        advise (file, "possibly corrupt file");
1529                    result = 0;
1530                }
1531
1532                dbm_close (db);
1533                lkclose(lockfd, file);
1534                free (cp);
1535                fclose (in);
1536                return result;
1537                break;
1538
1539           case BODY:
1540           case BODYEOF:
1541           case FILEEOF:
1542                break;
1543
1544           case LENERR:
1545           case FMTERR:
1546           default:
1547                break;
1548        }
1549
1550        break;
1551    }
1552
1553    fclose (in);
1554    return 0;
1555}
Note: See TracBrowser for help on using the repository browser.