mirror of
https://github.com/Cisco-Talos/clamav.git
synced 2025-10-19 10:23:17 +00:00
clamd: Add options to toggle SHUTDOWN, RELOAD, STATS and VERSION (#1502)
The `clamd` protocol lacks authentication or authorization controls needed to limit access to more administrative commands. Depending on your use case, disabling some commands like `SHUTDOWN` may improve the security of the scanning daemon. This commit adds options to enable/disable the `SHUTDOWN`, `RELOAD`, `STATS` and `VERSION` commands in `clamd.conf`. When a client sends one of the following commands but it is disabled, `clamd` will respond with "COMMAND UNAVAILABLE". The new `clamd.conf` options are: - `EnableShutdownCommand`: Enable the `SHUTDOWN` command. Setting this to no prevents a client to stop `clamd` via the protocol. Default: yes - `EnableReloadCommand` Enable the `RELOAD` command. Setting this to no prevents a client to reload the database. This disables Freshclam's `NotifyClamd` option. `clamd` monitors for database directory changes, so this should Default: yes - `EnableStatsCommand` Enable the `STATS` command. Setting this to no prevents a client from querying statistics. This disables the `clamdtop` program. Default: yes - `EnableVersionCommand` Enable the `VERSION` command. Setting this to no prevents a client from querying version information. This disables the `clamdtop` program and will cause `clamdscan` to display a warning when using the `--version` option. Default: yes Resolves: https://github.com/Cisco-Talos/clamav/issues/922 Resolves: https://github.com/Cisco-Talos/clamav/issues/1169 Related: https://github.com/Cisco-Talos/clamav/pull/347
This commit is contained in:
parent
e86919789f
commit
a3be0d2d45
8 changed files with 169 additions and 16 deletions
|
@ -551,17 +551,24 @@ int execute_or_dispatch_command(client_conn_t *conn, enum commands cmd, const ch
|
|||
|
||||
switch (cmd) {
|
||||
case COMMAND_SHUTDOWN:
|
||||
pthread_mutex_lock(&exit_mutex);
|
||||
progexit = 1;
|
||||
pthread_mutex_unlock(&exit_mutex);
|
||||
if (optget(conn->opts, "EnableShutdownCommand")->enabled) {
|
||||
pthread_mutex_lock(&exit_mutex);
|
||||
progexit = 1;
|
||||
pthread_mutex_unlock(&exit_mutex);
|
||||
} else {
|
||||
conn_reply_single(conn, NULL, "COMMAND UNAVAILABLE");
|
||||
}
|
||||
return 1;
|
||||
case COMMAND_RELOAD:
|
||||
pthread_mutex_lock(&reload_mutex);
|
||||
reload = 1;
|
||||
pthread_mutex_unlock(&reload_mutex);
|
||||
mdprintf(desc, "RELOADING%c", term);
|
||||
/* we set reload flag, and we'll reload before closing the
|
||||
* connection */
|
||||
if (optget(conn->opts, "EnableReloadCommand")->enabled) {
|
||||
pthread_mutex_lock(&reload_mutex);
|
||||
reload = 1;
|
||||
pthread_mutex_unlock(&reload_mutex);
|
||||
mdprintf(desc, "RELOADING%c", term);
|
||||
/* we set reload flag, and we'll reload before closing the connection */
|
||||
} else {
|
||||
conn_reply_single(conn, NULL, "COMMAND UNAVAILABLE");
|
||||
}
|
||||
return 1;
|
||||
case COMMAND_PING:
|
||||
if (conn->group)
|
||||
|
@ -570,10 +577,15 @@ int execute_or_dispatch_command(client_conn_t *conn, enum commands cmd, const ch
|
|||
mdprintf(desc, "PONG%c", term);
|
||||
return conn->group ? 0 : 1;
|
||||
case COMMAND_VERSION: {
|
||||
if (conn->group)
|
||||
mdprintf(desc, "%u: ", conn->id);
|
||||
print_ver(desc, conn->term, engine);
|
||||
return conn->group ? 0 : 1;
|
||||
if (optget(conn->opts, "EnableVersionCommand")->enabled) {
|
||||
if (conn->group)
|
||||
mdprintf(desc, "%u: ", conn->id);
|
||||
print_ver(desc, conn->term, engine);
|
||||
return conn->group ? 0 : 1;
|
||||
} else {
|
||||
conn_reply_single(conn, NULL, "COMMAND UNAVAILABLE");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
case COMMAND_COMMANDS: {
|
||||
if (conn->group)
|
||||
|
@ -598,9 +610,16 @@ int execute_or_dispatch_command(client_conn_t *conn, enum commands cmd, const ch
|
|||
conn->mode = MODE_STREAM;
|
||||
return 0;
|
||||
}
|
||||
case COMMAND_STATS: {
|
||||
if (optget(conn->opts, "EnableStatsCommand")->enabled) {
|
||||
return dispatch_command(conn, cmd, argument);
|
||||
} else {
|
||||
conn_reply_single(conn, NULL, "COMMAND UNAVAILABLE");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
case COMMAND_MULTISCAN:
|
||||
case COMMAND_CONTSCAN:
|
||||
case COMMAND_STATS:
|
||||
case COMMAND_FILDES:
|
||||
case COMMAND_SCAN:
|
||||
case COMMAND_INSTREAMSCAN:
|
||||
|
|
|
@ -357,6 +357,15 @@ int get_clamd_version(const struct optstruct *opts)
|
|||
logg(LOGG_ERROR, "Error occurred while receiving version information.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check if the response was "COMMAND UNAVAILABLE", which means that
|
||||
clamd has the VERSION command disabled. */
|
||||
if (len >= 19 && memcmp(buff, "COMMAND UNAVAILABLE", 19) == 0) {
|
||||
logg(LOGG_WARNING, "VERSION command disabled in clamd, printing the local version.\n");
|
||||
closesocket(sockd);
|
||||
return 2;
|
||||
}
|
||||
|
||||
printf("%s\n", buff);
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ static void cleanup(void);
|
|||
static int send_string_noreconn(conn_t *conn, const char *cmd);
|
||||
static void send_string(conn_t *conn, const char *cmd);
|
||||
static int read_version(conn_t *conn);
|
||||
static int check_stats_available(conn_t *conn);
|
||||
char *get_ip(const char *ip);
|
||||
char *get_port(const char *ip);
|
||||
char *make_ip(const char *host, const char *port);
|
||||
|
@ -790,6 +791,7 @@ done:
|
|||
static int make_connection(const char *soname, conn_t *conn)
|
||||
{
|
||||
int rc;
|
||||
int rv;
|
||||
|
||||
if (!soname) {
|
||||
return -1;
|
||||
|
@ -801,8 +803,20 @@ static int make_connection(const char *soname, conn_t *conn)
|
|||
send_string(conn, "nIDSESSION\nnVERSION\n");
|
||||
free(conn->version);
|
||||
conn->version = NULL;
|
||||
if (!read_version(conn))
|
||||
return 0;
|
||||
|
||||
rv = read_version(conn);
|
||||
if (rv == -3) {
|
||||
print_con_info(conn, "VERSION command unavailable, consider enabling it in the clamd configuration.\n");
|
||||
EXIT_PROGRAM(FAIL_INITIAL_CONN);
|
||||
} else if (!rv) {
|
||||
// check if STATS command is available
|
||||
if (check_stats_available(conn)) {
|
||||
return 0;
|
||||
} else {
|
||||
print_con_info(conn, "STATS command unavailable, consider enabling it in the clamd configuration.\n");
|
||||
EXIT_PROGRAM(FAIL_INITIAL_CONN);
|
||||
}
|
||||
}
|
||||
|
||||
/* clamd < 0.95 */
|
||||
if ((rc = make_connection_real(soname, conn)))
|
||||
|
@ -1328,6 +1342,9 @@ static int read_version(conn_t *conn)
|
|||
return -1;
|
||||
if (!strcmp(buf, "UNKNOWN COMMAND\n"))
|
||||
return -2;
|
||||
// check if VERSION command is available
|
||||
if (!strcmp(strchr(buf, ':'), ": COMMAND UNAVAILABLE\n"))
|
||||
return -3;
|
||||
|
||||
conn->version = strdup(buf);
|
||||
OOM_CHECK(conn->version);
|
||||
|
@ -1337,6 +1354,17 @@ static int read_version(conn_t *conn)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int check_stats_available(conn_t *conn)
|
||||
{
|
||||
char buf[1024];
|
||||
send_string(conn, "nSTATS\n");
|
||||
if (!recv_line(conn, buf, sizeof(buf)))
|
||||
return 0;
|
||||
if (!strcmp(strchr(buf, ':'), ": COMMAND UNAVAILABLE\n"))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void sigint(int a)
|
||||
{
|
||||
UNUSEDPARAM(a);
|
||||
|
|
|
@ -312,6 +312,14 @@ const struct clam_option __clam_options[] = {
|
|||
|
||||
{"TCPSocket", NULL, 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, -1, NULL, 0, OPT_CLAMD, "A TCP port number the daemon will listen on.", "3310"},
|
||||
|
||||
{"EnableShutdownCommand", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD, "Enables the SHUTDOWN command for clamd", "no"},
|
||||
|
||||
{"EnableReloadCommand", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD, "Enables the RELOAD 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"},
|
||||
|
||||
/* FIXME: add a regex for IP addr */
|
||||
{"TCPAddr", NULL, 0, CLOPT_TYPE_STRING, NULL, -1, NULL, FLAG_MULTIPLE, OPT_CLAMD, "By default clamd binds to INADDR_ANY.\nThis option allows you to restrict the TCP address and provide\nsome degree of protection from the outside world.", "localhost"},
|
||||
|
||||
|
|
|
@ -144,6 +144,34 @@ This option allows you to restrict the TCP address and provide some degree of pr
|
|||
.br
|
||||
Default: disabled
|
||||
.TP
|
||||
\fBEnableShutdownCommand BOOL\fR
|
||||
Enables the SHUTDOWN command. Setting this to no prevents a client to stop clamd via the protocol.
|
||||
.br
|
||||
When disabled, clamd responds to this command with COMMAND UNAVAILABLE.
|
||||
.br
|
||||
Default: yes
|
||||
.TP
|
||||
\fBEnableReloadCommand BOOL\fR
|
||||
Enables the RELOAD command. Setting this to no prevents a client to reload the database.
|
||||
.br
|
||||
When disabled, clamd responds to this command with COMMAND UNAVAILABLE.
|
||||
.br
|
||||
Default: yes
|
||||
.TP
|
||||
\fBEnableVersionCommand BOOL\fR
|
||||
Enables the VERSION command. Setting this to no prevents a client from querying version information.
|
||||
.br
|
||||
When disabled, clamd responds to this command with COMMAND UNAVAILABLE.
|
||||
.br
|
||||
Default: yes
|
||||
.TP
|
||||
\fBEnableStatsCommand BOOL\fR
|
||||
Enables the STATS command. Setting this to no prevents a client from querying statistics.
|
||||
.br
|
||||
When disabled, clamd responds to this command with COMMAND UNAVAILABLE.
|
||||
.br
|
||||
Default: yes
|
||||
.TP
|
||||
\fBMaxConnectionQueueLength NUMBER\fR
|
||||
Maximum length the queue of pending connections may grow to.
|
||||
.br
|
||||
|
|
|
@ -130,6 +130,31 @@ Example
|
|||
# Default: no
|
||||
#TCPAddr localhost
|
||||
|
||||
# Enable or disable certain commands.
|
||||
# Disabling some commands like SHUTDOWN may improve the security of the daemon.
|
||||
# When a client sends one of the following commands but it is disabled,
|
||||
# clamd responds with COMMAND UNAVAILABLE.
|
||||
#
|
||||
# Enable the SHUTDOWN command.
|
||||
# Setting this to no prevents a client to stop clamd via the protocol.
|
||||
# Default: yes
|
||||
#EnableShutdownCommand no
|
||||
#
|
||||
# Enable the RELOAD command
|
||||
# Setting this to no prevents a client to reload the database.
|
||||
# Default: yes
|
||||
#EnableReloadCommand no
|
||||
#
|
||||
# Enable the STATS command
|
||||
# Setting this to no prevents a client from querying statistics.
|
||||
# Default: yes
|
||||
#EnableStatsCommand no
|
||||
#
|
||||
# Enable the VERSION command
|
||||
# Setting this to no prevents a client from querying version information.
|
||||
# Default: yes
|
||||
#EnableVersionCommand no
|
||||
|
||||
# Maximum length the queue of pending connections may grow to.
|
||||
# Default: 200
|
||||
#MaxConnectionQueueLength 30
|
||||
|
|
|
@ -62,6 +62,11 @@ int clamd_connect(const char *cfgfile, const char *option)
|
|||
return -11;
|
||||
}
|
||||
|
||||
if (!optget(opts, "EnableReloadCommand")->enabled) {
|
||||
logg(LOGG_WARNING, "Clamd was NOT notified: The RELOAD command is disabled. Consider enabling it in the clamd configuration!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
if ((opt = optget(opts, "LocalSocket"))->enabled) {
|
||||
memset(&server, 0x00, sizeof(server));
|
||||
|
@ -163,6 +168,12 @@ int notify(const char *cfgfile)
|
|||
|
||||
memset(buff, 0, sizeof(buff));
|
||||
if ((bread = recv(sockd, buff, sizeof(buff), 0)) > 0) {
|
||||
if (strstr(buff, "COMMAND UNAVAILABLE")) {
|
||||
// this will only happen when the running clamd instance has EnableReloadCommand set to no,
|
||||
// but the config on disk differs (e.g. after a config change without clamd restart)
|
||||
logg(LOGG_ERROR, "NotifyClamd: RELOAD command unavailable, consider enabling it in the clamd configuration and restarting clamd.\n");
|
||||
return -1;
|
||||
}
|
||||
if (!strstr(buff, "RELOADING")) {
|
||||
logg(LOGG_ERROR, "NotifyClamd: Unknown answer from clamd: '%s'\n", buff);
|
||||
closesocket(sockd);
|
||||
|
|
|
@ -102,6 +102,31 @@ TCPSocket 3310
|
|||
# Default: no
|
||||
TCPAddr localhost
|
||||
|
||||
# Enable or disable certain commands.
|
||||
# Disabling some commands like SHUTDOWN may improve the security of the daemon.
|
||||
# When a client sends one of the following commands but it is disabled,
|
||||
# clamd responds with COMMAND UNAVAILABLE.
|
||||
#
|
||||
# Enable the SHUTDOWN command.
|
||||
# Setting this to no prevents a client to stop clamd via the protocol.
|
||||
# Default: yes
|
||||
#EnableShutdownCommand no
|
||||
#
|
||||
# Enable the RELOAD command
|
||||
# Setting this to no prevents a client to reload the database.
|
||||
# Default: yes
|
||||
#EnableReloadCommand no
|
||||
#
|
||||
# Enable the STATS command
|
||||
# Setting this to no prevents a client from querying statistics.
|
||||
# Default: yes
|
||||
#EnableStatsCommand no
|
||||
#
|
||||
# Enable the VERSION command
|
||||
# Setting this to no prevents a client from querying version information.
|
||||
# Default: yes
|
||||
#EnableVersionCommand no
|
||||
|
||||
# Maximum length the queue of pending connections may grow to.
|
||||
# Default: 200
|
||||
#MaxConnectionQueueLength 30
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue