/* ** mod_clamav.c -- Apache module to virus check everything returned to clients ** ** (c) 2003 Dr. Andreas Mueller, Beratung und Entwicklung ** ** This module is distributed under the GNU General Public License (GPL). ** See the file COPYING for details. ** ** $Id: mod_clamav.c,v 1.21 2008/01/05 16:14:27 afm Exp $ */ #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "http_log.h" #include "http_core.h" #include "ap_config.h" #include "apr_strings.h" #include "apr_time.h" #include "apr_compat.h" #include #include #include #include #include #ifdef HAVE_SYS_STAT_H #include #endif #include #ifdef HAVE_SYS_TYPES_H #include #endif #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include "mod_clamav_version.h" #ifdef HAVE_ALLOCA_H #include #endif #ifndef CLAMAV_DEBUG #define CLAMAV_DEBUG 0 #endif /* CLAMAV_DEBUG */ #if CLAMAV_DEBUG #define clamav_ap_log_rerror(x, args...) ap_log_rerror(x, ##args) #else #define clamav_ap_log_rerror(args...) #endif #define MOD_CLAMAV_LOCAL 0 #define MOD_CLAMAV_DAEMON 1 #define MOD_CLAMAV_NOTE_PREFIX "clamav:" #define MOD_CLAMAV_STATUS_NOTE MOD_CLAMAV_NOTE_PREFIX "status" #define MOD_CLAMAV_DETAILS_NOTE MOD_CLAMAV_NOTE_PREFIX "details" #define MOD_CLAMAV_VIRUSNAME_NOTE MOD_CLAMAV_NOTE_PREFIX "virusname" #define MOD_CLAMAV_LONGSTATUS_NOTE MOD_CLAMAV_NOTE_PREFIX "longstatus" #define MOD_CLAMAV_PASSED_STATUS "passed" #define MOD_CLAMAV_BYPASSED_STATUS "bypassed" #define MOD_CLAMAV_ABORTED_STATUS "aborted" #define MOD_CLAMAV_INFECTED_STATUS "INFECTED" #define MOD_CLAMAV_FAILED_STATUS "failed" module AP_MODULE_DECLARE_DATA clamav_module; /* the clamav statistics context */ #define VIRUS_LIST_LENGTH 10 #define VIRUS_NAME_LENGTH 64 #define VIRUS_URI_LENGTH 256 #define VIRUS_REQ_LENGTH 64 #define MATCH_SAFE_URI 0 #define MATCH_SAFE_HOST 1 typedef struct clamav_virusinfo { pid_t pid; /* the process id of the apache process that detected */ /* the virus */ char uri[VIRUS_URI_LENGTH]; /* the uri that contained the virus */ char req[VIRUS_REQ_LENGTH]; /* request that contained the virus */ char virus[VIRUS_NAME_LENGTH]; /* the virus name */ off_t size; /* the size of the data checked */ apr_time_t when; /* the time of the virus event */ } clamav_virusinfo; typedef struct clamav_stats { unsigned long requests; /* the number of requests */ unsigned long checked; /* requests actally checked */ unsigned long aborted; /* requests aborted prematurely */ unsigned long long totalsize; /* total size of data transfered */ off_t maxsize; /* maximum request checked */ off_t maxvirussize; /* maximum virus file size */ double cpu; /* cpu time used for virus checks */ unsigned long viruses; /* number of viruses find */ clamav_virusinfo lastviruses[VIRUS_LIST_LENGTH]; /* list of viruses */ int last; /* last entry index in lastviruses */ unsigned long reloads; /* number of database reloads (local) */ } clamav_stats; static void clamav_virus_record(clamav_stats *stats, const char *uri, const char *req, const char *virus, off_t size) { /* find the index of the entry we are about to create */ if (++stats->last >= VIRUS_LIST_LENGTH) stats->last = 0; stats->lastviruses[stats->last].pid = getpid(); stats->lastviruses[stats->last].when = apr_time_now(); stats->lastviruses[stats->last].size = size; /* reset the string fields */ memset(stats->lastviruses[stats->last].uri, 0, VIRUS_URI_LENGTH); memset(stats->lastviruses[stats->last].req, 0, VIRUS_REQ_LENGTH); memset(stats->lastviruses[stats->last].virus, 0, VIRUS_NAME_LENGTH); /* remember uri and virus name */ strncpy(stats->lastviruses[stats->last].uri, uri, VIRUS_URI_LENGTH - 1); strncpy(stats->lastviruses[stats->last].req, req, VIRUS_REQ_LENGTH - 1); strncpy(stats->lastviruses[stats->last].virus, virus, VIRUS_NAME_LENGTH -1); } /* clamav_virus_record */ /* we don't want to duplicate the clamav trie, so we keep it in a separate */ /* structure */ typedef struct cl_daemon_t { struct sockaddr *s; int len; int domain; } cl_daemon_t; typedef struct cl_local_t { struct cl_engine *engine; time_t lastreload; } cl_local_t; typedef union clamav_common { cl_local_t *local; cl_daemon_t *daemon; } clamav_common; typedef struct clamav_safepattern { char mode; /* type of pattern */ char tag[31]; /* short name of pattern */ char pattern[16]; /* pattern */ char mask[16]; /* bit mask */ } clamav_safepattern; typedef struct clamav_safeuri { char pattern[VIRUS_URI_LENGTH]; regex_t *regex; int matchtype; } clamav_safeuri; /* the clamav module configuration */ typedef struct clamav_config_rec { char *tmpdir; int mode; /* mod_clamav mode: local or daemon */ const char *dbdir; /* local: where are virus patterns */ int port; /* daemon: clamd port number */ char *socket; /* daemon: unix domain socket path */ int maxfiles; int maxfilesize; int maxreclevel; int trickle_interval; int trickle_size; int sizelimit; int reload_interval; int acceptdaemonproblem; int extendedlogging; mode_t perms; apr_table_t *safetypes; /* types safe to bypass av checking */ apr_array_header_t *safepatterns; apr_array_header_t *safeuris; char *message; /* virus scanning engine */ clamav_common *common; /* shared data for statistics */ apr_shm_t *shm; char *shmname; clamav_stats *stats; /* mutex to mediate access to shared data */ apr_proc_mutex_t *mutex; char *mutexname; /* the pool to use for long term allocations */ apr_pool_t *pool; } clamav_config_rec; /* the clamav filter context */ typedef struct clamav_ctx { apr_bucket_brigade *bb; char *filename; int file; time_t last_trickle; int trickle_offset; clamav_config_rec *module; int bypass; int bytes; } clamav_ctx; /* get the module configuration record */ static clamav_config_rec *clamav_get_module_config(request_rec *r) { clamav_config_rec *result; /* make sure we were not handed an empty request, since we don't */ /* know the server, we cannot log anywhere using Apache's API */ if (NULL == r) { fprintf(stderr, "[%d] NULL request handed to clamav_get_module_config", (int)getpid()); return NULL; } /* try to recover the directory configuration record from the */ /* the apache runtime */ result = (clamav_config_rec *)ap_get_module_config( r->per_dir_config, &clamav_module); if (NULL != result) { return result; } /* if we get to this point, then there is no module config */ /* record around. This really should not happen, but it did */ /* an a SuSE Linux 9.0 System with the prefork MT */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[%d] no config record", (int)getpid()); return NULL; } /* set up the mutex lock, delayed until the first request happens */ static void clamav_mutex(clamav_config_rec *rec, request_rec *r) { mode_t mask; /* skip creating the lock if it already exists */ if (NULL != rec->mutex) return; /* create the mutex lock */ mask = umask(077); if (APR_SUCCESS != apr_proc_mutex_create(&rec->mutex, (rec->mutexname) ? rec->mutexname : "/var/tmp/clamav.lock", APR_LOCK_DEFAULT, rec->pool)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[%d] cannot create mutex %s", (int)getpid(), (rec->mutexname) ? rec->mutexname : "/var/tmp/clamav.lock"); rec->mutex = NULL; } umask(mask); } /* functions to lock and unlock */ static void clamav_lock(clamav_config_rec *rec, request_rec *r) { clamav_mutex(rec, r); if (NULL != rec->mutex) apr_proc_mutex_lock(rec->mutex); } /* clamav_lock */ static void clamav_unlock(clamav_config_rec *rec, request_rec *r) { clamav_mutex(rec, r); if (NULL != rec->mutex) apr_proc_mutex_unlock(rec->mutex); } /* clamav_unlock */ /* set request notes */ #define clamav_set_bypassed_note(rec, f, reason) \ clamav_set_status_note(rec, f, MOD_CLAMAV_BYPASSED_STATUS, \ reason, NULL) static void clamav_set_status_note(clamav_config_rec *rec, ap_filter_t *f, const char *status, const char *details, const char *virusname) { apr_table_t *notes = f->r->notes; const char *longstatus = NULL; if (!rec->extendedlogging) return; /* set the individual notes */ ap_table_set(notes, MOD_CLAMAV_STATUS_NOTE, status ? status : "-"); ap_table_set(notes, MOD_CLAMAV_DETAILS_NOTE, details ? details : "-"); ap_table_set(notes, MOD_CLAMAV_VIRUSNAME_NOTE, virusname ? virusname : "-"); /* build a long status message from the individual parts */ if (status) { longstatus = status; } /* add the details to the longstatus */ if (details) { if (longstatus) { longstatus = apr_psprintf(f->r->pool, "%s, %s", longstatus, details); } else { longstatus = details; } } /* add the virus name */ if (virusname) { if (longstatus) { longstatus = apr_psprintf(f->r->pool, "%s, found virus: %s", longstatus, virusname); } else { longstatus = virusname; } } /* add the status to the notes table */ ap_table_set(notes, MOD_CLAMAV_LONGSTATUS_NOTE, longstatus ? longstatus : "-"); } /* clamav_set_status_note */ /* function to decide whether it is safe to bypass virus checking */ static int clamav_safe_to_bypass(clamav_config_rec *rec, ap_filter_t *f) { int rc = 1, i, l; char *ct; const char *action; clamav_safeuri *p; /* requests with only headers can be bypassed */ if (f->r->header_only) { clamav_set_bypassed_note(rec, f, "header only request"); ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, "[%d] safe to skip HEAD", (int)getpid()); goto safe; } /* get the module record */ if (NULL == rec) { clamav_set_bypassed_note(rec, f, "module not configured"); ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, "[%d] no config, assuming safe", (int)getpid()); goto safe; } /* only GET and POST return something to the caller, so all others */ /* can safely bypass the virus checker */ if (strcasecmp(f->r->method, "GET") && strcasecmp(f->r->method, "POST")) { clamav_set_bypassed_note(rec, f, apr_psprintf(f->r->pool, "%s request", f->r->method)); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, f->r, "[%d] bypassing secure request type: %s", (int)getpid(), f->r->method); goto safe; } /* check safe uri */ p = (clamav_safeuri *)(rec->safeuris->elts); for (i = 0; i < rec->safeuris->nelts; i++) { switch (p[i].matchtype) { case MATCH_SAFE_URI: clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] checking uri '%s' against pattern '%s'", (int)getpid(), f->r->uri, p[i].pattern); if (ap_regexec(p[i].regex, f->r->uri, 0, NULL, 0) == 0) { clamav_set_bypassed_note(rec, f, apr_psprintf(f->r->pool, "safe uri")); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] safe uri '%s' matched pattern '%s'", (int)getpid(), f->r->uri, p[i].pattern); goto safe; } break; case MATCH_SAFE_HOST: clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] checking host '%s' against pattern '%s'", (int)getpid(), f->r->hostname, p[i].pattern); if (ap_regexec(p[i].regex, f->r->hostname, 0, NULL, 0) == 0) { clamav_set_bypassed_note(rec, f, apr_psprintf(f->r->pool, "safe host")); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] safe host '%s' matched pattern '%s'", (int)getpid(), f->r->uri, p[i].pattern); goto safe; } break; default: clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] ignoring unrecognized safe match type... ", (int)getpid()); } } /* if we don't know the type, we cannot bypass */ if (NULL == f->r->content_type) goto unsafe; /* if mod_dnsbl told us something, we may have no other choice */ action = ap_table_get(f->r->notes, "dnsbl:scan"); if (NULL != action) { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] checking dnsbl:action: %s", (int)getpid(), action); if (NULL != strstr(action, "no")) { clamav_set_bypassed_note(rec, f, "told to bypass by dnsbl"); ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, "[%d] skipping scan by dnsbl", (int)getpid()); goto safe; } if (NULL != strstr(action, "yes")) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, "[%d] forcing scan by dnsbl", (int)getpid()); goto unsafe; } } /* find the type */ ct = (char *)apr_pstrdup(f->r->pool, f->r->content_type); l = strlen(ct); for (i = 0; i < l; i++) ct[i] = tolower(ct[i]); /* if the type is among the safe types, we are safe */ if (apr_table_get(rec->safetypes, ct)) { clamav_set_bypassed_note(rec, f, apr_psprintf(f->r->pool, "safe types '%s'", ct)); goto safe; } unsafe: rc = 0; safe: clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] bypass for %s is %s", (int)getpid(), (NULL == f->r->uri) ? "(null)" : f->r->uri, (rc) ? "safe" : "not safe"); return rc; } /* set up shared memory, delayed until the first request happens */ static void clamav_shm(clamav_config_rec *rec, request_rec *r) { apr_status_t rc; mode_t mask; /* skip creating of shared memory */ if (NULL != rec->shm) return; /* create share memory */ clamav_lock(rec, r); rc = apr_shm_attach(&rec->shm, (rec->shmname) ? rec->shmname : "/var/tmp/clamav.shm", rec->pool); if (APR_SUCCESS != rc) { /* simply attaching to the shared memory didn't work so we must */ /* create it first */ mask = umask(077); rc = apr_shm_create(&rec->shm, sizeof(clamav_stats), (rec->shmname) ? rec->shmname : "/var/tmp/clamav.shm", rec->pool); umask(mask); if (APR_SUCCESS != rc) { fprintf(stderr, "%s:%d: cannot create shared memory %s: " "statistics will not be available\n", __FILE__, __LINE__, (rec->shmname) ? rec->shmname : "/var/tmp/clamav.shm"); rec->shm = NULL; } else { /* since we are the creator of the shared memory segment, */ /* we must also initialize it */ rec->stats = (clamav_stats *)apr_shm_baseaddr_get(rec->shm); memset(rec->stats, 0, sizeof(clamav_stats)); rec->stats->cpu = 0.; } } else { /* initialize the pointer to the shm structure */ rec->stats = (clamav_stats *)apr_shm_baseaddr_get(rec->shm); } clamav_unlock(rec, r); } /* clamav_shm */ /* initialize the engine (depends on mode) */ static int clamav_init_local(clamav_config_rec *rec, ap_filter_t *f) { int ret; unsigned int signo = 0; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] local init", (int)getpid()); /* initializa clam */ if (0 != (ret = cl_init(CL_INIT_DEFAULT))) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot initialize clamav: %s", (int)getpid(), cl_strerror(ret)); return -1; } /* make sure that rec->common->local is allocated */ if (NULL == rec->common->local) { rec->common->local = (cl_local_t *)apr_palloc(rec->pool, sizeof(cl_local_t)); rec->common->local->engine = cl_engine_new(); rec->common->local->lastreload = 0; } /* make sure we know in which directory to look for databases */ if (NULL == rec->dbdir) { rec->dbdir = cl_retdbdir(); } /* reload the database from the directory */ if (0 != (ret = cl_load(rec->dbdir, rec->common->local->engine, &signo, CL_DB_STDOPT))) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot load clamav patterns: %s", (int)getpid(), cl_strerror(ret)); return -1; } if (rec->shm) { clamav_lock(rec, f->r); rec->stats->reloads++; clamav_unlock(rec, f->r); } time(&rec->common->local->lastreload); /* remember when reloaded */ if (0 != (ret = cl_build(rec->common->local->engine))) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot build clamav tree: %s", (int)getpid(), cl_strerror(ret)); cl_free(rec->common->local->engine); return -1; } /* if we get to this point, we were successfull initializing the local*/ /* scanner */ return 0; } /* clamav_init_local */ static int clamav_init_daemon(clamav_config_rec *rec, ap_filter_t *f) { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] daemon init", (int)getpid()); rec->common->daemon = (cl_daemon_t *)apr_palloc(rec->pool, sizeof(cl_daemon_t)); if (rec->socket) { /* initialize daemon connections via a Unix domain socket */ struct sockaddr_un *sau; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] init unix domain socket '%s'", (int)getpid(), rec->socket); sau = (struct sockaddr_un *)apr_palloc(rec->pool, sizeof(struct sockaddr_un)); rec->common->daemon->s = (struct sockaddr *)sau; rec->common->daemon->len = sizeof(struct sockaddr_un); rec->common->daemon->domain = AF_UNIX; /* initialize unix domain socket address */ /* sau->sun_len = strlen(rec->socket); */ sau->sun_family = PF_UNIX; strncpy(sau->sun_path, rec->socket, sizeof(sau->sun_path) - 1); sau->sun_path[sizeof(sau->sun_path) - 1] = '\0'; } else { /* initialize daemon connections via a TCP socket */ struct sockaddr_in *sai; sai = (struct sockaddr_in *)apr_palloc(rec->pool, sizeof(struct sockaddr_in)); rec->common->daemon->s = (struct sockaddr *)sai; rec->common->daemon->len = sizeof(struct sockaddr_in); rec->common->daemon->domain = AF_INET; /* initialize INET domain socket address */ /* sai->sin_len = sizeof(sai->sin_addr); */ sai->sin_family = PF_INET; sai->sin_port = htons((short)rec->port); sai->sin_addr.s_addr = htonl(0x7f000001); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] init inet socket to %s:%d", (int)getpid(), inet_ntoa(sai->sin_addr), rec->port); } clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] daemon init complete", (int)getpid()); return 0; } /* clamav_init_daemon */ /* initialize the virus scanning engine: either the library inside the binary, or using the daemon (prefered) */ static int clamav_engine_init(clamav_config_rec *rec, ap_filter_t *f) { /* initialize share memory */ clamav_shm(rec, f->r); /* initialize the engine */ switch (rec->mode) { case MOD_CLAMAV_LOCAL: return clamav_init_local(rec, f); break; case MOD_CLAMAV_DAEMON: return clamav_init_daemon(rec, f); break; default: ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] unknown mode. %d", (int)getpid(), rec->mode); } clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] working in mode %d", (int)getpid(), rec->mode); return -1; } /* clamav_engine_init */ /* check whether the engine is ready. In the case of the local scanner, reloads the database if necessary as a side effect */ static int clamav_engine_ready(clamav_config_rec *rec, ap_filter_t *f) { time_t now; switch (rec->mode) { case MOD_CLAMAV_LOCAL: /* the module is not read if the local structure has never been */ /* allocated */ if (NULL == rec->common->local) return 0; /* even if the local structure is allocated, it may be the case */ /* that the reload interval has passed, so we may have to reload*/ /* the database */ if (0 == rec->reload_interval) return 1; /* always ready, since we are not going to ever */ /* reload the pattern database */ time(&now); if ((now - rec->common->local->lastreload) > rec->reload_interval) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, "[%d] database reload necessary after %d seconds", (int)getpid(), (int)(now - rec->common->local->lastreload)); /* make sure the engine is properly deallocated */ cl_free(rec->common->local->engine); return 0; } return 1; break; case MOD_CLAMAV_DAEMON: return (NULL != rec->common->daemon); break; } /* no other state is possible, but how should the compiler know */ /* that? so if we get a different state, will just tell the caller */ /* that we aren't ready, which isn't that wrong after all */ return 0; } /* clamav_engine_ready */ /* create HTML page informing about virus */ static int clamav_virus_info(clamav_config_rec *rec, ap_filter_t *f, const char *virname) { char infopage[HUGE_STRING_LEN]; clamav_ctx *ctx = (clamav_ctx *)f->ctx; /* set content type header to text/html */ f->r->content_type = "text/html"; /* format page output */ if (!f->r->header_only) { apr_table_setn(f->r->headers_out, "Cache-Control", "no-cache"); apr_table_setn(f->r->headers_out, "Pragma", "no-cache"); if (rec->message) { char *msg = rec->message; const char *p; int i = 0, j = 0; while (i < sizeof(infopage) - 1 && msg[j]) { if (msg[j] != '%') { infopage[i++] = msg[j++]; } else { j++; switch (msg[j]) { case 'v': j++; p = virname; while (i < sizeof(infopage) - 1 && *p) { infopage[i++] = *p++; } break; case 'i': j++; p = "mod_clamav "; while (i < sizeof(infopage) - 1 && *p) { infopage[i++] = *p++; } p = mod_clamav_version; while (i < sizeof(infopage) - 1 && *p) { infopage[i++] = *p++; } break; case 'u': j++; p = (NULL == f->r->uri) ? "(null)" : f->r->uri; while (i < sizeof(infopage) - 1 && *p) { infopage[i++] = *p++; } break; case '\0': break; default: infopage[i++] = msg[j++]; } } } infopage[i] = '\0'; } else { snprintf(infopage, sizeof(infopage), "\n" "\n" "Virus found\n" "\n" "\n" "

Virus found

\n" "

mod_clamav version %s found the virus

" "

%s

" "

while downloading %s." "The transfer has been aborted.

\n" "
© mod_clamav %s\n" "\n" "\n", mod_clamav_version, virname, (NULL == f->r->uri) ? "(null)" : f->r->uri, mod_clamav_version); } clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] sending %d bytes to client", (int)getpid(), strlen(infopage)); ap_fwrite(f->next, ctx->bb, infopage, strlen(infopage)); } /* this never happens, but we want the compiler to be happy */ return 0; } /* clamav_virus_info */ /* perform a virus scan */ static int clamav_scanfile(clamav_config_rec *rec, ap_filter_t *f, const char **virname, apr_size_t *len) { int s, l, bytes; char *repl, *code, *v; clamav_ctx *ctx = (clamav_ctx *)f->ctx; FILE *fp; char scancmd[2048]; int badreply = (rec->acceptdaemonproblem) ? CL_CLEAN : CL_VIRUS; /* using this return code has the effect of accepting all files with which the daemon had a problem */ switch (rec->mode) { case MOD_CLAMAV_LOCAL: /* virus scan using local clamav library */ return cl_scanfile(ctx->filename, virname, (unsigned long *)len, rec->common->local->engine, CL_SCAN_ARCHIVE); break; case MOD_CLAMAV_DAEMON: /* set reasonable defaults for virname */ *virname = "daemon connection problem"; *len = 25; /* connect to daemon */ s = socket(rec->common->daemon->domain, SOCK_STREAM, 0); if (s < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot create socket: %s (%d)", (int)getpid(), strerror(errno), errno); return badreply; } clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] daemon socket created: %d", (int)getpid(), s); if (connect(s, rec->common->daemon->s, rec->common->daemon->len) < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot connect: %s (%d)", (int)getpid(), strerror(errno), errno); close(s); return badreply; } clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] connected to daemon", (int)getpid()); /* just before we do the scan, we change permissions */ fchmod(ctx->file, rec->perms); /* send scanning request */ clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] sending scan command for %s", (int)getpid(), ctx->filename); bytes = snprintf(scancmd, sizeof(scancmd), "SCAN %s\n", ctx->filename); if (bytes != write(s, scancmd, bytes)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] failed to write scan command: %s", (int)getpid(), strerror(errno)); } /* it seems to be easier to read the reply using stdio, so we fdopen the socket */ if (NULL == (fp = fdopen(s, "r"))) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot fdopen: %s (%d)", (int)getpid(), strerror(errno), errno); close(s); fchmod(ctx->file, S_IRUSR|S_IWUSR); return badreply; } fchmod(ctx->file, S_IRUSR|S_IWUSR); /* read until we meet end of file */ l = strlen(ctx->filename) + 1024; repl = (char *)apr_palloc(f->r->pool, l); memset(repl, 0, l); if (repl != fgets(repl, l, fp)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot read reply: %s (%d)", (int)getpid(), strerror(errno), errno); fclose(fp); return badreply; } clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] got scan reply: %s", (int)getpid(), (repl) ? repl : "(null)"); fclose(fp); /* elementary checks on the reply */ if (repl == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] NULL scan reply", (int)getpid()); return badreply; } /* check that the reply string is not empty. Since we'll be using a pointer to the last character later, we compute it now */ l = strlen(repl) - 1; if (l < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] short scan reply %d", (int)getpid(), l); return badreply; } /* as long as we are inside the string, and have \n and \r characters at the end, we keep removing them */ while ((l >= 0) && ((repl[l] == '\n') || (repl[l] == '\r'))) { repl[l] = '\0'; /* remove the last character */ l--; /* go to the last but one character */ } /* last word: FOUND or ERROR */ code = strrchr(repl, ' '); if (code == repl) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] reply has " "only one word: %s\n", (int)getpid(), repl); return badreply; } *code = '\0'; code++; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] reply code: %s", (int)getpid(), code); if (0 == strcmp(code, "OK")) return CL_CLEAN; if (0 == strcmp(code, "ERROR")) return CL_VIRUS; if (0 == strcmp(code, "FOUND")) { /* last but one word: virus name */ v = strrchr(repl, ' '); if (v == NULL) { *virname = "unknown virus"; } else { *virname = (char *)apr_pstrdup(f->r->pool, v + 1); } *len = strlen(*virname); return CL_VIRUS; } /* if nothing bad has happend, then the file must be clean */ return CL_CLEAN; break; } /* this should not happen, but if it does, accept the file */ return CL_CLEAN; } /* clamav_scan_file */ /* initialize the virus filter */ static int clamav_initialize(clamav_config_rec *rec, ap_filter_t *f) { clamav_ctx *ctx; int retries; #ifdef HAVE_MKSTEMP mode_t mask; #else char *tmpfilename; #endif /* HAVE_MKSTEMP */ /* get the module record */ if (NULL == rec) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] no dir config for clamav", (int)getpid()); return APR_ENODIR; } /* initialize the clamav filter context */ if (NULL == f->ctx) { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] ctx alloc required, rec = %x", (int)getpid(), (unsigned int)rec); /* increment the request counter */ clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] mutex at %x", (int)getpid(), (unsigned int)rec->mutex); if (rec->shm) { clamav_lock(rec, f->r); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] stats at %x", (int)getpid(), (unsigned int)rec->stats); rec->stats->requests++; clamav_unlock(rec, f->r); } /* allocate a new context structure */ f->ctx = apr_pcalloc(f->r->pool, sizeof(clamav_ctx)); ctx = (clamav_ctx *)f->ctx; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] ctx alloced", (int)getpid()); /* find out whether it is safe to bypass this request, if so, */ /* we don't need the rest of the structure and can safely skip */ /* its initialization */ if (0 != (ctx->bypass = clamav_safe_to_bypass(rec, f))) return APR_SUCCESS; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] not bypassed", (int)getpid()); /* create additional data structures */ ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] new output brigade created", (int)getpid()); ctx->module = rec; ctx->last_trickle = time(NULL); ctx->trickle_offset = 0; ctx->bytes = 0; /* retry up to five times to create a temporary file */ for (retries = 0; retries < 5; retries++) { #ifdef HAVE_MKSTEMP /* allocate a temporary filename template */ ctx->filename = apr_pstrcat(f->r->pool, (rec->tmpdir) ? rec->tmpdir : "/tmp", "/clamXXXXXX", NULL); /* create the file and assert access restrictions */ mask = umask(077); ctx->file = mkstemp(ctx->filename); umask(mask); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] temporary filename is %s", (int)getpid(), ctx->filename); #else /* HAVE_MKSTEMP */ /* allocate a temporary filename */ tmpfilename = tempnam((rec->tmpdir) ? rec->tmpdir : "/tmp", "clam"); ctx->filename = apr_pstrdup(f->r->pool, tmpfilename); free(tmpfilename); ctx->file = open(ctx->filename, O_CREAT|O_TRUNC|O_RDWR, 0600); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] temporary filename is %s", (int)getpid(), ctx->filename); #endif /* HAVE_MKSTEMP */ if (ctx->file >= 0) { /* during the download, we use strict permissions, */ /* which we will open up just for the virus scan. */ fchmod(ctx->file, S_IRUSR|S_IWUSR); break; /* stop retrying temporary file creation */ } } /* we open the file read/write, so we can read from the file */ /* without a need to reopen it */ if (ctx->file < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot open temporary file: %s", (int)getpid(), strerror(errno)); return APR_EGENERAL; } else { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] temporary filename is %s", (int)getpid(), ctx->filename); } clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] method %s request for %s", (int)getpid(), (NULL == f->r->method) ? "(null)" : f->r->method, (NULL == f->r->uri) ? "(null)" : f->r->uri); } else { #if 0 clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] context already initialized at %x", (int)getpid(), (unsigned int)f->ctx); #endif } /* check for an initialized clamav scan engine, if it is */ /* initialized, we can skip its initialization */ if (clamav_engine_ready(rec, f)) return APR_SUCCESS; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] clamav engine initialization required", (int)getpid()); /* initialize Clamav engine */ if (clamav_engine_init(rec, f)) { return APR_EGENERAL; } /* if we get to this point, we can be sure that the clamav engine */ /* has been configured correctly */ clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] initialization of clamav engine complete", (int)getpid()); return APR_SUCCESS; } /* clamav_initialize */ /* The content handler */ static void clamav_display_virus(clamav_config_rec *rec, request_rec *r, clamav_virusinfo *v) { char when[32]; apr_size_t ret; apr_time_exp_t t; /* if pid is zero, the record is not initialized */ if (v->pid == 0) return; ap_rputs(" \n", r); ap_rprintf(r, " %d\n", (int)(v->pid)); apr_explode_localtime(&t, v->when); apr_strftime(when, &ret, sizeof(when), "%d %b %Y %H:%M:%S", &t); ap_rprintf(r, " %s\n", when); ap_rprintf(r, " %s\n", v->req); ap_rprintf(r, " %s\n", v->virus); ap_rprintf(r, " %s\n", v->uri); ap_rprintf(r, " %.0f\n", (double)(v->size)); ap_rputs(" \n", r); } /* clamav_display_virus */ /* * clamav_handler * * the clamav statistics content handler */ static int clamav_handler(request_rec *r) { clamav_config_rec *rec; int i, from; /* get the module record */ rec = clamav_get_module_config(r); /* make sure the shared memory is initialized */ clamav_shm(rec, r); /* initialize the clamav filter context */ if (strcmp(r->handler, "clamav")) { return DECLINED; } r->content_type = "text/html"; if (!r->header_only) { /* lock the shared memory */ clamav_lock(rec, r); ap_rputs("", r); ap_rputs("Clamav Module Statistics\n", r); ap_rputs("\n", r); ap_rputs("

Clamav Module Status

\n", r); ap_rputs("

Configuration

\n", r); ap_rputs("\n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", mod_clamav_version); ap_rputs(" \n", r); ap_rputs(" \n", r); if (rec->mode == MOD_CLAMAV_LOCAL) { ap_rputs(" \n", r); } else { if (rec->socket) ap_rprintf(r, " \n", rec->socket); else ap_rprintf(r, " " "\n", rec->port); } ap_rputs(" \n", r); ap_rputs("
Version:%s
Scanner:localDaemon Unix Socket:%sDaemon TCP Socket:127.0.0.1:%d
\n", r); ap_rputs("

Statistics

\n", r); if (rec->shm) { ap_rputs("\n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", rec->stats->requests); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", rec->stats->checked); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", (double)(rec->stats->totalsize)/1024.); ap_rputs(" \n", r); if (rec->stats->checked > 0) { ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", (double)rec->stats->totalsize/(double)rec->stats->checked); ap_rputs(" \n", r); } ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", (double)(rec->stats->maxsize)); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", (double)(rec->stats->maxvirussize)); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", rec->stats->viruses); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", rec->stats->aborted); ap_rputs(" \n", r); if (rec->mode == MOD_CLAMAV_LOCAL) { ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", rec->stats->cpu); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rprintf(r, " \n", rec->stats->reloads); ap_rputs(" \n", r); } ap_rputs("
Total requests:%ld
Checked for viruses:%ld
Total data checked:%.0fkB
Average size of checked requests:%.fbytes
Maximum request size:%.0fbytes
Maximum virus size:%.0fbytes
Viruses found:%ld
Aborted:%ld
Total CPU time:%.3fsec
Database reloads:%ld
\n", r); ap_rprintf(r, "

Last %d viruses found:

\n", VIRUS_LIST_LENGTH); ap_rputs("\n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); /* tail portion of the list */ from = rec->stats->last + 1; if (from < 1) from = 1; for (i = from; i < VIRUS_LIST_LENGTH; i++) clamav_display_virus(rec, r, &rec->stats->lastviruses[i]); /* front portion of the list */ for (i = 0; i <= rec->stats->last; i++) clamav_display_virus(rec, r, &rec->stats->lastviruses[i]); ap_rputs("
PIDWhenRequestorVirusRequest URISize
\n", r); } else { ap_rputs("

No statistics available

\n", r); } ap_rputs("
\n", r); ap_rputs("© 2003 Dr. Andreas Müller, " "Beratung und Entwicklung\n", r); ap_rputs("\n", r); ap_rputs("\n", r); clamav_unlock(rec, r); } return OK; } /* clamav_handler */ /* * clamav_cleanup * * remove temperary files etc */ static void clamav_cleanup(clamav_config_rec *rec, ap_filter_t *f) { clamav_ctx *ctx = (clamav_ctx *)f->ctx; /* when done, remove the temporary file */ if (NULL != ctx->filename) { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] unlinking %s", (int)getpid(), ctx->filename); close(ctx->file); unlink(ctx->filename); ctx->filename = NULL; } } /* clamav_cleanup */ /* * clamav_send_trickle * * send a byte every now and then, so that the client does not turn * impatient */ static apr_status_t clamav_sendtrickle(clamav_config_rec *rec, ap_filter_t *f) { time_t now; clamav_ctx *ctx; int file, bytes; char *str; apr_status_t rc; ctx = (clamav_ctx *)f->ctx; time(&now); if ((now - ctx->last_trickle) < rec->trickle_interval) return APR_SUCCESS; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] trickle time for request %s has arrived", (int)getpid(), (NULL == f->r->uri) ? "(null)" : f->r->uri); /* even if we fail to send the trickle, we just remember the */ /* failure */ ctx->last_trickle = now; /* allocate a buffer on the stack which is certailnly large enough */ str = (char *)alloca(rec->trickle_size); /* read a suitable number of bytes from the file */ if ((file = open(ctx->filename, O_RDONLY)) < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot open file %s: %s (%d)", (int)getpid(), ctx->filename, strerror(errno), errno); return APR_SUCCESS; } if (lseek(file, ctx->trickle_offset, SEEK_SET) < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot seek file %s: %s (%d)", (int)getpid(), ctx->filename, strerror(errno), errno); close(file); return APR_SUCCESS; } if (0 >= (bytes = read(file, str, rec->trickle_size))) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot read file %s: %s (%d)", (int)getpid(), ctx->filename, strerror(errno), errno); close(file); return APR_SUCCESS; } /* send it to the client as one bucket followed by a flush bucket */ rc = ap_fwrite(f->next, ctx->bb, str, bytes); ctx->trickle_offset += bytes; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] sending %d trickle bytes @ %d", (int)getpid(), bytes, ctx->trickle_offset); APR_BRIGADE_INSERT_TAIL(ctx->bb, apr_bucket_flush_create(f->c->bucket_alloc)); if (rc != APR_SUCCESS) { ap_pass_brigade(f->next, ctx->bb); } else { rc = ap_pass_brigade(f->next, ctx->bb); } if (f->r->connection->aborted) rc = APR_ECONNABORTED; if (rc != APR_SUCCESS) ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] sending trickle failed: %d", (int)getpid(), rc); /* close the file, file descriptor leak found by Jerome Auge */ close(file); return rc; } /* clamav_send_trickle */ /* * clamav_flush * * auxiliary function to close the conection to the client */ static apr_status_t clamav_flush(clamav_config_rec *rec, ap_filter_t *f) { clamav_ctx *ctx = (clamav_ctx *)f->ctx; /* send a flush bucket tot he client */ clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] sending eos bucket for request %s", (int)getpid(), (NULL == f->r->uri) ? "(null)" : f->r->uri); APR_BRIGADE_INSERT_TAIL(ctx->bb, apr_bucket_flush_create(f->c->bucket_alloc)); return ap_pass_brigade(f->next, ctx->bb); } /* clamav_flush */ /* * clamav_endfile * * auxiliary function to close the conection to the client */ static apr_status_t clamav_endfile(clamav_config_rec *rec, ap_filter_t *f) { clamav_ctx *ctx = (clamav_ctx *)f->ctx; apr_status_t rc; /* send a flush bucket tot he client */ clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] sending eos bucket for request %s", (int)getpid(), (NULL == f->r->uri) ? "(null)" : f->r->uri); APR_BRIGADE_INSERT_TAIL(ctx->bb, apr_bucket_eos_create(f->c->bucket_alloc)); rc = ap_pass_brigade(f->next, ctx->bb); /* after this we will certainly no longer need the temporary file */ clamav_cleanup(rec, f); return rc; } /* clamav_endfile */ /* * clamav_cpu * * auxiliary function to compute the cpu time consumed for virus scanning */ static double clamav_cpu(clamav_config_rec *rec, struct rusage *begin, struct rusage *end) { long sec, usec; double cpu; sec = end->ru_utime.tv_sec - begin->ru_utime.tv_sec; usec = end->ru_utime.tv_usec - begin->ru_utime.tv_usec; cpu = sec + (0.000001 * usec); sec = end->ru_stime.tv_sec - begin->ru_stime.tv_sec; usec = end->ru_stime.tv_usec - begin->ru_stime.tv_usec; return cpu + sec + (0.0000001 * usec); } /* clamav_cpu */ /* * clamav_record_aborted * * auxiliary function to handle an abortet connection */ static void clamav_record_aborted(clamav_config_rec *rec, ap_filter_t *f) { clamav_set_status_note(rec, f, MOD_CLAMAV_ABORTED_STATUS, "client has disconnected", NULL); ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, f->r, "[%d] client requesting %s has disconnected", (int)getpid(), (NULL == f->r->uri) ? "(null)" : f->r->uri); if (rec->shm) { clamav_lock(rec, f->r); rec->stats->aborted++; clamav_unlock(rec, f->r); } clamav_cleanup(rec, f); } /* clamav_record_aborted */ /* * clamav_check_file * * check the file we already have for viruses */ static apr_status_t clamav_check_file(clamav_config_rec *rec, ap_filter_t *f) { const char *virname; struct rusage begin, end; int ret; unsigned int len; clamav_ctx *ctx = (clamav_ctx *)f->ctx; struct stat sb; off_t size = 0; /* perform virus scan */ clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] initiating virus check in file %s", (int)getpid(), ctx->filename); virname = NULL; getrusage(RUSAGE_SELF, &begin); ret = clamav_scanfile(rec, f, &virname, &len); getrusage(RUSAGE_SELF, &end); /* log the virus checking achievement */ if (stat(ctx->filename, &sb) < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot stat file %s: %s (%d)", (int)getpid(), ctx->filename, strerror(errno), errno); } else { size = sb.st_size; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] virus checked %s (%s), %.0f bytes, in %.0fms", (int)getpid(), (NULL == f->r->uri) ? "(null)" : f->r->uri, f->r->content_type, (double)size, 1000. * clamav_cpu(rec, &begin, &end)); } /* perform the statistics update */ if (rec->shm) { clamav_lock(rec, f->r); rec->stats->cpu += clamav_cpu(rec, &begin, &end); rec->stats->checked++; rec->stats->totalsize += size; if (rec->stats->maxsize < size) rec->stats->maxsize = size; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] statistics: %d+%d bytes, %d checked", (int)getpid(), (int)rec->stats->totalsize, (int)size, (int)rec->stats->checked); clamav_unlock(rec, f->r); } /* if we have found a virus, we also have to register it in the */ /* statistics */ if (CL_VIRUS == ret) { int is_ip; /* remember this virus */ if (rec->shm) { clamav_lock(rec, f->r); rec->stats->viruses++; if (rec->stats->maxvirussize < size) rec->stats->maxvirussize = size; clamav_virus_record(rec->stats, (NULL == f->r->uri) ? "(null)" : f->r->uri, ap_get_remote_host(f->r->connection, f->r->per_dir_config, REMOTE_NAME, &is_ip), virname, size); clamav_unlock(rec, f->r); } /* log the virus we have found */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] virus %s found in request %s", (int)getpid(), virname, (NULL == f->r->uri) ? "(null)" : f->r->uri); /* if we have not sent any data to the client yet, we can generate a HTML page informing about the virus */ clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] trickle bytes sent so far: %d", (int)getpid(), ctx->trickle_offset); if (ctx->trickle_offset == 0) { clamav_set_status_note(rec, f, MOD_CLAMAV_INFECTED_STATUS, "client notified", virname); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "sending virus info to client"); clamav_virus_info(rec, f, virname); } else { clamav_set_status_note(rec, f, MOD_CLAMAV_INFECTED_STATUS, apr_psprintf(f->r->pool, "client NOT notified, " "%d bytes sent so far", ctx->trickle_offset), virname); } } /* return the result of the virus scanner, so we know what has */ /* happend from the caller */ return ret; } /* clamav_check_file */ /* * clamav_send_file * * send all the data we have in the temporary file to the client, and * clean the temporary file up */ static apr_size_t clamav_send_file(clamav_config_rec *rec, ap_filter_t *f) { clamav_ctx *ctx; int bytes; apr_size_t len = 0; size_t totalsize = 0; apr_status_t rc = APR_SUCCESS; char buffer[2048]; struct stat sb; ctx = (clamav_ctx *)f->ctx; /* send the remaining data as bunch of buckets to the, this is done */ /* as follows */ clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] sending file contents to client", (int)getpid()); /* find out how many bytes there currently are in the file */ if (fstat(ctx->file, &sb) < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot stat file %s", (int)getpid(), ctx->filename); } else { totalsize = sb.st_size; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] expect %d bytes", (int)getpid(), totalsize); } /* seek to the current trickle position */ if (lseek(ctx->file, ctx->trickle_offset, SEEK_SET) < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot seek to %d: %s (%d)\n", (int)getpid(), ctx->trickle_offset, strerror(errno), errno); } /* while we can write to the client, and while there is data, read */ /* a buffer full of data an write it to the client */ while ((rc == APR_SUCCESS) && (bytes = read(ctx->file, buffer, sizeof(buffer))) > 0) { /* log if there is a difference between what we asked for and */ /* what we got */ if (bytes != sizeof(buffer)) clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] read %d bytes from %s", (int)getpid(), bytes, ctx->filename); len += bytes; rc = ap_fwrite(f->next, ctx->bb, buffer, bytes); } if (bytes < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] read from descriptor %d failed: %s (%d)", (int)getpid(), ctx->file, strerror(errno), errno); } clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] file %s written to client (%d bytes)", (int)getpid(), ctx->filename, len); /* send a flush down the filter chaing */ clamav_flush(rec, f); /* check whether we have seen all the file */ if (totalsize > 0) { if ((totalsize - ctx->trickle_offset) != len) { clamav_set_status_note(rec, f, MOD_CLAMAV_FAILED_STATUS, apr_psprintf(f->r->pool, "sent %d of %d bytes", len + ctx->trickle_offset, totalsize), NULL); ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, f->r, "[%d] not all the file sent to the client: %d != %d\n", (int)getpid(), totalsize - ctx->trickle_offset, len); } else { clamav_set_status_note(rec, f, MOD_CLAMAV_PASSED_STATUS, NULL, NULL); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] sent size matches totalsize %d", (int)getpid(), len); } } else { clamav_set_status_note(rec, f, MOD_CLAMAV_PASSED_STATUS, NULL, NULL); } /* set the trickle_offset to the current offset, as if we had sent */ /* all the stuff as trickles */ ctx->trickle_offset = totalsize; /* clean up the temporary file */ clamav_cleanup(rec, f); /* return the number of bytes sent to the client */ return len; } /* clamav_send_file */ /* * clamav_save_block * * utility function to write a block to the temporary file */ static int clamav_save_block(clamav_config_rec *rec, ap_filter_t *f, const char *str, int len) { clamav_ctx *ctx; int bytes; ctx = (clamav_ctx *)f->ctx; /* make sure to position the file at the end */ if (lseek(ctx->file, 0, SEEK_END) < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] cannot seek to eof", (int)getpid()); return 0; } /* now write the block to the file */ bytes = write(ctx->file, str, len); if (bytes != len) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "[%d] error during write to %s", (int)getpid(), ctx->filename); } ctx->bytes += bytes; clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] %d bytes written to tmp file %s", (int)getpid(), bytes, ctx->filename); return bytes; } /* clamav_save_block */ /* * clamav_filter * * the apache2 filter function for clamav based virus scanning */ static apr_status_t clamav_filter(ap_filter_t *f, apr_bucket_brigade *bb) { clamav_ctx *ctx; clamav_config_rec *rec; const char *str; int bytes; apr_size_t len = 81920; apr_bucket *e; apr_status_t rc = APR_SUCCESS; /* get the configu record */ rec = clamav_get_module_config(f->r); /* initialize (if necessary) the virus filter */ if (APR_SUCCESS != (rc = clamav_initialize(rec, f))) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "clamav initialization failed"); clamav_set_status_note(rec, f, MOD_CLAMAV_FAILED_STATUS, "could not initialize clamav", NULL); return rc; } ctx = (clamav_ctx *)f->ctx; /* if it is safe to bypass the virus checker, just pass the bucket */ /* brigade to the next filter */ if (ctx->bypass) { return ap_pass_brigade(f->next, bb); } /* every data bucket that comes in must be written to the file */ clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] scanning bucket brigade", (int)getpid()); APR_BRIGADE_FOREACH(e, bb) { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] bucket type: %s (uri %s)", (int)getpid(), e->type->name, (NULL == f->r->uri) ? "(null)" : f->r->uri); /* read the data in the bucket */ apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ); if (len > 0) { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] perform pattern checks", (int)getpid()); if (ctx->bytes == 0) { /* perform checking of the patterns */ clamav_safepattern *p = (clamav_safepattern *)(rec->safepatterns->elts); int total = rec->safepatterns->nelts; int i,j; for (i = 0; i < total; i++) { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] check pattern %s", (int)getpid(), p[i].tag); /* check for mode -1, i.e. ??? */ if (p[i].mode == -1) { /* the check only makes sense if we have enough data */ if(len >= sizeof(p->pattern)) { for (j = 0; j < sizeof(p->pattern); j++) { /* is this text? */ if(str[j] >= 0x20 && str[j] <= 0x7e); else if(str[j]=='\t'); else if(str[j]=='\r'); else if(str[j]=='\n'); else break; } /* if we have not left up to the end of the */ /* buffer, it must be text */ if (j == sizeof(p->pattern)) { ctx->bypass = 1; clamav_cleanup(rec, f); clamav_set_bypassed_note(rec, f, apr_psprintf( f->r->pool, "'%s' allows bypass", p[i].tag)); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] %s allows bypass for %s", (int)getpid(), p[i].tag, (f->r->uri) ? f->r->uri : "(null)"); return ap_pass_brigade(f->next, bb); } } } else { /* check for binary stuff, need at least as */ /* many bytes as the mode indicates */ if(len >= p[i].mode) { /* compare the data to the pattern */ for (j = 0; j < p[i].mode; j++) { if ((str[j] & p[i].mask[j]) != p[i].pattern[j]) break; } /* if we have not left prematurely, then we */ /* have a match */ if (j == p[i].mode) { ctx->bypass = 1; clamav_cleanup(rec, f); clamav_set_bypassed_note(rec, f, apr_psprintf( f->r->pool, "'%s' allows bypass", p[i].tag)); clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] %s allows bypass for %s", (int)getpid(), p[i].tag, (f->r->uri) ? f->r->uri : "(null)"); return ap_pass_brigade(f->next, bb); } } } } } /* there really is something to write */ if (len != (bytes = clamav_save_block(rec, f, str, len))) { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] %d bytes written to tmp file %s", (int)getpid(), bytes, ctx->filename); } } /* should give EOS and FLUSH buckets special treatment. But as */ /* noted below, Flush buckets don't really help, so we just drop*/ /* them, not without a debug log message though */ if ((!APR_BUCKET_IS_EOS(e)) && (!APR_BUCKET_IS_FLUSH(e))) { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] data bucket found", (int)getpid()); apr_bucket_delete(e); /* removes any non-EOS or non-FLUSH buckets */ } else { clamav_ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "[%d] bucket of type %s found", (int)getpid(), APR_BUCKET_IS_EOS(e) ? "End of Stream" : "Flush"); if (APR_BUCKET_IS_FLUSH(e)) { apr_bucket_delete(e); /* removes any FLUSH buckets */ } } /* at this point, all non-EOS buckets have been removed */ } /* now check whether we should send a trickle byte, but sending */ /* a trickle may fail caused by the client having aborted the */ /* the connection, so we return an error */ if (APR_SUCCESS != clamav_sendtrickle(rec, f)) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, "[%d] trickle detects connection abort", (int)getpid()); clamav_record_aborted(rec, f); return APR_ECONNABORTED; } /* if we have read up to the size limit already, then we check */ /* the piece we have and send it to the client, and switch to */ /* bypass mode */ if ((rec->sizelimit > 0) && (rec->sizelimit <= ctx->bytes)){ /* perform the check of the part we already have */ if (CL_VIRUS == clamav_check_file(rec, f)) { clamav_endfile(rec, f); return APR_SUCCESS; } /* send the file to the client */ clamav_send_file(rec, f); /* switch to bypass mode */ clamav_set_bypassed_note(rec, f, "size limit reached"); ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, "bypassing check of %s due to size limit %d", f->r->uri, rec->sizelimit); ctx->bypass = 1; /* but we should hand over the bucket brigade to the next */ /* as if we had been bypassing already at the beginning */ return ap_pass_brigade(f->next, bb); } /* at this point we only have EOS buckets left */ APR_BRIGADE_FOREACH(e, bb) { /* if we have met an EOS bucket, we should trigger the virus */ /* scanning */ if (APR_BUCKET_IS_EOS(e)) { /* check for viruses and update the statistics */ if (CL_VIRUS == clamav_check_file(rec, f)) { /* close the connection to the client */ clamav_endfile(rec, f); return APR_SUCCESS; } /* send the remaining data as bunch of buckets to the */ /* client */ clamav_send_file(rec, f); rc = clamav_endfile(rec, f); if (rc != APR_SUCCESS) clamav_record_aborted(rec, f); return rc; } else { /* for the record: we ignore FLUSH buckets, they don't help */ /* to detect that the connection was aborted */ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, "[%d] non-EOS bucket found when only EOS buckets should be " "left", (int)getpid()); } /* for the record: we ignore FLUSH buckets, they don't help to */ /* detect that the connection was aborted */ } clamav_set_status_note(rec, f, MOD_CLAMAV_PASSED_STATUS, "reached end of clamav_filter", NULL); apr_brigade_cleanup(bb); return APR_SUCCESS; } /* clamav_filter */ static void clamav_register_hooks(apr_pool_t *p) { ap_hook_handler(clamav_handler, NULL, NULL, APR_HOOK_MIDDLE); ap_register_output_filter("CLAMAV", clamav_filter, NULL, AP_FTYPE_CONTENT_SET); } /* clamav_register_hooks */ /* configuration methods */ static void *clamav_create_dir_config(apr_pool_t *p, char *d) { clamav_config_rec *cfg; cfg = (clamav_config_rec *)apr_pcalloc(p, sizeof(clamav_config_rec)); cfg->mode = MOD_CLAMAV_LOCAL; cfg->dbdir = NULL; cfg->tmpdir = NULL; cfg->port = 0; cfg->common = apr_pcalloc(p, sizeof(clamav_common)); cfg->common->local = NULL; cfg->trickle_interval = 60; cfg->trickle_size = 1; cfg->reload_interval = 0; cfg->acceptdaemonproblem = 0; cfg->extendedlogging = 0; cfg->perms = 0640; cfg->sizelimit = 0; cfg->maxfiles = 100; cl_engine_set_num(cfg->common->local->engine, CL_ENGINE_MAX_FILES, &cfg->maxfiles); cfg->maxfilesize = 10 * 1048576; cl_engine_set_num(cfg->common->local->engine, CL_ENGINE_MAX_FILESIZE, &cfg->maxfilesize); cfg->maxreclevel = 8; cl_engine_set_num(cfg->common->local->engine, CL_ENGINE_MAX_RECURSION, &cfg->maxreclevel); cfg->safetypes = apr_table_make(p, 0); cfg->safepatterns = apr_array_make(p, 64, sizeof(clamav_safepattern)); cfg->safeuris = apr_array_make(p, 64, sizeof(clamav_safeuri)); cfg->message = NULL; cfg->mutex = NULL; cfg->mutexname = NULL; cfg->shm = NULL; cfg->shmname = NULL; cfg->pool = p; return cfg; } /* clamav_create_dir_config */ static void *clamav_merge_dir_config(apr_pool_t *p, void *pp, void *cp) { clamav_config_rec *parent = (clamav_config_rec *)pp; clamav_config_rec *child = (clamav_config_rec *)cp; /* allways share the common part */ /* XXX what is the correct way to free memory that was apr_palloced? if (child->common != parent->common) { apr_free(child->common); } */ child->common = parent->common; child->mode = parent->mode; child->extendedlogging = parent->extendedlogging; child->perms = parent->perms; if (parent->dbdir) child->dbdir = (char *)apr_pstrdup(p, parent->dbdir); if (parent->socket) child->socket = (char *)apr_pstrdup(p, parent->socket); if (parent->reload_interval > 0) child->reload_interval = parent->reload_interval; child->trickle_size = parent->trickle_size; child->trickle_interval = parent->trickle_interval; child->sizelimit = parent->sizelimit; /* merge rest (except shared memory and mutex, which are not */ /* shared) */ if (child->port != parent->port) child->port = parent->port; if (child->shmname == NULL) child->shmname = parent->shmname; if (child->mutexname == NULL) child->mutexname = parent->mutexname; if (child->maxfiles == 0) child->maxfiles = parent->maxfiles; if (child->maxfilesize == 0) child->maxfilesize = parent->maxfilesize; if (child->maxreclevel == 0) child->maxreclevel = parent->maxreclevel; if ((!child->tmpdir) && (parent->tmpdir)) child->tmpdir = (char *)apr_pstrdup(p, parent->tmpdir); if (apr_is_empty_table(child->safetypes)) apr_table_overlay(p, parent->safetypes, child->safetypes); if (apr_is_empty_array(child->safepatterns)) child->safepatterns = apr_array_copy_hdr(p, parent->safepatterns); if ((!child->message) && (parent->message)) child->message = (char *)apr_pstrdup(p, parent->message); /* note: acceptdaemonproblem is not merged */ /* return child configuration */ return child; } /* clamav_merge_dir_config */ /* add safe types to the list of safe types */ static const char *clamav_add_safetype(cmd_parms *parms, void *mconfig, const char *arg) { char *argi; int i, l; /* get the module configuration record */ clamav_config_rec *rec = (clamav_config_rec *)mconfig; /* convert the type to lower case */ argi = apr_pstrdup(parms->server->process->pconf, arg); l = strlen(argi); for (i = 0; i < l; i++) argi[i] = tolower(argi[i]); apr_table_set(rec->safetypes, argi, "safe"); return NULL; } /* clamav_add_safe_type */ /* mode configuration parsing function */ static const char *clamav_set_mode_cmd(cmd_parms *parms, void *mconfig, const char *arg) { clamav_config_rec *cfg = (clamav_config_rec *)mconfig; if (0 == strcasecmp("daemon", arg)) { cfg->mode = MOD_CLAMAV_DAEMON; return NULL; } if (0 == strcasecmp("local", arg)) { cfg->mode = MOD_CLAMAV_LOCAL; return NULL; } return "unknown Clamav Mode value"; } /* clamav_set_mode_cmd */ /* permissions configuration parsing function */ static const char *clamav_set_perms_cmd(cmd_parms *parms, void *mconfig, const char *arg) { clamav_config_rec *cfg = (clamav_config_rec *)mconfig; if (1 != sscanf(arg, "%o", &cfg->perms)) { return "unparsable Clamav temporary file permissions value"; } if (cfg->perms > 0777) { return "Clamav temporary file permissions value out of range"; } return NULL; } /* clamav_set_perms_cmd */ /* add a pattern for which it is safe to bypass the virus scanner */ static const char *clamav_add_safepattern(cmd_parms *parms, void *mconfig, const char *arg1, const char *arg2, const char *arg3) { clamav_safepattern *p; int mode; int i; unsigned char pattern[16]; unsigned char mask[16]; /* get the module configuration record */ clamav_config_rec *rec = (clamav_config_rec *)mconfig; if (strlen(arg1) >= sizeof(p->tag)) return "Clamav Safepattern tag is too long"; if (!arg2) { /* recognize the text tag */ mode = -1; } else { for (mode = 0; mode < sizeof(pattern); mode++) { if (!*arg2) { break; } else if (*arg2 == '\\') { if (!arg2[1]) break; arg2++; if (*arg2 == 'x' || *arg2 == 'X') { if (!arg2[1] || !arg2[2]) break; arg2++; if (*arg2 >= '0' && *arg2 <= '9') pattern[mode] = *arg2 - '0'; else if (*arg2 >= 'A' && *arg2 <= 'F') pattern[mode] = *arg2 - 'A' + 10; else if (*arg2 >= 'a' && *arg2 <= 'f') pattern[mode] = *arg2 - 'a' + 10; else break; pattern[mode] <<= 4; arg2++; if (*arg2 >= '0' && *arg2 <= '9') pattern[mode] |= *arg2 - '0'; else if (*arg2 >= 'A' && *arg2 <= 'F') pattern[mode] |= *arg2 - 'A' + 10; else if (*arg2 >= 'a' && *arg2 <= 'f') pattern[mode] |= *arg2 - 'a' + 10; else break; arg2++; } else { pattern[mode] = *arg2++; } } else { pattern[mode] = *arg2++; } mask[mode] = 0xff; } if (*arg2) return "Clamav Safepattern pattern is invalid or too long"; if (arg3) { for (i = 0; i < mode; i++) { if (!*arg3) { break; } else if (*arg3 == '\\') { if (!arg3[1]) break; arg3++; if (*arg3 == 'x' || *arg3 == 'X') { if (!arg3[1] || !arg3[2]) break; arg3++; if (*arg3 >= '0' && *arg3 <= '9') mask[i] = *arg3 - '0'; else if (*arg3 >= 'A' && *arg3 <= 'F') mask[i] = *arg3 - 'A' + 10; else if (*arg3 >= 'a' && *arg3 <= 'f') mask[i] = *arg3 - 'a' + 10; else break; mask[i] <<= 4; arg3++; if (*arg3 >= '0' && *arg3 <= '9') mask[i] |= *arg3 - '0'; else if (*arg3 >= 'A' && *arg3 <= 'F') mask[i] |= *arg3 - 'A' + 10; else if (*arg3 >= 'a' && *arg3 <= 'f') mask[i] |= *arg3 - 'a' + 10; else break; arg3++; } else { mask[i] = *arg3++; } } else { mask[i] = *arg3++; } } if (*arg3) return "Clamav Safepattern mask is invalid or too long"; } } if (mode) { p = apr_array_push(rec->safepatterns); p->mode = (char)(mode); strcpy(p->tag, arg1); memcpy(p->pattern, pattern, sizeof(pattern)); memcpy(p->mask, mask, sizeof(mask)); } else { return "Clamav Safepattern pattern is empty"; } return NULL; } /* clamav_add_safe_pattern */ /* add a uri/host pattern for which it is safe to bypass the virus scanner */ static const char *clamav_add_safeuri(cmd_parms *parms, void *mconfig, const char *type, const char *safeuri) { clamav_safeuri *p; regex_t *preg; /* get the module configuration record */ clamav_config_rec *rec = (clamav_config_rec *)mconfig; if (strlen(safeuri) >= sizeof(p->pattern)) return "Clamav Safeuri pattern is too long"; p = apr_array_push(rec->safeuris); if (strcasecmp(type, "host") == 0) { p->matchtype = MATCH_SAFE_HOST; } else if (strcasecmp(type, "uri") == 0) { p->matchtype = MATCH_SAFE_URI; } else { return "Clamav unrecognized match type"; } preg = ap_pregcomp(rec->pool, safeuri, REG_EXTENDED); if (preg == NULL) { return "Clamav error compiling Safeuri regex pattern"; } strcpy(p->pattern, safeuri); p->regex = preg; return NULL; } /* clamav_add_safeuri */ /* configuration directives */ static const command_rec clamav_cmds[] = { AP_INIT_TAKE1( "ClamavMode", clamav_set_mode_cmd, (void *)APR_OFFSETOF(clamav_config_rec, mode), RSRC_CONF, "virus scanning mode: local or daemon" ), AP_INIT_TAKE1( "ClamavSocket", ap_set_string_slot, (void *)APR_OFFSETOF(clamav_config_rec, socket), RSRC_CONF, "Socket file name if clamd uses Unix domain socket" ), AP_INIT_TAKE1( "ClamavDbdir", ap_set_string_slot, (void *)APR_OFFSETOF(clamav_config_rec, dbdir), RSRC_CONF, "Directory containing clamav databases" ), AP_INIT_TAKE1( "ClamavTmpdir", ap_set_string_slot, (void *)APR_OFFSETOF(clamav_config_rec, tmpdir), RSRC_CONF, "Directory for temporary files during virus checking" ), AP_INIT_TAKE1( "ClamavReloadInterval", ap_set_int_slot, (void *)APR_OFFSETOF(clamav_config_rec, reload_interval), RSRC_CONF, "Database reload interval" ), AP_INIT_TAKE1( "ClamavTrickleInterval", ap_set_int_slot, (void *)APR_OFFSETOF(clamav_config_rec, trickle_interval), RSRC_CONF, "Trickle interval" ), AP_INIT_TAKE1( "ClamavTrickleSize", ap_set_int_slot, (void *)APR_OFFSETOF(clamav_config_rec, trickle_size), RSRC_CONF, "Trickle size" ), AP_INIT_TAKE1( "ClamavMaxfiles", ap_set_int_slot, (void *)APR_OFFSETOF(clamav_config_rec, maxfiles), RSRC_CONF, "Maximum number of files in an archive" ), AP_INIT_TAKE1( "ClamavMaxfilesize", ap_set_int_slot, (void *)APR_OFFSETOF(clamav_config_rec, maxfilesize), RSRC_CONF, "Maximum archive size" ), AP_INIT_TAKE1( "ClamavRecursion", ap_set_int_slot, (void *)APR_OFFSETOF(clamav_config_rec, maxreclevel), RSRC_CONF, "Maximum recursion depth when analyzing archives" ), AP_INIT_TAKE1( "ClamavPort", ap_set_int_slot, (void *)APR_OFFSETOF(clamav_config_rec, port), RSRC_CONF, "Port number where clamd can be reached" ), AP_INIT_TAKE1( "ClamavShm", ap_set_string_slot, (void *)APR_OFFSETOF(clamav_config_rec, shmname), RSRC_CONF, "shared memory file name" ), AP_INIT_TAKE1( "ClamavMutex", ap_set_string_slot, (void *)APR_OFFSETOF(clamav_config_rec, mutexname), RSRC_CONF, "mutex lock file name" ), AP_INIT_ITERATE( "ClamavSafetypes", clamav_add_safetype, (void *)APR_OFFSETOF(clamav_config_rec, safetypes), RSRC_CONF, "a list of MIME types for which it is safe to bypass the virus checker" ), AP_INIT_ITERATE( "ClamavSizelimit", ap_set_int_slot, (void *)APR_OFFSETOF(clamav_config_rec, sizelimit), RSRC_CONF, "files larger than the sizelimit will only be checked up to that point" ), AP_INIT_FLAG( "ClamavAcceptDaemonproblem", ap_set_flag_slot, (void *)APR_OFFSETOF(clamav_config_rec, acceptdaemonproblem), RSRC_CONF, "wether or not to live with a misbehaving daemon" ), AP_INIT_FLAG( "ClamavExtendedLogging", ap_set_flag_slot, (void *)APR_OFFSETOF(clamav_config_rec, extendedlogging), RSRC_CONF, "wether to use extended logging (yes|no)" ), AP_INIT_TAKE1( "ClamavPermissions", clamav_set_perms_cmd, (void *)APR_OFFSETOF(clamav_config_rec, perms), RSRC_CONF, "Access permissions for temporary files (octal permission value)" ), AP_INIT_TAKE1( "ClamavMessage", ap_set_string_slot, (void *)APR_OFFSETOF(clamav_config_rec, message), RSRC_CONF, "Custom virus notification message" ), AP_INIT_TAKE123( "ClamavSafepattern", clamav_add_safepattern, (void *)APR_OFFSETOF(clamav_config_rec, safepatterns), RSRC_CONF, "A file pattern for which it is safe to bypass the virus checker" ), AP_INIT_TAKE2( "ClamavSafeURI", clamav_add_safeuri, (void *)APR_OFFSETOF(clamav_config_rec, safeuris), RSRC_CONF, "A match type (`host' or `uri') and a pattern for which it is safe to bypass the virus checker" ), { NULL } }; /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA clamav_module = { STANDARD20_MODULE_STUFF, clamav_create_dir_config, /* create per-dir config structures */ clamav_merge_dir_config, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ clamav_cmds, /* table of config file commands */ clamav_register_hooks /* register hooks */ };