/*
*
*  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 "options.h"
#include "defaults.h"
#include "../version.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;

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, no = 0;
                unsigned long int size = 0;
                long double mb;
                const char *virname;
                struct cl_node *root = NULL;
                struct cl_limits limits;
                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);
                }

		//snprintf(fullname, sizeof(fullname), "%s/pdfXXXXXX", dir);

                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) {
                                cli_dbgmsg("clamdmail -> Can't write() file.\n");
                                close(fd);
                                return CL_EMEM;
                        }
                }

                if(fsync(fd) == -1) {
                        cli_dbgmsg("fsync() failed for descriptor %d\n", fd);
                        close(fd);
                        return CL_EFSYNC;
                }
                /*if (!(tmp = fdopen(fd,"r"))){
                        mprintf("@Can't open file \n");
                        return 54;
                }
                lseek(fd, 0, SEEK_SET);*/
                /*close(fd);*/
                /*
                * Extract the attachments into the temporary directory
                */

                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);

        
                /* load all available databases from default directory */
                if((ret == 2)) {
                        startclamd(opt);
                        if(optc(opt, 'd')) {
                                stat(getargc(opt, 'd'), &sb);
                                switch(sb.st_mode & S_IFMT) {
                                        case S_IFREG:
                                        if((ret = cl_loaddb(getargc(opt, 'd'), &root, &no))) {
                                                mprintf("@%s\n", cl_strerror(ret));
                                                return 50;
                                        }
                                        break;
                                        case S_IFDIR:
                                        if((ret = cl_loaddbdir(getargc(opt, 'd'), &root, &no))) {
                                                mprintf("@%s\n", cl_strerror(ret));
                                                return 50;
                                        }
                                        break;
                                        default:
                                        mprintf("@%s: Not supported database file type\n", getargc(opt, 'd'));
                                        return 50;
                                        }
                        }else{
                                if((ret = cl_loaddbdir(cl_retdbdir(), &root, &no))) {
                                printf("cl_loaddbdir: %s\n", cl_strerror(ret));
                                close(fd);
                                exit(2);
                                }
                        }	
                        /* build the final trie */
                        if((ret = cl_build(root))) {
        //		printf("Database initialization error: %s\n", cl_strerror(ret));;
                                cl_free(root); /* free the partial trie */
                                close(fd);
                                exit(2);
                        }
                        
                        /* set up archive limits */
                        memset(&limits, 0, sizeof(struct cl_limits));
                        limits.maxfiles = 1000; /* max files */
                        limits.maxfilesize = 10 * 1048576; /* maximal archived file size == 10 Mb */
                        limits.maxreclevel = 5; /* maximal recursion level */
                        limits.maxratio = 200; /* maximal compression ratio */
                        limits.archivememlim = 0; /* disable memory limit for bzip2 scanner */
                        
                        ret = cl_scandesc(fd, &virname, &size, root, &limits, CL_SCAN_STDOPT | CL_ARCHIVE | CL_MAIL | CL_OLE2 | CL_SCAN_HTML);

                }
//		printf("Loaded %d signatures.\n", no);
                
                
                /* scan descriptor (with archive and mail scanning enabled) */
        
                close(fd);
                
                fd = open(tmpnm,O_RDWR, S_IRWXU);

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

                if((ret == 2))
                        cl_free(root);
                        
                unlink(tmpnm);




                mexit(0);
}

int spoolstdin(int fd, int ret, 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 \n");
                                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) {
                                        cli_dbgmsg("clamdmail -> Can't write() file.\n");
                                                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", fd);
                                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", fd);
                        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 TRUE\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);
        
        //execlp("/usr/local/sbin/clamd", "/usr/local/sbin/clamd", ("--config-file=%s",tmpnm),  NULL);
        
}
