/* slocal.c - MH style mailer to write to a local user's mailbox */
static char ident
[] = "@(#)$Id: slocal.c,v 1.24 1993/08/25 17:28:16 jromine Exp $";
/* This program implements mail delivery in the MH/MMDF style.
Under SendMail, users should add the line
"| /usr/local/lib/mh/slocal"
to their $HOME/.forward file.
Under MMDF-I, users should (symbolically) link /usr/local/lib/mh/slocal
Under stand-alone MH, post will automatically run this during local
This program should be used ONLY if you have "mts sendmail" or "mts mh"
or "mts mmdf1" set in your MH configuration.
#include "../h/dropsbr.h"
#include "../h/rcvmail.h"
#include "../zotnet/tws.h"
#include "../zotnet/mts.h"
#undef DBM /* used by ndbm.h */
#if defined(LOCKF) && !defined(F_ULOCK)
static struct swit switches
[] = {
static char *addr
= NULLCP
;
static char *user
= NULLCP
;
static char *info
= NULLCP
;
static char *file
= NULLCP
;
static char *sender
= NULLCP
;
static char *unixfrom
= NULLCP
;
static char *mbox
= NULLCP
;
static char *home
= NULLCP
;
static struct passwd
*pw
;
static char ddate
[BUFSIZ
];
static struct pair
*lookup ();
static struct pair hdrs
[NVEC
+ 1] = {
"Return-Path", NULL
, P_ADR
,
"Resent-Reply-To", NULL
, P_ADR
,
"Resent-From", NULL
, P_ADR
,
"Resent-Sender", NULL
, P_ADR
,
"Resent-To", NULL
, P_ADR
,
"Resent-cc", NULL
, P_ADR
,
static struct pair vars
[] = {
static TYPESIG
alrmser ();
struct passwd
*getpwnam ();
static int localmail(), usr_delivery(), split(), parse(), logged_in();
static int timely(), usr_file(), usr_pipe(), copyfile();
static expand(), glob(), copyinfo();
invo_name
= r1bindex (*argv
, '/');
switch (smatch (++cp
, switches
)) {
adios (NULLCP
, "-%s unknown", cp
);
(void) sprintf (buf
, "%s [switches] [address info sender]",
if (!(addr
= *argp
++))/* allow -xyz arguments */
adios (NULLCP
, "missing argument to %s", argp
[-2]);
if (!(info
= *argp
++))/* allow -xyz arguments */
adios (NULLCP
, "missing argument to %s", argp
[-2]);
if (!(user
= *argp
++))/* allow -xyz arguments */
adios (NULLCP
, "missing argument to %s", argp
[-2]);
if (!(file
= *argp
++) || *file
== '-')
adios (NULLCP
, "missing argument to %s", argp
[-2]);
if (!(sender
= *argp
++))/* allow -xyz arguments */
adios (NULLCP
, "missing argument to %s", argp
[-2]);
if (!(mbox
= *argp
++) || *mbox
== '-')
adios (NULLCP
, "missing argument to %s", argp
[-2]);
if (!(home
= *argp
++) || *home
== '-')
adios (NULLCP
, "missing argument to %s", argp
[-2]);
if (!(cp
= *argp
++) || *cp
== '-')
adios (NULLCP
, "missing argument to %s", argp
[-2]);
adios (NULLCP
, "only one maildelivery file at a time!");
switch (argp
- (argv
+ 1)) {
user
= (cp
= index (addr
, '.')) ? ++cp
: addr
;
if ((pw
= getpwnam (user
)) == NULL
)
adios (NULLCP
, "no such local user as %s", user
);
if (chdir (pw
-> pw_dir
) == NOTOK
)
(void) inigrp (pw
-> pw_name
, pw
-> pw_gid
);
(void) setgid (pw
-> pw_gid
);
(void) initgroups (pw
-> pw_name
, pw
-> pw_gid
);
(void) setuid (pw
-> pw_uid
);
if ((fd
= copyfile (fileno (stdin
), file
= tmpfil
, 1)) == NOTOK
)
adios (NULLCP
, "unable to create temporary file");
fprintf (stderr
, "temporary file \"%s\" selected\n", tmpfil
);
if ((fp
= fdopen (fd
, "r+")) == NULL
)
adios (NULLCP
, "unable to access temporary file");
(void) sprintf (mailbox
, "%s/%s",
mmdfldir
[0] ? mmdfldir
: pw
-> pw_dir
,
mmdflfil
[0] ? mmdflfil
: pw
-> pw_name
);
if ((now
= dtwstime ()) == NULL
)
adios (NULLCP
, "unable to ascertain local time");
(void) sprintf (ddate
, "Delivery-Date: %s\n", dtimenow ());
fprintf (stderr
, "addr=\"%s\" user=\"%s\" info=\"%s\" file=\"%s\"\n",
fprintf (stderr
, "sender=\"%s\" mbox=\"%s\" home=\"%s\" from=\"%s\"\n",
sender
, mbox
, home
, from
);
fprintf (stderr
, "ddate=\"%s\" now=%02d:%02d\n",
ddate
, now
-> tw_hour
, now
-> tw_min
);
done (localmail (fd
, from
, mdlvr
) != NOTOK
? RCV_MOK
: RCV_MBX
);
static int localmail (fd
, from
, mdlvr
)
if (stat (".maildelivery.pag", &st
) != NOTOK
&& check_msgid (fd
, ".maildelivery") == DONE
)
if (usr_delivery (fd
, mdlvr
? mdlvr
: ".maildelivery", 0, from
) != NOTOK
)
if (usr_delivery (fd
, maildelivery
, 1, from
) != NOTOK
)
printf ("(invoking hook)\n");
if (usr_hook (fd
, mbox
) != NOTOK
)
printf ("(trying normal delivery)\n");
return usr_file (fd
, mbox
, from
);
#define matches(a,b) (stringdex (b, a) >= 0)
static int usr_delivery (fd
, delivery
, su
, from
)
if ((fp
= fopen (delivery
, "r")) == NULL
)
if (fstat (fileno (fp
), &st
) == NOTOK
|| (st
.st_uid
!= 0 && (su
|| st
.st_uid
!= pw
-> pw_uid
))
printf ("%s: ownership/modes bad (%d, %d,%d,0%o)\n",
delivery
, su
, pw
-> pw_uid
, st
.st_uid
, st
.st_mode
);
while (fgets (buffer
, sizeof buffer
, fp
) != NULL
) {
if (cp
= index (buffer
, '\n'))
if ((vecp
= split (buffer
, vec
)) < 5)
fprintf (stderr
, "vec[%d]: \"%s\"\n", i
, vec
[i
]);
continue; /* if previous condition failed, don't
do this - else fall through */
continue; /* else fall */
if (uleq (vec
[5], "select")) {
if (logged_in () != NOTOK
)
if (vecp
> 7 && timely (vec
[6], vec
[7]) == NOTOK
)
if (uleq (field
, "default")) {
if (!parsed
&& parse (fd
) == NOTOK
) {
if ((p
= lookup (hdrs
, field
)) == NULL
|| (p
->p_value
== NULL
) /* XXX */
|| !matches (p
-> p_value
, pattern
)) {
if (!uleq (action
, "qpipe"))
continue; /* else fall */
expand (tmpbuf
, string
, fd
);
if (split (tmpbuf
, vec
) < 1)
status
= usr_pipe (fd
, tmpbuf
, vec
[0], vec
);
if (!uleq (action
, "pipe"))
continue; /* else fall */
expand (tmpbuf
, string
, fd
);
status
= usr_pipe (fd
, tmpbuf
, "/bin/sh", vec
+ 2);
if (!uleq (action
, "file"))
continue; /* else fall */
status
= usr_file (fd
, string
, from
); /* UUCP format? */
status
= usr_file (fd
, string
, NULLCP
);
if (!uleq (action
, "mbox"))
status
= usr_file (fd
, string
, NULLCP
);
if (!uleq (action
, "destroy"))
if (accept
&& status
== OK
)
return (won
? OK
: NOTOK
);
static int split (cp
, vec
)
for (i
= 0, s
= cp
; i
<= NVEC
;) {
while (isspace (*s
) || *s
== ',')
for (vec
[i
++] = ++s
; *s
!= 0 && *s
!= '"'; s
++)
(void) strcpy (s
- 1, s
);
if (*s
== QUOTE
&& *++s
!= '"')
while (*s
!= 0 && !isspace (*s
) && *s
!= ',')
if ((fd1
= dup (fd
)) == NOTOK
)
if ((in
= fdopen (fd1
, "r")) == NULL
) {
if (p
= lookup (hdrs
, "source"))
p
-> p_value
= getcpy (sender
);
if (p
= lookup (hdrs
, "addr"))
p
-> p_value
= getcpy (addr
);
for (i
= 0, state
= FLD
;;) {
switch (state
= m_getfld (state
, name
, field
, sizeof field
, in
)) {
lp
= add (field
, NULLCP
);
while (state
== FLDPLUS
) {
state
= m_getfld (state
, name
, field
, sizeof field
, in
);
for (p
= hdrs
; p
-> p_name
; p
++)
if (uleq (p
-> p_name
, name
)) {
if (!(p
-> p_flags
& P_HID
)) {
if (p
-> p_flags
& P_ADR
) {
dp
= cp
+ strlen (cp
) - 1;
p
-> p_value
= add (lp
, cp
);
if (p
-> p_name
== NULL
&& i
< NVEC
) {
p
-> p_name
= getcpy (name
);
advise (NULLCP
, "format error in message");
advise (NULLCP
, "internal error");
if (p
= lookup (vars
, "reply-to")) {
if ((q
= lookup (hdrs
, "reply-to")) == NULL
|| q
-> p_value
== NULL
)
q
= lookup (hdrs
, "from");
p
-> p_value
= getcpy (q
? q
-> p_value
: "");
fprintf (stderr
, "vars[%d]: name=\"%s\" value=\"%s\"\n",
p
- vars
, p
-> p_name
, p
-> p_value
);
for (p
= hdrs
; p
-> p_name
; p
++)
fprintf (stderr
, "hdrs[%d]: name=\"%s\" value=\"%s\"\n",
p
- hdrs
, p
-> p_name
, p
-> p_value
);
static expand (s1
, s2
, fd
)
if (c
!= '$' || *s2
!= LPAREN
)
for (cp
= ++s2
; *s2
&& *s2
!= RPAREN
; s2
++)
if (p
= lookup (vars
, cp
)) {
if (!parsed
&& (p
-> p_flags
& P_CHK
))
(void) strcpy (s1
, p
-> p_value
);
if (p
= lookup (vars
, "sender"))
p
-> p_value
= getcpy (sender
);
if (p
= lookup (vars
, "address"))
p
-> p_value
= getcpy (addr
);
if (p
= lookup (vars
, "size")) {
(void) sprintf (buffer
, "%d",
fstat (fd
, &st
) != NOTOK
? (int) st
.st_size
: 0);
p
-> p_value
= getcpy (buffer
);
if (p
= lookup (vars
, "info"))
p
-> p_value
= getcpy (info
);
for (p
= vars
; p
-> p_name
; p
++)
fprintf (stderr
, "vars[%d]: name=\"%s\" value=\"%s\"\n",
p
- vars
, p
-> p_name
, p
-> p_value
);
static struct pair
*lookup (pairs
, key
)
register struct pair
*pairs
;
for (; cp
= pairs
-> p_name
; pairs
++)
static int logged_in () {
if ((uf
= fopen ("/etc/utmp", "r")) == NULL
)
while (fread ((char *) &ut
, sizeof ut
, 1, uf
) == 1)
&& strncmp (user
, ut
.ut_name
, sizeof ut
.ut_name
) == 0) {
static int timely (t1
, t2
)
#define check(t,a,b) if (t < a || t > b) return NOTOK
#define cmpar(h1,m1,h2,m2) if (h1 < h2 || (h1 == h2 && m1 < m2)) return OK
if (sscanf (t1
, "%d:%d", &t1hours
, &t1mins
) != 2)
if (sscanf (t2
, "%d:%d", &t2hours
, &t2mins
) != 2)
cmpar (now
-> tw_hour
, now
-> tw_min
, t1hours
, t1mins
);
cmpar (t2hours
, t2mins
, now
-> tw_hour
, now
-> tw_min
);
static int usr_file (fd
, mailbox
, from
)
printf ("\tdelivering to file \"%s\"", mailbox
);
printf (" (uucp style)");
(void) sprintf (buffer
, "%s%s", from
, ddate
);
if ((md
= mbx_open (mailbox
, pw
-> pw_uid
, pw
-> pw_gid
, m_gmprot ()))
adorn ("", "unable to open:");
(void) lseek (fd
, (off_t
)0, 0);
if (mbx_copy (mailbox
, md
, fd
, mapping
, bp
, verbose
) == NOTOK
) {
adorn ("", "error writing to:");
(void) mbx_close (mailbox
, md
);
static int usr_hook (fd
, mailbox
)
if ((fd
= copyfile (fd
, tmpfil
, 0)) == NOTOK
) {
adorn ("unable to copy message; skipping hook\n");
(void) chown (tmpfil
, pw
-> pw_uid
, pw
-> pw_gid
);
(void) sprintf (receive
, "%s/.mh_receive", pw
-> pw_dir
);
switch (access (receive
, 01)) {
(void) sprintf (receive
, "%s/bin/rcvmail", pw
-> pw_dir
);
if (access (receive
, 01) == NOTOK
) {
printf ("\tnot present\n");
vec
[0] = r1bindex (receive
, '/');
i
= usr_pipe (fd
, "rcvmail", receive
, vec
);
static int usr_pipe (fd
, cmd
, pgm
, vec
)
printf ("\tdelivering to pipe \"%s\"", cmd
);
(void) lseek (fd
, (off_t
)0, 0);
for (i
= 0; (child_id
= fork ()) == NOTOK
&& i
< 5; i
++)
adorn ("fork", "unable to");
(void) freopen ("/dev/null", "w", stdout
);
(void) freopen ("/dev/null", "w", stderr
);
if ((fd
= open ("/dev/tty", 2)) != NOTOK
) {
(void) ioctl (fd
, TIOCNOTTY
, NULLCP
);
(void) setpgrp (0, getpid ());
(void) m_putenv ("USER", pw
-> pw_name
);
(void) m_putenv ("HOME", pw
-> pw_dir
);
(void) m_putenv ("SHELL", pw
-> pw_shell
);
switch (setjmp (myctx
)) {
(void) signal (SIGALRM
, alrmser
);
bytes
= fstat (fd
, &st
) != NOTOK
? (int) st
.st_size
: 100;
(void) alarm ((unsigned) (bytes
* 60 + 300));
status
= pidwait (child_id
, OK
);
if (status
== RP_MOK
|| status
== RP_OK
)
if ((status
& 0xff00) == 0xff00)
printf (", system error\n");
(void) pidstatus (status
, stdout
, ", loses");
return (status
== 0 ? OK
: NOTOK
);
(void) kill (child_id
, SIGKILL
);
(void) killpg (child_id
, SIGKILL
);
printf (", timed-out; terminated\n");
static TYPESIG
alrmser (i
)
static copyinfo (fp
, from
)
static char buffer
[BUFSIZ
];
if (unixfrom
) /* interface from copyfile */
else if (fgets (from
, BUFSIZ
, fp
) == NULL
)
adios (NULLCP
, "no message");
if (strncmp (from
, "From ", i
= strlen ("From "))) {
(void) strcpy (buffer
, from
+ i
);
if (cp
= index (buffer
, '\n')) {
for (cp
= buffer
+ strlen (buffer
) - 1; cp
>= buffer
; cp
--)
static int copyfile (qd
, tmpfil
, fold
)
(void) strcpy (tmpfil
, m_tmpfil (invo_name
));
if ((fd1
= creat (tmpfil
, 0600)) == NOTOK
)
if ((fd1
= open (tmpfil
, 2)) == NOTOK
)
while ((i
= read (qd
, buffer
, sizeof buffer
)) > 0)
if (write (fd1
, buffer
, i
) != i
) {
(void) lseek (fd1
, (off_t
)0, 0);
if ((fd2
= dup (qd
)) == NOTOK
) {
if ((qfp
= fdopen (fd2
, "r")) == NULL
) {
if ((fd2
= dup (fd1
)) == NOTOK
) {
if ((ffp
= fdopen (fd2
, "r+")) == NULL
) {
while (fgets (buffer
, sizeof buffer
, qfp
)) {
if (!strncmp (buffer
, "From ", i
))
register char *fp
, *cp
, *hp
, *ep
;
unixfrom
= getcpy (buffer
); /* save for later */
continue; /* but don't put in file */
hp
= cp
= index (fp
= unixfrom
+ i
, ' ');
while (hp
= index (++hp
, 'r'))
if (uprf (hp
, "remote from")) {
ep
= rindex (++hp
, '\n');
sprintf (buffer
, "Return-Path: %.*s!%.*s\n",
sprintf (buffer
, "Return-Path: %.*s\n",
#ifdef notdef /* mbx_copy does this */
(void) lseek (fd1
, (off_t
)0, 0);
static void adorn (what
, fmt
, a
, b
, c
, d
, e
, f
)
advise (what
, fmt
, a
, b
, c
, d
, e
, f
);
static int check_msgid (fd
, file
)
if ((fd1
= dup (fd
)) == NOTOK
)
if ((in
= fdopen (fd1
, "r")) == NULL
) {
switch (state
= m_getfld (state
, name
, buf
, sizeof buf
, in
)) {
if (!uleq (name
, "Message-ID")) {
state
= m_getfld (state
, name
, buf
, sizeof buf
, in
);
while (state
== FLDPLUS
) {
state
= m_getfld (state
, name
, buf
, sizeof buf
, in
);
key
.dsize
= strlen (key
.dptr
= trimcpy (cp
)) + 1;
if ((db
= dbm_open (file
, O_RDWR
| O_CREAT
, 0600)) == NULL
) {
advise (file
, "unable to perform dbm_open on");
if (fcntl (dbm_pagfno (db
), F_SETLK
, &fl
) == -1) {
advise (file
, "unable to perform flock on");
if (lockf (dbm_pagfno (db
), F_LOCK
) == NOTOK
) {
advise (file
, "unable to perform lockf on");
if (flock (dbm_pagfno (db
), LOCK_EX
) == NOTOK
) {
advise (file
, "unable to perform flock on");
value
= dbm_fetch (db
, key
);
if (value
.dptr
!= NULL
) {
"Message-ID: %s already received on\n\tDate: %s",
value
.dsize
= strlen (value
.dptr
=
ddate
+ sizeof "Delivery-Date:") + 1;
if (dbm_store (db
, key
, value
, DBM_INSERT
))
advise (file
, "possibly corrupt file");