Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clamd/server-th.c
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ void sighandler_th(int sig)
logg(LOGG_DEBUG_NV, "Failed to write to syncpipe\n");
}

static int need_db_reload(void)
int need_db_reload(void)
{
if (!dbstat.entries) {
logg(LOGG_INFO, "No stats for Database check - forcing reload\n");
Expand Down
2 changes: 2 additions & 0 deletions clamd/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include "thrmgr.h"
#include "session.h"

int need_db_reload(void);

struct thrarg {
int sid;
struct cl_scan_options *options;
Expand Down
26 changes: 26 additions & 0 deletions clamd/session.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ static struct {
{CMD1, sizeof(CMD1) - 1, COMMAND_SCAN, 1, 1, 0},
{CMD3, sizeof(CMD3) - 1, COMMAND_SHUTDOWN, 0, 1, 0},
{CMD4, sizeof(CMD4) - 1, COMMAND_RELOAD, 0, 1, 0},
{CMD25, sizeof(CMD25) - 1, COMMAND_SELFCHECK, 0, 1, 0},
{CMD5, sizeof(CMD5) - 1, COMMAND_PING, 0, 1, 0},
{CMD6, sizeof(CMD6) - 1, COMMAND_CONTSCAN, 1, 1, 0},
/* must be before VERSION, because they share common prefix! */
Expand Down Expand Up @@ -570,6 +571,31 @@ int execute_or_dispatch_command(client_conn_t *conn, enum commands cmd, const ch
conn_reply_single(conn, NULL, "COMMAND UNAVAILABLE");
}
return 1;
case COMMAND_SELFCHECK:
if (optget(conn->opts, "EnableSelfCheckCommand")->enabled) {
int reload_flag;
int db_reload_needed;
pthread_mutex_lock(&reload_mutex);
reload_flag = reload;
pthread_mutex_unlock(&reload_mutex);
if (reload_flag) {
mdprintf(desc, "RELOADING%c", term);
return 1;
}
db_reload_needed = need_db_reload();
if (db_reload_needed) {
pthread_mutex_lock(&reload_mutex);
reload = 1;
pthread_mutex_unlock(&reload_mutex);
mdprintf(desc, "RELOADING%c", term);
return 1;
}
mdprintf(desc, "DBUPTODATE%c", term);
return 1;
} else {
conn_reply_single(conn, NULL, "COMMAND UNAVAILABLE");
return 1;
}
case COMMAND_PING:
if (conn->group)
mdprintf(desc, "%u: PONG%c", conn->id, term);
Expand Down
3 changes: 3 additions & 0 deletions clamd/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
#define CMD23 "GET / HTTP/2"
#define CMD24 ""

#define CMD25 "SELFCHECK"

// libclamav
#include "clamav.h"

Expand All @@ -62,6 +64,7 @@ enum commands {
COMMAND_UNKNOWN = 0,
COMMAND_SHUTDOWN = 1,
COMMAND_RELOAD,
COMMAND_SELFCHECK,
COMMAND_END,
COMMAND_SCAN,
COMMAND_PING,
Expand Down
2 changes: 2 additions & 0 deletions common/optparser.c
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ const struct clam_option __clam_options[] = {

{"EnableReloadCommand", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD, "Enables the RELOAD command for clamd", "no"},

{"EnableSelfCheckCommand", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD, "Enables the SELFCHECK command for clamd", "no"},

{"EnableVersionCommand", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD, "Enables the VERSION command for clamd", "yes"},

{"EnableStatsCommand", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD, "Enables the STATS command for clamd", "yes"},
Expand Down
7 changes: 7 additions & 0 deletions docs/man/clamd.conf.5.in
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ When disabled, clamd responds to this command with COMMAND UNAVAILABLE.
.br
Default: yes
.TP
\EnableSelfCheckCommand BOOL\fR
Enables the SELFCHECK command. Setting this to no prevents a client to reload the database.
.br
When disabled, clamd responds to this command with COMMAND UNAVAILABLE. When enabled and reload is required clamd responds RELOADING. If reload not needed - DBUPTODATE.
.br
Default: yes
.TP
\fBEnableVersionCommand BOOL\fR
Enables the VERSION command. Setting this to no prevents a client from querying version information.
.br
Expand Down
5 changes: 5 additions & 0 deletions etc/clamd.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ Example
# Default: yes
#EnableReloadCommand no
#
# Enable the SELFCHECK command
# Setting this to no prevents a client to reload the database.
# Default: yes
#EnableSelfCheckCommand no
#
# Enable the STATS command
# Setting this to no prevents a client from querying statistics.
# Default: yes
Expand Down
61 changes: 60 additions & 1 deletion unit_tests/check_clamd.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ static void commands_teardown(void)

#define VERSION_REPLY "ClamAV " REPO_VERSION "" VERSION_SUFFIX

#define VCMDS_REPLY VERSION_REPLY "| COMMANDS: SCAN QUIT RELOAD PING CONTSCAN VERSIONCOMMANDS VERSION END SHUTDOWN MULTISCAN FILDES STATS IDSESSION INSTREAM DETSTATSCLEAR DETSTATS ALLMATCHSCAN"
#define VCMDS_REPLY VERSION_REPLY "| COMMANDS: SCAN QUIT RELOAD SELFCHECK PING CONTSCAN VERSIONCOMMANDS VERSION END SHUTDOWN MULTISCAN FILDES STATS IDSESSION INSTREAM DETSTATSCLEAR DETSTATS ALLMATCHSCAN"

enum idsession_support {
IDS_OK, /* accepted */
Expand Down Expand Up @@ -376,6 +376,64 @@ START_TEST(test_stats)
}
END_TEST

/* Robust SELFCHECK: tolerate RELOADING for a short while, then require DBUPTODATE */
#define SELFCHECK_EXPECT "DBUPTODATE"
#define SELFCHECK_RELOADING "RELOADING"
START_TEST(test_selfcheck)
{
char *recvdata = NULL;
size_t len;
int rc;
int attempts = 0;
const int max_attempts = 60; /* timeout ~3m with check each 3s */
const int sleep_ms = 3000;

conn_setup();

do {
const char *cmd = "nSELFCHECK\n";
len = strlen(cmd);
rc = send(sockd, cmd, len, 0);
ck_assert_msg((size_t)rc == len, "Unable to send(): %s\n", strerror(errno));

recvdata = (char *)recvfull(sockd, &len);
ck_assert_msg(recvdata != NULL, "recvfull() returned NULL");

/* Trim trailing newlines */
while (len > 0 && (recvdata[len - 1] == '\n' || recvdata[len - 1] == '\r')) {
recvdata[--len] = '\0';
}

if (strcmp(recvdata, SELFCHECK_EXPECT) == 0) {
/* success */
free(recvdata);
conn_teardown();
return;
}

if (strcmp(recvdata, SELFCHECK_RELOADING) != 0) {
ck_abort_msg("Wrong reply for SELFCHECK: '%s' (expected DBUPTODATE or RELOADING)", recvdata);
}

/* still reloading, wait then retry */
free(recvdata);
recvdata = NULL;

#if defined(_WIN32)
Sleep(sleep_ms);
#else
struct timespec ts = { .tv_sec = sleep_ms / 1000,
.tv_nsec = (sleep_ms % 1000) * 1000000L };
nanosleep(&ts, NULL);
#endif
} while (++attempts < max_attempts);

ck_abort_msg("SELFCHECK did not reach DBUPTODATE within timeout");

conn_teardown();
}
END_TEST

static size_t prepare_instream(char *buf, size_t off, size_t buflen)
{
STATBUF stbuf;
Expand Down Expand Up @@ -869,6 +927,7 @@ static Suite *test_clamd_suite(void)
#endif

tcase_add_test(tc_commands, test_stats);
tcase_add_test(tc_commands, test_selfcheck);
tcase_add_test(tc_commands, test_instream);
tcase_add_test(tc_commands, test_idsession);

Expand Down
5 changes: 5 additions & 0 deletions win32/conf_examples/clamd.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ TCPAddr localhost
# Default: yes
#EnableReloadCommand no
#
# Enable the SELFCHECK command
# Setting this to no prevents a client to running selfcheck on clamd.
# Default: yes
#EnableSelfCheckCommand no
#
# Enable the STATS command
# Setting this to no prevents a client from querying statistics.
# Default: yes
Expand Down
Loading