Logo Search packages:      
Sourcecode: filter version File versions  Download package

filter.c


/******************************************************************************
 *  The Elm Mail System  -  forked from $Revision: 5.7 $   $State: Exp $
 *
 *                Copyright (c) 1988-1992 USENET Community Trust
 *                Copyright (c) 1986,1987 Dave Taylor
 ******************************************************************************
 * Bug reports, patches, comments, suggestions should be sent to:
 *
 *    Philip Brown    filter@bolthole.com
 *
 ******************************************************************************
 * $Log: filter.c,v $
 *
 * may 31, 2000; changed checksetgid() on security suggestion from
 * Andrew Stribblehill
 *
 * 1999/11/23 phil
 *  added in setting user_mailbox from getenv("MAIL")
 *
 * 1998/07/18 phil
 * fixed ifdefs for RCPT_HDR
 *
 * 1998/03/31 phil
 * Added "rcpt-to" support
 *
 * Revision 5.7  1994/05/30  16:31:40  syd
 * make getpwuid dependent on ANSI_C not posix flag
 * From: Syd
 *
 * Revision 5.6  1993/08/03  19:28:39  syd
 * Elm tries to replace the system toupper() and tolower() on current
 * BSD systems, which is unnecessary.  Even worse, the replacements
 * collide during linking with routines in isctype.o.  This patch adds
 * a Configure test to determine whether replacements are really needed
 * (BROKE_CTYPE definition).  The <ctype.h> header file is now included
 * globally through hdrs/defs.h and the BROKE_CTYPE patchup is handled
 * there.  Inclusion of <ctype.h> was removed from *all* the individual
 * files, and the toupper() and tolower() routines in lib/opt_utils.c
 * were dropped.
 * From: chip@chinacat.unicom.com (Chip Rosenthal)
 *
 * Revision 5.5  1993/06/06  17:58:20  syd
 * make white space skipping work for blank or tab
 *
 * Revision 5.4  1993/01/27  19:40:01  syd
 * I implemented a change to filter's default verbose message format
 * including %x %X style date and time along with username
 * From: mark@drd.com (Mark Lawrence)
 *
 * Revision 5.3  1992/11/15  01:40:43  syd
 * Add regexp processing to filter.
 * Add execc operator
 * From: Jan Djarv <Jan.Djarv@sa.erisoft.se>
 *
 * Revision 5.2  1992/11/07  16:20:56  syd
 * The first is that when doing a summary, macros are expanded when printing the
 * rule. IMHO they should be printed as with the -r option (i.e. %t is
 * printed as "<time>" and so on).
 *
 * The second one is that the summary printed "applied n time" regardless of
 * the value of n, not "applied n times" when n > 1.
 * From: Jan Djarv <Jan.Djarv@sa.erisoft.se>
 *
 * Revision 5.1  1992/10/03  22:18:09  syd
 * Initial checkin as of 2.4 Release at PL0
 *
 *
 ******************************************************************************/


/** This program is used as a filter within the users ``.forward'' file
    and allows intelligent preprocessing of mail at the point between
    when it shows up on the machine and when it is actually put in the
    mailbox.

    The program allows selection based on who the message is FROM, TO, or
    what the subject is.  Acceptable actions are for the program to DELETE_MSG
    the message, SAVE the message in a specified folder, FORWARD the message
    to a specified user, SAVE the message in a folder, but add a copy to the
    users mailbox anyway, or simply add the message to the incoming mail.

    Filter also keeps a log of what it does as it goes along, and at the
    end of each `quantum' mails a summary of actions, if any, to the user.

    Uses the files: $HOME/.filter/filter for instructions to this program, and
    $HOME/.filter/filterlog for a list of what has been done since last summary.

**/

#include <stdio.h>
#include <pwd.h>
#include "defs.h"
#include <errno.h>

#ifdef I_TIME
#  include <time.h>
#endif
#ifdef I_SYSTIME
#  include <sys/time.h>
#endif
#include <fcntl.h>

#define  MAIN_ROUTINE               /* for the filter.h file, of course! */
#include "filter.h"
#include "s_filter.h"

extern char *date_n_user();



#include "version.h"

void usage()
{
      fprintf(stderr,"%s\n",versionstring);
      fprintf(stderr,"     http://www.bolthole.com/filter/\n");
      fprintf(stderr,"This is a program you can install as an email filter.\n");
      fprintf(stderr," (to sort, delete, or forward incoming email)\n");
      fprintf(stderr,   catgets(elm_msg_cat,FilterSet,FilterUsage,
  "Usage: | filter [-nrvlq] [-f rules] [-o file] [-m mailbox]\n\
\tor: filter [-c] -[s|S]\n"));

      fprintf(stderr,"See the manual page for details. (\"man filter\")\n");
}

/* check if we are installed setgid. This is a security check to disable
 * unsecure functions.
 * return 1 if yes, 0 if no.
 */
int checksetgid()
{
      return (getegid() != getgid());
}


#ifdef USE_RCPTHDR
void save_rcpt_to(buffer)
char *buffer;
{
        /** save all but the word "x-cpt-to:" **/
 
        register int skip = strlen(RCPT_TO_HDR);
 
        while (whitespace(buffer[skip]))
                skip++;
 
        stringcopy(rcpt_to, (char *) buffer + skip, LONG_STRING);
}
#endif

main(argc, argv)
int argc;
char *argv[];
{
      extern char *optarg;
      FILE   *tmpfptr;                    /* for output to temp file! */
      struct passwd  *passwd_entry;
#ifndef     ANSI_C
      struct passwd  *getpwuid();         /* for /etc/passwd          */
#endif
      char action_argument[SLEN],         /* action arg, per rule     */
           buffer[MAX_LINE_LEN];          /* input buffer space       */
      char overridemailbox[MAX_LINE_LEN]; /* for -m option            */
      int  in_header = TRUE,              /* for header parsing       */
           in_to     = FALSE,             /* are we on 'n' line To: ? */
           summary   = FALSE,             /* a summary is requested?  */
           c;                             /* var for getopt routine   */
      int amsetgid=0;
      int exit_stat=0;

      amsetgid=checksetgid();

      tolist=NULL;      /* lets be paranoid */

#ifdef I_LOCALE
        setlocale(LC_ALL, "");
#endif

        elm_msg_cat = catopen(versioncat, 0);

            
      /* first off, let's get the info from /etc/passwd */ 
      
      if ((passwd_entry = getpwuid(getuid())) == NULL) 
        leave(catgets(elm_msg_cat,FilterSet,FilterCantGetPasswdEntry,
            "Cannot get password entry for this uid!"));

      stringcopy(home, passwd_entry->pw_dir, SLEN);
      stringcopy(username, passwd_entry->pw_name, SLEN);

      user_uid = passwd_entry->pw_uid;
      user_gid = passwd_entry->pw_gid;
      
      /* nothing read in yet, right? */
      outfname[0] = filterfile[0] = '\0';
      
      
#ifdef HOSTCOMPILED
      stringcopy(hostname, HOSTNAME, sizeof(hostname));
#else
      gethostname(hostname, sizeof(hostname));
#endif
      
      /* now parse the starting arguments... */

      while ((c = getopt(argc, argv, "chlm:no:qrSsvf:")) != EOF)
      {
            switch (c) {
            case 'c' :
                clear_logs = TRUE;
                break;
            case 'f' :
                stringcopy(filterfile,optarg, SLEN);
                break;
            case 'h' :
                usage();
                exit(1); /* return as error, in case someone
                        * accidentally uses -h in an actual use situation
                        */
                break;
             
            case 'l' :
                log_actions_only = TRUE;
                break;
            case 'm' :
                if(amsetgid){
                  break;
                }
                stringcopy(overridemailbox, optarg, SLEN);
                break;
            case 'n' :
                show_only = TRUE;
                break;
            case 'o' :
                stringcopy(outfname, optarg, SLEN);
                break;
            case 'q' :
                logging = FALSE;
                break;
            case 'r' :
                printing_rules = TRUE;
                break;
            case 's' :
                summary = TRUE;
                break;
            case 'S' :
                long_summary = TRUE;
                break;
            case 'v' :
                verbose = TRUE;
                break;
            case '?' :
            default  :
                  usage();
                  exit(1);
        }
      }

      /* inline function, to set up default mail folder */
      {
            char *tmpstr=NULL;
            sprintf(user_mailbox, "%s", mailhome);
            if(amsetgid==0)
                  tmpstr=getenv("MAIL");
            if(tmpstr!=NULL){
                  strcpy(user_mailbox, tmpstr);
            }
            if(overridemailbox[0]!='\0'){
                  strcpy(user_mailbox, overridemailbox);
            }

            /* Now do special substitutions */

#ifdef STRSTR
            tmpstr=strstr(user_mailbox,"%f");
#else
            tmpstr=index(user_mailbox,'%');
            if(tmpstr!=NULL){
                  if(tmpstr[1]!='f')
                        tmpstr=NULL;
            }
#endif
            if(tmpstr!=NULL){
                  char firstletter[2];
                  int tmplen=tmpstr-user_mailbox;
                  firstletter[1]='\0';
                  firstletter[0]=username[0];
                  stringcopy(buffer,user_mailbox,tmplen+1);
                  /* I think this is security-safe */
                  strcat(buffer,firstletter);
                  strcat(buffer,tmpstr+2);
                  stringcopy(user_mailbox,buffer,strlen(buffer)+1);
            }


#ifdef STRSTR
            tmpstr=strstr(user_mailbox,"%u");
#else
            tmpstr=rindex(user_mailbox,'%');
            if(tmpstr!=NULL){
                  if(tmpstr[1]!='u')
                        tmpstr=NULL;
            }
#endif
            if(tmpstr!=NULL){
                  int tmplen=tmpstr-user_mailbox;
                  stringcopy(buffer,user_mailbox,tmplen+1);
                  strcat(buffer,username);
                  strcat(buffer,tmpstr+2);
                  stringcopy(user_mailbox,buffer,strlen(buffer)+1);
            }

      }

      sprintf(filterlog,"%s/.filter/%s",home,FILTERLOG);
      sprintf(filtersum,"%s/.filter/%s",home,FILTERSUM);

      /* use default filter file name if none specified */
      if (!*filterfile){
           sprintf(filterfile,"%s/.filter/%s",home,FILTERFILE);
      }
      
            
      /* let's open our outfptr logfile as needed... */

      if (outfname[0] == '\0')      /* default is stdout */
        outfptr = stdout;
      else 
        if ((outfptr = fopen(outfname, "a")) == NULL) {
          if (isatty(fileno(stderr)))
            fprintf(stderr,
                  catgets(elm_msg_cat,FilterSet,FilterCouldntOpenLogFile,
            "filter (%s): couldn't open output file %s\n"),
                  username, outfname);
            /* Note that this is NOT actually the "log file" */
        }

      if (summary || long_summary) {
          if (get_filter_rules() == -1) {
          if (outfptr != NULL) fclose(outfptr);
          exit(1);
        }
        printing_rules = TRUE;
        show_summary();
        if (outfptr != NULL) fclose(outfptr);
        exit(0);
      }

      if (printing_rules) {
          if (get_filter_rules() == -1)
          fprintf(outfptr,catgets(elm_msg_cat,FilterSet,FilterCouldntGetRules,
              "filter (%s): Couldn't get rules!\n"), date_n_user());
          else
          print_rules();
        if (outfptr != NULL) fclose(outfptr);
          exit(0);
      }

      /* next, create the tempfile and save the incoming message */

      if ((tmpfptr = (FILE*)make_tempfile()) == NULL)
        {
            sprintf(buffer,
                  catgets(elm_msg_cat,FilterSet,
                        FilterCantOpenTempFileLeave,
                  "Cannot open temporary file"));
            leave(buffer);
        }
      

      while (fgets(buffer, MAX_LINE_LEN, stdin) != NULL) {

        remove_return(buffer);

        if (in_header) {

          if (! whitespace(buffer[0])) 
            in_to = FALSE;

      /* this "save_xxx" really means "copy field". sigh */
      
          if (THE_SAME(buffer, "From ")) 
            save_from(buffer);
#ifdef USE_RCPTHDR
          else if (THE_SAME(buffer, RCPT_TO_HDR)) 
            save_rcpt_to(buffer);
#endif
          else if (THE_SAMEHDR(buffer, "Subject:")) 
            save_subject(buffer);
          else if (THE_SAMEHDR(buffer, "Sender:")) 
            save_sender(buffer);
          else if (THE_SAMEHDR(buffer, "x-mailing-list:")) 
            save_mlist(buffer);
          else if (THE_SAMEHDR(buffer, "To:") ||
                 THE_SAMEHDR(buffer, "Cc:") ||
                 THE_SAMEHDR(buffer, "Apparently-To:")) {
            in_to++;
            save_to(buffer);
          }
          else if (THE_SAME(buffer, "X-Filtered-By:")) 
            already_been_forwarded++;     /* could be a loop here! */
#ifdef USE_EMBEDDED_ADDRESSES
          else if (THE_SAMEHDR(buffer, "From:"))
            save_embedded_address(buffer, "From:");
          else if (THE_SAMEHDR(buffer, "Reply-To:"))
            save_embedded_address(buffer, "Reply-To:");
#endif
          else if (strlen(buffer) < 2) 
            in_header = 0;

          else if (whitespace(buffer[0]) && in_to)
          /* im not sure about this, but I think sokay */
            save_to(buffer);
        }
      
          fprintf(tmpfptr, "%s\n", buffer);     /* and save it always! */
        lines++;
      } /* End of "dump to file" section */

      fflush(tmpfptr);

      rewind(tmpfptr);

      /** next let's see if the user HAS a filter file, and if so what's in
            it (and so on) **/

      if (get_filter_rules() == -1)
        mail_message(username,tmpfptr);
      else {
        int action = action_from_ruleset();

        if (rule_choosen >= 0) {
          expand_macros(rules[rule_choosen].argument2, action_argument,
                  rules[rule_choosen].line, printing_rules);
          /* Got to do this because log_msg() uses argument2 in rules[] */
          stringcopy(rules[rule_choosen].argument2, action_argument, SLEN);
        }

      /* Match all cases in here, to the cases in summarize.c */
        switch (action) {

          case BOUNCE : log_msg(BOUNCE);
                      remove_tempfile();
                      exit(67); /* EX_NOUSER */

          case DELETE_MSG : if (verbose && outfptr != NULL)
                      fprintf(outfptr,
                            catgets(elm_msg_cat,FilterSet,
                                  FilterMessageDeleted,
                                  "filter (%s): Message deleted\n"),
                            date_n_user());
                    log_msg(DELETE_MSG);                    break;

          case SAVE   : exit_stat=save_message(rules[rule_choosen].argument2,
                                 tmpfptr);
                    if(exit_stat!=0)
                    {
                      /* This actaually does local delivery */
                      mail_message(username,tmpfptr);
                      log_msg(FAILED_SAVE);
                    }
                    else
                      log_msg(SAVE);
                        break;

          case SAVECC : exit_stat=save_message(rules[rule_choosen].argument2,
                                 tmpfptr);
                    if(exit_stat!=0)
                      log_msg(FAILED_SAVE);
                    else
                        log_msg(SAVECC);
                    mail_message(username,tmpfptr);
                        break;

          case FORWARDC:mail_message(username,tmpfptr);
                    mail_message(rules[rule_choosen].argument2,tmpfptr);
                    log_msg(FORWARDC);
                        break;
          case FORWARD: mail_message(rules[rule_choosen].argument2,tmpfptr);
                    log_msg(FORWARD);
                        break;

          case RESEND: mail_message(rules[rule_choosen].argument2,tmpfptr,1);
                    log_msg(RESEND);
                        break;

          case EXECC    : mail_message(username,tmpfptr);
                    execute(rules[rule_choosen].argument2,tmpfptr);
                    log_msg(EXECC);
                        break;

          case EXEC   : execute(rules[rule_choosen].argument2,tmpfptr);
                    log_msg(EXEC);
                        break;

          case LEAVE  : mail_message(username,tmpfptr);
                    log_msg(LEAVE);
                        break;
        }
      }

      fclose(tmpfptr);
      remove_tempfile();
      if (outfptr != NULL)
        fclose(outfptr);
      exit(exit_stat);
}

/* This saves the "envelop sender", aka "From " line */
void save_from(buffer)
char *buffer;
{
      /** save the SECOND word of this string as FROM **/

      register char *f = Efrom;

      while (!whitespace(*buffer))
        buffer++;                   /* get to word     */

      while (whitespace(*buffer))
        buffer++;                   /* skip delimited  */

      for (; (!whitespace(*buffer)) && *buffer; buffer++, f++) 
        *f = *buffer;                     /* copy it and     */

      *f = '\0';                    /* Null terminate! */
}


/* This is a utility function for save_subject().
 * If an encoded subject line (eg iso-8859-1) is encountered,
 * it is up to us to decode it.
 * we assume that the first two chars are =?, as in
 *  =?ISO-8859-1?B?xxxxxxxxx?=
 */
void iso_convert_sub(char *subject)
{
      static char *base=
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
      unsigned char b1,b2,b3,b4;

      char *ptr, *src,*dest, *end;
      char *start=strstr(subject, "=?");
      char tmpsubj[LONG_STRING];
      int chunklen=0;
      int enc_type=0; /*1=quoted-printable, 2=binary */
      char enc_bytes[2];

      /* There are two kinds of encoding: "quoted-prinable", and "binary".
       * substitute single char for "quoted-printable",
       * so that single x-out matching can happen. eg:
       *  /p.nis/
       */

      if(start==NULL) return;
      ptr=strchr(&start[2],'?');

      switch(ptr[1]){
          case 'Q':
          case 'q':
            enc_type=1;
            break;

          case 'B':
          case 'b':
            enc_type=2;
            break;

          default:
            return;
      }

      ptr+=2;
      if(*ptr != '?'){
            fprintf(stderr,
             "DEBUG: fractured encoded subj found in iso_convert_sub\n");
            return;
      }
      if(strchr(&ptr[1],'?')==NULL){
            fprintf(stderr,
             "DEBUG: truncated encoded subj found in iso_convert_sub\n");
            return;
      }
      ptr++;

      /* hack up quoted-printable, then get outta here*/
      if(enc_type == 1){
            src=ptr;
            dest=tmpsubj;

            while(*src != '?'){
                  if(*src == '='){
                        src++;
                        switch(*src){
                              case 'E':
                                    *dest='e';
                                    break;
                              case 'F':
                                    *dest='n';
                                    break;
                              default:
                                    *dest='X';
                        }
                  } else {
                        *dest = *src;
                  }

                  dest++; src++;
            }
            *dest='\0';
            stringcopy(subject, tmpsubj, dest+1 -tmpsubj);
            return;
      }

      /* else, enc_type==2 : binary encoding, base64 */
      /* ptr == where start of encoded data is, in caller's buffer */
      end=strstr(ptr, "?=");
      dest=tmpsubj;

      if(end==NULL){
            fprintf(stderr,
             "DEBUG: truncated encoded subj found in iso_convert_sub\n");
            return;
      }
      while(ptr<end){
            b1=strchr(base, ptr[0]) - base;
            b2=strchr(base, ptr[1]) - base;
            b3=strchr(base, ptr[2]) - base;
            b4=strchr(base, ptr[3]) - base;
            ptr+=4;
            dest[0] = (b1<<2) | ((b2>>4)&0xff);
            dest[1] = (b2 <<4 ) | ((b3>>2)&0xff);
            dest[2] = (b3<<6)&0xff | (b4 & 0xff);
            dest +=3;
      }
      end+=2;
      stringcopy(dest, end, strlen(end)+1);
      stringcopy(start, tmpsubj, strlen(tmpsubj)+1);
      
}


/* reasonably assume buffer is at least SLEN long */
void save_subject(buffer)
char *buffer;
{
      /** save all but the word "Subject:" for the subject **/

      register int skip = 8;  /* skip "Subject:" initially */
      register int frompos, topos;

      while (whitespace(buffer[skip]))
            skip++;

      stringcopy(subject, (char *) buffer + skip, LONG_STRING);
      if(strncmp(subject, "=?", 2)==0){
            iso_convert_sub(subject);
      }

      /* now make an ascii-only copy of subject line, to de-mangle
       * funky subject lines.
       * Furthermore, force to lowercase, so regex's on it dont have
       * to get all funky.
       */
      topos=0;
      for(frompos=0; frompos<LONG_STRING, subject[frompos]!='\0'; frompos++){
            if(isalpha(subject[frompos])){
                  alphasubject[topos++]=(tolower(subject[frompos]));
            }
      }
      alphasubject[topos]='\0';
      
}

void save_sender(buffer)
char *buffer;
{
      /** save all but the word "Sender:" for the sender **/

      register int skip = 7;  /* skip "Sender:" initially */

      while (whitespace(buffer[skip]))
            skip++;

      stringcopy(sender, (char *) buffer + skip, LONG_STRING);
}

void save_to(buffer)
char *buffer;
{
      /** save all but the word "To:" or "Cc:" or
          "Apparently-To:" for the to list **/

      register int skip = 0, to_len;
      char *newstr, *strstart;

      while (!whitespace(buffer[skip]))
            skip++;

      while (whitespace(buffer[skip]))
            skip++;

      strstart=(char*)buffer + skip;

      to_len=strlen(strstart)+1;
      newstr=(char*)safemalloc(to_len);
      stringcopy(newstr, strstart, to_len);

      if(tolist==NULL) {
            tolist=(LIST*)safemalloc(sizeof(LIST));
            tolist->next=NULL;
            tolist->str = newstr;
            
      } else {
            LIST * listend = tolist;
            while(listend->next != NULL)
                  listend=listend->next;
            listend->next=(LIST*)safemalloc(sizeof(LIST));
            listend=listend->next;
            listend->next=NULL;
            listend->str = newstr;
      }
      
}

/* If x-mailing-list header or whatever present, add to list of
 * mailing lists
 */
void save_mlist(buffer)
char *buffer;
{
      register int field_len;
      char *newstr, *strstart;

#ifdef STRSTR
      /* must have strchr if they have strstr ! */
      strstart=strchr(buffer,':');
#else
      strstart=index(buffer,':');
#endif
      /* This should never happen, but... */
      if(strstart==NULL){
            strstart=buffer;
      }

      field_len=strlen(strstart)+1;
      newstr=(char*)safemalloc(field_len);
      stringcopy(newstr, strstart, field_len);

      if(mail_lists==NULL) {
            mail_lists=(LIST*)safemalloc(sizeof(LIST));
            mail_lists->next=NULL;
            mail_lists->str = newstr;
            
      } else {
            LIST * listend = mail_lists;
            while(listend->next != NULL)
                  listend=listend->next;
            listend->next=(LIST*)safemalloc(sizeof(LIST));
            listend=listend->next;
            listend->next=NULL;
            listend->str = newstr;
      }
      
}

#ifdef USE_EMBEDDED_ADDRESSES

void save_embedded_address(buffer, fieldname)
char *buffer, *fieldname;
{
      /** this will replace the 'from' address with the one given, 
          unless the address is from a 'reply-to' field (which overrides 
          the From: field).  The buffer given to this routine can have one 
            of three forms:
            fieldname: username <address>
            fieldname: address (username)
            fieldname: address
      **/
      
      static int processed_a_reply_to = 0;
      char address[LONG_STRING];
      register int i, j = 0;

      /** first let's extract the address from this line.. **/
      /* Note that if buffer is obnoxiously long, we may truncate*/

      if (buffer[strlen(buffer)-1] == '>') {    /* case #1 */
        for (i=strlen(buffer)-1; buffer[i] != '<' && i > 0; i--)
            /* nothing - just move backwards .. */ ;
        i++;      /* skip the leading '<' symbol */
        while ((buffer[i] != '>') && (j < (LONG_STRING-1) )){
          address[j++] = buffer[i++];
        }
        address[j] = '\0';
      }
      else {      /* get past "from:" and copy until white space or paren hit */
        for (i=strlen(fieldname); whitespace(buffer[i]); i++)
           /* skip past that... */ ;
        while (buffer[i] != '(' && ! whitespace(buffer[i]) &&
               buffer[i]!='\0' && (j < (LONG_STRING-1) ))
          address[j++] = buffer[i++];
        address[j] = '\0';
      }

      /** now let's see if we should overwrite the existing from address
          with this one or not.. **/



      if (istrcmp(fieldname, "Reply-To:") == 0){
        if (processed_a_reply_to)
          return; /* forget it! */
        processed_a_reply_to++;
        stringcopy(replyto, address, LONG_STRING);    /* replaced!! */
      } else {
        /* Assume "From:"  header */
        stringcopy(Hfrom, address, LONG_STRING);
      }
}
#endif


Generated by  Doxygen 1.6.0   Back to index