/*
** 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, " Version: | %s | \n", mod_clamav_version);
ap_rputs("
\n", r);
ap_rputs(" \n", r);
if (rec->mode == MOD_CLAMAV_LOCAL) {
ap_rputs(" Scanner: | local | \n", r);
} else {
if (rec->socket)
ap_rprintf(r, " Daemon Unix Socket: | %s | \n",
rec->socket);
else
ap_rprintf(r, " Daemon TCP Socket: | "
"127.0.0.1:%d | \n", rec->port);
}
ap_rputs("
\n", r);
ap_rputs("
\n", r);
ap_rputs("Statistics
\n", r);
if (rec->shm) {
ap_rputs("\n", r);
ap_rputs(" \n", r);
ap_rputs(" Total requests: | \n", r);
ap_rprintf(r, " %ld | \n",
rec->stats->requests);
ap_rputs("
\n", r);
ap_rputs(" \n", r);
ap_rputs(" Checked for viruses: | \n", r);
ap_rprintf(r, " %ld | \n",
rec->stats->checked);
ap_rputs("
\n", r);
ap_rputs(" \n", r);
ap_rputs(" Total data checked: | \n", r);
ap_rprintf(r, " %.0f | kB | \n",
(double)(rec->stats->totalsize)/1024.);
ap_rputs("
\n", r);
if (rec->stats->checked > 0) {
ap_rputs(" \n", r);
ap_rputs(" Average size of checked requests: | \n", r);
ap_rprintf(r, " %.f | bytes | \n",
(double)rec->stats->totalsize/(double)rec->stats->checked);
ap_rputs("
\n", r);
}
ap_rputs(" \n", r);
ap_rputs(" Maximum request size: | \n", r);
ap_rprintf(r, " %.0f | bytes | \n",
(double)(rec->stats->maxsize));
ap_rputs("
\n", r);
ap_rputs(" \n", r);
ap_rputs(" Maximum virus size: | \n", r);
ap_rprintf(r, " %.0f | bytes | \n",
(double)(rec->stats->maxvirussize));
ap_rputs("
\n", r);
ap_rputs(" \n", r);
ap_rputs(" Viruses found: | \n", r);
ap_rprintf(r, " %ld | \n",
rec->stats->viruses);
ap_rputs("
\n", r);
ap_rputs(" \n", r);
ap_rputs(" Aborted: | \n", r);
ap_rprintf(r, " %ld | \n",
rec->stats->aborted);
ap_rputs("
\n", r);
if (rec->mode == MOD_CLAMAV_LOCAL) {
ap_rputs(" \n", r);
ap_rputs(" Total CPU time: | \n", r);
ap_rprintf(r, " %.3f | sec | \n",
rec->stats->cpu);
ap_rputs("
\n", r);
ap_rputs(" \n", r);
ap_rputs(" Database reloads: | \n", r);
ap_rprintf(r, " %ld | \n",
rec->stats->reloads);
ap_rputs("
\n", r);
}
ap_rputs("
\n", r);
ap_rprintf(r, "Last %d viruses found:
\n",
VIRUS_LIST_LENGTH);
ap_rputs("\n", r);
ap_rputs(" \n", r);
ap_rputs(" PID | \n", r);
ap_rputs(" When | \n", r);
ap_rputs(" Requestor | \n", r);
ap_rputs(" Virus | \n", r);
ap_rputs(" Request URI | \n", r);
ap_rputs(" Size | \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("
\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 */
};