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