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

p3scan.c

/*
 * P3Scan v2.1
 *
 * (C) 2003-2005 by Jack S. Lai <laitcg@cox.net>
 *
 * It's intent is to provide a follow on program to POP3-Virusscan-Proxy 0.4
 * by Folke Ashberg <folke@ashberg.de>.
 *
 * It is based upon his program but provides numerous changes to include
 * scanning pop3 mail for spam, hardening the program, addaption to todays
 * email environment, and many other changes.
 *
 * The initial release of p3scan includes patches made and submitted to the
 * original project but were never incorporated. Please see the README for
 * further information.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
/* List of features wanted (for grep'ing)
TODO: Wanted: SMTP scanning
TODO: Wanted: Header parser
TODO: Wanted: extra notification receipient
TODO: Wanted: white-list support
TODO: Wanted: no iptables support
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <pwd.h>
#include <time.h>
#include <sys/time.h>
#include <syslog.h>
#include <sys/param.h>
#include <ctype.h>
#include <linux/netfilter_ipv4.h>
#include <malloc.h>
#include <getopt.h>
#include <netdb.h>
#include <libgen.h>
#include <errno.h>
#include <dirent.h>
#include <sys/statvfs.h>
#include <assert.h>
#include <sys/select.h>

#include "p3scan.h"
#include "getline.h"
#include "parsefile.h"
#include "scanner.h"

#include "ripmime/mime.h"
#include "ripmime/ripmime-api.h"          /* >= ripmime-1.4.0.1
                                             Do Not Use any ripmime before
                                             this version due to mime errors
                                          */

/* globals */
int numprocs;
struct configuration_t * config;
static int stralloc_ptr;
static char *strings[8];
static int str_tag[8];
/* default configuration, anything can be changed at runtime */
#define PORT_NUMBER              8110
#define MAX_CHILDS               10
#define RUNAS_USER               "mail"
#define VIRUS_DIR                "/var/spool/p3scan"
#define NOTIFY_MAIL_DIR          "/var/spool/p3scan/notify"
#define VIRUS_SCANNER            NULL
#define VIRUS_SCANNER_VIRUSCODE  1
#define PID_FILE                 "/var/run/p3scan/p3scan.pid"
#define SYSLOG_NAME              "p3scan"
#define CONFIGFILE               "/etc/p3scan/p3scan.conf"
#define VIRUS_TEMPLATE           "/etc/p3scan/p3scan.mail"
#define DEBUG                    0
#define QUIET                    0
#define OVERWRITE                NULL
#define CHECKSPAM                0
#define SPAMCHECK                "/usr/bin/spamc"
#define MINSPACE                 0
#define DELIT                    0
#define NEWLINE                  '\n'
#define SUBJECT                  "[Virus] found in a mail to you:"
#define NOTIFY                   "Per instruction, the message has been deleted."
/* Defaut maximum mail size for scanning. ZERO for no limit! */
#define MAX_SIZE_SCAN            0
/* TOS:  do not set, or use IPTOS_[LOWDELAY|THROUGHPUT|RELIABILITY|LOWCOST] */
#define SET_TOS                  IPTOS_THROUGHPUT
#define  MOVEIT                  "/bin/mv"
//#define  COPYIT                  "/bin/cp"
#undef DEBUG_MEM                 /* print meminfo every log message when debugging */
#undef DEBUG_MESSAGE             /* print message lines */
#undef DEBUG_SCANNING            /* print message lines while scanning */
// Logging options defaults
#ifndef LOGOPT
#define LOGOPT LOG_PID|LOG_CONS
#endif
#ifndef LOGFAC
#define LOGFAC LOG_DAEMON
#endif

/* p->ismail legend:
   ismail=0 not processing a message - parsing client commands
   ismail=1 enable message parsing (got RETR/TOP) - start trapping email
   ismail=2 got "+ok", read the mail into file
   ismail=3 closed header buffer
   ismail=4 have entire message, start processing - email complete, start server noop's
   ismail=5 scanning done, send mail to client
*/

/* default configuration ends here */

/* globals / protos */

/* filecount from ripmime/mime.c */
extern int filecount;

/* MIMEH_set_outputdir() from ripmime/MIME_headers.c */
extern int MIMEH_set_outputdir(char *);

static int sockfd; /* has to be global, do_sigterm_main() want's to close them */
/* the proxycontext is global for do_sigterm_proxy().
 * no other function should use it! */

static struct proxycontext* global_p;

void do_log(int level, const char *fmt,...){
   char puffer[4096];
   va_list az;
#ifdef DEBUG_MEM
   struct mallinfo m=mallinfo();
#endif

   if (!config->debug && level==LOG_DEBUG) return;
   if (config->quiet && level==LOG_NOTICE) return;
   va_start(az,fmt);
   vsnprintf(puffer, 4000, fmt, az);
   if (!config->debug){
      openlog(config->syslogname, LOGOPT, LOGFAC);
      syslog(LOG_NOTICE, "%s\n", puffer);
      closelog();
   } else {
      fflush(stdout);
      fprintf(stderr, "%s[%i]: "
#ifdef DEBUG_MEM
      "Mem: %i "
#endif
      "%s\n", config->syslogname, getpid(),
#ifdef DEBUG_MEM
      m.uordblks,
#endif

      puffer);
      fflush(NULL);
   }
   if (level==LOG_EMERG){
      do_log(LOG_NOTICE, "Exiting now...\n");
      fprintf(stderr, "%s\n", puffer);
      /* TODO: Close open descriptors on ANY errors in all this code! */
      exit(1);
   }
   return;
}

void avoid_root(void){
   struct passwd *userInfo;
   if (getuid()!=0) return; /* then it's ok */
   do_log(LOG_DEBUG, "Changing uid (we are root)");
   userInfo = getpwnam(config->runasuser);
   if ((setgid(userInfo->pw_gid)) == -1) do_log(LOG_EMERG, "Can't change to group of user %s (%i.%i)", config->runasuser, userInfo->pw_uid, userInfo->pw_gid);
   if ((setuid(userInfo->pw_uid)) == -1) do_log(LOG_EMERG, "Can't change to user %s (%i.%i)", config->runasuser, userInfo->pw_uid, userInfo->pw_gid);
   do_log(LOG_DEBUG, "Changed UID.GID to %i.%i", userInfo->pw_uid, userInfo->pw_gid);
}

int scan_directory(struct proxycontext *p){
   int ret, ret_all;
   DIR *dp;
   struct dirent *de;
   struct stat s;
   char * file;
   int maildirlen;
   char * virname;
#define VISIZE 1000
   char *vi;
   int vipos = 0;

   /* scan directory */
   maildirlen=strlen(p->maildir);
   if (stat (p->maildir, &s) == -1){
      context_uninit(p);
      do_log(LOG_EMERG, "%s does not exists", p->maildir);
      return SCANNER_RET_ERR;
   }
   if (!S_ISDIR(s.st_mode)){
      context_uninit(p);
      do_log(LOG_EMERG, "oops, %s is not a directory", p->maildir);
      return SCANNER_RET_ERR;
   }
   if ((dp = opendir (p->maildir)) == NULL){
      context_uninit(p);
      do_log(LOG_EMERG, "oops, can't open directory %s", p->maildir);
      return SCANNER_RET_ERR;
   }
   if ((vi=malloc(VISIZE))==NULL){
      context_uninit(p);
      do_log(LOG_EMERG, "Could not allocate memory for vi!");
      return SCANNER_RET_ERR;
   }
   ret_all=SCANNER_RET_OK;
   vi[0]='\0';
   while ((de = readdir (dp)) != NULL){
      if (strcmp (de->d_name, ".") == 0) continue;
      if (strcmp (de->d_name, "..") == 0) continue;
      file=malloc(maildirlen + strlen(de->d_name) +2);
      if (file==NULL){
         context_uninit(p);
         do_log(LOG_EMERG, "Could not allocate memory for file!");
         return SCANNER_RET_ERR;
      }
      sprintf(file, "%s/%s", p->maildir, de->d_name);
      if (lstat (file, &s) == -1){
         context_uninit(p);
         free(file);
         do_log(LOG_EMERG, "Can't stat %s - I won't touch it.", file);
         continue;
      }
      if (!S_ISREG(s.st_mode)){
         context_uninit(p);
         do_log(LOG_EMERG, "%s is not a regular file. I won't touch it.", file);
         free(file);
         continue;
      }
      /* build filename */
      do_log(LOG_DEBUG, "Going to scan '%s'", file);

      p->scanthis=file;
      virname=NULL;
      ret=config->scanner->scan(p, &virname);

      if (ret==SCANNER_RET_VIRUS){
         ret_all=SCANNER_RET_VIRUS;
         if (virname && strlen(virname)<VISIZE - vipos - 4){
            strcat(&vi[vipos], virname);
            vipos+=strlen(virname);
            strcat(vi, " & ");
            vipos+=3;
         }
      } else if (ret == SCANNER_RET_ERR && ret_all != SCANNER_RET_VIRUS) ret_all = SCANNER_RET_ERR;
      if (virname) free(virname);
      free(file);
   }
   closedir (dp);
   if (vipos>4){
      vi[vipos-3]='\0';
      p->virinfo=vi;
   } else p->virinfo=NULL;
   return ret_all;
}

/* clean_child_directory -- iterate through directory
 *                          removing all files and subdirectories
 *
 * args:
 * childpid -- used to construct directory path
 *             to erase. If called by child pass
 *             getpid()
 *
 * returns:
 * 0 -- OK
 * 1 -- mem allocation error
 *
 * note: do_log(LOG_EMERG, ...) currently
 * doesn't return!!!
 */
int clean_child_directory(pid_t childpid){
   DIR           *dp;
   struct dirent *de;
   char          *dir,*file;
   int           dirlen,filelen;

   dirlen=strlen(config->virusdirbase)+20;
   dir=calloc(dirlen,1);
   if (dir==NULL){
      //context_uninit(p);
      do_log(LOG_EMERG, "Could not allocate memory for dirbase!");
      return 1;
   }
   snprintf(dir, dirlen, "%s/children/%d/", config->virusdirbase, childpid);
   /* Erase directory if it exists */
   if ((dp = opendir (dir)) != NULL){
      do_log(LOG_DEBUG, "Erasing %s contents", dir);
      while ((de = readdir (dp)) != NULL){
         if (strcmp (de->d_name, ".") == 0) continue;
         if (strcmp (de->d_name, "..") == 0) continue;
         filelen=dirlen + strlen(de->d_name) + 2;
         file=calloc(filelen,1);
         if (file==NULL){
            //context_uninit(p);
            do_log(LOG_EMERG, "Could not allocate memory for virusdir file entry!");
            return 1;
         }
         snprintf(file, filelen, "%s%s", dir, de->d_name);
         do_log(LOG_DEBUG, "Unlinking (%s)", file);
         if ((unlink(file)<0)) do_log(LOG_EMERG, "File Error! Could not erase %s",file);
         free(file);
      }
      closedir (dp);
      do_log(LOG_DEBUG, "Removing directory %s", dir);
      if ((rmdir(dir)<0)) do_log(LOG_EMERG, "Directory Error! Could not erase %s",dir);
   }
   free(dir);
   return 0;
}

char *stralloc(size_t length){
   register int i;

   if (UINT_MAX == length)       /* Assume size_t == unsigned int    */
      return NULL;

   i = stralloc_ptr++;
   ++length;                     /* Allow for terminating NUL        */

   if ((!strings[i]) || (length > strlen(strings[i]))){
      strings[i] = (char *)realloc(strings[i], length);
      assert(NULL != strings[i]);
      str_tag[i] = -1;
   }
   else  str_tag[i] = 0;
   stralloc_ptr &= 7;
   return (strings[i]);
   /* Maintains 8 strings in a circular buffer */
}

char *right(char *string, size_t i){
   char *buf;
   size_t strlength = strlen(string);

   if (i > strlength)
      i = strlength;
   buf = stralloc(i);
   strcpy(buf, &string[strlength-i]);
   return buf;
}

char *strreplace(char *haystack,char *needle,char *rstr){
   size_t size=strlen(haystack)+1;
   char *p=malloc(size * sizeof(char));
   char *ptrp=p;
   char *newstr=haystack;
   char *match;
   char *replace;
   int i,j;

   if (p==NULL){
      //context_uninit(p);
      do_log(LOG_EMERG, "Could not allocate memory for strreplace!");
      return(p);
   }
   while (*newstr){
      match=needle;
      replace=rstr;
      i=0;
      while(*newstr && *match){
         if(*newstr != *match){
            *ptrp++=*newstr++;
            match=needle;
            i=0;
         } else if(*newstr==*match){
            *ptrp++=*newstr++;
            match++;
            i++;
         }
      }
      if(i==(int)strlen(needle)){
         j=0;
         while(j<i){
            ptrp--;
            j++;
         }
         while(*replace){
            *ptrp++=*replace++;
         }
      }
   }
   *ptrp='\0';
   return(p);
}

int checktimeout(struct proxycontext *p){
   int readerr=0, senterr=0;
   char svrout[1];

   if (p->now+30<time(NULL)){
      if (p->ismail != 5){ // Are we sending message to client?
         if (config->broken){
            /* Line parsing */
            if (!p->gobogus) readerr=getline(p->header_fd,p->hdrbuf);
            if (readerr>=0 && !p->gobogus){
               senterr=writeline(p->client_fd, WRITELINE_LEADING_RN, p->hdrbuf->line);
               if (senterr < 0) return senterr;
               do_log(LOG_DEBUG, "Timeout: Sent client a line: %s", p->hdrbuf->line);
               p->hdroffset++;
            }else{
               /* End of hdrbuf! We are still parsing! */
               if (!p->gobogus) p->gobogus=1;
               senterr=writeline(p->client_fd, WRITELINE_LEADING_RN, BOGUSX);
               if (senterr < 0) return senterr;
               do_log(LOG_DEBUG, "Timeout: Sent client a bogus line.");
            }
         } else {
            /* Character parsing */
            if (!p->gobogus) readerr=read(p->header_fd,p->cbuff,1);
            if (readerr>=0 && !p->gobogus){
               senterr=secure_write(p->client_fd,p->cbuff,1);
               if (senterr < 0) return senterr;
               do_log(LOG_DEBUG, "Timeout: Sent client a character: %s",p->cbuff);
               p->hdroffset++;
            }else{
               /* End of hdrbuff! We are still parsing! */
               p->gobogus=1;
               if (p->boguspos < 74){
                  svrout[0]=BOGUSX[p->boguspos];
                  senterr=secure_write(p->client_fd,svrout,1);
                  if (senterr < 0) return senterr;
                  do_log(LOG_DEBUG, "Timeout: Sent client a character: %s",svrout);
                  p->boguspos++;
               }else{
                  if (p->boguspos < 422){
                     senterr=secure_write(p->client_fd,PERIOD,1);
                     if (senterr < 0) return senterr;
                     p->boguspos++;
                  }else{
                     senterr=writeline(p->client_fd,WRITELINE_LEADING_N,PERIOD);
                     if (senterr < 0) return senterr;
                     p->boguspos=0;
                  }
               }
            }
         }
      }
      /* Only send NOOP when we are processing or sending to client*/
      if (p->ismail > 3){
         do_log(LOG_DEBUG, "Sending server a NOOP...");
         senterr=writeline(p->server_fd, WRITELINE_LEADING_RN, SVRCMD);
         if (senterr < 0) return senterr;
         p->noop++;
      }
      do_log(LOG_DEBUG, "Reseting time...");
      p->now = time(NULL);
   }
   return 0;
}

int checkbuff(int fdc) {
   fd_set rfds;
   struct timeval tv;
   int retval,fdc2;

   FD_ZERO(&rfds);
   FD_SET(fdc, &rfds);
   tv.tv_sec = 0;
   tv.tv_usec = 10000;
   fdc2=fdc+1;
   retval = select(fdc2, &rfds, NULL, NULL, &tv);
   if (retval == -1) return 2;
   else if (retval) return 1;
   return 0;
}

int scan_mailfile(struct proxycontext *p){
   int ret, ret2, viret, fdc;
   DIR *dp;
   struct dirent *de;
   int maildirlen;
   char * file;
   int spamfd=-1;
   unsigned long len=0;
   char spmcmd[512];
   char newmsg[512];
#define NEWMSG "newmsg"
#define COPYMSG "/var/spool/p3scan/copymsg"
   FILE * scanner;
   static char  line[4096*16];
   //static char  line[4096];
   struct statvfs fs;
   unsigned long kbfree;
   int htmlfd;

   ret=checktimeout(p);
   if (ret < 0) return SCANNER_RET_CRIT;
   /* See if we have enough room to process the message based upon
   what the user determines is enough room in p3scan.conf */
   if ( statvfs( config->virusdir, &fs ) == SCANNER_RET_ERR){
      p->errmsg=1;
      context_uninit(p);
      do_log(LOG_EMERG, "Unable to get available space!");
      return SCANNER_RET_CRIT; // Should never reach here, but keep it clean. :)
   }
   kbfree=(fs.f_bavail * fs.f_frsize / 1024);
   if ( config->freespace != 0 && kbfree < config->freespace ){
      p->errmsg=1;
      do_log(LOG_CRIT, "Not enough space! Available space: %d", kbfree);
      return SCANNER_RET_CRIT;
   }

   /* Do we want to rename attachments? */
   if (config->renattach != NULL){
      ret=checktimeout(p);
      if (ret < 0) return SCANNER_RET_CRIT;
      /* Only rename non-infected attachments */
      len=strlen(config->virusdir)+strlen(NEWMSG);
      snprintf(newmsg, len, "%s%s", config->virusdir,NEWMSG);
      if ((spamfd=open(newmsg,O_WRONLY | O_CREAT | O_TRUNC,  S_IRUSR | S_IWUSR))<0){
         p->errmsg=1;
         do_log(LOG_ALERT, "Can't create newmsg!");
         return SCANNER_RET_CRIT;
      }
      len=strlen(config->renattach)+strlen(" < ")+strlen(p->mailfile)+strlen(" 2>&1 ");
      snprintf(spmcmd, len, "%s < %s 2>&1", config->renattach, p->mailfile);
      if ((scanner=popen(spmcmd, "r"))==NULL){
         p->errmsg=1;
         do_log(LOG_ALERT, "Can't start renattach '%s' !!!", spmcmd);
         return SCANNER_RET_ERR;
      }
      /* call made, check timeout until data returned */
      fdc=fileno(scanner);
      ret2=checkbuff(fdc);
      if (ret2 > 1) return SCANNER_RET_CRIT;
      while (!ret2){
         ret2=checkbuff(fdc);
         if (ret2 > 1) return SCANNER_RET_CRIT;
         ret=checktimeout(p);
         if (ret < 0) return SCANNER_RET_CRIT;
      }
      while ((fgets(line, 4095, scanner))!=NULL){
         line[strlen(line)-1]='\0';
#ifdef DEBUG_SCANNING
         do_log(LOG_DEBUG, "AttachLine: '%s'", line);
#endif
         writeline(spamfd, WRITELINE_LEADING_N, line);
         ret=checktimeout(p);
         if (ret < 0) return SCANNER_RET_CRIT;
      }
      if((spamfd=close(spamfd))<0){
         p->errmsg=1;
         context_uninit(p);
         do_log(LOG_EMERG, "Can't close newmsg Err: %s", spamfd);
         return SCANNER_RET_CRIT;
      }
      ret=pclose(scanner);
      len=strlen(MOVEIT)+1+strlen(newmsg)+1+strlen(p->mailfile)+1;
      snprintf(spmcmd, len, "%s %s %s",MOVEIT,newmsg,p->mailfile);
      if ((scanner=popen(spmcmd, "r"))==NULL){
         do_log(LOG_ALERT, "Can't '%s' !!!", spmcmd);
         p->errmsg=1;
         return SCANNER_RET_ERR;
      }
      ret=pclose(scanner);
   }
   /* end renattach */

   ret=checktimeout(p);
   if (ret < 0) return SCANNER_RET_CRIT;
   p->virinfo=NULL;
   if (config->demime){
      /* extract MIME Parts into maildir */
      do_log(LOG_DEBUG, "DeMIMEing to %s", p->maildir);
      viret = mkdir(p->maildir, S_IRWXU);
      if ((viret == -1)&&(errno != EEXIST)){
         do_log(LOG_CRIT, "Cannot create directory '%s' (%s). Can't scan mail.\n",
         p->maildir, strerror(errno));
         p->errmsg=1;
         return SCANNER_RET_CRIT;
      }
      MIMEH_set_outputdir( p->maildir );
      MIME_init();
      MIME_unpack( p->maildir, p->mailfile, 0);
      MIME_close();
      /* TODO: ripmime error checking */
      p->scanthis = p->maildir;

      /* SCAN */
      if (config->scanner->dirscan){
         /* scanner wants to scan the directory itself, so call it once */
         /* give directory name to scanner, if he want (basic) */
         do_log(LOG_DEBUG, "Invoking scanner");
         p->virinfo=NULL;
         viret=config->scanner->scan(p, &p->virinfo);
         do_log(LOG_DEBUG, "Scanner returned %i", viret);
      } else {
         /* give all files to scanner
          * also connect all virusinfos to one string */
         viret=scan_directory(p);
      }

      /* unlinking MIME-files */
      do_log(LOG_DEBUG, "Unlinking deMIMEd files", p->maildir);
      maildirlen=strlen(p->maildir);
      if ((dp = opendir (p->maildir)) == NULL){
         p->errmsg=1;
         context_uninit(p);
         do_log(LOG_EMERG, "Oops, can't open directory %s for unlinking files", p->maildir);
      } else {
         while ((de = readdir (dp)) != NULL){
            if (strcmp (de->d_name, ".") == 0) continue;
            if (strcmp (de->d_name, "..") == 0) continue;
            ret=checktimeout(p);
            if (ret < 0) return SCANNER_RET_CRIT;
            file=malloc(maildirlen + strlen(de->d_name) +2);
            if (file==NULL){
               p->errmsg=1;
               context_uninit(p);
               do_log(LOG_EMERG, "Could not allocate memory for unlink deMIME!");
            }
            sprintf(file, "%s/%s", p->maildir, de->d_name);
            //do_log(LOG_DEBUG, "unlink(%s)", file);
            if ((unlink(file)<0))do_log(LOG_EMERG, "File Error! Could not erase %s",file);
            free(file);
         }
         closedir (dp);
         rmdir(p->maildir);
      }
   } else { /* if config->demime */
      /* no demime */
      p->scanthis = p->mailfile;
      /* invoke configured scanner */
      do_log(LOG_DEBUG, "Invoking scanner");
      p->virinfo=NULL;
      viret=config->scanner->scan(p, &p->virinfo);
      do_log(LOG_DEBUG, "Scanner returned %i", viret);
      /* TODO: Fail on unexpected scanner return code. */
   }
   if (p->virinfo){
      TRIM(p->virinfo);
   }
   ret=checktimeout(p);
   if (ret < 0) return SCANNER_RET_CRIT;

   /* Do we want to scan for spam? */
   if (config->checkspam && !config->ispam && !viret){
      /* Ok, first, create a new message */
      do_log(LOG_DEBUG, "Checking for spam");
      len=strlen(config->virusdir)+strlen(NEWMSG);
      snprintf(newmsg, len, "%s%s", config->virusdir,NEWMSG);
      if ((spamfd=open(newmsg,O_WRONLY | O_CREAT | O_TRUNC,  S_IRUSR | S_IWUSR))<0){
         do_log(LOG_ALERT, "Can't create newmsg!");
         p->errmsg=1;
         return SCANNER_RET_CRIT;
      }
      /* First, find out if we need to replace "dspamuser" */
      if (strstr(config->spamcheck,"dspamuser")!=NULL){
         config->spamcheck=strreplace(config->spamcheck,"dspamuser",config->dspamuser);
      }
      /* Now call anti-spam program */
      len=strlen(config->spamcheck)+strlen(" < ")+strlen(p->mailfile)+strlen(" 2>&1 ");
      /* Trap output to a buffer. */
      snprintf(spmcmd, len, "%s < %s 2>&1", config->spamcheck, p->mailfile);
      if ((scanner=popen(spmcmd, "r"))==NULL){
         p->errmsg=1;
         do_log(LOG_ALERT, "Can't start spammer '%s' !!!", spmcmd);
         return SCANNER_RET_ERR;
      }
      /* call made, check timeout until data returned */
      fdc=fileno(scanner);
      ret2=checkbuff(fdc);
      if (ret2 > 1) return SCANNER_RET_CRIT;
      while (!ret2){
         ret2=checkbuff(fdc);
         if (ret2 > 1) return SCANNER_RET_CRIT;
         ret=checktimeout(p);
         if (ret < 0) return SCANNER_RET_CRIT;
      }
      while ((fgets(line, 4095, scanner))!=NULL){
         ret=checktimeout(p);
         if (ret < 0) return SCANNER_RET_CRIT;
         line[strlen(line)-1]='\0';
         /* write the buffer to our new message */
#ifdef DEBUG_SCANNING
         do_log(LOG_DEBUG, "SpammerLine: '%s'", line);
#endif
         ret=checktimeout(p);
         if (ret < 0) return SCANNER_RET_CRIT;
         if ((strncmp(line,".",1 ) == 0 && strlen(line) == 1)){
            do_log(LOG_DEBUG, "Not writing '.'");
         } else if ((strncmp(line,".\r",2) == 0 && strlen(line) == 2)){
            do_log(LOG_DEBUG, "Not writing '.'");
         } else {
            writeline(spamfd, WRITELINE_LEADING_N, line);
         }
      }
      do_log(LOG_DEBUG, "Writing new .");
      writeline(spamfd, WRITELINE_LEADING_N, ".");
      if((spamfd=close(spamfd))<0){
         context_uninit(p);
         do_log(LOG_EMERG, "Can't close newmsg Err: %s", spamfd);
         return SCANNER_RET_CRIT;
      }
      ret=pclose(scanner);
      /* Spam report is now in $virusdir/newmsg
      so now replace original msg with newmsg */
      len=strlen(MOVEIT)+1+strlen(newmsg)+1+strlen(p->mailfile)+1;
      snprintf(spmcmd, len, "%s %s %s",MOVEIT,newmsg,p->mailfile);
      if ((scanner=popen(spmcmd, "r"))==NULL){
         p->errmsg=1;
         do_log(LOG_ALERT, "Can't '%s' !!!", spmcmd);
         return SCANNER_RET_ERR;
      }
      ret=pclose(scanner);
   }
   /* End of spam checking */

   /* Start HTML parsing */
   if (config->overwrite && !viret){
      do_log(LOG_DEBUG,"HTML Parsing now...");
      ret=checktimeout(p);
      if (ret < 0) return SCANNER_RET_CRIT;
      /* Do not parse infected mail as client will not see it anyway. */
      len=strlen(config->virusdir)+strlen(NEWMSG);
      snprintf(newmsg, len, "%s%s", config->virusdir,NEWMSG);
      if ((htmlfd=open(newmsg,O_WRONLY | O_CREAT | O_TRUNC,  S_IRUSR | S_IWUSR))<0){
         p->errmsg=1;
         do_log(LOG_ALERT, "Can't create newmsg!");
         return SCANNER_RET_CRIT;
      }
      /* First, find out if we need to replace "htmluser" */
      if (strstr(config->overwrite,"htmluser")!=NULL){
         config->overwrite=strreplace(config->overwrite,"htmluser",config->dspamuser);
      }
      /* Now call HTML Parsing program */
      len=strlen(config->overwrite)+strlen(" < ")+strlen(p->mailfile)+strlen(" 2>&1 ");
      /* Trap parser program output to a buffer. */
      snprintf(spmcmd, len, "%s < %s 2>&1", config->overwrite, p->mailfile);
      if ((scanner=popen(spmcmd, "r"))==NULL){
         p->errmsg=1;
         do_log(LOG_ALERT, "Can't start HTML parser '%s' !!!", spmcmd);
         return SCANNER_RET_ERR;
      }
      /* call made, check timeout until data returned */
      fdc=fileno(scanner);
      ret2=checkbuff(fdc);
      if (ret2 > 1) return SCANNER_RET_CRIT;
      while (!ret2){
         ret2=checkbuff(fdc);
         if (ret2 > 1) return SCANNER_RET_CRIT;
         ret=checktimeout(p);
         if (ret < 0) return SCANNER_RET_CRIT;
      }
      while ((fgets(line, 4095, scanner))!=NULL){
         line[strlen(line)-1]='\0';
         /* write the buffer to our new message */
#ifdef DEBUG_SCANNING
         do_log(LOG_DEBUG, "HTML Line: '%s'", line);
#endif
         writeline(htmlfd, WRITELINE_LEADING_N, line);
         ret=checktimeout(p);
         if (ret < 0) return SCANNER_RET_CRIT;
      }
      if((htmlfd=close(htmlfd))<0){
         context_uninit(p);
         do_log(LOG_EMERG, "Can't close newmsg Err: %s", htmlfd);
         return SCANNER_RET_CRIT;
      }
      /* See if the call worked */
      ret=pclose(scanner);

      if(ret==0){
         len=strlen(MOVEIT)+1+strlen(newmsg)+1+strlen(p->mailfile)+1;
         snprintf(spmcmd, len, "%s %s %s",MOVEIT,newmsg,p->mailfile);
         if ((scanner=popen(spmcmd, "r"))==NULL){
            p->errmsg=1;
            do_log(LOG_ALERT, "Can't '%s' !!!", spmcmd);
            return SCANNER_RET_ERR;
         }
         ret=pclose(scanner);
      } else do_log(LOG_ALERT, "HTML Parser returned error! Ignoring it's output!");
   }
   /* End HTML parsing */

   if (strlen(NONULL(p->virinfo))<1){
      if (p->virinfo) free(p->virinfo);
      p->virinfo=strdup(MESSAGE_NOVIRINFO);
   }
   ret=viret;
   return ret;
}

unsigned long send_mailfile(char * mailfile, int fd, struct proxycontext *p){
   struct linebuf *filebuf;
   int mailfd;
   int res, sendret;
   unsigned long len=0;
   int gotprd;
   char svrout[1];

   if ((mailfd=open(mailfile, O_RDONLY ))<0){
      context_uninit(p);
      do_log(LOG_EMERG, "Can't open mailfile (%s)!\n", mailfile);
      return 0;
   }
   filebuf=linebuf_init(16384);
   if (!filebuf){
      close(mailfd);
      close(fd);
      context_uninit(p);
      do_log(LOG_EMERG, "Could not allocate memory for sending mail!");
   }
   gotprd=0;
   /* advance to mailfd pointer to past data already sent: */
   if (config->broken){
      if(p->hdroffset && !p->gobogus){
         while (p->hdroffset){
            res=getline(mailfd, filebuf);
            p->hdroffset--;
         }
      }
   } else {
      if(p->hdroffset){
         lseek(mailfd, p->hdroffset, SEEK_SET);
      }
      /* See if bogus headerline sent */
      if (p->gobogus){
         if (p->boguspos < 91){
            svrout[0]=BOGUSX[p->boguspos];
            secure_write(p->client_fd,svrout,1);
            p->boguspos++;
         }
         /* now close it */
         writeline(p->client_fd,WRITELINE_LEADING_RN,PERIOD);
         p->gobogus=0;
      }
   }
   while (1){
      sendret=checktimeout(p);
      if (sendret==GETLINE_PIPE){
         do_log(LOG_CRIT, "Client disappeared during mail send!");
         linebuf_uninit(filebuf);
         return EPIPE;
      } else if (sendret){
         context_uninit(p);
         do_log(LOG_EMERG, "Error sending mail to client");
         /* we are dead now. Should not reach here. But allow it
         to fall through in case LOG_EMERG is changed in the future. */
         linebuf_uninit(filebuf);
         return 0;
      }
      if ((res=getline(mailfd, filebuf))<0){
         if (res==GETLINE_TOO_LONG){
            /* Buffer contains part of line,
               take care of later */
         } else {
            /* Other error, take care of later */
            break;
         }
      }
      if (filebuf->linelen >=0 ){
         len += filebuf->linelen;
#ifdef DEBUG_MESSAGE
         do_log(LOG_DEBUG, ">%s", filebuf->line);
#endif
         if ((strncmp(filebuf->line,".",1 ) == 0 && strlen(filebuf->line) == 1)) gotprd=1;
         if ((strncmp(filebuf->line,".\r",2) == 0 && strlen(filebuf->line) == 2)) gotprd=1;
         /* Take care of buffer here */
         if (res==GETLINE_TOO_LONG){
            sendret=writeline(fd, WRITELINE_LEADING_NONE, filebuf->line);
         } else {
            sendret=writeline(fd, WRITELINE_LEADING_RN, filebuf->line);
         }
        if (sendret==GETLINE_PIPE){
            do_log(LOG_CRIT, "Client disappeared during mail send!");
            linebuf_uninit(filebuf);
            return EPIPE;
         } else if (sendret){
            context_uninit(p);
            do_log(LOG_EMERG, "Error sending mail to client");
            /* we are dead now. Should not reach here. But allow it
            to fall through in case LOG_EMERG is changed in the future. */
            linebuf_uninit(filebuf);
            return 0;
         }
      }
   }
   if (res!=GETLINE_EOF){
      do_log(LOG_CRIT, "error reading from mailfile %s, error code: %d", mailfile, res);
      linebuf_uninit(filebuf);
      return 0;
   }
   if (!gotprd){
      do_log(LOG_DEBUG, "Wrote new EOM.");
      writeline(fd, WRITELINE_LEADING_N, ".");
   }
   linebuf_uninit(filebuf);
   close(mailfd);
   return len;
}

void set_defaultparams(struct proxycontext * p){
   char buf[256];
   gethostname(buf, 256);
   paramlist_set(p->params, "%HOSTNAME%", buf);
   getdomainname(buf, 256);
   paramlist_set(p->params, "%DOMAINNAME%", buf);
   paramlist_set(p->params, "%PROGNAME%", PROGNAME);
   paramlist_set(p->params, "%VERSION%", VERSION);
   paramlist_set(p->params, "%CLIENTIP%", inet_ntoa(p->client_addr.sin_addr));
   sprintf(buf, "%i", ntohs(p->client_addr.sin_port));
   paramlist_set(p->params, "%CLIENTPORT%", buf);
   paramlist_set(p->params, "%SERVERIP%", inet_ntoa(p->server_addr.sin_addr));
   sprintf(buf, "%i", ntohs(p->server_addr.sin_port));
   paramlist_set(p->params, "%SERVERPORT%", buf);
}

void set_maildateparam(struct paramlist * params){
   char buf[256];
   int diff_hour, diff_min;
   time_t now = time(NULL);
   struct tm *tm = localtime(&now);
   struct tm local;
   struct tm *gmt;
   int len;
   memcpy(&local, tm, sizeof(struct tm));
   gmt = gmtime(&now);

   diff_min = 60*(local.tm_hour - gmt->tm_hour) + local.tm_min - gmt->tm_min;
   if (local.tm_year != gmt->tm_year) diff_min += (local.tm_year > gmt->tm_year)? 1440 : -1440;
   else if (local.tm_yday != gmt->tm_yday)
   diff_min += (local.tm_yday > gmt->tm_yday)? 1440 : -1440;
   diff_hour = diff_min/60;
   diff_min  = abs(diff_min - diff_hour*60);
   len = strftime(buf, sizeof(buf), "%a, ", &local);
   (void) sprintf(buf + len, "%02d ", local.tm_mday);
   len += strlen(buf + len);
   len += strftime(buf + len, sizeof(buf) - len, "%b %Y %H:%M:%S", &local);
   (void) sprintf(buf + len, " %+03d%02d", diff_hour, diff_min);
   paramlist_set(params, "%MAILDATE%", buf);
}

void set_paramsfrommailheader(char * mailfile, struct paramlist * params){
   struct linebuf *l;
   int fd;
   char * c;
   if ( (fd=open(mailfile, O_RDONLY ))<0) return;
   l=linebuf_init(4096*16);
   while (getline(fd, l)>=0){
      if (l->linelen >0 ){
         if (!strncasecmp(l->line, "from: ", 6)){
            c=l->line+6;
            TRIM(c);
            paramlist_set(params, "%MAILFROM%", c);
         } else if (!strncasecmp(l->line, "subject: ", 9)) {
            c=l->line+9;
            TRIM(c);
            paramlist_set(params, "%SUBJECT%", c);
         } else if (!strncasecmp(l->line, "To: ", 4)) {
            c=l->line+4;
            TRIM(c);
            paramlist_set(params, "%MAILTO%", c);
         }
      } else if (l->linelen == 0) break; /* only the header */
   }
   linebuf_uninit(l);
   close(fd);
}

void unset_paramsfrommailheader(struct paramlist * params){
   paramlist_set(params, "%MAILFROM%", NULL);
   paramlist_set(params, "%SUBJECT%", NULL);
   paramlist_set(params, "%MAILTO%", NULL);
   paramlist_set(params, "%VIRUSNAME%", NULL);
   paramlist_set(params, "%MAILFILE%", NULL);
   paramlist_set(params, "%P3SCANID%", NULL);
}

int do_virusaction(struct proxycontext * p){
   char *mail;
   char svrout[1];
   char comm[4096];
   unsigned long len;
   int readerr=0, bufferr=0;
#define CHMODCMD "/bin/chmod 0600"

   /* first, complete the header of the original message... */
   if (config->broken){
      if(p->hdroffset && !p->gobogus){
         while ((readerr=getline(p->header_fd,p->hdrbuf)!=GETLINE_EOF)){
            writeline(p->client_fd, WRITELINE_LEADING_RN, p->hdrbuf->line);
         }
      } else {
         while ((readerr=read(p->header_fd,p->cbuff,1)>0)){
            if (readerr < 0){
               context_uninit(p);
               do_log(LOG_EMERG, "Could not read header file!");
            }
            bufferr=secure_write(p->client_fd,p->cbuff,1);
            if (bufferr==GETLINE_ERR) {
               context_uninit(p);
               do_log(LOG_EMERG, "Could not flush header buffer to client!");
            }
         }
      }
   } else {
      if (p->gobogus){
         if (p->boguspos < 74){
            svrout[0]=BOGUSX[p->boguspos];
            secure_write(p->client_fd,svrout,1);
            p->boguspos++;
         }
         /* now close it */
         writeline(p->client_fd,WRITELINE_LEADING_RN,PERIOD);
         p->gobogus=0;
      }else{
         while ((readerr=read(p->header_fd,p->cbuff,1)>0)){
            if (readerr < 0){
               context_uninit(p);
               do_log(LOG_EMERG, "Could not read header file!");
            }
            bufferr=secure_write(p->client_fd,p->cbuff,1);
            if (bufferr==GETLINE_ERR) {
               context_uninit(p);
               do_log(LOG_EMERG, "Could not flush header buffer to client!");
            }
         }
      }
   }
   if (!p->hdrdate) writeline_format(p->client_fd,WRITELINE_LEADING_RN,"Date: %s", paramlist_get(p->params, "%MAILDATE%"));
   if (!p->hdrfrom) writeline_format(p->client_fd,WRITELINE_LEADING_RN,"From: %s", paramlist_get(p->params, "%MAILFROM%"));
   if (!p->hdrto) writeline_format(p->client_fd,WRITELINE_LEADING_RN,"To: %s", paramlist_get(p->params, "%MAILTO%"));
   /* There is no Subject line... */
   writeline_format(p->client_fd,WRITELINE_LEADING_RN,"Subject: %s %s",config->subject,paramlist_get(p->params, "%VIRUSNAME%"));
   do_log(LOG_DEBUG,"Finished.");
   p->hdroffset=0;
   mail=malloc(strlen(p->mailfile)+10);
   if (mail==NULL){
      context_uninit(p);
      do_log(LOG_EMERG, "Can't allocate memory for mail!");
      return SCANNER_RET_ERR;
   }
   /* If mail is infected AND we just want to delete it, just don't move it */
   if (!config->delit){
      snprintf(comm,4096,"%s %s %s",MOVEIT,p->mailfile,config->virusdirbase);
      system(comm);
      snprintf(comm,4096,"%s %s%s",CHMODCMD,config->virusdirbase,p->filename);
      do_log(LOG_DEBUG,"Forcing infected file 0600 %s",comm);
      system(comm);
   }
   sprintf(mail, "%s/%i.mailout", config->notifydir,getpid());
   if (parsefile(config->virustemplate, mail, p->params, WRITELINE_LEADING_RN)) {
      context_uninit(p);
      unlink(mail);
      free(mail);
      do_log(LOG_EMERG, "Can't open mail notification template %s",config->virustemplate);
      return -1;
   }
   do_log(LOG_DEBUG, "sending new mail");
   len=send_mailfile(mail, p->client_fd,p);
   p->bytecount+=len;
   unlink(mail);
   free(mail);
   p->ismail=0;
   if (len>0) return 0;
   return -1;
}

struct proxycontext * context_init(void){
   struct proxycontext * p;
   p=malloc(sizeof(struct proxycontext));
   p->ismail=0;
   p->msgnum=0;
   p->mailcount=0;
   p->bytecount=0;
   p->socksize=sizeof(struct sockaddr_in);
   p->client_fd=-1;
   p->server_fd=-1;
   p->header_fd=-1;
   p->hdroffset=0;
   p->clientbuf=NULL;
   p->serverbuf=NULL;
   p->hdrbuf=NULL;
   p->virinfo=NULL;
   p->scanthis=NULL;
   p->scannerinit=SCANNER_INIT_NO;
   return p;
}

void context_uninit(struct proxycontext * p){
   if (p->client_fd > 0 ) close(p->client_fd);
   if (p->server_fd > 0 ) close(p->server_fd);
   if (p->header_fd > 0 ) close(p->header_fd);
   paramlist_uninit(&p->params);
   linebuf_uninit(p->clientbuf);
   linebuf_uninit(p->serverbuf);
   if (config->broken) linebuf_uninit(p->hdrbuf);
   free(p);
}

void closehdrfile(struct proxycontext * p){
   p->fakehdrdone=1;
   close(p->header_fd);
   p->hdroffset=0;
   p->header_fd = open(p->p3shdrfile, O_RDONLY);
   if (p->header_fd<0){
      context_uninit(p);
      do_log(LOG_EMERG,"Critical error opening file '%s', Program aborted.", p->p3shdrfile);
      /* should not reach here as we are dead */
   }
   p->now = time(NULL);
   if (!p->notified){
      do_log(LOG_DEBUG, "Informing email client to wait...");
      writeline_format(p->client_fd, WRITELINE_LEADING_RN,"+OK P3Scan'ing...");
      p->notified=1;
   }
   p->ismail=3;
}

void do_sigterm_proxy(int signr){
   do_log(LOG_DEBUG, "do_sigterm_proxy, signal %i", signr);
   if (global_p == NULL){
      do_log(LOG_DEBUG, "already uninitialized");
      return;
   }
   if (signr != -1) do_log(LOG_CRIT, "We cot SIGTERM!"); /* sig -1 is ok */
   if (global_p->client_fd != -1) close(global_p->client_fd);
   if (global_p->server_fd != -1) close(global_p->server_fd);
   if (global_p->scannerinit==SCANNER_INIT_OK && config->scanner->uninit2){
      do_log(LOG_DEBUG, "calling uninit2");
      config->scanner->uninit2(global_p);
      do_log(LOG_DEBUG, "uninit2 done");
   }
   do_log(LOG_DEBUG, "Uninit context");
   context_uninit(global_p);
   global_p=NULL;
   do_log(LOG_DEBUG,"context_uninit done, exiting now");
   if (signr != -1) exit(1);
}

int proxy(struct proxycontext *p){
   fd_set fds_read;
   struct timeval timeout;
   int scanfd=-1;
   int error;
   int maybe_a_space; // signals a space in the keyword for setting USERNAME var
   int clientret, serverret;
   unsigned long len;
   char buf[64];
#ifdef SET_TOS
   int tos;
#endif
   int scannerret, ret;

   p->now = time(NULL);
   p->header_fd=-1;
   p->noop=0;

   do_log(LOG_NOTICE, "Connection from %s:%i", inet_ntoa(p->client_addr.sin_addr), ntohs(p->client_addr.sin_port));

   p->server_addr.sin_family = AF_INET;
   if (htonl(INADDR_ANY) == config->targetaddr.sin_addr.s_addr) {
      if (getsockopt(p->client_fd, SOL_IP, SO_ORIGINAL_DST, &p->server_addr, &p->socksize)){
         do_log(LOG_CRIT, "No IP-Conntrack-data (getsockopt failed)");
         return 1;
      }
      /* try to avoid loop */
      if (((ntohl(p->server_addr.sin_addr.s_addr) == INADDR_LOOPBACK) &&        p->server_addr.sin_port == config->addr.sin_port ) || /* src.ip == dst.ip */      (p->server_addr.sin_addr.s_addr == p->client_addr.sin_addr.s_addr)){
         do_log(LOG_CRIT, "Oops, that would loop!");
         return 1;
      }
   }
   else {
      /* non-proxy mode */
      p->server_addr.sin_addr.s_addr = config->targetaddr.sin_addr.s_addr;
      p->server_addr.sin_port = config->targetaddr.sin_port;
   }

   do_log(LOG_NOTICE, "Real-server adress is %s:%i", inet_ntoa(p->server_addr.sin_addr), ntohs(p->server_addr.sin_port));
   /* open socket to 'real-server' */
   if ( (p->server_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0){
      do_log(LOG_CRIT, "Cannot open socket to real-server");
      return 1;
   }
#ifdef SET_TOS
   tos=SET_TOS;
   if (setsockopt(p->client_fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)))       do_log(LOG_WARNING, "Can't set TOS (incoming connection)");
   if (setsockopt(p->server_fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)))        do_log(LOG_WARNING, "Can't set TOS (outgoing connection)");
#endif
   if (connect(p->server_fd, (struct sockaddr *) &p->server_addr, p->socksize)){
      do_log(LOG_CRIT, "Cannot connect to real-server");
      return 1;
   }

   p->clientbuf=linebuf_init(4096);
   p->serverbuf=linebuf_init(4096);
   p->params=paramlist_init();
   set_defaultparams(p);
   /* releasing sockfd
   * it seems to work, that if the listener (our parent-PID) gots kicked
   * we can work AND another listener can bind to the port
   */
   close(sockfd);
   do_log(LOG_DEBUG, "starting mainloop");
   while ( 1 ){
      /* read from client */
      if ((clientret=getline(p->client_fd, p->clientbuf))<0){
         do_log(LOG_DEBUG, "Closing connection (no more input from client)");
         return 0;
      }
      if (clientret==GETLINE_OK) do_log(LOG_DEBUG, "--> %s", p->clientbuf->line);

      /* read from server */
      if ((serverret=getline(p->server_fd, p->serverbuf))<0){
         if (serverret==GETLINE_TOO_LONG){
            do_log(LOG_DEBUG, "Line too long: Getting rest of line.");
         } else {
            do_log(LOG_DEBUG, "Closing connection (no more input from server)");
            return 0;
         }
      } else {
         if (p->noop){
            if (!strncasecmp(p->serverbuf->line,"+OK", 3)){
               do_log(LOG_DEBUG, "%s: NOOP response. Flushed %i NOOP's", p->serverbuf->line, p->noop);
               linebuf_zero(p->serverbuf);
               p->noop=0;
            } else {
               do_log(LOG_DEBUG, "Oops, %s doesn't looks like a server's NOOP response. Waiting next...", p->serverbuf->line);
            }
         }
      }
      if (serverret==GETLINE_OK && p->serverbuf->line != NULL
#ifndef DEBUG_MESSAGE
          && (p->ismail < 2 || p->ismail > 3)// Are we processing a message?
#endif
      ) do_log(LOG_DEBUG, "<-- %s", p->serverbuf->line);

      if (clientret == GETLINE_NEED_READ && serverret == GETLINE_NEED_READ){
         FD_ZERO(&fds_read);
         FD_SET(p->client_fd, &fds_read);
         FD_SET(p->server_fd, &fds_read);
         timeout.tv_sec = 300;
         timeout.tv_usec = 0;
         if ((ret=select(p->server_fd + 1, &fds_read, NULL, NULL, &timeout))<1){
            /* timeout */
            do_log(LOG_DEBUG, "select returned %i", ret);
            break;
         } else continue ;
      }
      if (p->ismail>3) p->serverbuf->line=NULL;
      if ( p->clientbuf->linelen>=0 && p->ismail<2 ){ // Not processing message
         /* scan command the client sent */
         if (!strncasecmp(p->clientbuf->line,"retr", 4)){
            p->msgnum=atoi(&p->clientbuf->line[5]);
            if (p->msgnum<1){
               /* that's not ok */
               do_log(LOG_WARNING,"RETR msg %i (<1) !!!! ", p->msgnum);
               p->ismail=0;
            } else {
               do_log(LOG_DEBUG,"RETR %i", p->msgnum);
               /* enable message parsing (only if scanner enabled) */
               if (config->scannerenabled){
                  p->ismail=1;
               }
               p->mailcount++;
            }
         } else if (!strncasecmp(p->clientbuf->line,"top", 3)){
            p->msgnum=atoi(&p->clientbuf->line[4]);
            if (p->msgnum<1){
               /* that's not ok */
               do_log(LOG_WARNING,"TOP msg %i (<1) !!!! ", p->msgnum);
               p->ismail=0;
            } else {
               do_log(LOG_DEBUG,"TOP %i", p->msgnum);
               /* enable message parsing (only if scanner enabled) */
               if (config->scannerenabled){
                  p->ismail=1;
               }
               p->mailcount++;
            }
         } else {
            p->ismail=0;
         }
         if ((maybe_a_space = !strncasecmp(p->clientbuf->line,"apop", 4)) || !strncasecmp(p->clientbuf->line,"user", 4)){
            len=p->clientbuf->linelen -5;
            if( len >= sizeof(buf)) len=sizeof(buf)-1;
            if (len>0){
               memcpy(buf, (char *)(p->clientbuf->line)+5, len );
               buf[len]='\0';
               /* we don't want a space (surely with "apop") strtok is another choice. */
               if(maybe_a_space){
                  char *pbuf=strchr(buf,' ');
                  if(NULL != pbuf) *pbuf='\0';
               }
               TRIM(buf);
               paramlist_set(p->params, "%USERNAME%", buf);
            } else {
               paramlist_set(p->params, "%USERNAME%", "unknown");
            }
            do_log(LOG_NOTICE, "USER '%s'", paramlist_get(p->params, "%USERNAME%"));
            config->dspamuser=paramlist_get(p->params,"%USERNAME%");
         }
         /* write clientbuf to server_fd */
         /* ok, we can write */

         if (writeline(p->server_fd, WRITELINE_LEADING_RN, p->clientbuf->line)){
            do_log(LOG_CRIT, "can't send to server_fd");
            return 1;
         }
         p->clientbuf->linelen=-2;
      }
      if (p->serverbuf->linelen>=0){
         if (p->ismail==1){
            /* scan for answer */
            if (!strncasecmp(p->serverbuf->line,"+ok", 3)){
               /* Set timer now because we might have parsed alot of message numbers */
               p->now = time(NULL);
               /* generate unique filename */
               len=strlen(config->virusdir)+14;
               snprintf(p->mailfile, len, "%sp3scan.XXXXXX", config->virusdir);
               snprintf(p->p3shdrfile, len, "%sp3shdr.XXXXXX", config->virusdir);
               if (( scanfd=mkstemp(p->mailfile)) < 0 ){
                  p->ismail=0;
                  context_uninit(p);
                  do_log(LOG_EMERG,"Critical error opening file '%s', Program aborted.", p->mailfile);
                  /* Should not reach here as we are dead */
               } else {
                  p->filename=right(p->mailfile,14);
                  if (( p->header_fd=mkstemp(p->p3shdrfile)) < 0 ){
                     p->ismail=0;
                     context_uninit(p);
                     do_log(LOG_EMERG,"Critical error opening file '%s', Program aborted.", p->p3shdrfile);
                  /* Should not reach here as we are dead */
                  }
                  p->ismail=2;
                  p->header_exists=0;
                  p->fakehdrdone=0;
                  p->notified=0;
                  p->gobogus=0;
                  p->boguspos=0;
                  p->hdrdate=0;
                  p->hdrfrom=0;
                  p->hdrto=0;
                  p->errmsg=0;
                  p->serverbuf->linelen=-2; /* don't send response */
                  if (config->broken) p->hdrbuf=linebuf_init(16384);
               }
            } else {
               do_log(LOG_DEBUG, "ismail=1, but we haven't got '+ok' so... setting p->ismail=0");
               p->ismail=0;
            }
         } else if (p->ismail>1){
            /* that means that we have to read the mail into file */
            if (p->serverbuf->linelen == 0 && !p->header_exists){
               writeline(scanfd, WRITELINE_LEADING_N, "X-" PROGNAME ": Version " VERSION " by <laitcg@cox.net>/<folke@ashberg.de>");
               writeline(p->header_fd, WRITELINE_LEADING_N, "X-" PROGNAME ": Version " VERSION " by <laitcg@cox.net>/<folke@ashberg.de>");
               p->header_exists=1;
               /* This is the first response the client gets after "RETR/TOP", so start
                  countdown timer now...
               */
               if (p->ismail < 3){
                  do_log(LOG_DEBUG,"Closing header buffer.");
                  closehdrfile(p);
               }else do_log(LOG_DEBUG,"notified=%i",p->notified);
            }
            if(!strncasecmp(p->serverbuf->line,"Date:",5) && p->ismail < 3) p->hdrdate=1;
            if(!strncasecmp(p->serverbuf->line,"From:",5) && p->ismail < 3) p->hdrfrom=1;
            if(!strncasecmp(p->serverbuf->line,"To:",3) && p->ismail < 3) p->hdrto=1;
            if(strstr(p->serverbuf->line,"MIME") || strstr(p->serverbuf->line,"Content-Type") || !strncasecmp(p->serverbuf->line,"Subject:",8)){
               if (p->ismail < 3){
                  do_log(LOG_DEBUG,"Caught MIME/Subj line, closing header buffer.");
                  closehdrfile(p);
               }
            }
            if (serverret==GETLINE_TOO_LONG){
               writeline(scanfd, WRITELINE_LEADING_NONE, p->serverbuf->line);
               if (p->ismail < 3) writeline(p->header_fd, WRITELINE_LEADING_NONE, p->serverbuf->line);
            } else {
               writeline(scanfd, WRITELINE_LEADING_N, p->serverbuf->line);
               if (p->ismail < 3) writeline(p->header_fd, WRITELINE_LEADING_N, p->serverbuf->line);
            }
            /* check if isp already marked as spam so we don't tie up
               anti-spam resources (Read as "SLOW Perl SpamAssassin!" :)
               For example cox.net marks spam as "-- Spam --".
            */
            if (config->ispspam != NULL && strstr(p->serverbuf->line,config->ispspam)!=NULL) config->ispam=1;
               /* Here is where we start feeding the client part of our header buffer
                  until the message has been processed */
            error=checktimeout(p);
            if (error < 0) do_log(LOG_CRIT,"Error writing to client!");
            if (p->serverbuf->linelen==1 && p->serverbuf->line[0]=='.'){
               /* mail is complete */
               error=0;
               close(scanfd);
               do_log(LOG_DEBUG, "got '.\\r\\n', mail is complete.");
               p->ismail=4;
               /* initialize the scanner before scanning the first mail
                  but only if scanning is enabled */
               if (config->scannerenabled && p->scannerinit == SCANNER_INIT_NO){
                  if (config->scanner->init2){
                     if (config->scanner->init2(p)!=0){
                        context_uninit(p);
                        do_log(LOG_EMERG, "Can't initialize scanner!");
                        /* Dead now. Configuration error! */
                        p->scannerinit=SCANNER_INIT_ERR;
                     }else p->scannerinit=SCANNER_INIT_OK;
                  }else p->scannerinit=SCANNER_INIT_NULL;
               }
               /* Scan the file now */
               scannerret=SCANNER_RET_OK;
               snprintf(p->maildir, 4090, "%s.dir", p->mailfile);
               if (p->scannerinit > 0){
                  if ((scannerret=scan_mailfile(p))==SCANNER_RET_VIRUS){
                     /* virus */
                     if (p->virinfo) TRIM(p->virinfo);
                     paramlist_set(p->params, "%VIRUSNAME%", NONULL(p->virinfo));
                     do_log(LOG_WARNING, "'%s' contains a virus (%s)!", p->mailfile, paramlist_get(p->params, "%VIRUSNAME%"));
                     paramlist_set(p->params, "%MAILFILE%", p->mailfile);
                     set_maildateparam(p->params);
                     set_paramsfrommailheader(p->mailfile, p->params);
                     if (config->delit) paramlist_set(p->params, "%P3SCANID%",config->notify);
                     else paramlist_set(p->params, "%P3SCANID%", p->filename);
                     if (do_virusaction(p)!=0){
                        do_log(LOG_CRIT,"Virusaction failed. Sending -ERR                   and closing connection.");
                        writeline_format(p->client_fd, WRITELINE_LEADING_RN,                 "-ERR Message %i contains a virus (%s).",                        p->msgnum, paramlist_get(p->params, "%VIRUSNAME%"));
                        p->ismail=0;
                        return 1;
                     };
                     unset_paramsfrommailheader(p->params);
                     p->clientbuf->linelen=-2;
                     p->serverbuf->linelen=-2;
                     if (p->virinfo) free(p->virinfo);
                     if (config->delit) unlink(p->mailfile);
                  } /* virus */
                  /* see if there was a critical error */
                  if (scannerret==SCANNER_RET_CRIT){
                     if (!p->errmsg) do_log(LOG_CRIT,"Error writing to client!");
                     /* exact error already reported so kill the child. This
                        should get the sysadmins attention. */
                     p->ismail=0;
                     return 1;
                  }
               }else scannerret=SCANNER_RET_ERR; /* ! error */

               if (scannerret!=SCANNER_RET_VIRUS){ /* send mail if no virus */
                  if (scannerret==SCANNER_RET_ERR){
                     do_log(LOG_ALERT, "We can't say if it is a virus! So we have to give the client the mail! You should check your configuration/system");
                     context_uninit(p);
                     do_log(LOG_EMERG, "Scanner returned unexpected error code. You should check your configuration/system.");
                     /* We are dead now. Don't let virus mails pass */
                  }
                  /* no virus  / error / scanning disabled */
                  do_log(LOG_DEBUG, "Scanning done, sending mail now.");
                  p->ismail=5;
                  if ((len=send_mailfile(p->mailfile, p->client_fd, p))<0){
                     do_log(LOG_CRIT, "Can't send mail! We have to quit now!");
                     p->ismail=0;
                     return 1;
                  }
                  do_log(LOG_DEBUG, "Sending done.");
                  p->ismail=0;
                  p->bytecount+=len;
                  p->serverbuf->linelen=-2;
                  unlink(p->mailfile); /* we do not unlink virusmails, so only here */
               }
               close(p->header_fd);
               unlink(p->p3shdrfile);
               do_log(LOG_DEBUG, "Mail action complete");
            } /* mail complete */
            /* enabling the following serverbuf flush kills p3scan */
            p->serverbuf->linelen=-2; /* don't sent to client */
         }
      } /* server_buf_len >0 */

      /* we are not in mail-reading mode (ismail==0) */
      if ( p->serverbuf->linelen>=0 ){
          /* write server_buf to fd */
         if (writeline(p->client_fd, WRITELINE_LEADING_RN, p->serverbuf->line)){
            do_log(LOG_CRIT, "can't send to client");
            return 1;
         }
         p->serverbuf->linelen=-2;
         p->clientbuf->linelen=-2;
      }
   } //end of while
   do_log(LOG_WARNING, "Connection timeout");
   do_log(LOG_DEBUG, "Child finished");
   return 0;
}

int do_sigchld_check(pid_t pid, int stat){
   int termsig = WTERMSIG(stat);

   if (numprocs>0) numprocs--;
   if (termsig){
      do_log(LOG_CRIT, "Attention: child with pid %i died with abnormal termsignal (%i)! "
      "This is probably a bug. Please report to the author. "
      "numprocs is now %i",
      pid, termsig, numprocs );
   }else{
      do_log(LOG_DEBUG, "waitpid: child %i died with status %i, numprocs is now %i", pid, WEXITSTATUS(stat), numprocs);
   }

   if(clean_child_directory(pid)) do_log(LOG_DEBUG, "Error cleaning child directory!");
   return 1;
}

void do_sigchld(int signr){
   pid_t pid;
   int stat;
   while ((pid=waitpid(-1, &stat, WNOHANG)) > 0){
      do_sigchld_check(pid, stat);
   }
}

void printversion(void){
   printf(
   "\n"
   PROGNAME " " VERSION "\n"
   "(C) 2003,2004 by Jack S. Lai, <laitcg@cox.net> 12/05/2003\n"
   "         and by Folke Ashberg <folke@ashberg.de>\n"
   "\n"
   );
}

void usage(char * progname){
   int i=0;
   printversion();
   printf(
   "Usage: %s [options]\n"
   "\n"
   "where options are:\n"
   "  -a, --renattach=FILE    Specify location of renattach if wanted\n"
   "  -b, --bytesfree=NUM     Number (in KBytes) that should be available before we\n"
   "                          can process messages. If not enough, report it and die.\n"
   "  -B, --broken            Enable broken processing (Outlook/Outlook Express clients).\n"
   "  -c, --viruscode=N[,N]   The code(s) the scanner returns when a virus is found\n"
   "  -d, --debug             Turn on debugging. See " CONFIGFILE " for recommended\n"
   "                          debug procedure.\n"
   "  -f, --configfile=FILE   Specify a configfile\n"
   "                          Default is " CONFIGFILE "\n"
   "  -g, --virusregexp=RX    Specify a RegularExpression which describes where to\n"
   "                          get the name of the virus. The first substring is\n"
   "                          used, or if regexp ends with /X the X substring\n"
   "  -G  --goodcode          The codes that enable the message to be delivered without a\n"
   "                          warning. For example Kaspersky AV reports code 10 for an\n"
   "                          encrypted .zip file\n"
   "  -h, --help              Prints this text\n"
   "  -i, --ip=IP             Listen only on IP <IP>. Default: ANY\n"
   "  -I, --targetip=IP       Connect only to IP <IP>. Default: use transparent-proxy\n"
   "  -j, --justdelete        Just delete infected mail after reporting infection\n"
   "  -k, --checkspam         Turn on Spam Checking\n"
   "  -l, --pidfile=FILE      Specify where to write a pid-file\n"
   "  -m, --maxchilds=NUM     Allow not more then NUM childs\n"
   "  -M, --ispspam           Specify a line used by your ISP to mark Spam\n"
   "                          For example, cox.net uses -- Spam --\n"
   "  -n, --notifydir=DIR     Create notification mails in <DIR>\n"
   "                          Default: " NOTIFY_MAIL_DIR "\n"
   "                          Also used for temporary storing\n"
   "  -N, --notify            Change infected file status line\n"
   "  -o, --overwrite         Specify path to HTML parsing program executable.\n"
   "                          Default none\n"
   "  -p, --port=PORT         Listen on port <PORT>. Default: %i\n"
   "  -P, --targetport=PORT   Connect to port <PORT>. Default: %i\n"
   "                          Ignored in transparent proxy mode\n"
   "  -q, --quiet             Turn off normal reporting\n"
   "  -r, --virusdir=DIR      Save infected mails in <DIR>\n"
   "                          Default: " VIRUS_DIR "\n"
   "  -s, --scanner=FILE      Specify the scanner. Every scannertype handles this\n"
   "                          in an other way. This could be the scanner-\n"
   "                          executable or a FIFO, Socket, ...\n"
   "  -S, --subject           Change virus reporting subject line\n"
   "  -t, --template=FILE     Use virus-notification-template <FILE>\n"
   , basename(progname), PORT_NUMBER, PORT_NUMBER);
   printf(
   "  -T, --scannertype=T     Define which buildin scanner-frontend to use.\n\n"
   "  Supported types:\n");
   while (scannerlist[i]){
      printf("%17s: %s\n",
      scannerlist[i]->name, scannerlist[i]->descr);
      i++;
   }
   printf( "\n"
   "  -u, --user=[UID|NAME]   Run as user <UID>. Default: " RUNAS_USER "\n"
   "                          Only takes effect when started as superuser\n"
   "  -v, --version           Prints version information\n"
   "  -x, --demime            eXtract all MIME-Parts before scanning\n"
   "  -z, --spamcheck         Specify path to Spam Checking program executable\n"
   "                          Default /usr/bin/spamc (Mail::SpamAssassin)\n"
   "\n"
   "        see " CONFIGFILE " for detailed information\n"
   "\n"
   );
}
void parseoptions(int argc, char **argv){
   long i, ii;
   char * rest;
   int error = 0;
   struct servent *port;
   int fp, res;
   struct linebuf *cf;
   char *pargv[MAX_PSEUDO_ARGV];
   int pargc;
   char *line;
   int pidfd;
   struct option long_options[] = {
   { "renattach",    required_argument,   NULL, 'a' },
   { "bytesfree",    required_argument,   NULL, 'b' },
   { "broken",       no_argument,         NULL, 'B' },
   { "viruscode",    required_argument,   NULL, 'c' },
   { "debug",        no_argument,         NULL, 'd' },
   { "configfile",   required_argument,   NULL, 'f' },
   { "virusregexp",  required_argument,   NULL, 'g' },
   { "goodcode",     required_argument,   NULL, 'G' },
   { "help",         no_argument,         NULL, 'h' },
   { "ip",           required_argument,   NULL, 'i' },
   { "targetip",     required_argument,   NULL, 'I' },
   { "justdelete",   no_argument,         NULL, 'j' },
   { "checkspam",    no_argument,         NULL, 'k' },
   { "pidfile",      required_argument,   NULL, 'l' },
   { "maxchilds",    required_argument,   NULL, 'm' },
   { "ispspam",      required_argument,   NULL, 'M' },
   { "notifydir",    required_argument,   NULL, 'n' },
   { "notify",       required_argument,   NULL, 'N' },
   { "overwrite",    required_argument,   NULL, 'o' },
   { "port",         required_argument,   NULL, 'p' },
   { "targetport",   required_argument,   NULL, 'P' },
   { "quiet",        no_argument,         NULL, 'q' },
   { "virusdir",     required_argument,   NULL, 'r' },
   { "scanner",      required_argument,   NULL, 's' },
   { "subject",      required_argument,   NULL, 'S' },
   { "template",     required_argument,   NULL, 't' },
   { "scannertype",  required_argument,   NULL, 'T' },
   { "user",         required_argument,   NULL, 'u' },
   { "version",      no_argument,         NULL, 'v' },
   { "demime",       no_argument,         NULL, 'x' },
   { "spamcheck",    required_argument,   NULL, 'z' },
   { NULL,           no_argument,         NULL, 0 }
   };
   char getoptparam[] = "hvf:a:b:Bc:dg:G:i:I:jkl:m:M:n:N:o:p:P:qr:s:S:t:T:u:xz:";
   void switchoption(char opt, char * arg, char * optstr, char * where, int state){
      char *next_tok;
      switch (opt){
         case 'h':
         case 'v':
         case 'f':
            /* don't check in second run (is in the first) */
            if (state==CONFIG_STATE_CMD) return;
            /* disallow help/version/configfile in configfile */
            if (state==CONFIG_STATE_FILE){
               fprintf(stderr, "%s '%s' is not allowed in configfile!\n", where, optstr);
               error=1;
               return;
            }
         break;
         default:
         /* only check help/version/configfile for the first cmd run */
         if (state==CONFIG_STATE_CMDPRE) return;
      }

      switch (opt){
         case 'h': /* usage */
            usage(argv[0]);
            exit(0);
            break;
         case 'v': /* version */
            printversion();
            exit(0);
            break;
         case 'f': /* config (file) */
            config->configfile = arg;
            break;
         case 'd': /* debug */
            config->debug=1;
            break;
         case 'l': /* PID File */
            config->pidfile=arg;
            if ((pidfd=open(config->pidfile,O_RDONLY ))>=0){
               do_log(LOG_EMERG, "%s exists! Aborting!",config->pidfile);
               /* Should not reach here. We are dead. */
               pidfd=close(pidfd);
               exit(0);
            }
            break;
         case 'a': /* rename attachments using renattach */
            config->renattach=arg;
            break;
         case 'r': /* virusdir */
            config->virusdirbase=arg;
            config->virusdir=config->virusdirbase;
            break;
         case 'n': /* notifydir */
            config->notifydir=arg;
            break;
         case 'm': /* Max Childs */
            i=strtol(arg, &rest, 10);
            if ((rest && strlen(rest)>0) || i<1 || i>9999){
               fprintf(stderr, "%s --maxchilds has to be 1 < val < 10000\n", where);
               error=1;
            } else config->maxchilds=(int)i;
            break;
         case 'i': /* IP (to listen on) */
            if (!strcmp(arg, "0.0.0.0")){
               config->addr.sin_addr.s_addr=htonl(INADDR_ANY);
            }else if (!inet_aton(arg, &config->addr.sin_addr)){
               fprintf(stderr, "%s %s isn't a valid IP Adress\n", where, arg);
               error=1;
            }
            break;
         case 'I': /* IP (to connect) */
            if (!strcmp(arg, "0.0.0.0")){
               config->targetaddr.sin_addr.s_addr=htonl(INADDR_ANY);
            }else if (!inet_aton(arg, &config->targetaddr.sin_addr)){
               fprintf(stderr, "%s %s isn't a valid IP Adress\n", where, arg);
               error=1;
            }
            break;
         case 'o': /* overwrite (disable) HTML */
            config->overwrite=arg;
            break;
         case 'p': /* Port */
            i=strtol(arg, &rest, 10);
            if (rest && strlen(rest)>0){
               if (i>0){ /* 123abc */
                  fprintf(stderr, "%s %s isn't a valid port\n", where, arg);
                  error=1;
               }else{
                  if((port=getservbyname(arg, "tcp"))!=NULL){
                     config->addr.sin_port=port->s_port;
                  }else{
                     fprintf(stderr, "Port lookup for '%s/tcp' failed! Check /etc/services\n", arg);
                     error=1;
                  }
               }
            }else{
               if (i>0)config->addr.sin_port=htons((int)i);
               else{
                  fprintf(stderr, "%s Incorrect portnumber\n", where);
                  error=1;
               }
            }
            break;
         case 'P': /* target Port */
            i=strtol(arg, &rest, 10);
            if (rest && strlen(rest)>0){
               if (i>0){ /* 123abc */
                  fprintf(stderr, "%s %s isn't a valid port\n", where, arg);
                  error=1;
               }else{
                  if((port=getservbyname(arg, "tcp"))!=NULL){
                     config->targetaddr.sin_port=port->s_port;
                  }else{
                     fprintf(stderr, "Port lookup for '%s/tcp' failed! "
                     "Check /etc/services\n", arg);
                     error=1;
                  }
               }
            }else{
               if (i>0)config->targetaddr.sin_port=htons((int)i);
               else{
                  fprintf(stderr, "%s Incorrect portnumber\n", where);
                  error=1;
               }
            }
            break;
         case 'q': /* quiet */
            config->quiet=1;
            break;
         case 'u': /* Run as User */
            config->runasuser=arg;
            /* getpwnam will also accept UID's, so we need no converting*/
            break;
         case 's': /* Scanner */
            config->virusscanner=arg;
            break;
         case 't': /* template */
            config->virustemplate=arg;
            break;
         case 'c': /* Virus (exit) code */
            ii = 0;
            next_tok = strtok(arg, " \t,");
            if (next_tok){
               do{
                  if (ii < MAX_VIRUS_CODES){
                     i=strtol(next_tok, &rest, 10);
                     if ( (rest && strlen(rest)>0) || i<1 || i>99){
                        fprintf(stderr, "%s --viruscode has be a list of numbers (%s)\n", where, rest);
                        error=1;
                     }else config->viruscode[ii]=(int)i;
                     ii++;
                  }
               }while ((next_tok = strtok(NULL, " \t,")) || (ii >= MAX_VIRUS_CODES));
            }
            config->viruscode[ii] = -1;
            if (ii == 0){
               fprintf(stderr, "%s --viruscode has be a list of numbers (%s)\n", where, rest);
               error=1;
            }
            break;
         case 'G': /* Good Virus (exit) code */
            ii = 0;
            next_tok = strtok(arg, " \t,");
            if (next_tok){
               do{
                  if (ii < MAX_VIRUS_CODES){
                     i=strtol(next_tok, &rest, 10);
                     if ( (rest && strlen(rest)>0) || i<1 || i>99){
                        fprintf(stderr, "%s --good viruscode has be a list of numbers (%s)\n", where, rest);
                        error=1;
                     }else config->gvcode[ii]=(int)i;
                     ii++;
                  }
               }while ((next_tok = strtok(NULL, " \t,")) || (ii >= MAX_VIRUS_CODES));
            }
            config->gvcode[ii] = -1;
            if (ii == 0){
               fprintf(stderr, "%s --good viruscode has be a list of numbers (%s)\n", where, rest);
               error=1;
            }
            break;
         case 'x': /* demime */
            config->demime = 1;
            break;
         case 'T': /* scannertype */
            i=0;
            while (scannerlist[i]){
               if(!strcasecmp(arg, scannerlist[i]->name)){
                  config->scanner=scannerlist[i];
                  i=-1;
                  break;
               }
               i++;
            }
            if (i!=-1){
               fprintf(stderr, "%s scannertype '%s' is not supported", where, arg);
               error=1;
            }
            break;
         case 'g': /* virusregexp */
            config->virusregexp = arg;
            i=strlen(arg);
            if (arg[i-2]=='/' && isdigit(arg[i-1])){
               arg[i-2]='\0';
               config->virusregexpsub=arg[i-1]-'0';
            }
            break;
         case 'k': /* check for spam */
            config->checkspam=1;
            break;
         case 'z': /* path to spam checking executable */
            config->spamcheck = arg;
            break;
         case 'b': /* bytes free */
            i=strtol(arg, &rest, 10);
            config->freespace=(int)i;
            break;
         case 'j': /* justdelete */
            config->delit=1;
            break;
         case 'B': /* broken */
            config->broken=1;
            break;
         case 'S': /* Subject line for virus notification */
            config->subject = arg;
            break;
         case 'N': /* deleted file notification */
            config->notify = arg;
            break;
         case 'M': /* ISP marked as SPAM */
            config->ispspam = arg;
            break;

         default:
            fprintf(stderr, "%s Option '%s' isn't known\n", where, optstr);
            error=1;
      }
   }/* sub function switchoption  }}} */
   void parseargs(int c, char **v, char * where, int state){
      int option, option_index = 0;
      opterr=0;
      optind=1;
      while (1){
         option = getopt_long(c, v, getoptparam,
         long_options, &option_index);
         if (option == EOF) break;
         switchoption(option, optarg, v[optind-1], where, state);
      }
      if (state != CONFIG_STATE_CMDPRE && optind < c){
         error=1;
         while (optind < c) fprintf(stderr, "%s Unknown option '%s'\n", where, v[optind++]);
      }
   }
   if ((config=malloc(sizeof(struct configuration_t)))==NULL){
      fprintf(stderr, "malloc error\n");
      exit(1);
   }
   /* set defaults for in, char* to NULL */
   config->debug=DEBUG;
   config->quiet=QUIET;
   config->overwrite=OVERWRITE;
   config->renattach=NULL;
   config->addr.sin_port=htons((int)PORT_NUMBER);
   config->targetaddr.sin_port=htons((int)PORT_NUMBER);
   config->maxchilds=MAX_CHILDS;
   config->viruscode[0]=VIRUS_SCANNER_VIRUSCODE;
   config->viruscode[1]=-1;
   config->gvcode[0]=0;
   config->runasuser=NULL;
   config->virusdir=NULL;
   config->virusdirbase=NULL;
   config->notifydir=NULL;
   config->virustemplate=NULL;
   config->virusscanner=NULL;
   config->demime=0;
   config->pidfile=NULL;
   config->syslogname=NULL;
   config->addr.sin_addr.s_addr=htonl(INADDR_ANY);
   config->targetaddr.sin_addr.s_addr=htonl(INADDR_ANY);
   config->scanner=NULL;
   config->virusregexp=NULL;
   config->virusregexpsub=1;
   config->checkspam=CHECKSPAM;
   config->spamcheck=SPAMCHECK;
   config->freespace=MINSPACE;
   config->delit=DELIT;
   config->broken=0;
   config->ispspam=NULL;
   /* parse line args, but for the first time only configfile/version/help */
   parseargs(argc, argv, "\t[cmdlineparm]", CONFIG_STATE_CMDPRE);
   /* parse configfile */
   if (!config->configfile) config->configfile=strdup(CONFIGFILE);
   if ((fp=open(config->configfile, O_RDONLY))>=0){
      cf=linebuf_init(4096);
      pargc=1;
      pargv[0]="";
      while ((res=getline(fp, cf))>=0 && pargc<MAX_PSEUDO_ARGV-1){
         if (cf->linelen > 2){
            TRIM(cf->line);
            if (cf->line[0]!='#' && cf->line[0]!=';' &&
            !(cf->line[0]=='/' && cf->line[1]=='/') &&
            cf->line[0]!='='){
            /* copy to pseudo argv, change
            * 'x = y' or 'x y' to 'x='
            * This code is the horror, but it seems to work */
               line=malloc(strlen(cf->line)+3);
               if (line==NULL){
                  do_log(LOG_EMERG, "Cant allocate memory (parseargs)!");
                  return;
               }
               line[0]='-'; line[1]='-'; line[2]='\0';
               strcat(line, cf->line);
               pargv[pargc]=line;
               if ((i=strcspn(line, " =\t"))>1){
                  if (i<strlen(line)){
                     line[i]='\0';
                     if ((ii=strspn(line+i+1," =\t"))>=0){
                        rest=line+i+ii+1;
                        if (rest && strlen(rest)>0 ){
                           pargv[pargc][strlen(pargv[pargc])]='=';
                           memcpy(pargv[pargc]+i+1, rest, strlen(rest)+1);
                        }
                     }
                  }
               }
               pargc++;
            }
         }
      }
      close(fp);
      linebuf_uninit(cf);
      pargv[pargc]=NULL;
      parseargs(pargc, pargv, "\t[configfile]", CONFIG_STATE_FILE);
   }
   /* now check the rest of commandline args (higher precedence than configfile) */
   parseargs(argc, argv, "\t[cmdlineparm]", CONFIG_STATE_CMD);
   if (error){
      printf(
      "Commandline options/configfile are not ok\n"
      "try --help or RTFM to get some information\n"
      "\n"
      );
      exit(1);
   }

   /* set unset values to default */
   SETIFNULL(config->runasuser, RUNAS_USER);
   SETIFNULL(config->virusdirbase, VIRUS_DIR);
   SETIFNULL(config->virusdir, config->virusdirbase );
   SETIFNULL(config->notifydir, NOTIFY_MAIL_DIR);
   SETIFNULL(config->virustemplate, VIRUS_TEMPLATE);
   SETIFNULL(config->virusscanner, VIRUS_SCANNER);
   SETIFNULL(config->pidfile, PID_FILE);
   SETIFNULL(config->syslogname, SYSLOG_NAME);
   SETIFNULL(config->scanner, scannerlist[0]);
   SETIFNULL(config->subject, SUBJECT);
   SETIFNULL(config->notify, NOTIFY);
}

void do_sigterm_main(int signr){
   int ret;

   if (signr != -1 ) do_log(LOG_NOTICE, "signalled, doing cleanup");
   close(sockfd);
   if (config->scannerenabled && config->scanner->uninit1){
      do_log(LOG_DEBUG, "calling uninit1");
      config->scanner->uninit1();
      do_log(LOG_DEBUG, "uninit1 done");
   }
   if((ret=unlink(config->pidfile)!=0)) do_log(LOG_NOTICE, "Unable to remove %s", config->pidfile);
   do_log(LOG_NOTICE, PROGNAME " terminates now");
   exit(0);
}

int main(int argc, char ** argv){
   int connfd, i;
   struct sockaddr_in  addr;
   size_t socksize = sizeof(struct sockaddr_in);
   pid_t pid;
   int stat;
   FILE * fp;
   struct proxycontext * p;
   char * responsemsg;
   int virusdirlen;
   char chownit[100];
#define CHOWNCMD "/bin/chown"
   int len;
   int ret;
   FILE * chowncmd;


   parseoptions(argc, argv);

   do_log(LOG_NOTICE, PROGNAME " Version " VERSION);
   do_log(LOG_NOTICE, "Selected scannertype: %s (%s)",
   config->scanner->name,config->scanner->descr);

   if((sockfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)do_log(LOG_EMERG, "Can't open socket");

   i = 1;
   setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
   config->addr.sin_family = AF_INET;

   if (bind(sockfd, (struct sockaddr *) &config->addr, sizeof(config->addr))) do_log(LOG_EMERG, "Can't bind to socket %s:%i",inet_ntoa(config->addr.sin_addr), ntohs(config->addr.sin_port));

   if (listen(sockfd, 5)) do_log(LOG_EMERG, "Can't listen on socket");

   do_log(LOG_NOTICE, "Listen now on %s:%i", inet_ntoa(config->addr.sin_addr), ntohs(config->addr.sin_port));
   if (!config->debug){
      /* daemonize */
      do_log(LOG_DEBUG, "Daemonize");
      if ((pid = fork())<0) return(-1);
      else if(pid != 0) exit(0);
      setsid();
      if (!config->debug){
         if ( (fp=fopen(config->pidfile, "w+"))!=NULL){
            fprintf(fp, "%i\n", getpid());
            fclose(fp);
         }else do_log(LOG_CRIT, "Can't write PID to %s", PID_FILE);
      };
      /* chown /var/run/p3scan/p3scan.pid mail.mail */
      if (!config->debug){
         len=strlen(CHOWNCMD)+1+strlen(config->runasuser)+1+strlen(config->runasuser)+1+strlen(config->pidfile)+1;
         //do_log(LOG_DEBUG, "%s %s.%s %s=%i",CHOWNCMD, config->runasuser, config->runasuser, config->pidfile, len);
         snprintf(chownit, len, "%s %s.%s %s", CHOWNCMD, config->runasuser, config->runasuser, config->pidfile);
         if ((chowncmd=popen(chownit, "r"))==NULL){
            do_log(LOG_ALERT, "Can't '%s' !!!", chowncmd);
            return SCANNER_RET_ERR;
         }
         ret=pclose(chowncmd);
      }
   }
   avoid_root();
   signal(SIGCHLD, do_sigchld);
   signal(SIGTERM, do_sigterm_main);
   signal(SIGINT, do_sigterm_main);

   if (config->scanner->init1){
      if (config->scanner->init1()!=0){
         do_log(LOG_EMERG, "Scanner init failed! config and restart " PROGNAME);
         config->scannerenabled = 0;
      }else config->scannerenabled = 1;
   }else config->scannerenabled = 1;
   if (config->quiet){
      config->quiet=0;
      do_log(LOG_NOTICE, "%s %s started.", PROGNAME, VERSION);
      config->quiet=1;
   }
   if (config->debug){
      do_log(LOG_DEBUG,"p3scan.conf:");
      do_log(LOG_DEBUG,"pidfile: %s",config->pidfile);
      do_log(LOG_DEBUG,"maxchilds: %i",config->maxchilds);
      if (!ntohs(config->addr.sin_addr.s_addr)){
         do_log(LOG_DEBUG,"ip: Any");
      } else do_log(LOG_DEBUG,"ip: %i",ntohs(config->addr.sin_addr.s_addr));
      do_log(LOG_DEBUG,"port: %d",htons(config->addr.sin_port));
      if (htonl(INADDR_ANY) == (config->targetaddr.sin_addr.s_addr)) do_log(LOG_DEBUG,"targetip: %i",ntohs(config->targetaddr.sin_addr.s_addr));
      if (htons(config->targetaddr.sin_port)) do_log(LOG_DEBUG,"targetport: %d",htons(config->targetaddr.sin_port));
      do_log(LOG_DEBUG,"user: %s",config->runasuser);
      do_log(LOG_DEBUG,"notifydir: %s",config->notifydir);
      do_log(LOG_DEBUG,"virusdir: %s",config->virusdir);
      do_log(LOG_DEBUG,"justdelete: %i",config->delit);
      do_log(LOG_DEBUG,"bytesfree: %i",config->freespace);
      do_log(LOG_DEBUG,"demime: %i",config->demime);
      do_log(LOG_DEBUG,"scanner: %s",config->virusscanner);
      do_log(LOG_DEBUG,"virusregexp: %s",config->virusregexp);
      do_log(LOG_DEBUG,"broken: %i",config->broken);
      do_log(LOG_DEBUG,"checkspam: %i",config->checkspam);
      do_log(LOG_DEBUG,"spamcheck: %s",config->spamcheck);
      do_log(LOG_DEBUG,"renattach: %s",config->renattach);
      do_log(LOG_DEBUG,"overwrite: %s",config->overwrite);
      do_log(LOG_DEBUG,"debug: %i",config->debug);
      do_log(LOG_DEBUG,"quiet: %i",config->quiet);
      do_log(LOG_DEBUG,"template: %s",config->virustemplate);
      do_log(LOG_DEBUG,"subject: %s",config->subject);
      do_log(LOG_DEBUG,"notify: %s",config->notify);
   }
   numprocs=0;
   do_log(LOG_DEBUG, "Waiting for connections.....");
   while ((connfd = accept(sockfd, (struct sockaddr *)&addr,&socksize)) >= 0){
      if ( (pid=fork())>0){
         /* parent */
         numprocs++;
         do_log(LOG_DEBUG, "Forked, pid=%i, numprocs=%i", pid, numprocs);
         close (connfd);
         /* wir brauchen die nicht, der childprocess kümmert sich drum
         we don't need "them" (connfd?), child process takes care of that */
         if (numprocs>=config->maxchilds){
            do_log(LOG_WARNING, "MAX_CHILDS (%i) reached!", config->maxchilds);
            while (1){
               pid=waitpid(-1, &stat, 0); /* blocking */
               if (do_sigchld_check(pid, stat)) break;
            }
         }
      }else{
         /* child */
         virusdirlen=strlen(config->virusdirbase)+20;
         config->virusdir=malloc(virusdirlen);
         if (config->virusdir==NULL){
            do_log(LOG_EMERG, "Could not allocate memory for child!");
            return SCANNER_RET_ERR;
         }
         snprintf(config->virusdir, virusdirlen, "%s/children/%d/", config->virusdirbase,getpid());
         do_log(LOG_DEBUG, "setting the virusdir to %s", config->virusdir);

        if(clean_child_directory(getpid())) do_log(LOG_EMERG, "Error calling clean child directory!");

         if((mkdir (config->virusdir, S_IRWXU)<0))do_log(LOG_EMERG,"Could not create virusdir %s",config->virusdir);
         signal(SIGCHLD, NULL); /* unset signal handler for child */
         signal(SIGPIPE, SIG_IGN); /* don't die on SIGPIPE */
         do_log(LOG_DEBUG, "Initialize Context");
         p=context_init();
         p->client_fd=connfd;
         p->client_addr=addr;
         global_p=p;
         signal(SIGTERM, do_sigterm_proxy);
         signal(SIGINT,  do_sigterm_proxy);
         do_log(LOG_DEBUG, "starting proxy");
         if (proxy(p)){
            /* error, but a message has already be sent */
            responsemsg=strdup("Critial abort");
         }else responsemsg=strdup("Clean Exit");

         if (config->scannerenabled){
            do_log(LOG_NOTICE, "Session done (%s). Mails: %i Bytes: %lu",
            responsemsg, p->mailcount, p->bytecount);
         }else{
            do_log(LOG_NOTICE, "Session done (%s). Mails: %i",
            responsemsg, p->mailcount);
         }
         free(responsemsg);
         do_sigterm_proxy(-1);
         exit(0);
      }
   }
   do_log(LOG_NOTICE, "accept error");
   do_sigterm_main(-1);
   return 0;
}

Generated by  Doxygen 1.6.0   Back to index