1 /* slocal.c - MH style mailer to write to a local user's mailbox */
3 static char ident[] = "@(#)$Id: slocal.c,v 1.25 1995/12/07 00:01:25 jromine Exp shettich $";
6 /* This program implements mail delivery in the MH/MMDF style.
8 Under SendMail, users should add the line
10 "| /usr/local/lib/mh/slocal"
12 to their $HOME/.forward file.
14 Under MMDF-I, users should (symbolically) link /usr/local/lib/mh/slocal
17 Under stand-alone MH, post will automatically run this during local
20 This program should be used ONLY if you have "mts sendmail" or "mts mh"
21 or "mts mmdf1" set in your MH configuration.
27 #include "../h/dropsbr.h"
28 #include "../h/rcvmail.h"
29 #include "../zotnet/tws.h"
30 #include "../zotnet/mts.h"
35 #include <sys/ioctl.h>
47 #undef DBM /* used by ndbm.h */
50 #include <sys/types.h>
58 #if defined(LOCKF) && !defined(F_ULOCK)
59 #include <sys/fcntl.h>
69 static struct swit switches[] = {
86 "maildelivery file", 0,
104 static int debug = 0;
105 static int globbed = 0;
106 static int parsed = 0;
107 static int utmped = 0;
108 static int verbose = 0;
110 static char *addr = NULLCP;
111 static char *user = NULLCP;
112 static char *info = NULLCP;
113 static char *file = NULLCP;
114 static char *sender = NULLCP;
115 static char *unixfrom = NULLCP;
116 static char *mbox = NULLCP;
117 static char *home = NULLCP;
120 static struct passwd *pw;
123 static char ddate[BUFSIZ];
128 static jmp_buf myctx;
143 static struct pair *lookup ();
146 static struct pair hdrs[NVEC + 1] = {
147 "source", NULL, P_HID,
150 "Return-Path", NULL, P_ADR,
151 "Reply-To", NULL, P_ADR,
153 "Sender", 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,
166 static struct pair vars[] = {
167 "sender", NULL, P_NIL,
168 "address", NULL, P_NIL,
170 "reply-to", NULL, P_CHK,
178 extern char **environ;
180 static void adorn ();
181 static TYPESIG alrmser ();
187 struct passwd *getpwnam ();
190 static int localmail(), usr_delivery(), split(), parse(), logged_in();
191 static int timely(), usr_file(), usr_pipe(), copyfile();
192 static expand(), glob(), copyinfo();
198 main (argc, argv, envp)
214 setlocale(LC_ALL, "");
216 invo_name = r1bindex (*argv, '/');
218 mts_init (invo_name);
222 while (cp = *argp++) {
224 switch (smatch (++cp, switches)) {
226 ambigsw (cp, switches);
229 adios (NULLCP, "-%s unknown", cp);
231 (void) sprintf (buf, "%s [switches] [address info sender]",
233 help (buf, switches);
237 if (!(addr = *argp++))/* allow -xyz arguments */
238 adios (NULLCP, "missing argument to %s", argp[-2]);
241 if (!(info = *argp++))/* allow -xyz arguments */
242 adios (NULLCP, "missing argument to %s", argp[-2]);
245 if (!(user = *argp++))/* allow -xyz arguments */
246 adios (NULLCP, "missing argument to %s", argp[-2]);
249 if (!(file = *argp++) || *file == '-')
250 adios (NULLCP, "missing argument to %s", argp[-2]);
253 if (!(sender = *argp++))/* allow -xyz arguments */
254 adios (NULLCP, "missing argument to %s", argp[-2]);
257 if (!(mbox = *argp++) || *mbox == '-')
258 adios (NULLCP, "missing argument to %s", argp[-2]);
261 if (!(home = *argp++) || *home == '-')
262 adios (NULLCP, "missing argument to %s", argp[-2]);
266 if (!(cp = *argp++) || *cp == '-')
267 adios (NULLCP, "missing argument to %s", argp[-2]);
269 adios (NULLCP, "only one maildelivery file at a time!");
285 switch (argp - (argv + 1)) {
305 user = (cp = index (addr, '.')) ? ++cp : addr;
306 if ((pw = getpwnam (user)) == NULL)
307 adios (NULLCP, "no such local user as %s", user);
309 if (chdir (pw -> pw_dir) == NOTOK)
313 if (geteuid () == 0) {
315 (void) inigrp (pw -> pw_name, pw -> pw_gid);
317 (void) setgid (pw -> pw_gid);
319 (void) initgroups (pw -> pw_name, pw -> pw_gid);
321 (void) setuid (pw -> pw_uid);
327 setbuf (stdin, NULLCP);
330 if ((fd = copyfile (fileno (stdin), file = tmpfil, 1)) == NOTOK)
331 adios (NULLCP, "unable to create temporary file");
333 fprintf (stderr, "temporary file \"%s\" selected\n", tmpfil);
335 (void) unlink (tmpfil);
336 if ((fp = fdopen (fd, "r+")) == NULL)
337 adios (NULLCP, "unable to access temporary file");
348 (void) sprintf (mailbox, "%s/%s",
349 mmdfldir[0] ? mmdfldir : pw -> pw_dir,
350 mmdflfil[0] ? mmdflfil : pw -> pw_name);
356 if ((now = dtwstime ()) == NULL)
357 adios (NULLCP, "unable to ascertain local time");
358 (void) sprintf (ddate, "Delivery-Date: %s\n", dtimenow ());
361 fprintf (stderr, "addr=\"%s\" user=\"%s\" info=\"%s\" file=\"%s\"\n",
362 addr, user, info, file);
363 fprintf (stderr, "sender=\"%s\" mbox=\"%s\" home=\"%s\" from=\"%s\"\n",
364 sender ? sender : "", mbox, home, from);
365 fprintf (stderr, "ddate=\"%s\" now=%02d:%02d\n",
366 ddate, now -> tw_hour, now -> tw_min);
369 done (localmail (fd, from, mdlvr) != NOTOK ? RCV_MOK : RCV_MBX);
374 static int localmail (fd, from, mdlvr)
382 if (stat (".maildelivery.pag", &st) != NOTOK
383 && check_msgid (fd, ".maildelivery") == DONE)
387 if (usr_delivery (fd, mdlvr ? mdlvr : ".maildelivery", 0, from) != NOTOK)
390 if (usr_delivery (fd, maildelivery, 1, from) != NOTOK)
395 printf ("(invoking hook)\n");
396 if (usr_hook (fd, mbox) != NOTOK)
401 printf ("(trying normal delivery)\n");
402 return usr_file (fd, mbox, from);
407 #define matches(a,b) (stringdex (b, a) >= 0)
409 static int usr_delivery (fd, delivery, su, from)
430 register struct pair *p;
433 if ((fp = fopen (delivery, "r")) == NULL)
435 if (fstat (fileno (fp), &st) == NOTOK
436 || (st.st_uid != 0 && (su || st.st_uid != pw -> pw_uid))
437 || st.st_mode & 0022) {
439 printf ("%s: ownership/modes bad (%d, %d,%d,0%o)\n",
440 delivery, su, pw -> pw_uid, st.st_uid, st.st_mode);
441 (void) fflush (stdout);
448 while (fgets (buffer, sizeof buffer, fp) != NULL) {
451 if (cp = index (buffer, '\n'))
453 if ((vecp = split (buffer, vec)) < 5)
456 for (i = 0; vec[i]; i++)
457 fprintf (stderr, "vec[%d]: \"%s\"\n", i, vec[i]);
467 continue; /* if previous condition failed, don't
468 do this - else fall through */
472 continue; /* else fall */
488 if (uleq (vec[5], "select")) {
489 if (logged_in () != NOTOK)
491 if (vecp > 7 && timely (vec[6], vec[7]) == NOTOK)
501 if (uleq (field, "default")) {
508 if (!parsed && parse (fd) == NOTOK) {
512 if ((p = lookup (hdrs, field)) == NULL
513 || (p->p_value == NULL) /* XXX */
514 || !matches (p -> p_value, pattern)) {
525 if (!uleq (action, "qpipe"))
526 continue; /* else fall */
528 expand (tmpbuf, string, fd);
529 if (split (tmpbuf, vec) < 1)
531 status = usr_pipe (fd, tmpbuf, vec[0], vec);
535 if (!uleq (action, "pipe"))
536 continue; /* else fall */
540 expand (tmpbuf, string, fd);
543 status = usr_pipe (fd, tmpbuf, "/bin/sh", vec + 2);
547 if (!uleq (action, "file"))
548 continue; /* else fall */
551 status = usr_file (fd, string, from); /* UUCP format? */
553 status = usr_file (fd, string, NULLCP);
558 if (!uleq (action, "mbox"))
560 status = usr_file (fd, string, NULLCP);
564 if (!uleq (action, "destroy"))
570 if (accept && status == OK)
575 return (won ? OK : NOTOK);
582 static int split (cp, vec)
589 for (i = 0, s = cp; i <= NVEC;) {
591 while (isspace (*s) || *s == ',')
597 for (vec[i++] = ++s; *s != 0 && *s != '"'; s++)
600 (void) strcpy (s - 1, s);
607 if (*s == QUOTE && *++s != '"')
611 while (*s != 0 && !isspace (*s) && *s != ',')
621 static int parse (fd)
632 register struct pair *p,
639 if ((fd1 = dup (fd)) == NOTOK)
641 if ((in = fdopen (fd1, "r")) == NULL) {
647 if (p = lookup (hdrs, "source"))
648 p -> p_value = getcpy (sender);
649 if (p = lookup (hdrs, "addr"))
650 p -> p_value = getcpy (addr);
652 for (i = 0, state = FLD;;) {
653 switch (state = m_getfld (state, name, field, sizeof field, in)) {
657 lp = add (field, NULLCP);
658 while (state == FLDPLUS) {
659 state = m_getfld (state, name, field, sizeof field, in);
660 lp = add (field, lp);
662 for (p = hdrs; p -> p_name; p++)
663 if (uleq (p -> p_name, name)) {
664 if (!(p -> p_flags & P_HID)) {
665 if (cp = p -> p_value)
666 if (p -> p_flags & P_ADR) {
667 dp = cp + strlen (cp) - 1;
670 cp = add (",\n\t", cp);
674 p -> p_value = add (lp, cp);
679 if (p -> p_name == NULL && i < NVEC) {
680 p -> p_name = getcpy (name);
682 p -> p_flags = P_NIL;
697 advise (NULLCP, "format error in message");
701 advise (NULLCP, "internal error");
709 if (p = lookup (vars, "reply-to")) {
710 if ((q = lookup (hdrs, "reply-to")) == NULL || q -> p_value == NULL)
711 q = lookup (hdrs, "from");
712 p -> p_value = getcpy (q ? q -> p_value : "");
713 p -> p_flags &= ~P_CHK;
715 fprintf (stderr, "vars[%d]: name=\"%s\" value=\"%s\"\n",
716 p - vars, p -> p_name, p -> p_value);
718 #define empty(s) ((s) ? (s) : "")
720 for (p = hdrs; p -> p_name; p++)
721 fprintf (stderr, "hdrs[%d]: name=\"%s\" value=\"%s\"\n",
722 p - hdrs, p -> p_name, empty(p -> p_value));
733 static expand (s1, s2, fd)
740 register struct pair *p;
746 if (c != '$' || *s2 != LPAREN)
749 for (cp = ++s2; *s2 && *s2 != RPAREN; s2++)
756 if (p = lookup (vars, cp)) {
757 if (!parsed && (p -> p_flags & P_CHK))
760 (void) strcpy (s1, p -> p_value);
774 register struct pair *p;
779 if (p = lookup (vars, "sender"))
780 p -> p_value = getcpy (sender);
781 if (p = lookup (vars, "address"))
782 p -> p_value = getcpy (addr);
783 if (p = lookup (vars, "size")) {
784 (void) sprintf (buffer, "%d",
785 fstat (fd, &st) != NOTOK ? (int) st.st_size : 0);
786 p -> p_value = getcpy (buffer);
788 if (p = lookup (vars, "info"))
789 p -> p_value = getcpy (info);
792 for (p = vars; p -> p_name; p++)
793 fprintf (stderr, "vars[%d]: name=\"%s\" value=\"%s\"\n",
794 p - vars, p -> p_name, p -> p_value);
799 static struct pair *lookup (pairs, key)
800 register struct pair *pairs;
805 for (; cp = pairs -> p_name; pairs++)
814 static int logged_in () {
821 if ((uf = fopen ("/etc/utmp", "r")) == NULL)
824 while (fread ((char *) &ut, sizeof ut, 1, uf) == 1)
825 if (ut.ut_name[0] != 0
826 && strncmp (user, ut.ut_name, sizeof ut.ut_name) == 0) {
830 return (utmped = DONE);
834 return (utmped = NOTOK);
838 static int timely (t1, t2)
842 #define check(t,a,b) if (t < a || t > b) return NOTOK
843 #define cmpar(h1,m1,h2,m2) if (h1 < h2 || (h1 == h2 && m1 < m2)) return OK
850 if (sscanf (t1, "%d:%d", &t1hours, &t1mins) != 2)
852 check (t1hours, 0, 23);
853 check (t1mins, 0, 59);
855 if (sscanf (t2, "%d:%d", &t2hours, &t2mins) != 2)
857 check (t2hours, 0, 23);
858 check (t2mins, 0, 59);
860 cmpar (now -> tw_hour, now -> tw_min, t1hours, t1mins);
861 cmpar (t2hours, t2mins, now -> tw_hour, now -> tw_min);
868 static int usr_file (fd, mailbox, from)
879 printf ("\tdelivering to file \"%s\"", mailbox);
883 printf (" (uucp style)");
884 (void) sprintf (buffer, "%s%s", from, ddate);
893 (void) fflush (stdout);
895 if ((md = mbx_open (mailbox, pw -> pw_uid, pw -> pw_gid, m_gmprot ()))
897 adorn ("", "unable to open:");
901 (void) lseek (fd, (off_t)0, 0);
902 if (mbx_copy (mailbox, md, fd, mapping, bp, verbose) == NOTOK) {
903 adorn ("", "error writing to:");
907 (void) mbx_close (mailbox, md);
909 printf (", done.\n");
910 (void) fflush (stdout);
918 static int usr_hook (fd, mailbox)
924 char receive[BUFSIZ],
928 if ((fd = copyfile (fd, tmpfil, 0)) == NOTOK) {
930 adorn ("unable to copy message; skipping hook\n");
933 (void) chown (tmpfil, pw -> pw_uid, pw -> pw_gid);
936 (void) sprintf (receive, "%s/.mh_receive", pw -> pw_dir);
937 switch (access (receive, 01)) {
939 (void) sprintf (receive, "%s/bin/rcvmail", pw -> pw_dir);
940 if (access (receive, 01) == NOTOK) {
941 (void) unlink (tmpfil);
943 printf ("\tnot present\n");
944 (void) fflush (stdout);
949 vec[vecp++] = tmpfil;
950 vec[vecp++] = sender;
954 vec[vecp++] = tmpfil;
955 vec[vecp++] = mailbox;
958 vec[vecp++] = sender;
961 vec[0] = r1bindex (receive, '/');
964 i = usr_pipe (fd, "rcvmail", receive, vec);
965 (void) unlink (tmpfil);
973 static int usr_pipe (fd, cmd, pgm, vec)
986 printf ("\tdelivering to pipe \"%s\"", cmd);
987 (void) fflush (stdout);
989 (void) lseek (fd, (off_t)0, 0);
991 for (i = 0; (child_id = fork ()) == NOTOK && i < 5; i++)
995 adorn ("fork", "unable to");
1000 (void) dup2 (fd, 0);
1001 (void) freopen ("/dev/null", "w", stdout);
1002 (void) freopen ("/dev/null", "w", stderr);
1004 (void) dup2 (fd, 3);
1007 if ((fd = open ("/dev/tty", 2)) != NOTOK) {
1008 (void) ioctl (fd, TIOCNOTTY, NULLCP);
1011 #endif /* TIOCNOTTY */
1013 (void) setpgrp (0, getpid ());
1017 (void) m_putenv ("USER", pw -> pw_name);
1018 (void) m_putenv ("HOME", pw -> pw_dir);
1019 (void) m_putenv ("SHELL", pw -> pw_shell);
1025 switch (setjmp (myctx)) {
1027 (void) signal (SIGALRM, alrmser);
1028 bytes = fstat (fd, &st) != NOTOK ? (int) st.st_size : 100;
1031 (void) alarm ((unsigned) (bytes * 60 + 300));
1033 status = pidwait (child_id, OK);
1037 if (status == RP_MOK || status == RP_OK)
1042 printf (", wins.\n");
1044 if ((status & 0xff00) == 0xff00)
1045 printf (", system error\n");
1047 (void) pidstatus (status, stdout, ", loses");
1048 (void) fflush (stdout);
1050 return (status == 0 ? OK : NOTOK);
1054 (void) kill (child_id, SIGKILL);
1056 (void) killpg (child_id, SIGKILL);
1059 printf (", timed-out; terminated\n");
1060 (void) fflush (stdout);
1071 static TYPESIG alrmser (i)
1074 longjmp (myctx, DONE);
1079 static copyinfo (fp, from)
1085 static char buffer[BUFSIZ];
1087 if (unixfrom) /* interface from copyfile */
1088 strcpy (from, unixfrom);
1089 else if (fgets (from, BUFSIZ, fp) == NULL)
1090 adios (NULLCP, "no message");
1092 if (strncmp (from, "From ", i = strlen ("From "))) {
1098 (void) strcpy (buffer, from + i);
1099 if (cp = index (buffer, '\n')) {
1109 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1120 static int copyfile (qd, tmpfil, fold)
1123 register char *tmpfil;
1129 char buffer[BUFSIZ];
1133 (void) strcpy (tmpfil, m_tmpfil (invo_name));
1134 if ((fd1 = creat (tmpfil, 0600)) == NOTOK)
1137 if ((fd1 = open (tmpfil, 2)) == NOTOK)
1141 while ((i = read (qd, buffer, sizeof buffer)) > 0)
1142 if (write (fd1, buffer, i) != i) {
1145 (void) unlink (tmpfil);
1150 (void) lseek (fd1, (off_t)0, 0);
1154 if ((fd2 = dup (qd)) == NOTOK) {
1158 if ((qfp = fdopen (fd2, "r")) == NULL) {
1164 if ((fd2 = dup (fd1)) == NOTOK) {
1166 (void) fclose (qfp);
1169 if ((ffp = fdopen (fd2, "r+")) == NULL) {
1172 (void) fclose (qfp);
1176 i = strlen ("From ");
1177 while (fgets (buffer, sizeof buffer, qfp)) {
1178 if (!strncmp (buffer, "From ", i))
1181 register char *fp, *cp, *hp, *ep;
1183 unixfrom = getcpy (buffer); /* save for later */
1185 continue; /* but don't put in file */
1187 hp = cp = index (fp = unixfrom + i, ' ');
1188 while (hp = index (++hp, 'r'))
1189 if (uprf (hp, "remote from")) {
1190 hp = rindex (hp, ' ');
1194 ep = rindex (++hp, '\n');
1195 sprintf (buffer, "Return-Path: %.*s!%.*s\n",
1200 sprintf (buffer, "Return-Path: %.*s\n",
1204 #ifdef notdef /* mbx_copy does this */
1209 fputs (buffer, ffp);
1212 (void) fclose (ffp);
1213 (void) fclose (qfp);
1218 (void) fclose (ffp);
1221 (void) fclose (qfp);
1224 (void) fclose (qfp);
1226 (void) lseek (fd1, (off_t)0, 0);
1235 static void adorn (what, fmt, a, b, c, d, e, f)
1245 char *cp = invo_name;
1252 advise (what, fmt, a, b, c, d, e, f);
1260 static int check_msgid (fd, file)
1274 if ((fd1 = dup (fd)) == NOTOK)
1276 if ((in = fdopen (fd1, "r")) == NULL) {
1282 for (state = FLD;;) {
1283 switch (state = m_getfld (state, name, buf, sizeof buf, in)) {
1287 if (!uleq (name, "Message-ID")) {
1288 while (state == FLDPLUS)
1289 state = m_getfld (state, name, buf, sizeof buf, in);
1293 cp = add (buf, NULLCP);
1294 while (state == FLDPLUS) {
1295 state = m_getfld (state, name, buf, sizeof buf, in);
1298 key.dsize = strlen (key.dptr = trimcpy (cp)) + 1;
1302 if ((db = dbm_open (file, O_RDWR | O_CREAT, 0600)) == NULL) {
1303 advise (file, "unable to perform dbm_open on");
1313 fl.l_type = F_WRLCK;
1317 if (fcntl (dbm_pagfno (db), F_SETLK, &fl) == -1) {
1318 advise (file, "unable to perform flock on");
1324 if (lockf (dbm_pagfno (db), F_LOCK) == NOTOK) {
1325 advise (file, "unable to perform lockf on");
1329 if (flock (dbm_pagfno (db), LOCK_EX) == NOTOK) {
1330 advise (file, "unable to perform flock on");
1336 value = dbm_fetch (db, key);
1337 if (value.dptr != NULL) {
1340 "Message-ID: %s already received on\n\tDate: %s",
1347 value.dsize = strlen (value.dptr =
1348 ddate + sizeof "Delivery-Date:") + 1;
1350 if (dbm_store (db, key, value, DBM_INSERT))
1351 advise (file, "possibly corrupt file");