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