home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / misc / volume4 / lmail / lmail.c
Encoding:
C/C++ Source or Header  |  1989-02-03  |  15.6 KB  |  630 lines

  1. /* #ident "@(#)lmail.c    1.1.1.1    88/09/09 15:07:52 " */
  2. /*
  3.  *    lmail replacement  V 2.7 
  4.  *
  5.  * Copyright 1988 Jon Zeeff    (umix!b-tech!zeeff)
  6.  * Updated by "Greg A. Woods" <umix!ixpierre!woods>
  7.  *
  8.  * Permission is granted to use this in any manner provided that    
  9.  * 1) the copyright notice is left intact, 2) you don't hold me 
  10.  * responsible for any bugs and 3) you mail me any improvements that you 
  11.  * make.  
  12.  * 
  13.  * This program can be used with smail as the local delivery agent (lmail).
  14.  * It's primary benefit is that it allows forwarding to programs and files.
  15.  * It also allows undeliverable local mail to be saved.
  16.  * 
  17.  * Caution: I wrote this for my own use and it does what I want.  I    
  18.  * haven't looked into all portability and security issues nor is the 
  19.  * code as clean as I would like.  Use at your own risk.  
  20.  * 
  21.  * Note that a .fwd file in /usr/spool/uucppublic is ignored since    
  22.  * it is usually publically writable.  If you have other publically 
  23.  * writable home directories, you need to change this program to also 
  24.  * exclude these other directories.  
  25.  * 
  26.  * This program should be:
  27.  * 
  28.  *    -rws--x--x   1 root     mail       19030 Sep 14 12:11 /bin/lmail
  29.  * 
  30.  * You need to create an empty file /usr/mail/.lock
  31.  * and lmail-aliases.  Aliases should contain the name
  32.  * you want aliased followed by new name(s).  Eg.
  33.  * 
  34.  *    postmaster sam "|/usr/postmaster/postsaver -c"
  35.  * 
  36.  * will cause mail to postmaster to go to sam and be piped into  the 
  37.  * program postsaver with the -c option.  Both will be done while suid 
  38.  * MAILMAN.
  39.  * 
  40.  * Users can also forward mail with a .fwd file in their home    
  41.  * directory.  This contains just new names.  References to files and 
  42.  * programs in user's .fwd file will be executed suid that user.  
  43.  * This is a security hole if the directory is writable by others!
  44.  * Also be careful with .fwd files where several ids share a home
  45.  * directory.
  46.  * 
  47.  * This program was written for a Sys V.2 or V.3 system running smail 2.5.
  48.  * 
  49.  * Step by step installation:
  50.  * 
  51.  * 1) Change MAILGID define if mail is not group 6.
  52.  * 2) Add mailman to /etc/passwd with no special uid or group.
  53.  * 3) Compile with 'cc lmail.c -s -O -o lmail'
  54.  * 4) Create empty files lmail-aliases and /usr/mail/.lock
  55.  *    These should be rw group mail.
  56.  * 
  57.  * 5) Install as /bin/lmail with suid root perms.
  58.  * 6) Test 
  59.  */
  60.  
  61. #include <stdio.h>
  62. #include <limits.h>
  63. #include <sys/types.h>
  64. #include <pwd.h>
  65. #include <grp.h>
  66. #include <utmp.h>
  67. #include <signal.h>
  68. #include <ctype.h>
  69. #include <time.h>
  70. #include <sys/stat.h>
  71. #include <setjmp.h>
  72. #include <sys/utsname.h>
  73. #include <string.h>
  74.  
  75. void        exit();
  76. void        _exit();
  77. unsigned int    sleep();
  78. time_t        time();
  79.  
  80. /*
  81.  *    Change these defines to fit your needs
  82.  */
  83. #define MAX_LINE    512            /* input line buffer size */
  84. #define DEF_PATH    "PATH=/bin:/usr/bin:/usr/lbin"
  85. #define MAIL_TMPFILE    "/tmp/rmXXXXXX"
  86. #define ROOT        "root"
  87. #define DEF_IFS        "IFS= \t\n"
  88. #define DEF_SHELL    "SHELL=/bin/sh"
  89. #define FROM_LINE    "From "
  90. #define FROM_FMT    "From %s %24.24s\n"
  91. #define FROM_PREFX    '>'
  92. #define REMOTE_FROM    " remote from "
  93. #define ALIAS_FILE    "/usr/local/lib/lmail-aliases"
  94. #define MAIL_DIR    "/usr/mail/"
  95. #define LOCK_SUFF    ".lock"
  96. #define LOCK_FILE    "/usr/mail/.lock"
  97. #define FWD_LINE    "Forward to "
  98. #define FWD_FILE    "/.fwd"
  99. #define PUBDIR_FWD    "/usr/spool/uucppublic/.fwd"
  100. #define REMOTE_MAILER    "/bin/rmail %s"     /* %s will be replace by address */
  101. #ifndef PATH_MAX
  102. #define PATH_MAX    1024
  103. #endif
  104.  
  105.  
  106.  
  107. /*
  108.  * GID of mail for creating mail files in /usr/mail. It's faster to just type
  109.  * it in here, and it probably won't change anyway (we hope).
  110.  */
  111. #define MAILGID        6
  112.  
  113. /*
  114.  * MAILMAN should be a user id with no special permissions or files.  Make
  115.  * sure you add a /etc/passwd entry for it, 'cause I'm going to check.
  116.  */
  117. #define MAILMAN        "mailman"    /* "mailman" */
  118.  
  119. /*
  120.  * BADMAIL is a name to send a copy of bad mail to.  Can be left undefined.
  121.  */
  122. #define BADMAIL        "/usr/mail/badmail"    /* "/usr/mail/badmail" */
  123.  
  124. /*
  125.  * Maximum size of aliasing table (NOTE: this uses a lot of space!)
  126.  */
  127. #define MAX_ADDR    50
  128.  
  129. struct {
  130.     char    dest[MAX_LINE];        /* to whom it should go */
  131.     char    source[MAX_LINE];    /* who it is from */
  132.     char    user[MAX_LINE];     /* user to suid to for '|' and files */
  133. } table[MAX_ADDR];
  134.  
  135. char        *thissys;        /* This system's name */
  136. FILE        *mailfile;        /* FILE pointer for mailbox */
  137. int        num_addresses;
  138. long        iop;
  139. struct utsname    utsn;
  140. struct passwd    *pwd;
  141. struct passwd    *pwd_mailman;
  142.  
  143. struct passwd    *getpwnam();
  144. FILE        *fopen();
  145. unsigned short    getuid();
  146. char        *mktemp();
  147. FILE        *popen();
  148.  
  149. void        alias();
  150. void        copy();
  151. void        lock();
  152. void        unlock();
  153.  
  154. /* ARGSUSED */
  155. int
  156. main(argc, argv, envp)
  157.     int    argc;
  158.     char    *argv[];
  159.     char    *envp[];
  160. {
  161.     char    *address;
  162.     char    from[MAX_LINE];        /* Original author */
  163.     char    line[MAX_LINE];
  164.     int    i;
  165.     int    error_flag = 0;
  166.     char    *ptr;
  167.  
  168.     static char    tempfname[] = MAIL_TMPFILE;
  169.  
  170.     if (argc < 2)
  171.         exit(1);    /* return error if not at least 1 address */
  172.     umask(006);        /* mail files MUST be group write-able */
  173.     uname(&utsn);
  174.     /*
  175.      * If being fed from a pipe, copy to a temp file so we can rewind
  176.      */
  177.     if (fseek(stdin, 0L, 0) != 0) {
  178.         mailfile = fopen(mktemp(tempfname), "w+");
  179.         unlink(tempfname);        /* it'll be gone when we are */
  180.         if (mailfile == NULL) {
  181.             (void) fprintf(stderr, "Can't create temp file - no mail delivered\n");
  182.             exit(3);
  183.         }
  184.         while (fgets(line, MAX_LINE, stdin) != NULL) {
  185.             if (fputs(line, mailfile) == EOF) {
  186.                 (void) fprintf(stderr, "Can't write temp file - no mail delivered\n");
  187.                 exit(3);
  188.             }
  189.         }
  190.         rewind(mailfile);
  191.     } else
  192.         mailfile = stdin;
  193.     thissys = utsn.nodename;
  194.     if ((pwd_mailman = getpwnam(MAILMAN)) == NULL) {
  195.         (void) fprintf(stderr, "*** Error - can't find mailman uid\n");
  196.         exit(7);
  197.     }
  198.     time(&iop);
  199.     putenv(DEF_PATH);
  200.     putenv(DEF_IFS);
  201.     putenv(DEF_SHELL);
  202.     /*
  203.      * Get the From_ line for the author - assume smail has folded it
  204.      */
  205.     from[0] = '\0';
  206.     fgets(line, MAX_LINE, mailfile);
  207.     rewind(mailfile);
  208.     if (strncmp(line, FROM_LINE, sizeof(FROM_LINE) - 1) != 0) {
  209.         (void) fprintf(stderr, "*** Error - mail is in incorrect format\n");
  210.         exit(2);
  211.     }
  212.     /*
  213.      * If remote from exists, then include that site name in address so that
  214.      * there is no remote from on the end
  215.      */
  216.     ptr = strrchr(line, ' ') - 1;
  217.     while (*ptr != ' ')
  218.         --ptr;
  219.     --ptr;
  220.     while (*ptr != ' ')
  221.         --ptr;
  222.     if (strncmp(ptr, REMOTE_FROM, sizeof(REMOTE_FROM) - 1) == 0) {
  223.         (void) strcat(from, strrchr(line, ' ') + 1);
  224.         *(strrchr(from, '\n')) = '!';
  225.     }
  226.     (void) strcat(from, strchr(line, ' ') + 1);
  227.     *(strchr(from, ' ')) = '\0';
  228.     /*
  229.      * Put the first entrys into the aliasing table
  230.      */
  231.     --argc;
  232.     for (num_addresses = 0; num_addresses < argc; ++num_addresses) {
  233.         address = argv[num_addresses+1];
  234.         if (ptr = strchr(address, ' '))
  235.             *ptr = '\0';    /* Remove trailing spaces */
  236.         /*
  237.          * Mailing to file or program is illegal at this point We
  238.          * can't have outsiders mailing directly to files
  239.          */
  240.         if (address[0] == '/' || address[0] == '|')
  241.             exit(1);
  242.         (void) strcpy(table[num_addresses].dest, address);
  243.         (void) strcpy(table[num_addresses].source, from);
  244.         (void) strcpy(table[num_addresses].user, MAILMAN);
  245.     }
  246.     alias();            /* expand address recursively */
  247.     signal(SIGHUP, SIG_IGN);
  248.     signal(SIGINT, SIG_IGN);
  249.     signal(SIGQUIT, SIG_IGN);
  250.     /*
  251.      * Now deliver them
  252.      */
  253.     for (i = 0; i < num_addresses; i++) {
  254.         if (*(table[i].source) != '\0')    /* if not deleted */
  255.             error_flag |= deliver(mailfile, from, table[i].dest, table[i].user);
  256.     }
  257. #ifdef BADMAIL
  258.     if (error_flag)
  259.         deliver(mailfile, from, BADMAIL, ROOT);
  260. #endif
  261.     /*
  262.      * Realize here that if there is any kind of error, smail will return
  263.      * mail to the author.  Even if the error was on the part of some
  264.      * local user who did an alias wrong.
  265.      */
  266.     return(error_flag);
  267. }
  268.  
  269.  
  270. /*
  271.  * This routine recursively aliases an address creating a table of addresses.
  272.  */
  273. void
  274. alias()
  275. {
  276.     FILE        *in_file;
  277.     int        i;
  278.     int        j;
  279.     char        *p;
  280.     char        *p2;
  281.     char        owner[MAX_LINE];
  282.     char        file[PATH_MAX];
  283.     char        line[MAX_LINE];
  284.     FILE        *aliases;
  285.     struct stat    statbuf;
  286.  
  287.     if ((aliases = fopen(ALIAS_FILE, "r")) == (FILE *) NULL)
  288.         return;
  289.     /*
  290.      * Make sure someone didn't break mail and link some file to aliases.
  291.      * Sys V doesn't have sym links, so we don't worry about that.
  292.      */
  293.     fstat(fileno(aliases), &statbuf);
  294.     if (statbuf.st_nlink > 1) {
  295.         fclose(aliases);
  296.         return; 
  297.     }
  298.     for (i = 0; i < num_addresses; ++i) {
  299.         /*
  300.          * Only alias it if it has never been seen before
  301.          */
  302.         for (j = 0; j < i; ++j) {
  303.             if (strcmp(table[j].dest, table[i].dest) == 0)
  304.                 break;
  305.         }
  306.         if (j < i)
  307.             continue;
  308.         /*
  309.          * find a matching line in aliases
  310.          */
  311.         rewind(aliases);
  312.         while (fscanf(aliases, "%s %s", file, line) > 0) {
  313.             if (strcmp(file, table[i].dest) == 0)
  314.                 break;
  315.         }
  316.         if (!feof(aliases)) {         /* we found one in aliases */
  317.             p = line;
  318.             (void) strcpy(owner, MAILMAN);
  319.         } else {
  320.             /*
  321.              * try the users .fwd file.  This is preferred over
  322.              * Forward to lines
  323.              */
  324.             if ((pwd = getpwnam(table[i].dest)) == NULL)
  325.                 continue;
  326.             (void) strcpy(file, pwd->pw_dir);
  327.             (void) strcat(file, FWD_FILE);
  328.             /*
  329.              * /usr/spool/uucppublic is normally a publically
  330.              * writable home directory.  Ignore any .fwd there.
  331.              */
  332.             if (strcmp(file, PUBDIR_FWD) != 0 && (in_file = fopen(file, "r")) != NULL) {
  333.                 /*
  334.                  * ignore it if there is anything funny going
  335.                  * on with links
  336.                  */
  337.                 fstat(fileno(in_file), &statbuf);
  338.                 if (statbuf.st_nlink > 1 || fgets(line, MAX_LINE, in_file) == NULL) {
  339.                     fclose(in_file);
  340.                     continue;
  341.                 }
  342.                 fclose(in_file);
  343.                 p = line;
  344.                 (void) strcpy(owner, table[i].dest);
  345.             } else {
  346.                 /*
  347.                  * maybe they have a Forward to in their mail
  348.                  * file
  349.                  */
  350.                 (void) strcpy(file, MAIL_DIR);
  351.                 (void) strcat(file, table[i].dest);
  352.                 if ((in_file = fopen(file, "r")) == (FILE *) NULL)
  353.                     continue;
  354.                 /*
  355.                  * ignore it if there is anything funny going
  356.                  * on with links
  357.                  */
  358.                 fstat(fileno(in_file), &statbuf);
  359.                 if (statbuf.st_nlink > 1 || fgets(line, MAX_LINE, in_file) == (char *) NULL) {
  360.                     fclose(in_file);
  361.                     continue;
  362.                 }
  363.                 fclose(in_file);
  364.                 if (strncmp(line, FWD_LINE, sizeof(FWD_LINE) - 1) != 0) continue;
  365.                 p = line + 11;
  366.                 /*
  367.                  * we only allow simple forwards with this
  368.                  * method this is overly simple
  369.                  */
  370.                 if (strchr(p, '/') || strchr(p, '|'))
  371.                     continue;
  372.                 (void) strcpy(owner, MAILMAN);
  373.             }
  374.         }
  375.         /*
  376.          * p now points to a line of addresses.  Mark the current
  377.          * entry as deleted since it was just aliased.
  378.          */
  379.         *(table[i].source) = '\0';
  380.         while (*p > '\n') {
  381.             if (*p == '"') { 
  382.                 p2 = p + 1;
  383.                 while (*(++p) && *p != '"')
  384.                     ;    /* NO_OP */
  385.             } else {
  386.                 p2 = p;
  387.                 while (*p > ' ')
  388.                     ++p;
  389.             }
  390.             if (*p != '\0')
  391.                 *(p++) = '\0';
  392.             /*
  393.              * Is an unaliased version of this already in the
  394.              * table?
  395.              */
  396.             for (j = 0; j < num_addresses; ++j) {
  397.                 if (strcmp(table[j].dest, p2) == 0 && *(table[j].source) != '\0')
  398.                     break;
  399.             }
  400.             /*
  401.              * Add new entry if it finished the above loop
  402.              */
  403.             if (j == num_addresses) {
  404.                 (void) strcpy(table[num_addresses].dest, p2);
  405.                 (void) strcpy(table[num_addresses].source, table[i].dest);
  406.                 (void) strcpy(table[num_addresses].user, owner);
  407.                 if (++num_addresses >= MAX_ADDR) { 
  408.                     --num_addresses;
  409.                     return;
  410.                 }
  411.             }
  412.             /*
  413.              * Move past spaces
  414.              */
  415.             while (*p == ' ')
  416.                 ++p;
  417.         }
  418.     }
  419.     fclose(aliases);
  420.     return;
  421. }
  422.  
  423. /*
  424.  * This routine attempts to deliver the mail
  425.  */
  426. int
  427. deliver(in_fd, author, dest_ptr, user)
  428.     FILE    *in_fd;        /* Input file */
  429.     char    *author;    /* Who message is from (with address) */
  430.     char    *dest_ptr;    /* Who message is to */
  431.     char    *user;        /* User responsible for this to address */
  432. {
  433.     char    dest[MAX_LINE];
  434.     char    temp[MAX_LINE];
  435.     FILE    *outfile;
  436.     int    pid;
  437.     int    w;
  438.     int    status;
  439.  
  440.     rewind(in_fd);
  441.     (void) strcpy(dest, dest_ptr);
  442.     if ((strchr(dest, '!') || strchr(dest, '@') || strchr(dest, '%')) && dest[0] != '|' && dest[0] != '/') {    /* A remote address */
  443.         /*
  444.          * fix things that have only a %
  445.          */
  446.         if (strchr(dest, '!') == (char *) NULL && strchr(dest, '@') == (char *) NULL) 
  447.             *(strchr(dest, '%')) = '@';
  448.         sprintf(temp, REMOTE_MAILER, dest);
  449.         if ((pwd = getpwnam(user)) == (struct passwd *) NULL)
  450.             pwd = pwd_mailman;
  451.         if (pid = fork()) {
  452.             while ((w = wait(&status)) != pid && w != -1)
  453.                 ;    /* NO_OP */
  454.             if (status || w == -1) {
  455.                 (void) fprintf(stderr, "\nCannot run %s\n", temp);
  456.                 return(8);
  457.             }
  458.         } else {
  459.             setgid(pwd->pw_gid);
  460.             setuid(pwd->pw_uid);
  461.             umask(066);
  462.             if ((outfile = popen(temp, "w")) == (FILE *) NULL)
  463.                 _exit(1);
  464.             copy(outfile, in_fd, author);
  465.             if (pclose(outfile))
  466.                 _exit(1);
  467.             _exit(0);
  468.         }
  469.     } else {
  470.         if (dest[0] == '|') {
  471.             if ((pwd = getpwnam(user)) == (struct passwd *) NULL)
  472.                 pwd = pwd_mailman;
  473.             if (pid = fork()) {
  474.                 while ((w = wait(&status)) != pid && w != -1)
  475.                     ;
  476.                 if (status || w == -1) {
  477.                     (void) fprintf(stderr, "\nCannot pipe to program %s\n", dest);
  478.                     return(8);
  479.                 }
  480.             } else {
  481.                 setgid(pwd->pw_gid);
  482.                 setuid(pwd->pw_uid);
  483.                 umask(066);
  484.                 if ((outfile = popen(dest + 1, "w")) == (FILE *) NULL)
  485.                     _exit(1);
  486.                 copy(outfile, in_fd, author);
  487.                 if (pclose(outfile))
  488.                     _exit(1);
  489.                 _exit(0);
  490.             }
  491.         } else { 
  492.             if (dest[0] == '/') {
  493.                 if ((pwd = getpwnam(user)) == (struct passwd *) NULL)
  494.                     pwd = pwd_mailman;
  495.                 if (pid = fork()) {
  496.                     while ((w = wait(&status)) != pid && w != -1)
  497.                         ;    /* NO_OP */
  498.                     if (status || w == -1) {
  499.                         (void) fprintf(stderr, "\nCannot save in file %s\n", dest);
  500.                         return(8);
  501.                     }
  502.                 } else {
  503.                     setgid(pwd->pw_gid);
  504.                     setuid(pwd->pw_uid);
  505.                     umask(066);
  506.                     lock2(dest);
  507.                     if ((outfile = fopen(dest, "a")) == (FILE *) NULL)
  508.                         _exit(1);
  509.                     copy(outfile, in_fd, author);
  510.                     fclose(outfile);
  511.                     unlock(dest);
  512.                     _exit(0);
  513.                 }
  514.             } else {        /* a local user address */
  515.                 /*
  516.                  * Check if this is a valid user
  517.                  */
  518.                 if ((pwd = getpwnam(dest)) == NULL) {
  519.                     (void) fprintf(stderr, "User %s does not exist\n", dest);
  520.                     return(4);
  521.                 }
  522.                 if (pid = fork()) {
  523.                     while ((w = wait(&status)) != pid && w != -1)
  524.                         ;
  525.                     if (status || w == -1) {
  526.                         (void) fprintf(stderr, "\nCannot save in file %s\n", dest);
  527.                         return(8);
  528.                     }
  529.                 } else {
  530.                     setgid(MAILGID);
  531.                     setuid(pwd_mailman->pw_uid);    /* give up root uid */
  532.                     sprintf(temp, "%s%s", MAIL_DIR, dest);
  533.                     lock(temp);
  534.                     status = 0;
  535.                     if ((outfile = fopen(temp, "a")) == NULL) {
  536.                         (void) fprintf(stderr, "** Can't open user mail file %s\n", temp);
  537.                         status = 5;
  538.                     } else
  539.                         copy(outfile, in_fd, author);
  540.                     chown(temp, (int)pwd->pw_uid, MAILGID);
  541.                     if (fclose(outfile)) {
  542.                         (void) fprintf(stderr, "** Could not close mail file %s\n", temp);
  543.                         status = 7;
  544.                     }
  545.                     unlock(temp);
  546.                     _exit(status);
  547.                 }
  548.             }
  549.         }
  550.     }
  551.     return(0);
  552. }
  553.  
  554. void
  555. copy(out, in, from)
  556.     FILE    *out;
  557.     FILE    *in;
  558.     char    *from;
  559. {
  560.     char    temp[MAX_LINE];
  561.  
  562.     /*
  563.      * Throw away From_ line and replace with our own
  564.      */
  565.     fgets(temp, MAX_LINE, in);
  566.     (void) fprintf(out, FROM_FMT, from, ctime(&iop));
  567.     while (fgets(temp, MAX_LINE, in) != NULL) {
  568.         if (strncmp(temp, FROM_LINE, sizeof(FROM_LINE) - 1) == 0)
  569.             (void) fputc(FROM_PREFX, out); 
  570.         (void) fputs(temp, out);
  571.     }
  572.     (void) fputc('\n', out);
  573.     return;
  574. }
  575.  
  576. /*
  577.  * This routine works while uid = root
  578.  */
  579. void
  580. lock(file)
  581.     char    *file;
  582. {
  583.     char    lockfile[PATH_MAX];
  584.     int    i;
  585.  
  586.     (void) strcpy(lockfile, file);
  587.     (void) strcat(lockfile, LOCK_SUFF);
  588.     for (i = 0; i < 100; i++) {
  589.         if (link(LOCK_FILE, lockfile) == 0)
  590.             return;
  591.         sleep(5);
  592.     }    
  593.     return;
  594. }
  595.  
  596. /*
  597.  * This routine is used for files in user's directories
  598.  */
  599. lock2(file)
  600.     char    *file;
  601. {
  602.     char    lockfile[PATH_MAX];
  603.     int    f;
  604.     int    i;
  605.  
  606.     (void) strcpy(lockfile, file);
  607.     (void) strcat(lockfile, LOCK_SUFF);
  608.     for (i = 0; i < 100; i++) {
  609.         if ((f = creat(lockfile, 0)) >= 0) {
  610.             close(f);
  611.             return;
  612.         } else
  613.             sleep(5);
  614.     }    
  615. }
  616.  
  617. void
  618. unlock(file)
  619.     char    *file;
  620. {
  621.     char    lockfile[PATH_MAX];
  622.  
  623.     (void) strcpy(lockfile, file);
  624.     (void) strcat(lockfile, LOCK_SUFF);
  625.     (void) unlink(lockfile);
  626.     return;
  627. }
  628.  
  629.  
  630.