/*
*
* 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 0213 9 , 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 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 ( ( engine = cl_engine_new ( ) ) = = NULL ) {
printf ( " Database initialization error: %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 ) ) ) {
printf ( " cl_load: %s \n " , cl_strerror ( ret ) ) ;
close ( fd ) ;
return 50 ;
}
} else {
if ( ( ret = cl_load ( cl_retdbdir ( ) , engine , & no , CL_DB_STDOPT ) ) ) {
printf ( " cl_loaddbdir: %s \n " , cl_strerror ( ret ) ) ;
close ( fd ) ;
exit ( 2 ) ;
}
}
/* build engine */
if ( ( ret = cl_engine_compile ( engine ) ) ) {
printf ( " Database initialization error: %s \n " , cl_strerror ( ret ) ) ; ;
cl_engine_free ( engine ) ;
close ( fd ) ;
exit ( 2 ) ;
}
ret = cl_scandesc ( fd , tmpnm , & virname , & size , engine ,
CL_SCAN_GENERAL_ALLMATCHES | CL_SCAN_GENERAL_HEURISTICS |
CL_SCAN_PARSE_ARCHIVE | CL_SCAN_PARSE_MAIL | CL_SCAN_PARSE_OLE2 |
CL_SCAN_PARSE_HTML | CL_SCAN_PARSE_PDF ) ;
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: \n Virus name: %s \n %s \n %s \n The 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 ) ;
}