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