home *** CD-ROM | disk | FTP | other *** search
- /*++
- /* NAME
- /* pc-maild 8
- /* SUMMARY
- /* deliver unsent mail
- /* PROJECT
- /* pc-mail
- /* PACKAGE
- /* nfs
- /* SYNOPSIS
- /* pc-maild [delay]
- /* DESCRIPTION
- /* This program should be run on the nfs file server that exports
- /* mail directories to MS-DOS pc-mail users. It replaces the
- /* (MS-DOS -> UNIX) transmission function of the MS-DOS \fIcico\fR
- /* program.
- /*
- /* The per-user mail directories (default: /usr/spool/pc-mail/\fIuser\fR)
- /* are scanned for outgoing mail every \fIdelay\fR seconds (default: 300).
- /* When outgoing mail is found, it is sent through the UNIX rmail program
- /* (uucp mail interface) and the corresponding files are removed from the
- /* user\'s mail directory.
- /*
- /* The program should run with root privileges. It will assume
- /* the (uid, gid) of the sending user before accessing mail files.
- /* COMMANDS
- /* rmail(1), uucp mail interface program
- /* FILES
- /* /usr/spool/pc-mail/\fIuser\fR/dNNNNN, mail message (unsent mail)
- /* /usr/spool/pc-mail/\fIuser\fR/xNNNNN, recipients, subject (unsent mail)
- /* /usr/spool/pc-mail/\fIuser\fR/qNNNNN, mail message (sent mail)
- /* /usr/spool/pc-mail/\fIuser\fR/rNNNNN, recipients, subject (sent mail)
- /* (NNNNN is the pc-mail "message id").
- /* SEE ALSO
- /* pc-mail(1)
- /* DIAGNOSTICS
- /* Errors found during initialization cause the program to
- /* terminate with a diagnostic on the standard error stream.
- /* All other errors are considered transient, i.e. if something
- /* fails, it is tried again at a later time. Where possible,
- /* diagnostics are logged through the syslog facility.
- /* BUGS
- /* Scanning mail directories is an inefficient way to detect
- /* unsent mail.
- /* AUTHOR(S)
- /* W.Z. Venema
- /* Eindhoven University of Technology
- /* Department of Mathematics and Computer Science
- /* Den Dolech 2, P.O. Box 513, 5600 MB Eindhoven, The Netherlands
- /* CREATION DATE
- /* Sun Oct 22 22:12:15 MED 1989
- /* LAST MODIFICATION
- /* 1/6/90 19:45:05
- /* VERSION/RELEASE
- /* 1.6
- /*--*/
-
- #ifndef lint
- static char sccsid[] = "@(#) pc-maild.c 1.6 1/6/90 19:45:05";
-
- #endif
-
- /*
- * General return-value conventions:
- *
- * int func(): 0 means OK
- *
- * stuff *func(): 0 means error
- *
- */
-
- #include <stdio.h>
- #include <pwd.h>
- #include <time.h>
- #include <signal.h>
- #include <sys/types.h>
- #include <sys/stat.h>
-
- #ifdef SYSLOG
- #include <syslog.h>
- #else
- #include "syslog.h"
- #endif
-
- #ifdef SYSV
- #include <sys/utsname.h>
- #include <ndir.h>
- #else
- #include <sys/types.h>
- #include <sys/dir.h>
- #include <sgtty.h>
- #endif
-
- #include "dosunix.h"
- #include "util.h"
- #include "mtime.h"
-
- /* Library functions */
-
- extern char *strtok();
- extern char *strncpy();
- extern struct passwd *getpwnam();
- extern unsigned sleep();
- extern void exit();
- extern void _exit();
- extern struct tm *localtime();
- extern char *asctime();
- extern long time();
-
- /* Local defines, declarations */
-
- #ifndef DELAY
- #define DELAY 300 /* default: scan every 5 minutes */
- #endif
-
- #ifndef MAILDIR
- #define MAILDIR "/usr/spool/pc-mail" /* default pc-mail directory tree */
- #endif
-
- #define MAXLINE 1024 /* max length of recipients line */
-
- void catchsig();
- void finduser();
- char *getrcpt();
- char *fullname();
-
- int delay = DELAY; /* the default delay */
- char *progname; /* my process name */
-
-
- int main(argc, argv)
- int argc;
- char **argv;
- {
- progname = *argv;
-
- /* Sanity checks */
-
- #ifndef DEBUG
-
- if (geteuid() != 0) {
- (void) fprintf(stderr, "%s: must be run as root\n", progname);
- exit(1);
- }
- #endif
-
- /* Check for command-line delay argument */
-
- if (argc > 1 && (delay = atoi(argv[1])) < 1)
- delay = DELAY;
-
- /* Become a daemon process */
-
- #ifndef DEBUG
- disconnect();
- #endif
-
- /* Set up signal handling */
-
- catchsig();
-
- /* Enable syslogging */
-
- (void) openlog(progname, LOG_PID, LOG_MAIL);
- syslog(LOG_WARNING, "daemon restarted");
-
- /* Setup a decent environment */
-
- if (putenv("PATH=/bin:/usr/bin:/usr/ucb") || putenv("IFS= \t\n")) {
- syslog(LOG_WARNING, "initialization failed (insufficient resources)");
- exit(1);
- }
- /* Enter the main loop */
-
- finduser();
- /* NOTREACHED */
- }
-
- /* finduser - repeatedly iterate over all pc-mail user directories */
-
- void finduser()
- {
- register DIR *maildp;
- register struct direct *dp;
- register struct passwd *uinfo;
- MTIME *dirtime;
- char userdir[BUFSIZ];
- struct stat st;
-
- /*
- * Ignore names that start with a period.
- *
- * Ignore names that do not correspond to a directory.
- *
- * Ignore directories that did not change since the last time they were
- * known to hold no unsent mail.
- *
- * Ignore directories that do not have a name equal to the login name of a
- * user.
- */
-
- for (;;) {
- if ((e_chdir(MAILDIR) == 0) && (maildp = e_opendir(MAILDIR))) {
- while (dp = readdir(maildp)) {
- if ((dp->d_name[0] != '.')
- && (stat(dp->d_name, &st) == 0)
- && ((st.st_mode & S_IFMT) == S_IFDIR)
- && (st.st_mtime > (dirtime = mtime(dp->d_name))->time)
- && ((uinfo = getpwnam(dp->d_name)) != 0)) {
- (void) sprintf(userdir, "%s/%s", MAILDIR, dp->d_name);
- if (findmail(uinfo, userdir) == 0)
- dirtime->time = st.st_mtime; /* say it was empty */
- }
- }
- closedir(maildp); /* done with this user */
- (void) chdir("/"); /* be friendly */
- }
- (void) sleep((unsigned) delay); /* try again later */
- }
- }
- /* findmail - enter a user\'s mail directory and scan for unsent mail */
-
- int findmail(uinfo, userdir)
- struct passwd *uinfo;
- char *userdir;
- {
- register DIR *dd;
- register struct direct *p;
- int seqno;
- static char xfile[BUFSIZ]; /* file with recipients (unsent mail) */
- static char rfile[BUFSIZ]; /* same, but with "Sent" status */
- static char dfile[BUFSIZ]; /* file with message (unsent mail) */
- static char qfile[BUFSIZ]; /* same, but with "Sent" status */
- int found = 0; /* no mail found yet */
-
- /*
- * Use the fact that 'x' files (recipient addresses) are created later
- * than 'd' files (message body) when a pc user generates a message.
- * Extract the pc-mail message id from the file name and try to pipe the
- * message through the UNIX rmail command. All knowledge about pc-mail
- * file names resides in this function. Return zero if no unsent mail was
- * found.
- */
-
- if ((e_chdir(userdir) == 0) && (dd = e_opendir(userdir))) {
- while (p = readdir(dd)) {
- if (*p->d_name == 'x' && sscanf(p->d_name + 1, "%d", &seqno) == 1) {
- (void) sprintf(xfile, "x%05d", seqno); /* recipients */
- if (strcmp(p->d_name, xfile) == 0) { /* ignore junk */
- (void) sprintf(dfile, "d%05d", seqno);
- (void) sprintf(rfile, "r%05d", seqno);
- (void) sprintf(qfile, "q%05d", seqno);
- pickup(uinfo, xfile, dfile, rfile, qfile);
- found = 1; /* found unsent mail */
- }
- }
- }
- closedir(dd);
- (void) chdir(MAILDIR);
- }
- return (found);
- }
-
- /* pickup - pick up one message from a user\'s mail directory */
-
- pickup(uinfo, xfile, dfile, rfile, qfile)
- struct passwd *uinfo;
- char *xfile;
- char *dfile;
- char *rfile;
- char *qfile;
- {
-
- /*
- * Actual delivery must be done with the (uid, gid) of the sender, or the
- * From: lines will not be correct. Therefore, we do delivery in a child
- * process. This also avoid all kinds of nasty security problems. All
- * errors are considered non-fatal.
- */
-
- #ifdef DEBUG
- sendasuser(uinfo, xfile, dfile, rfile, qfile); /* don't fork */
- #else
- switch (e_fork()) {
- case -1: /* failure */
- break;
- case 0: /* child */
- sendasuser(uinfo, xfile, dfile, rfile, qfile);
- _exit(0);
- /* NOTREACHED */
- default: /* parent */
- (void) wait((int *) 0);
- break;
- }
- #endif
- }
-
- /* sendasuser - send mail through rmail(1), having (uid, gid) of sender */
-
- sendasuser(uinfo, xfile, dfile, rfile, qfile)
- struct passwd *uinfo;
- char *xfile;
- char *dfile;
- char *rfile;
- char *qfile;
- {
- char *dest; /* recipient address(es) */
-
- /*
- * User-specific mail files must be opened AFTER we have assumed the
- * (uid, gid) of the pc-mail user; this in order to avoid nasty security
- * holes. When delivery succeeds, we rename the spool files so they
- * reflect the "Sent" status.
- */
-
- if ((setugid(uinfo) == 0) /* assume proper (uid, gid) */
- && (dest = getrcpt(uinfo, xfile)) /* extract recipients */
- && (rmail(uinfo, dfile, dest) == 0)) { /* pipe message through rmail */
- (void) unlink(rfile); /* just in case */
- (void) unlink(qfile); /* just in case */
- (void) u_link(uinfo, xfile, rfile); /* change status to "Sent" */
- (void) u_link(uinfo, dfile, qfile); /* change status to "Sent" */
- (void) u_unlink(uinfo, xfile); /* recipients file */
- (void) u_unlink(uinfo, dfile); /* message body file */
- }
- }
-
- /* setugid - assume (uid, gid) of user */
-
- int setugid(uinfo)
- struct passwd *uinfo;
- {
- if (setgid(uinfo->pw_gid)) {
- syslog(LOG_WARNING, "setgid(%s) failed: %m", uinfo->pw_name);
- return (1);
- }
- if (setuid(uinfo->pw_uid)) {
- syslog(LOG_WARNING, "setuid(%s) failed: %m", uinfo->pw_name);
- return (1);
- } else {
- return (0);
- }
- }
-
- /* getrcpt - extract recipient from user mail file */
-
- char *getrcpt(uinfo, xfile)
- struct passwd *uinfo;
- char *xfile;
- {
- FILE *xfp; /* recipient file pointer */
- static char dest[MAXLINE]; /* recipient names */
- register char *ret;
-
- if ((xfp = u_fopen(uinfo, xfile, "r")) == 0) {
- return (0);
- } else {
- pc_wait(fileno(xfp)); /* let pc finish writing */
- ret = dosgets(dest, sizeof(dest), xfp);
- (void) fclose(xfp);
- if (ret == 0)
- syslog(LOG_WARNING, "no recipients specified in %s/%s",
- uinfo->pw_name, xfile);
- return (ret);
- }
- }
-
- /* rmail - pipe a pc mail message through the UNIX rmail program */
-
- int rmail(uinfo, dfile, dest)
- struct passwd *uinfo; /* originator */
- char *dfile; /* message file */
- char *dest; /* recipients */
- {
- static char cmd[MAXLINE+20]; /* command + arguments */
- FILE *dfp; /* source stream */
- FILE *pfp; /* output stream */
- int ret; /* return value, 0 if OK */
- long secs; /* absolute UNIX time */
- static char hostname[BUFSIZ]; /* our host name */
-
- /*
- * The UNIX rmail command needs a UUCP-style From_ line.
- *
- * The To: and From: lines can be added for esthetical porposes.
- *
- * Report communication failures with the rmail command. Error returns from
- * rmail are ignored; they should be handled in sendmail.
- */
-
- if (dfp = u_fopen(uinfo, dfile, "r")) { /* open message file */
- (void) sprintf(cmd, "rmail %s", dest);
- if ((pfp = popen(cmd, "w")) == 0) { /* invoke rmail... */
- syslog(LOG_WARNING, "cannot invoke %.20s...: %m", rmail);
- ret = 1;
- } else {
- secs = time((long *) 0);
- (void) gethostname(hostname, sizeof(hostname));
- (void) fprintf(pfp, "From %s %.24s remote from %s\n",
- uinfo->pw_name,
- asctime(localtime(&secs)),
- hostname); /* add UUCP From_ line */
- #ifdef RFC822
- rfc822hdr(uinfo, dest, pfp); /* do RFC822 stuff */
- #endif
- if (ret = dos2unix(dfp, pfp)) /* append message body */
- syslog(LOG_WARNING, "write to rmail failed: %m");
- (void) pclose(pfp);
- }
- (void) fclose(dfp);
- }
- return (ret);
- }
-
- /* rfc822hdr - generate subset of RFC822 header lines */
-
- rfc822hdr(uinfo, dest, pfp)
- register struct passwd *uinfo;
- char *dest;
- register FILE *pfp;
- {
- char *sep = " ,\t\r\n";
- char *name;
- int n = 0;
-
- /*
- * There are a few problems with this function. First of all, it destroys
- * the dest argument. In the second place, putting each recipient on a
- * separate header line is ugly; fortunately, sendmail will fix this.
- */
-
- (void) fprintf(pfp, "From: %s (%s)\n", uinfo->pw_name,
- fullname(uinfo)); /* add From: header line */
- for (name = strtok(dest, sep); name; name = strtok((char *) 0, sep))
- (void) fprintf(pfp, "%s%s", n == 0 ? "To: " : ",\n ", name);
- if (n)
- (void) putc('\n', pfp);
- }
-
- /* fullname - extract full name from gecos field */
-
- char *fullname(uinfo)
- struct passwd *uinfo;
- {
- static char name[BUFSIZ];
-
- /* This code assumes BSD-style gecos fields (name,stuff,stuff...) */
-
- if (sscanf(uinfo->pw_gecos, "%[^,]", name) == 0)
- name[0] = '\0';
- return (name);
- }
-
- /* gotsig - caught a signal; terminate with diagnostic */
-
- void gotsig(sig)
- int sig;
- {
- syslog(LOG_WARNING, "going down on signal %d", sig);
- closelog();
- exit(sig);
- }
-
- /* catchsig - catch some signals */
-
- void catchsig()
- {
- #ifdef DEBUG
- (void) signal(SIGHUP, gotsig);
- (void) signal(SIGINT, gotsig);
- (void) signal(SIGQUIT, gotsig);
- #else
- (void) signal(SIGHUP, SIG_IGN);
- (void) signal(SIGINT, SIG_IGN);
- (void) signal(SIGQUIT, SIG_IGN);
- #endif
- (void) signal(SIGBUS, gotsig);
- (void) signal(SIGSEGV, gotsig);
- (void) signal(SIGTERM, gotsig);
- }
-
- /* disconnect - become a daemon process */
-
- disconnect()
- {
- #ifndef SYSV
- int fd;
-
- #endif
-
- /* Get rid of the parent process */
-
- switch (e_fork()) {
- case -1: /* failure */
- exit(1);
- /* NOTREACHED */
- default:
- _exit(0); /* parent */
- /* NOTREACHED */
- case 0: /* child */
- break;
- }
-
- /* Get rid of the controlling terminal */
-
- (void) close(0);
- (void) close(1);
- (void) close(2);
- #ifdef SYSV
- (void) setpgrp();
- #else
- if ((fd = open("/dev/tty", 0)) >= 0) {
- (void) ioctl(fd, TIOCNOTTY, 0);
- (void) close(fd);
- }
- #endif
- }
-
- /* pc_wait - wait till the pc has finished writing a file */
-
- pc_wait(fd)
- int fd;
- {
- struct stat st;
- long oldsize = 0;
-
- /*
- * Repeatedly sleep one second until the file size does not change
- * anymore.
- *
- * At first sight, this does not seem to be a very robust algorithm. It is,
- * however, sufficient. The pc sofware will first create a message file,
- * then the file with recipient addresses. The pc-maild program, on the
- * other hand, will read the recipient-address file first. If that file
- * turns out to be empty, it will try again at a later time. So, the only
- * time we may produce an incorrect result is under the following
- * conditions:
- *
- * (1) the file with recipient names is longer than the PC/NFS packet size
- * or the pc\'s stdio buffer size.
- *
- * (2) the network connection goes down for > 1 second after part of the
- * data has arrived in the file with recipient addresses.
- */
-
- while (fstat(fd, &st) == 0 && oldsize != st.st_size) {
- oldsize = st.st_size;
- (void) sleep(1);
- }
- }
-
- #ifdef SYSV
-
- /* gethostname - BSD compatibility routine */
-
- gethostname(name, len)
- char *name;
- int len;
- {
- struct utsname ut;
-
- (void) uname(&ut);
- (void) strncpy(name, ut.nodename, len);
- return (0);
- }
-
- #endif
-