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