You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
klamav/src/klammail/clamdmail.c

595 lines
17 KiB

/*
*
* 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 "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 <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);
}