// 
// QClam 0.2
// Copyright(C) Sergey A. Galin, 2004
// http://sageshome.net/oss/
// Please see COPYING.txt for legal information.
//

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/timeb.h>

#define QCLAM_VERSION "0.2"
#define QCLAM_BUFFER_SIZE (1024*256)
#define QCLAM_DEFAULT_CLAMSCAN "/usr/bin/clamscan"
#define QCLAM_DEFAULT_LOGNAME "qclam.log"
#define MAX_PATH 256
#define MAX_COMMAND (MAX_PATH*3)
#define MAX_VIRUS_INFO 1024

/*
       "man qmail_command" reads:
       
       SENDER  is  the envelope sender address.  NEWSENDER is the
       forwarding envelope sender address, as described  in  dot-
       qmail(5).   RECIPIENT  is  the envelope recipient address,
       local@domain.  USER is user.  HOME is your home directory,
       homedir.   HOST  is  the  domain  part  of  the  recipient
       address.  LOCAL is the local part.   EXT  is  the  address
       extension, ext.

       HOST2 is the portion of HOST preceding the last dot; HOST3
       is the portion of HOST preceding the  second-to-last  dot;
       HOST4  is  the portion of HOST preceding the third-to-last
       dot.

       EXT2 is the portion of EXT following the first dash;  EXT3
       is the portion following the second dash; EXT4 is the por-
       tion following the third dash.   DEFAULT  is  the  portion
       corresponding  to the default part of the .qmail-...  file
       name; DEFAULT is not set if the file  name  does  not  end
       with default.

       DTLINE  and  RPLINE are the usual Delivered-To and Return-
       Path lines, including newlines.  UFLINE is the  UUCP-style
       From_ line that qmail-local adds to mbox-format files.
*/




char *trim(char *s){
  int i;
  for(i=strlen(s); i>=0 && s[i]<32; i--) s[i]=0;
  return s;
}

char *nextline(char *s, char *outbuf){
  while(*s){
    if(*s=='\n'){
      *outbuf=0;
      return s+1;
    }
    *outbuf++=*s++;
  }
  *outbuf=0;
}

const int QMAIL_DONTPROCESS=99;
const int CLAMAV_VIRUS=1;

int main(int argc, char *argv[]){
  char *buf, *homedir, *sender, *recipient;
  size_t i, bufsz=0, sz=QCLAM_BUFFER_SIZE;
  buf=(char*)malloc(sz);
  if(buf==NULL){
    fprintf(stderr, "Failed to allocate initial buffer!\n");
    return 0;
  }
  int chr, exitcode=0, avcode=0, tmperror=0;
  char 
    filename[MAX_PATH], 
    clamscan[MAX_PATH],
    command[MAX_COMMAND], 
    logfile[MAX_PATH]="", 
    virinfo[MAX_VIRUS_INFO]="";
    

  FILE *tmp, *log;

  strcpy(clamscan, QCLAM_DEFAULT_CLAMSCAN);
  // Parse command line arguments
  for(i=1; i<argc; i++){
    if(!strcmp(argv[i], "--help")){
      printf("%s", "\n"      
        "QClam " QCLAM_VERSION ", (C) Sergey A. Galin, 2004\n"
	"Project Homepage: http://sageshome.net/oss/qclam.php\n"
	"Date built: " __DATE__ ", " __TIME__ "\n"
	"\n"
	"This program is intented to plug ClamAV antivirus into QMail.\n"
	"To have your e-mail scanned for viruses, add the following\n"
	"lines into your ~/.qmail file:\n"
	"\n"
	"|/usr/local/bin/qclam\n"
        "./Maildir/\n"
	"\n"
	"Please refer to QClam README.txt file and QMail documentation for more info.\n"
	"\n"
	"Command-line arguments:\n"
	"\n"
	"  --help                     - print this information and exit;\n"
	"  --version                  - print QClam version number only and exit;\n"
	"  --clam <path-to-clamscan>  - use another clamscan execultable;\n"
	"                               default: " QCLAM_DEFAULT_CLAMSCAN "\n"
	"  --log <log-file>           - specify QClam log file name;\n"
	"                               default: ~/" QCLAM_DEFAULT_LOGNAME "\n" 
	"\n"
        "Please report any bugs to <development@sageshome.net>\n"
      );
      return 0;
    }else if(!strcmp(argv[i], "--version")){      
      puts(QCLAM_VERSION);
      return 0;
    }else if(!strcmp(argv[i], "--clam")){      
      if(i==argc-1){
        fprintf(stderr, "--clam option specified, but no path to ClamAV executable.\n");
	return 0;
      }
      strncpy(clamscan, argv[++i], sizeof(clamscan));
      clamscan[sizeof(clamscan)-1] = '\0';
    }else if(!strcmp(argv[i], "--log")){      
      if(i==argc-1){
        fprintf(stderr, "--log option specified, but no path to log file.\n");
	return 0;
      }
      strncpy(logfile, argv[++i], sizeof(logfile));      
      logfile[sizeof(logfile)-1] = '\0';
    }else{
      fprintf(stderr, "Unknown command-line argument. Read QClam readme file for more information.\n");
      return 0;
    }
  }

  // Get message data from stdin
  while(!feof(stdin)){
    if(bufsz>=sz-1){
      buf=(char*)realloc(buf, sz+=1024*256);
      if(buf==NULL){
        fprintf(stderr, "Failed to allocate additional  buffer!\n");
        return 0;
      }
    }
    chr=fgetc(stdin);
    if(chr==EOF) break; else buf[bufsz++]=chr;
  }
  buf[bufsz]=0;

  sender=getenv("SENDER"); if(sender==NULL) sender="Unknown-sender";
  recipient=getenv("RECIPIENT"); if(recipient==NULL) recipient="Unknown-recipient";

  // Saving message into temporary file
  sprintf(filename, "/tmp/qclam_temp_%u", getpid());
  tmp=fopen(filename, "w");

  if(tmp!=NULL){
    chmod(filename, 0600);
    fwrite(buf, bufsz, 1, tmp);
    fclose(tmp);

    sprintf(command, "%s --stdout --infected --mbox %s", clamscan, filename);
    //avcode=system(command);

    // Scan temp file using antivirus and get its output
    FILE *process=popen(command, "r");
    if(process==NULL)
      avcode=-1;
    else{
      int indatasz=1024*4, indataptr=0;
      char *indata=(char*)malloc(indatasz);
      if(indata==NULL){ 
        fprintf(stderr, "Failed to allocate process input buffer!\n"); unlink(filename); return 0; }
      while(!feof(process)){
        if(indataptr>=indatasz-1){
          indata=(char*)realloc(indata, indatasz+=1024*256);
          if(indata==NULL){
            fprintf(stderr, "Failed to allocate additional process input buffer!\n");
            unlink(filename);
            return 0;
          }
        }
        chr=fgetc(process);
        if(chr==EOF) break; else indata[indataptr++]=chr;
      }
      indata[indatasz]=0;
      pclose(process);
      //printf("GOT OUT:\n%s\n", indata);
      // Scanning antivirus output...
      char *infoptr=virinfo;
      char *inptr=indata;
      char in_line[1024];
      for(;;){
        inptr=nextline(inptr, in_line);
        trim(in_line);
        if(*in_line==0) break;
        else{
          if(strstr(in_line, "FOUND")!=NULL){
            if(virinfo[0]) strcat(virinfo, "; ");
            strcat(virinfo, in_line);
            avcode=CLAMAV_VIRUS;
          }
        }
      }
      free(indata);
    }
    if(avcode==CLAMAV_VIRUS) exitcode=QMAIL_DONTPROCESS;
    unlink(filename);
  }else{
    tmperror=1;
  }

  // Write to log file...
  homedir=getenv("HOME");
  if(homedir!=NULL || logfile[0]){
    time_t t;
    time(&t);
    char timestr[64];
    ctime_r(&t, timestr);
    trim(timestr);
    
    if(!logfile[0])
      sprintf(logfile, "%s/%s", homedir, QCLAM_DEFAULT_LOGNAME);
    
    log=fopen(logfile, "a");
    if(log!=NULL){
      if(exitcode==0){
        if(avcode<0)
          fprintf(log, "%s AV_FAILED: From %s to %s, %d byte(s)\n", timestr, sender, recipient, bufsz);
        else if(tmperror)
          fprintf(log, "%s TEMP_ERROR: From %s to %s, %d byte(s)\n", timestr, sender, recipient, bufsz);
        else
          fprintf(log, "%s OK: From %s to %s, %d byte(s)\n", timestr, sender, recipient, bufsz);
      }else
        fprintf(log, "%s VIRUS: From %s to %s, %d byte(s): %s\n", timestr, sender, recipient, bufsz, virinfo);
      fclose(log);
    }else
      fprintf(stderr, "Failed to open log file!\n");
  }else{
    fprintf(stderr, "Cannot find user's home directory!\n");
  }

  free(buf);
  return exitcode;
}

