/*
*
*  Clamdmail.c is based loosely on clamdscan.c by Tomasz Kojm.
*
*  This program takes a mail message as input from stdin, uses rfc822.c to extract
*  attachments to a temp directory, gets clamd to scan them, and
*  handles the message appropriately.
*
*  Copyright (C) 2003 Robert Hogan <robert@roberthogan.net>
*
*  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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <clamav.h>

#include "defaults.h"
#include "memory.h"
#include "options.h"
#include "output.h"
#include "../version.h"
#include "../../config.h"

#include "output.h"

#define BUFFSIZE 1024



/* this local macro takes care about freeing memory at exit */
/*
#define mexit(i)    if(opt) free_opt(opt);			    \
                    mprintf("*Memory freed. Exit code: %d\n", i);   \
                    exit(i);
*/
#define mexit(i)    exit(i)

struct s_info {
  int signs; /* number of signatures loaded */
  int dirs; /* number of scanned directories */
  int files; /* number of scanned files */
  int ifiles; /* number of infected files */
  int notremoved; /* number of not removed files (if --remove) */
  int notmoved; /* number of not moved files (if --move) */
  int errors; /*  ... of errors */
  long int blocks; /* number of read 16kb blocks */
};


void help(void);
void printtag(void);
void startclamd(struct optstruct *opt);

struct s_info claminfo;
short printinfected = 0;
/* short int mprintf_stdout; */

extern int client(const char *dirname, struct optstruct *opt, const char **virname);

int spoolstdin(char *tmpnm, int fd, int ret, const char **virname, const char **bndrystore, struct optstruct *opt);

int clamdscan(struct optstruct *opt)
{
  int ds, dms;

  struct timeval t1, t2;
  struct timezone tz;
  const char *bndrystore;
  const char *tmpdir;
  char *tmpfil;
  char *pfx;
  char *tmper;
  char *dir;
  struct passwd *user = NULL;
  FILE *tmp;
  FILE *fs;
  int bytes;
  char buff[BUFFSIZE];
  struct cl_node *trie = NULL;
  int threads = 0;
  int fd, fdtmp, ret;
  unsigned int no = 0;
  unsigned long int size = 0;
  long double mb;
  const char *virname;
  struct cl_engine *engine = NULL;
  struct cl_scan_options options;
  struct stat sb;

  if(optc(opt, 'V')) {
    mprintf("clamdmail "KLAMAV_VERSION" \n");
    mexit(0);
  }

  if(optc(opt, 'h')) {
  free_opt(opt);
  help();
  }

  if(optc(opt, 'i')) printinfected = 1;
  else printinfected = 0;



  memset(&claminfo, 0, sizeof(struct s_info));

  gettimeofday(&t1, &tz);

  //if(user)


  char name[19];
  char *tmpnm;
  char *mdir;

  if((mdir = getenv("TMPDIR")) == NULL)
  #ifdef P_tmpdir
      mdir = P_tmpdir;
  #else
      mdir = "/tmp";
  #endif

  tmpnm = (char*) mcalloc(strlen(mdir) + 1 + 16 + 1 + 7, sizeof(char));
  if(tmpnm == NULL) {
      exit(2);
  }

  sprintf(tmpnm, "%s/", mdir);
  snprintf(name, sizeof(name), "klammailXXXXXX");
  strncat(tmpnm, name, 19);
  mkstemp(tmpnm);



  fd = open(tmpnm,O_RDWR|O_CREAT, S_IRWXU);

  while((bytes = read(0, buff, BUFFSIZE)) > 0) {

          if(write(fd, buff, bytes) != bytes) {
                  close(fd);
                  return CL_EMEM;
          }
  }

  if(fsync(fd) == -1) {
        close(fd);
	return CL_ETMPFILE;
  }

  close(fd);

  if((fd = open(tmpnm, O_RDONLY)) == -1) {
    printf("Can't open file %s\n", tmpnm);
    exit(2);
  }

  ret = 0;
  ret = client(tmpnm, opt, &virname);



  /* Clamd isn't running, scan the file ourselves */
  if(ret == 2) {
      /* Clamd isn't running, start it so it is available next time. */
      startclamd(opt);

      if ((ret = cl_init(CL_INIT_DEFAULT)) != CL_SUCCESS) {
          printf("Can't initialize libclamav: %s\n", cl_strerror(ret));
          close(fd);
          exit(2);
      }

      if(!(engine = cl_engine_new())) {
          printf("Cannot create scanner engine: %s\n", cl_strerror(ret));;
          cl_engine_free(engine);
          close(fd);
          exit(2);
      }

      if(optc(opt, 'd')) {
          if((ret = cl_load(getargc(opt, 'd'), engine, &no, CL_DB_STDOPT)) != CL_SUCCESS) {
              printf("cl_load: %s\n", cl_strerror(ret));
              close(fd);
              return 50;
          }
      }else{
          if((ret = cl_load(cl_retdbdir(), engine, &no, CL_DB_STDOPT)) != CL_SUCCESS) {
              printf("cl_loaddbdir: %s\n", cl_strerror(ret));
              close(fd);
              exit(2);
          }
      }

      /* build engine */
      if((ret = cl_engine_compile(engine)) != CL_SUCCESS ) {
          printf("Database initialization error: %s\n", cl_strerror(ret));
          cl_engine_free(engine);
          close(fd);
          exit(2);
      }

      /* scan file descriptor */
      memset(&options, 0, sizeof(struct cl_scan_options));
      options.parse    = ~0; // all parsers
      options.general |= CL_SCAN_GENERAL_HEURISTICS;
      options.mail    |= CL_SCAN_MAIL_PARTIAL_MESSAGE; 
      ret = cl_scandesc(fd, tmpnm, &virname, &size, engine, &options );
      printf("scandesc returned: %s\n", cl_strerror(ret));
      if( ret == CL_VIRUS )
	      printf("virus found\n");
      else
	      printf("file clean\n");
  }

  /* scan descriptor (with archive and mail scanning enabled) */
  close(fd);
  fd = open(tmpnm,O_RDWR, S_IRWXU);

  spoolstdin(tmpnm, fd, ret, &virname, &bndrystore, opt);

  if(ret == 2)
      cl_engine_free(engine);

  unlink(tmpnm);

  mexit(0);
}

int spoolstdin(char *tmpnm, int fd, int ret, const char **virname, const char **bndrystore, struct optstruct *opt)
{
  int bytes;
  int i, j;
  long int size = 0;
  char buff[BUFFSIZE];
  char string[1000];
  char To[1000];
  char ReplyTo[1000];
  char DelivTo[1000];
  char From[1000];
  char ToR[1000];
  char FromR[1000];
  char boundary[1000];
  char host[256];  /* could be HOST_NAME_MAX+1 on POSIX 1003.1-2001 */
  struct timeval tv;
  struct timezone tz;
  char *s;
  char *storage;
  FILE *tmp;
  int pcount = 0;
  time_t starttime;
  struct tm *tm;
  char *out = NULL;
  const char *format;
  size_t out_length = 0;
  time_t when;
  int childpid;


  strcpy (To, "To:");
  strcpy (From, "From:");
  strcpy (ReplyTo, "Reply-To:");
  strcpy (DelivTo, "Delivered");

  gethostname(host, sizeof(host));
  gettimeofday(&tv, &tz);
  time(&when);
  tm = localtime(&when);
  format = "%a, %_d %b %Y %H:%M:%S %z";
  do {
          out_length += 200;
          out = (char *) realloc (out, out_length);
          out[0] = '\1';
  } while (strftime (out, out_length, format, tm) == 0 && out[0] != '\0');

  if(ret == CL_VIRUS) {
      if(!(optc(opt, 'f'))){

          lseek(fd, 0, SEEK_SET);
          if (!(tmp = fdopen(fd,"r"))){
              mprintf("@Can't open file %s\n", tmpnm);
              return 54;
          }

          mprintf_stdout = 1;

          while (fgets(string, sizeof(string), tmp)) {
              if (strstr(string, To) && !(strstr(string, ReplyTo)) && !(strstr(string,DelivTo))){
                      strcpy(To, string);
                      strcpy(ToR, string);
              }
              if (strstr(string, From)){
                      strcpy(FromR, string);
              }
              if (strncmp(string, "\n", 1) == 0 && pcount != 0){
                      break;
              }
              ++pcount;
          }

          if (strcmp(To,"To:") == 0){
                  strcat(To, " ");
                  strcat(To, getargl(opt, "admin"));
                  strcat(To, "\n");
          }
          strcat(From, " ");
          strcat(From, "KlamAV");
          strcat(From, "\n");
          strcat(ReplyTo, " ");
          strcat(ReplyTo, "KlamAV");
          strcat(ReplyTo, "\n");
          mprintf("%s", From);
          mprintf("%s", ReplyTo);
          mprintf("%s", To);
          mprintf("Subject: Virus %s found in attached mail by KlamAV.\n", *virname);
          mprintf("Date: %s\n", out);
          if(!(optl(opt, "quar"))){
              mprintf("MIME-Version: 1.0\n");
              mprintf("Content-Type: multipart/mixed;\n");
              mprintf("  boundary=\"----------=_%d\"\n",tv.tv_sec);
              mprintf("X-Virus-Status: Yes\n");
              mprintf("X-Virus-Checker: Scanned by KlamAV %s on %s (virus-found %s);\n\t%s\n",
                      KLAMAV_VERSION, host, *virname, out);
              mprintf("\n");
              mprintf("\n");
              mprintf("------------=_%d\n",tv.tv_sec);
          }
          mprintf("Content-Type: text/plain;\n");
          mprintf("  charset=\"us-ascii\"\n");
          mprintf("Content-Transfer-Encoding: quoted-printable\n");
          if((optl(opt, "quar"))){
              mprintf("X-Virus-Status: Yes\n");
              mprintf("X-Virus-Checker: Scanned by KlamAV %s on %s (virus-found %s);\n\t%s\n",
                      KLAMAV_VERSION, host, *virname, out);
              mprintf("\n");
              mprintf("KlamAV anti-virus scanner has intercepted and quarantined a message addressed to you.\n");
          }
          else
          {
              mprintf("\n");
              mprintf("KlamAV anti-virus scanner has detected a virus in the attached message.\n");
          }
          mprintf("\n");
          mprintf("The following is a summary of the infected message:\n");
          mprintf("\n");
          mprintf("Virus name: %s\n", *virname);
          mprintf("%s", FromR);
          mprintf("%s", ToR);
          mprintf("\n");
          mprintf("Please be aware that a virus spread by email normally forges the \n");
          mprintf("address of the sender. There is a good chance that the infected message\n");
          mprintf("was not received from the sender listed above. \n");
          mprintf("\n");
          if(!(optl(opt, "quar"))){
              mprintf("------------=_%d\n",tv.tv_sec);
              mprintf("Content-Type: message/rfc822; x-virus-type=original\n");
              mprintf("Content-Description: Infected Message - Open At Your Own Risk\n");
              mprintf("Content-Disposition: inline\n");
              mprintf("Content-Transfer-Encoding: 8bit\n");
              mprintf("\n");
              fflush(stdout);

              lseek(fd, 0, SEEK_SET);
              while((bytes = read(fd, buff, BUFFSIZE)) > 0) {

                      if(write(1, buff, bytes) != bytes) {
                              close(fd);
                              return CL_EMEM;
                      }
              }
              mprintf("------------=_%d--\n",tv.tv_sec);
          }
          fflush(stdout);

          fclose(tmp);
      }else{
          int bcnt = 0;
          lseek(fd, 0, SEEK_SET);
          if (!(tmp = fdopen(fd,"r"))){
                  mprintf("@Can't open file %s\n", tmpnm);
                  return 54;
          }

          mprintf_stdout = 1;

          while (fgets(string, sizeof(string), tmp)) {
              if (*string == '\n') {
              mprintf("X-Virus-Status: Yes\n");
              mprintf("X-Virus-Checker: Scanned by KlamAV %s on %s (virus-found %s);\n\t%s\n",
                      KLAMAV_VERSION, host, *virname, out);
                      break;
              }
              fputs(string, stdout);
          }
          while (fgets(string, sizeof(string), tmp)) {
              fputs(string, stdout);
          }

          if(optl(opt, "tag")) {
              if (!(*bndrystore))
                      printtag();
          }
          fclose(tmp);
      }

      if ( ( childpid=fork() ) == -1 ) {
          perror("Failed to fork; quitting\n");
          exit(2);
      }

      if ( childpid == 0 ) {
          char *dialogmessage;
          dialogmessage = malloc(77+sizeof(virname)+sizeof(FromR)+sizeof(ToR)+4+84+54);
          sprintf(dialogmessage,"KlamAV has found an infected mail with the following details:\nVirus name: %s\n%s\n%s\nThe mail has been handled according to the filter rules set up in your mail client (probably put in your trash/deleted items directory).",*virname,FromR,ToR);

          if (setenv("PATH","/usr/local/sbin:/usr/sbin:/sbin:/usr/bin:/bin:/usr/local/bin:/opt/kde/bin",1) == 0)
                  execlp("kdialog", "kdialog", "--msgbox", dialogmessage, "--title", "Virus found by KlamAV:",  NULL);
          free(dialogmessage);
      }
  } else if(ret == CL_CLEAN) {
      int bcnt = 0;
      lseek(fd, 0, SEEK_SET);
      if (!(tmp = fdopen(fd,"r"))){
          mprintf("@Can't open file %s\n", tmpnm);
          return 54;
      }

      mprintf_stdout = 1;

      while (fgets(string, sizeof(string), tmp)) {
          if (*string == '\n') {
                  mprintf("X-Virus-Status: No\n");
                  mprintf("X-Virus-Checker: Scanned by KlamAV %s on %s (no viruses);\n\t%s\n\n",
                          KLAMAV_VERSION, host, out);
                  break;
          }
          fputs(string, stdout);
      }
      while (fgets(string, sizeof(string), tmp)) {
          fputs(string, stdout);
      }

      if(optl(opt, "tag"))
          if (!(*bndrystore))
                  printtag();

      fclose(tmp);
  } else {
      if(!printinfected)
          mprintf("stdin: %s\n", cl_strerror(ret));
  }
  return ret;
}

void printtag(void)
{

    mprintf_stdout = 1;
    mprintf("\n");
    mprintf("----------------------------------------------------------------------------\n");
    mprintf("			This message was scanned by\n");
    mprintf("		ClamAV Open Source Anti-Virus Technology\n");
    mprintf("				using KlamAV\n");
    mprintf("\n");
    mprintf("			http://www.clamav.net\n");
    mprintf("			http://klamav.sf.net\n");
    mprintf("----------------------------------------------------------------------------\n");
    mprintf("\n");
    fflush(stdout);

}

void help(void)
{

    mprintf_stdout = 1;

    mprintf("\n");
    mprintf("		   KlamAV Mail Processing Client "KLAMAV_VERSION"\n");
    mprintf("		   (c) 2004 Robert Hogan <robert@roberthogan.net>\n");
    mprintf("		   	Uses a lot of code written by:\n");
    mprintf("		       Tomasz Kojm <zolw@konarski.edu.pl>\n");
    mprintf("		       Nigel Horne <njh@bandsman.co.uk>\n");
    mprintf("	  \n");
    mprintf("    --help		-h	Show help\n");
    mprintf("    --version		-V	Print version number and exit\n");
    mprintf("    -f			Header Flag Only.\n");
    mprintf("    --tag		Tag messages as scanned.\n");
    mprintf("    -d			Location of virus definition database.\n");

    exit(0);
}

void startclamd(struct optstruct *opt)
{

    int pfds[2];
    int childpid;
    int fd;
    char tmpnm[19];
    char conffile[31];
    struct stat sb;
    FILE *tmp;
    char *fullpath;
    char cwd[200];
    char *scancmd;

    snprintf(tmpnm, sizeof(tmpnm), "klammailXXXXXX");
    mkstemp(tmpnm);

    fd = open(tmpnm,O_RDWR|O_CREAT, S_IRWXU);


    lseek(fd, 0, SEEK_SET);
    if (!(tmp = fdopen(fd,"w"))){
            mprintf("@Can't open file \n");
    }

    fprintf(tmp,"LocalSocket /tmp/KlamMailSock\n");
    fprintf(tmp,"MaxDirectoryRecursion 15\n");
    fprintf(tmp,"SelfCheck 900\n");
    //fprintf(tmp,"ScanArchive\n");
    if(optc(opt, 'd')) {
            stat(getargc(opt, 'd'), &sb);
            switch(sb.st_mode & S_IFMT) {
                    case S_IFREG:
                    fprintf(tmp,"DatabaseDirectory %s\n",getargc(opt, 'd'));
                    break;
                    case S_IFDIR:
                    fprintf(tmp,"DatabaseDirectory %s\n",getargc(opt, 'd'));
                    break;
                    default:
                    mprintf("@%s: Not supported database file type\n", getargc(opt, 'd'));
                    break;
                    }
    }else{
            fprintf(tmp,"DatabaseDirectory /usr/local/share/clamav\n");
    }
    //fprintf(tmp,"ScanMail\n");
    if ((strstr(cl_retver(), "0.8")) || (strstr(cl_retver(), "0.7")))
        fprintf(tmp,"FixStaleSocket\n");
    else
        fprintf(tmp,"FixStaleSocket yes\n");

    fflush(tmp);

    fclose(tmp);
    close(fd);

    fullpath = (char*) mcalloc(200 + strlen(tmpnm) + 10, sizeof(char));

    if(!getcwd(cwd, 200)) {
            mprintf("@Can't get absolute pathname of current working directory.\n");
            return;
    }
    sprintf(fullpath, "%s/%s", cwd, tmpnm);


    if ( pipe(pfds) == -1 ) {
      perror("Failed to create pipe; quitting\n");
      exit(1);
    }

    if ( ( childpid=fork() ) == -1 ) {
      perror("Failed to fork; quitting\n");
      exit(2);
    }

    /* instead of STDIN -> clamdmail -> STDOUT, do
    STDIN -> clamdmail -> spamc -> STDOUT
    (previously incorrect as STDIN -> spamc -> clamdmail -> STDOUT)
    */
    if ( childpid == 0 ) {
            if (setenv("PATH","/usr/local/sbin:/usr/sbin:/sbin:/usr/bin:/bin:/usr/local/bin:/opt/kde/bin",1) == 0)
                    execlp("clamd", "clamd", "-c", fullpath,  NULL);
    }

    sleep(1);
    unlink(fullpath);
}
