/* * mod_clamav - ClamAV virus scanning module for ProFTPD * Copyright (c) 2005, Joseph Benden * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * Furthermore, Joseph Benden gives permission to link this program with * ClamAV, and distribute the resulting executable, without including the * source code for ClamAV in the source distribution. * * ClamAV is available at http://www.clamav.net/ * Non-official SuSE RPM packages for both ClamAV and ProFTPD are available * at http://www.ispservices.com/ * * Thanks to TJ Saunders for his helpful comments and suggestions! * * DO NOT EDIT THE LINE BELOW * $Libraries: -lclamav $ */ #include "conf.h" #include "clamav.h" #define MOD_CLAMAV_VERSION "mod_clamav/0.3" static void clamav_restart_ev(const void *event_data, void *user_data); static int reload_av_database(void); module clamav_module; /* This works through the fork process on Linux. YMMV. If it doesn't work, ie. * seg. faults. A simple solution is to move the clamav database loader into * the session init handler. This will definately slow things down, though. */ struct cl_node *clamav_root = NULL; struct cl_stat dbstat; static int patcount = 0; MODRET clamav_scan(cmd_rec *cmd) { config_rec * c = NULL; struct cl_limits limits; const char * virname; unsigned long int size; int ret; mode_t prevmask; c = find_config(CURRENT_CONF, CONF_PARAM, "ClamAV", FALSE); if(!c || !*(int*)(c->argv[0])) return DECLINED(cmd); /* pr_log_pri(PR_LOG_INFO, MOD_CLAMAV_VERSION ": info: scanning file: %s",cmd->arg); */ memset(&limits, 0, sizeof(struct cl_limits)); limits.maxfiles = 1000; limits.maxfilesize = 20 * 1048576; limits.maxreclevel = 5; limits.maxratio = 200; limits.archivememlim = 0; /* cl_settempdir("",1); */ /* clamav insists on a tmp directory for handling archives */ prevmask = umask(0); if(pr_fsio_mkdir("tmp",S_IREAD|S_IWRITE|S_IEXEC)!=0) { if(errno != EEXIST) pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": notice: mkdir() failed: %d",errno); } umask(prevmask); /* hold on to the ClamWarn configuration option */ c = find_config(CURRENT_CONF, CONF_PARAM, "ClamWarn", TRUE); /* scan it! */ if((ret=cl_scanfile(cmd->arg,&virname,&size,clamav_root,&limits,CL_ARCHIVE|CL_MAIL|CL_OLE2)) == CL_VIRUS) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": warning: file '%s' contains the virus '%s'. File was deleted.", cmd->arg, virname); if(c && *(int*)(c->argv[0])) pr_response_add_err(R_DUP,"File contains the %s virus.", virname); if((ret=pr_fsio_unlink(cmd->arg))!=0) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": notice: unlink() failed: %d",errno); } return ERROR(cmd); } if(ret != CL_CLEAN) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: clamav returned the error message: %s", cl_strerror(ret)); } if(c && *(int*)(c->argv[0])) pr_response_add(R_226,"File passed ClamAV virus scanner containing %d patterns.", patcount); if(pr_fsio_mkdir("tmp")!=0) { if(errno != EEXIST) pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": notice: rmdir() failed: %d",errno); } return DECLINED(cmd); } MODRET set_clamav(cmd_rec *cmd) { int bool = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_ANON|CONF_LIMIT|CONF_VIRTUAL); if((bool = get_boolean(cmd,1)) == -1) CONF_ERROR(cmd, "requires a boolean value"); c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = bool; c->flags |= CF_MERGEDOWN; return HANDLED(cmd); } static int clamav_init(void) { int ret = 0; patcount = 0; if((ret=cl_loaddbdir(cl_retdbdir(), &clamav_root, &patcount))) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: cl_loaddbdir(): %s", cl_strerror(ret)); } if((ret=cl_buildtrie(clamav_root))) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: cl_buildtrie(): %s", cl_strerror(ret)); } memset(&dbstat, 0, sizeof(struct cl_stat)); cl_statinidir(cl_retdbdir(), &dbstat); pr_log_pri(PR_LOG_INFO, MOD_CLAMAV_VERSION ": info: loaded %d virus patterns", patcount); pr_event_register(&clamav_module, "core.restart", clamav_restart_ev, NULL); return 0; } static void clamav_restart_ev(const void *event_data, void *user_data) { reload_av_database(); } static int clamav_session_init(void) { return reload_av_database(); } static int reload_av_database(void) { int ret; /* check for updated virus defs */ pr_log_pri(PR_LOG_INFO, MOD_CLAMAV_VERSION ": info: checking for newer antivirus defs"); if(cl_statchkdir(&dbstat) == 1) { /* reload virus definitions */ cl_free(clamav_root); clamav_root = NULL; patcount = 0; if((ret=cl_loaddbdir(cl_retdbdir(), &clamav_root, &patcount))) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: cl_loaddbdir(): %s", cl_strerror(ret)); } if((ret=cl_buildtrie(clamav_root))) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: cl_buildtrie(): %s", cl_strerror(ret)); } pr_log_pri(PR_LOG_INFO, MOD_CLAMAV_VERSION ": info: loaded %d virus patterns", patcount); cl_statfree(&dbstat); memset(&dbstat, 0, sizeof(struct cl_stat)); cl_statinidir(cl_retdbdir(), &dbstat); } return 0; } static conftable clamav_conftab[] = { { "ClamAV", set_clamav, NULL }, { "ClamWarn", set_clamav, NULL }, { NULL } }; static cmdtable clamav_cmdtab[] = { { POST_CMD, C_STOR, G_NONE, clamav_scan, TRUE, FALSE }, { POST_CMD, C_STOU, G_NONE, clamav_scan, TRUE, FALSE }, { POST_CMD, C_APPE, G_NONE, clamav_scan, TRUE, FALSE }, { 0, NULL } }; module clamav_module = { NULL, NULL, 0x20, /* api ver */ "clamav", clamav_conftab, clamav_cmdtab, NULL, /* auth function table */ clamav_init, /* init function */ clamav_session_init /* session init function */ };