/* * * 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 * * 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 #include #include #include #include #include #include #include #include #include #include #include "options.h" #include "defaults.h" #include "memory.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; */ 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_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: %i\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, 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 \n"); mprintf(" Uses a lot of code written by:\n"); mprintf(" Tomasz Kojm \n"); mprintf(" Nigel Horne \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); }