[comp.sources.misc] v04i130: lmail, a local mail delivery program

zeeff@b-tech.ann-arbor.mi.us.UUCP (Jon Zeeff) (10/22/88)

Posting-number: Volume 4, Issue 130
Submitted-by: "Jon Zeeff" <zeeff@b-tech.ann-arbor.mi.us.UUCP>
Archive-name: lmail

Too many people are requesting this so I guess it should go to
comp.sources.misc.

This program is a local mail delivery agent.  It's primary use is to
add piping to files and programs for sites running smail2.5.  Make sure that
it does locking the way you want it to.

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  lmail.c
# Wrapped by zeeff@b-tech on Mon Oct 17 10:26:40 1988
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f lmail.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"lmail.c\"
else
echo shar: Extracting \"lmail.c\" \(15989 characters\)
sed "s/^X//" >lmail.c <<'END_OF_lmail.c'
X/* #ident "@(#)lmail.c	1.1.1.1	88/09/09 15:07:52 " */
X/*
X *	lmail replacement  V 2.7 
X *
X * Copyright 1988 Jon Zeeff	(umix!b-tech!zeeff)
X * Updated by "Greg A. Woods" <umix!ixpierre!woods>
X *
X * Permission is granted to use this in any manner provided that    
X * 1) the copyright notice is left intact, 2) you don't hold me 
X * responsible for any bugs and 3) you mail me any improvements that you 
X * make.  
X * 
X * This program can be used with smail as the local delivery agent (lmail).
X * It's primary benefit is that it allows forwarding to programs and files.
X * It also allows undeliverable local mail to be saved.
X * 
X * Caution: I wrote this for my own use and it does what I want.  I    
X * haven't looked into all portability and security issues nor is the 
X * code as clean as I would like.  Use at your own risk.  
X * 
X * Note that a .fwd file in /usr/spool/uucppublic is ignored since    
X * it is usually publically writable.  If you have other publically 
X * writable home directories, you need to change this program to also 
X * exclude these other directories.  
X * 
X * This program should be:
X * 
X *	-rws--x--x   1 root     mail       19030 Sep 14 12:11 /bin/lmail
X * 
X * You need to create an empty file /usr/mail/.lock
X * and lmail-aliases.  Aliases should contain the name
X * you want aliased followed by new name(s).  Eg.
X * 
X *	postmaster sam "|/usr/postmaster/postsaver -c"
X * 
X * will cause mail to postmaster to go to sam and be piped into  the 
X * program postsaver with the -c option.  Both will be done while suid 
X * MAILMAN.
X * 
X * Users can also forward mail with a .fwd file in their home    
X * directory.  This contains just new names.  References to files and 
X * programs in user's .fwd file will be executed suid that user.  
X * This is a security hole if the directory is writable by others!
X * Also be careful with .fwd files where several ids share a home
X * directory.
X * 
X * This program was written for a Sys V.2 or V.3 system running smail 2.5.
X * 
X * Step by step installation:
X * 
X * 1) Change MAILGID define if mail is not group 6.
X * 2) Add mailman to /etc/passwd with no special uid or group.
X * 3) Compile with 'cc lmail.c -s -O -o lmail'
X * 4) Create empty files lmail-aliases and /usr/mail/.lock
X *    These should be rw group mail.
X * 
X * 5) Install as /bin/lmail with suid root perms.
X * 6) Test 
X */
X
X#include <stdio.h>
X#include <limits.h>
X#include <sys/types.h>
X#include <pwd.h>
X#include <grp.h>
X#include <utmp.h>
X#include <signal.h>
X#include <ctype.h>
X#include <time.h>
X#include <sys/stat.h>
X#include <setjmp.h>
X#include <sys/utsname.h>
X#include <string.h>
X
Xvoid		exit();
Xvoid		_exit();
Xunsigned int	sleep();
Xtime_t		time();
X
X/*
X *	Change these defines to fit your needs
X */
X#define MAX_LINE	512			/* input line buffer size */
X#define DEF_PATH	"PATH=/bin:/usr/bin:/usr/lbin"
X#define MAIL_TMPFILE	"/tmp/rmXXXXXX"
X#define ROOT		"root"
X#define DEF_IFS		"IFS= \t\n"
X#define DEF_SHELL	"SHELL=/bin/sh"
X#define FROM_LINE	"From "
X#define FROM_FMT	"From %s %24.24s\n"
X#define FROM_PREFX	'>'
X#define REMOTE_FROM	" remote from "
X#define ALIAS_FILE	"/usr/local/lib/lmail-aliases"
X#define MAIL_DIR	"/usr/mail/"
X#define LOCK_SUFF	".lock"
X#define LOCK_FILE	"/usr/mail/.lock"
X#define FWD_LINE	"Forward to "
X#define FWD_FILE	"/.fwd"
X#define PUBDIR_FWD	"/usr/spool/uucppublic/.fwd"
X#define REMOTE_MAILER	"/bin/rmail %s"	 /* %s will be replace by address */
X#ifndef PATH_MAX
X#define PATH_MAX	1024
X#endif
X
X
X
X/*
X * GID of mail for creating mail files in /usr/mail. It's faster to just type
X * it in here, and it probably won't change anyway (we hope).
X */
X#define MAILGID		6
X
X/*
X * MAILMAN should be a user id with no special permissions or files.  Make
X * sure you add a /etc/passwd entry for it, 'cause I'm going to check.
X */
X#define MAILMAN		"mailman"	/* "mailman" */
X
X/*
X * BADMAIL is a name to send a copy of bad mail to.  Can be left undefined.
X */
X#define BADMAIL		"/usr/mail/badmail"	/* "/usr/mail/badmail" */
X
X/*
X * Maximum size of aliasing table (NOTE: this uses a lot of space!)
X */
X#define MAX_ADDR	50
X
Xstruct {
X	char	dest[MAX_LINE];		/* to whom it should go */
X	char	source[MAX_LINE];	/* who it is from */
X	char	user[MAX_LINE]; 	/* user to suid to for '|' and files */
X} table[MAX_ADDR];
X
Xchar		*thissys;		/* This system's name */
XFILE		*mailfile;		/* FILE pointer for mailbox */
Xint		num_addresses;
Xlong		iop;
Xstruct utsname	utsn;
Xstruct passwd	*pwd;
Xstruct passwd	*pwd_mailman;
X
Xstruct passwd	*getpwnam();
XFILE		*fopen();
Xunsigned short	getuid();
Xchar		*mktemp();
XFILE		*popen();
X
Xvoid		alias();
Xvoid		copy();
Xvoid		lock();
Xvoid		unlock();
X
X/* ARGSUSED */
Xint
Xmain(argc, argv, envp)
X	int	argc;
X	char	*argv[];
X	char	*envp[];
X{
X	char	*address;
X	char	from[MAX_LINE];		/* Original author */
X	char	line[MAX_LINE];
X	int	i;
X	int	error_flag = 0;
X	char	*ptr;
X
X	static char	tempfname[] = MAIL_TMPFILE;
X
X	if (argc < 2)
X		exit(1);	/* return error if not at least 1 address */
X	umask(006);		/* mail files MUST be group write-able */
X	uname(&utsn);
X	/*
X	 * If being fed from a pipe, copy to a temp file so we can rewind
X	 */
X	if (fseek(stdin, 0L, 0) != 0) {
X		mailfile = fopen(mktemp(tempfname), "w+");
X		unlink(tempfname);		/* it'll be gone when we are */
X		if (mailfile == NULL) {
X			(void) fprintf(stderr, "Can't create temp file - no mail delivered\n");
X			exit(3);
X		}
X		while (fgets(line, MAX_LINE, stdin) != NULL) {
X			if (fputs(line, mailfile) == EOF) {
X				(void) fprintf(stderr, "Can't write temp file - no mail delivered\n");
X				exit(3);
X			}
X		}
X		rewind(mailfile);
X	} else
X		mailfile = stdin;
X	thissys = utsn.nodename;
X	if ((pwd_mailman = getpwnam(MAILMAN)) == NULL) {
X		(void) fprintf(stderr, "*** Error - can't find mailman uid\n");
X		exit(7);
X	}
X	time(&iop);
X	putenv(DEF_PATH);
X	putenv(DEF_IFS);
X	putenv(DEF_SHELL);
X	/*
X	 * Get the From_ line for the author - assume smail has folded it
X	 */
X	from[0] = '\0';
X	fgets(line, MAX_LINE, mailfile);
X	rewind(mailfile);
X	if (strncmp(line, FROM_LINE, sizeof(FROM_LINE) - 1) != 0) {
X		(void) fprintf(stderr, "*** Error - mail is in incorrect format\n");
X		exit(2);
X	}
X	/*
X	 * If remote from exists, then include that site name in address so that
X	 * there is no remote from on the end
X	 */
X	ptr = strrchr(line, ' ') - 1;
X	while (*ptr != ' ')
X		--ptr;
X	--ptr;
X	while (*ptr != ' ')
X		--ptr;
X	if (strncmp(ptr, REMOTE_FROM, sizeof(REMOTE_FROM) - 1) == 0) {
X		(void) strcat(from, strrchr(line, ' ') + 1);
X		*(strrchr(from, '\n')) = '!';
X	}
X	(void) strcat(from, strchr(line, ' ') + 1);
X	*(strchr(from, ' ')) = '\0';
X	/*
X	 * Put the first entrys into the aliasing table
X	 */
X	--argc;
X	for (num_addresses = 0; num_addresses < argc; ++num_addresses) {
X		address = argv[num_addresses+1];
X		if (ptr = strchr(address, ' '))
X			*ptr = '\0';	/* Remove trailing spaces */
X		/*
X		 * Mailing to file or program is illegal at this point We
X		 * can't have outsiders mailing directly to files
X		 */
X		if (address[0] == '/' || address[0] == '|')
X			exit(1);
X		(void) strcpy(table[num_addresses].dest, address);
X		(void) strcpy(table[num_addresses].source, from);
X		(void) strcpy(table[num_addresses].user, MAILMAN);
X	}
X	alias();			/* expand address recursively */
X	signal(SIGHUP, SIG_IGN);
X	signal(SIGINT, SIG_IGN);
X	signal(SIGQUIT, SIG_IGN);
X	/*
X	 * Now deliver them
X	 */
X	for (i = 0; i < num_addresses; i++) {
X		if (*(table[i].source) != '\0')	/* if not deleted */
X			error_flag |= deliver(mailfile, from, table[i].dest, table[i].user);
X	}
X#ifdef BADMAIL
X	if (error_flag)
X		deliver(mailfile, from, BADMAIL, ROOT);
X#endif
X	/*
X	 * Realize here that if there is any kind of error, smail will return
X	 * mail to the author.  Even if the error was on the part of some
X	 * local user who did an alias wrong.
X	 */
X	return(error_flag);
X}
X
X
X/*
X * This routine recursively aliases an address creating a table of addresses.
X */
Xvoid
Xalias()
X{
X	FILE		*in_file;
X	int		i;
X	int		j;
X	char		*p;
X	char		*p2;
X	char		owner[MAX_LINE];
X	char		file[PATH_MAX];
X	char		line[MAX_LINE];
X	FILE		*aliases;
X	struct stat	statbuf;
X
X	if ((aliases = fopen(ALIAS_FILE, "r")) == (FILE *) NULL)
X		return;
X	/*
X	 * Make sure someone didn't break mail and link some file to aliases.
X	 * Sys V doesn't have sym links, so we don't worry about that.
X	 */
X	fstat(fileno(aliases), &statbuf);
X	if (statbuf.st_nlink > 1) {
X		fclose(aliases);
X		return; 
X	}
X	for (i = 0; i < num_addresses; ++i) {
X		/*
X		 * Only alias it if it has never been seen before
X		 */
X		for (j = 0; j < i; ++j) {
X			if (strcmp(table[j].dest, table[i].dest) == 0)
X				break;
X		}
X		if (j < i)
X			continue;
X		/*
X		 * find a matching line in aliases
X		 */
X		rewind(aliases);
X		while (fscanf(aliases, "%s %s", file, line) > 0) {
X			if (strcmp(file, table[i].dest) == 0)
X				break;
X		}
X		if (!feof(aliases)) { 		/* we found one in aliases */
X			p = line;
X			(void) strcpy(owner, MAILMAN);
X		} else {
X			/*
X			 * try the users .fwd file.  This is preferred over
X			 * Forward to lines
X			 */
X			if ((pwd = getpwnam(table[i].dest)) == NULL)
X				continue;
X			(void) strcpy(file, pwd->pw_dir);
X			(void) strcat(file, FWD_FILE);
X			/*
X			 * /usr/spool/uucppublic is normally a publically
X			 * writable home directory.  Ignore any .fwd there.
X			 */
X			if (strcmp(file, PUBDIR_FWD) != 0 && (in_file = fopen(file, "r")) != NULL) {
X				/*
X				 * ignore it if there is anything funny going
X				 * on with links
X				 */
X				fstat(fileno(in_file), &statbuf);
X				if (statbuf.st_nlink > 1 || fgets(line, MAX_LINE, in_file) == NULL) {
X					fclose(in_file);
X					continue;
X				}
X				fclose(in_file);
X				p = line;
X				(void) strcpy(owner, table[i].dest);
X			} else {
X				/*
X				 * maybe they have a Forward to in their mail
X				 * file
X				 */
X				(void) strcpy(file, MAIL_DIR);
X				(void) strcat(file, table[i].dest);
X				if ((in_file = fopen(file, "r")) == (FILE *) NULL)
X					continue;
X				/*
X				 * ignore it if there is anything funny going
X				 * on with links
X				 */
X				fstat(fileno(in_file), &statbuf);
X				if (statbuf.st_nlink > 1 || fgets(line, MAX_LINE, in_file) == (char *) NULL) {
X					fclose(in_file);
X					continue;
X				}
X				fclose(in_file);
X				if (strncmp(line, FWD_LINE, sizeof(FWD_LINE) - 1) != 0) continue;
X				p = line + 11;
X				/*
X				 * we only allow simple forwards with this
X				 * method this is overly simple
X				 */
X				if (strchr(p, '/') || strchr(p, '|'))
X					continue;
X				(void) strcpy(owner, MAILMAN);
X			}
X		}
X		/*
X		 * p now points to a line of addresses.  Mark the current
X		 * entry as deleted since it was just aliased.
X		 */
X		*(table[i].source) = '\0';
X		while (*p > '\n') {
X			if (*p == '"') { 
X				p2 = p + 1;
X				while (*(++p) && *p != '"')
X					;	/* NO_OP */
X			} else {
X				p2 = p;
X				while (*p > ' ')
X					++p;
X			}
X			if (*p != '\0')
X				*(p++) = '\0';
X			/*
X			 * Is an unaliased version of this already in the
X			 * table?
X			 */
X			for (j = 0; j < num_addresses; ++j) {
X				if (strcmp(table[j].dest, p2) == 0 && *(table[j].source) != '\0')
X					break;
X			}
X			/*
X			 * Add new entry if it finished the above loop
X			 */
X			if (j == num_addresses) {
X				(void) strcpy(table[num_addresses].dest, p2);
X				(void) strcpy(table[num_addresses].source, table[i].dest);
X				(void) strcpy(table[num_addresses].user, owner);
X				if (++num_addresses >= MAX_ADDR) { 
X					--num_addresses;
X					return;
X				}
X			}
X			/*
X			 * Move past spaces
X			 */
X			while (*p == ' ')
X				++p;
X		}
X	}
X	fclose(aliases);
X	return;
X}
X
X/*
X * This routine attempts to deliver the mail
X */
Xint
Xdeliver(in_fd, author, dest_ptr, user)
X	FILE	*in_fd;		/* Input file */
X	char	*author;	/* Who message is from (with address) */
X	char	*dest_ptr;	/* Who message is to */
X	char	*user;		/* User responsible for this to address */
X{
X	char	dest[MAX_LINE];
X	char	temp[MAX_LINE];
X	FILE	*outfile;
X	int	pid;
X	int	w;
X	int	status;
X
X	rewind(in_fd);
X	(void) strcpy(dest, dest_ptr);
X	if ((strchr(dest, '!') || strchr(dest, '@') || strchr(dest, '%')) && dest[0] != '|' && dest[0] != '/') {	/* A remote address */
X		/*
X		 * fix things that have only a %
X		 */
X		if (strchr(dest, '!') == (char *) NULL && strchr(dest, '@') == (char *) NULL) 
X			*(strchr(dest, '%')) = '@';
X		sprintf(temp, REMOTE_MAILER, dest);
X		if ((pwd = getpwnam(user)) == (struct passwd *) NULL)
X			pwd = pwd_mailman;
X		if (pid = fork()) {
X			while ((w = wait(&status)) != pid && w != -1)
X				;	/* NO_OP */
X			if (status || w == -1) {
X				(void) fprintf(stderr, "\nCannot run %s\n", temp);
X				return(8);
X			}
X		} else {
X			setgid(pwd->pw_gid);
X			setuid(pwd->pw_uid);
X			umask(066);
X			if ((outfile = popen(temp, "w")) == (FILE *) NULL)
X				_exit(1);
X			copy(outfile, in_fd, author);
X			if (pclose(outfile))
X				_exit(1);
X			_exit(0);
X		}
X	} else {
X		if (dest[0] == '|') {
X			if ((pwd = getpwnam(user)) == (struct passwd *) NULL)
X				pwd = pwd_mailman;
X			if (pid = fork()) {
X				while ((w = wait(&status)) != pid && w != -1)
X					;
X				if (status || w == -1) {
X					(void) fprintf(stderr, "\nCannot pipe to program %s\n", dest);
X					return(8);
X				}
X			} else {
X				setgid(pwd->pw_gid);
X				setuid(pwd->pw_uid);
X				umask(066);
X				if ((outfile = popen(dest + 1, "w")) == (FILE *) NULL)
X					_exit(1);
X				copy(outfile, in_fd, author);
X				if (pclose(outfile))
X					_exit(1);
X				_exit(0);
X			}
X		} else { 
X			if (dest[0] == '/') {
X				if ((pwd = getpwnam(user)) == (struct passwd *) NULL)
X					pwd = pwd_mailman;
X				if (pid = fork()) {
X					while ((w = wait(&status)) != pid && w != -1)
X						;	/* NO_OP */
X					if (status || w == -1) {
X						(void) fprintf(stderr, "\nCannot save in file %s\n", dest);
X						return(8);
X					}
X				} else {
X					setgid(pwd->pw_gid);
X					setuid(pwd->pw_uid);
X					umask(066);
X					lock2(dest);
X					if ((outfile = fopen(dest, "a")) == (FILE *) NULL)
X						_exit(1);
X					copy(outfile, in_fd, author);
X					fclose(outfile);
X					unlock(dest);
X					_exit(0);
X				}
X			} else {		/* a local user address */
X				/*
X				 * Check if this is a valid user
X				 */
X				if ((pwd = getpwnam(dest)) == NULL) {
X					(void) fprintf(stderr, "User %s does not exist\n", dest);
X					return(4);
X				}
X				if (pid = fork()) {
X					while ((w = wait(&status)) != pid && w != -1)
X						;
X					if (status || w == -1) {
X						(void) fprintf(stderr, "\nCannot save in file %s\n", dest);
X						return(8);
X					}
X				} else {
X					setgid(MAILGID);
X					setuid(pwd_mailman->pw_uid);	/* give up root uid */
X					sprintf(temp, "%s%s", MAIL_DIR, dest);
X					lock(temp);
X					status = 0;
X					if ((outfile = fopen(temp, "a")) == NULL) {
X						(void) fprintf(stderr, "** Can't open user mail file %s\n", temp);
X						status = 5;
X					} else
X						copy(outfile, in_fd, author);
X					chown(temp, (int)pwd->pw_uid, MAILGID);
X					if (fclose(outfile)) {
X						(void) fprintf(stderr, "** Could not close mail file %s\n", temp);
X						status = 7;
X					}
X					unlock(temp);
X					_exit(status);
X				}
X			}
X		}
X	}
X	return(0);
X}
X
Xvoid
Xcopy(out, in, from)
X	FILE	*out;
X	FILE	*in;
X	char	*from;
X{
X	char	temp[MAX_LINE];
X
X	/*
X	 * Throw away From_ line and replace with our own
X	 */
X	fgets(temp, MAX_LINE, in);
X	(void) fprintf(out, FROM_FMT, from, ctime(&iop));
X	while (fgets(temp, MAX_LINE, in) != NULL) {
X		if (strncmp(temp, FROM_LINE, sizeof(FROM_LINE) - 1) == 0)
X			(void) fputc(FROM_PREFX, out); 
X		(void) fputs(temp, out);
X	}
X	(void) fputc('\n', out);
X	return;
X}
X
X/*
X * This routine works while uid = root
X */
Xvoid
Xlock(file)
X	char	*file;
X{
X	char	lockfile[PATH_MAX];
X	int	i;
X
X	(void) strcpy(lockfile, file);
X	(void) strcat(lockfile, LOCK_SUFF);
X	for (i = 0; i < 100; i++) {
X		if (link(LOCK_FILE, lockfile) == 0)
X			return;
X		sleep(5);
X	}	
X	return;
X}
X
X/*
X * This routine is used for files in user's directories
X */
Xlock2(file)
X	char	*file;
X{
X	char	lockfile[PATH_MAX];
X	int	f;
X	int	i;
X
X	(void) strcpy(lockfile, file);
X	(void) strcat(lockfile, LOCK_SUFF);
X	for (i = 0; i < 100; i++) {
X		if ((f = creat(lockfile, 0)) >= 0) {
X			close(f);
X			return;
X		} else
X			sleep(5);
X	}	
X}
X
Xvoid
Xunlock(file)
X	char	*file;
X{
X	char	lockfile[PATH_MAX];
X
X	(void) strcpy(lockfile, file);
X	(void) strcat(lockfile, LOCK_SUFF);
X	(void) unlink(lockfile);
X	return;
X}
X
X
END_OF_lmail.c
if test 15989 -ne `wc -c <lmail.c`; then
    echo shar: \"lmail.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of shell archive.
exit 0
-- 
Jon Zeeff      			Branch Technology,
umix!b-tech!zeeff  		zeeff@b-tech.ann-arbor.mi.us