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