From 00324e3616527cf053194e9488cefd80c8eb6789 Mon Sep 17 00:00:00 2001 From: Micah Snyder Date: Mon, 23 Oct 2023 11:20:08 -0500 Subject: [PATCH] ClamD: Add new zOPTSCAN command w. scan options like ALLMATCH Added a new ClamD command which can pass options along with the actual scan command. Current options are just `ALLMATCH`. Current scan commands are: - `CONTSCAN` - `FILDES` - `INSTREAM` - `MULTISCAN` For example, you can send: zOPTSCAN CONTSCAN /some/file/path\0 or: zOPTSCAN ALLMATCH INSTREAM \0 <4-byte length> <4-byte length> <4-byte zero> Note that a space is presently required between the final option before the path or before the \0 for INSTREAM and FDPASS where the subsequent bytes appear are sent in the next message. --- clamd/scanner.c | 44 ++++--- clamd/scanner.h | 7 +- clamd/server-th.c | 221 +++++++++++++++++++++++++++++++++++- clamd/session.c | 111 +++++++++++------- clamd/session.h | 33 +----- clamdscan/client.c | 29 +++-- clamdscan/proto.c | 24 ++-- clamdscan/proto.h | 4 +- clamonacc/clamonacc.c | 3 + clamonacc/clamonacc.h | 4 +- clamonacc/client/client.c | 35 +++--- clamonacc/client/client.h | 2 +- clamonacc/client/protocol.c | 36 +++--- clamonacc/client/protocol.h | 2 +- clamonacc/scan/thread.c | 3 +- clamonacc/scan/thread.h | 3 +- common/clamdcom.c | 70 ++++++++---- common/clamdcom.h | 13 +-- common/scanmem.c | 6 +- libclamav/others_common.c | 2 +- unit_tests/check_clamd.c | 2 +- unit_tests/clamd_test.py | 12 +- 22 files changed, 475 insertions(+), 191 deletions(-) diff --git a/clamd/scanner.c b/clamd/scanner.c index a6718569e1..f67c0eed92 100644 --- a/clamd/scanner.c +++ b/clamd/scanner.c @@ -377,7 +377,8 @@ cl_error_t scanfd( struct cl_scan_options *options, const struct optstruct *opts, int odesc, - int stream) + int stream, + struct scan_cb_data *scandata) { cl_error_t ret = -1; int fd = conn->scanfd; @@ -426,7 +427,7 @@ cl_error_t scanfd( thrmgr_setactivetask(fdstr, NULL); context.filename = fdstr; context.virsize = 0; - context.scandata = NULL; + context.scandata = scandata; ret = cl_scandesc_callback(fd, log_filename, &virname, scanned, engine, options, &context); thrmgr_setactivetask(NULL, NULL); @@ -437,13 +438,17 @@ cl_error_t scanfd( } if (ret == CL_VIRUS) { - virusaction(log_filename, virname, opts); - if (conn_reply_virus(conn, reply_fdstr, virname) == -1) - ret = CL_ETIMEOUT; - if (context.virsize && optget(opts, "ExtendedDetectionInfo")->enabled) - logg(LOGG_INFO, "%s: %s(%s:%llu) FOUND\n", log_filename, virname, context.virhash, context.virsize); - else - logg(LOGG_INFO, "%s: %s FOUND\n", log_filename, virname); + if (options->general & CL_SCAN_GENERAL_ALLMATCHES || options->general & CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE) { + virusaction(log_filename, virname, opts); + } else { + virusaction(log_filename, virname, opts); + if (conn_reply_virus(conn, reply_fdstr, virname) == -1) + ret = CL_ETIMEOUT; + if (context.virsize && optget(opts, "ExtendedDetectionInfo")->enabled) + logg(LOGG_INFO, "%s: %s(%s:%llu) FOUND\n", log_filename, virname, context.virhash, context.virsize); + else + logg(LOGG_INFO, "%s: %s FOUND\n", log_filename, virname); + } } else if (ret != CL_CLEAN) { if (conn_reply(conn, reply_fdstr, cl_strerror(ret), "ERROR") == -1) ret = CL_ETIMEOUT; @@ -469,7 +474,8 @@ int scanstream( const struct cl_engine *engine, struct cl_scan_options *options, const struct optstruct *opts, - char term) + char term, + struct scan_cb_data *scandata) { int ret, sockfd, acceptd; int tmpd, bread, retval, firsttimeout, timeout, btread; @@ -606,7 +612,7 @@ int scanstream( thrmgr_setactivetask(peer_addr, NULL); context.filename = peer_addr; context.virsize = 0; - context.scandata = NULL; + context.scandata = scandata; ret = cl_scandesc_callback(tmpd, tmpname, &virname, scanned, engine, options, &context); thrmgr_setactivetask(NULL, NULL); } else { @@ -621,14 +627,18 @@ int scanstream( closesocket(sockfd); if (ret == CL_VIRUS) { - if (context.virsize && optget(opts, "ExtendedDetectionInfo")->enabled) { - mdprintf(odesc, "stream: %s(%s:%llu) FOUND%c", virname, context.virhash, context.virsize, term); - logg(LOGG_INFO, "stream(%s@%u): %s(%s:%llu) FOUND\n", peer_addr, port, virname, context.virhash, context.virsize); + if (options->general & CL_SCAN_GENERAL_ALLMATCHES || options->general & CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE) { + virusaction("stream", virname, opts); } else { - mdprintf(odesc, "stream: %s FOUND%c", virname, term); - logg(LOGG_INFO, "stream(%s@%u): %s FOUND\n", peer_addr, port, virname); + if (context.virsize && optget(opts, "ExtendedDetectionInfo")->enabled) { + mdprintf(odesc, "stream: %s(%s:%llu) FOUND%c", virname, context.virhash, context.virsize, term); + logg(LOGG_INFO, "stream(%s@%u): %s(%s:%llu) FOUND\n", peer_addr, port, virname, context.virhash, context.virsize); + } else { + mdprintf(odesc, "stream: %s FOUND%c", virname, term); + logg(LOGG_INFO, "stream(%s@%u): %s FOUND\n", peer_addr, port, virname); + } + virusaction("stream", virname, opts); } - virusaction("stream", virname, opts); } else if (ret != CL_CLEAN) { if (retval == 1) { mdprintf(odesc, "stream: %s ERROR%c", cl_strerror(ret), term); diff --git a/clamd/scanner.h b/clamd/scanner.h index 663c8e3325..5bc4201117 100644 --- a/clamd/scanner.h +++ b/clamd/scanner.h @@ -33,6 +33,7 @@ #include "thrmgr.h" #include "session.h" +#include "clamdcom.h" enum scan_type { TYPE_INIT = -1, TYPE_SCAN = 0, @@ -40,7 +41,7 @@ enum scan_type { TYPE_INIT = -1, TYPE_MULTISCAN = 2 }; struct scan_cb_data { - int scantype; + scantype_t scantype; int odesc; int type; int infected; @@ -65,8 +66,8 @@ struct cb_context { struct scan_cb_data *scandata; }; -cl_error_t scanfd(const client_conn_t *conn, unsigned long int *scanned, const struct cl_engine *engine, struct cl_scan_options *options, const struct optstruct *opts, int odesc, int stream); -int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *engine, struct cl_scan_options *options, const struct optstruct *opts, char term); +cl_error_t scanfd(const client_conn_t *conn, unsigned long int *scanned, const struct cl_engine *engine, struct cl_scan_options *options, const struct optstruct *opts, int odesc, int stream, struct scan_cb_data *scandata); +int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *engine, struct cl_scan_options *options, const struct optstruct *opts, char term, struct scan_cb_data *scandata); cl_error_t scan_callback(STATBUF *sb, char *filename, const char *msg, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data); int scan_pathchk(const char *path, struct cli_ftw_cbdata *data); void hash_callback(int fd, unsigned long long size, const unsigned char *md5, const char *virname, void *ctx); diff --git a/clamd/server-th.c b/clamd/server-th.c index 5de65204df..3c363e469f 100644 --- a/clamd/server-th.c +++ b/clamd/server-th.c @@ -152,6 +152,7 @@ static void scanner_thread(void *arg) closesocket(conn->sd); } cl_engine_free(conn->engine); + free(conn->options); free(conn); return; } @@ -669,6 +670,136 @@ static void *acceptloop_th(void *arg) return NULL; } +enum scanopts { + /* Scan options. */ + OPTSCAN_ALLMATCH, + + /* Scan commands. */ + OPTSCAN_CONTSCAN, + OPTSCAN_FILDES, + OPTSCAN_INSTREAM, + OPTSCAN_MULTISCAN, + + /* Error case. */ + OPTSCAN_UNKNOWN, +}; + +static struct { + const char *cmd; + const size_t len; + enum scanopts option; + bool need_value; +} scanopt_options[] = { + /* The following are scan options to modify a scan. */ + {"ALLMATCH", sizeof("ALLMATCH") - 1, OPTSCAN_ALLMATCH, false}, + + /* The following are traditional clamd scan commands. + A single scan command should always follow any scan options in a OPTSCAN request. */ + {"CONTSCAN", sizeof("CONTSCAN") - 1, OPTSCAN_CONTSCAN, false}, + {"FILDES", sizeof("FILDES") - 1, OPTSCAN_FILDES, false}, + {"INSTREAM", sizeof("INSTREAM") - 1, OPTSCAN_INSTREAM, false}, + {"MULTISCAN", sizeof("MULTISCAN") - 1, OPTSCAN_MULTISCAN, false}}; + +/** + * @brief Identify the option type and separate out the rest of the message for further processing. + * + * @param cmd The command, which may be like: ALLMATCH FILEPATH\nFILEDATATOSCAN... + * @param [out] remaining Bytes remamining after the option. E.g. "FILEPATH\nFILEDATATOSCAN..." + * @param [out] value A text value accompanying the option after an '=' symbol. + * For example, the `cmd` could be something this like: STOPAFTER=5 FILEPATH\n/some/file/path + * In this example the value for this hypothetical "STOPAFTER" option would be "5". + * @return enum scanopts Returns which type of option was found. + */ +enum scanopts parse_scanopt(const char *cmd, const char **remaining, size_t *remaining_len, char **value) +{ + size_t i; + + // First initialize the argument to NULL in case there isn't one. + *remaining = NULL; + *value = NULL; + + // Look through all the scanopt_options and find one that matches what was sent to us. + for (i = 0; i < sizeof(scanopt_options) / sizeof(scanopt_options[0]); i++) { + const size_t len = scanopt_options[i].len; + + // Does the command sent match one of the known options? + if (!strncmp(cmd, scanopt_options[i].cmd, len)) { + // Yes! Found a matching option. + + // The remaining is everything after the command. + const char *arg = cmd + len; + size_t arg_left = *remaining_len - len; + + if (arg_left == 0) { + // There is nothing after the command. + // This is an error because we expect a value. + logg(LOGG_ERROR, "OPTSCAN option %s is missing a value!\n", scanopt_options[i].cmd); + return OPTSCAN_UNKNOWN; + } + + // But wait! What if there is a value for this option? + if (*arg == '=') { + const char *value_start; + + if (!scanopt_options[i].need_value) { + // This option doesn't need a value. This is an error. + logg(LOGG_ERROR, "OPTSCAN option %s does not take a value!\n", scanopt_options[i].cmd); + return OPTSCAN_UNKNOWN; + } + + // There is a value for this option. Skip the '='. + arg++; + arg_left--; + + // The value is everything after the '=' until a space. + value_start = arg; + + // So let's find that space. + while (arg_left > 0 && *arg != ' ') { + arg++; + arg_left--; + } + + if (arg_left == 0) { + // There is no space after the value. + // This is an error because we at least a scan command to appear later. + logg(LOGG_ERROR, "OPTSCAN command appears to be missing scan command after scan options!\n"); + return OPTSCAN_UNKNOWN; + } + + *value = strndup(value_start, arg - value_start); + + *remaining = arg + 1; + *remaining_len = arg_left - 1; + + logg(LOGG_DEBUG_NV, "Found OPTSCAN option %s with value %s!\n", scanopt_options[i].cmd, *value); + } else if (*arg == ' ') { + // There is no value for this option. + + if (scanopt_options[i].need_value) { + // This option needs a value. This is an error. + logg(LOGG_ERROR, "OPTSCAN option %s requires a value!\n", scanopt_options[i].cmd); + return OPTSCAN_UNKNOWN; + } + + *remaining = arg + 1; + *remaining_len = arg_left - 1; + + logg(LOGG_DEBUG_NV, "Found OPTSCAN option %s!\n", scanopt_options[i].cmd); + } else { + // There should've been a space. This is an error. + logg(LOGG_ERROR, "OPTSCAN command improperly formatted. There should be a space between options and before the final scan command and command arguments!\n"); + return OPTSCAN_UNKNOWN; + } + + return scanopt_options[i].option; + } + } + + logg(LOGG_ERROR, "OPTSCAN command appears to be missing scan command after scan options!\n"); + return OPTSCAN_UNKNOWN; +} + static const char *parse_dispatch_cmd(client_conn_t *conn, struct fd_buf *buf, size_t *ppos, int *error, const struct optstruct *opts, int readtimeout) { const char *cmd = NULL; @@ -677,20 +808,86 @@ static const char *parse_dispatch_cmd(client_conn_t *conn, struct fd_buf *buf, s char term; int oldstyle; size_t pos = *ppos; + + *error = 0; + /* Parse & dispatch commands */ while ((conn->mode == MODE_COMMAND) && (cmd = get_cmd(buf, pos, &cmdlen, &term, &oldstyle)) != NULL) { const char *argument; + size_t argument_len; enum commands cmdtype; + + // Verify that commands inside of an IDSESSION group as all the newer n or z prefixed commands. if (conn->group && oldstyle) { logg(LOGG_DEBUG_NV, "Received oldstyle command inside IDSESSION: %s\n", cmd); - conn_reply_error(conn, "Only nCMDS\\n and zCMDS\\0 are accepted inside IDSESSION."); + conn_reply_error(conn, "Only new-line terminated nCMDS or NULL-terminated zCMDS are accepted inside IDSESSION."); *error = 1; break; } - cmdtype = parse_command(cmd, &argument, oldstyle); + + // Identify what command was received, and collect the argument, if there is one. + cmdtype = parse_command(cmd, cmdlen, &argument, &argument_len, oldstyle); logg(LOGG_DEBUG_NV, "got command %s (%u, %u), argument: %s\n", cmd, (unsigned)cmdlen, (unsigned)cmdtype, argument ? argument : ""); + + // For OPTSCAN commands, collect scan options followed by actual scan command type. + while ((cmdtype == COMMAND_OPTSCAN) && (*error == 0)) { + char *option_value = NULL; + enum scanopts opttype; + + opttype = parse_scanopt(argument, &argument, &argument_len, &option_value); + + switch (opttype) { + case OPTSCAN_ALLMATCH: { + logg(LOGG_DEBUG, "Received ALLMATCH scan option.\n"); + + // First verify that the clamd.conf allows allmatch. + if (!optget(opts, "AllowAllMatchScan")->enabled) { + logg(LOGG_DEBUG_NV, "Rejecting ALLMATCHSCAN command.\n"); + conn_reply(conn, conn->filename, "ALLMATCHSCAN command disabled by clamd configuration.", "ERROR"); + *error = 1; + break; + } + + conn->options->general |= CL_SCAN_GENERAL_ALLMATCHES; + break; + } + case OPTSCAN_CONTSCAN: { + logg(LOGG_DEBUG, "Received CONTSCAN scan option.\n"); + cmdtype = COMMAND_CONTSCAN; + break; + } + case OPTSCAN_FILDES: { + logg(LOGG_DEBUG, "Received FILEDES scan option.\n"); + cmdtype = COMMAND_FILDES; + break; + } + case OPTSCAN_INSTREAM: { + logg(LOGG_DEBUG, "Received INSTREAM scan option.\n"); + cmdtype = COMMAND_INSTREAM; + break; + } + case OPTSCAN_MULTISCAN: { + logg(LOGG_DEBUG, "Received MULTISCAN scan option.\n"); + cmdtype = COMMAND_MULTISCAN; + break; + } + case OPTSCAN_UNKNOWN: { + // An error occured, and we already complained about it. + *error = 1; + break; + } + } + + // Get next option. + FREE(option_value); + } + + if (*error != 0) { + break; + } + if (cmdtype == COMMAND_FILDES) { if (buf->buffer + buf->off <= cmd + strlen("FILDES\n")) { /* we need the extra byte from recvmsg */ @@ -718,6 +915,7 @@ static const char *parse_dispatch_cmd(client_conn_t *conn, struct fd_buf *buf, s } *error = 1; } + if (thrmgr_group_need_terminate(conn->group)) { logg(LOGG_DEBUG_NV, "Receive thread: have to terminate group\n"); *error = CL_ETIMEOUT; @@ -1622,6 +1820,9 @@ int recvloop(int *socketds, unsigned nsockets, struct cl_engine *engine, unsigne } while (!error && buf->fd != -1 && buf->buffer && pos < buf->off && buf->mode != MODE_WAITANCILL) { + + struct cl_scan_options og_options; + client_conn_t conn; const char *cmd = NULL; int rc; @@ -1642,6 +1843,9 @@ int recvloop(int *socketds, unsigned nsockets, struct cl_engine *engine, unsigne conn.mode = buf->mode; conn.term = buf->term; + // Save off the scan options no scan options set by the command will affect future scans + memcpy(&og_options, conn.options, sizeof(struct cl_scan_options)); + /* Parse & dispatch command */ cmd = parse_dispatch_cmd(&conn, buf, &pos, &error, opts, readtimeout); @@ -1656,15 +1860,24 @@ int recvloop(int *socketds, unsigned nsockets, struct cl_engine *engine, unsigne error = 1; } else if (buf->mode == MODE_STREAM) { rc = handle_stream(&conn, buf, opts, &error, &pos, readtimeout); - if (rc == -1) + + if (rc == -1) { break; - else + } else { + + // Reset the scan options so we don't affect future scans: + memcpy(conn.options, &og_options, sizeof(struct cl_scan_options)); + continue; + } } } if (error && error != CL_ETIMEOUT) { conn_reply_error(&conn, "Error processing command."); } + + // Reset the scan options so we don't affect future scans: + memcpy(conn.options, &og_options, sizeof(struct cl_scan_options)); } if (error) { if (buf->dumpfd != -1) { diff --git a/clamd/session.c b/clamd/session.c index 3bdd26bba8..4d299f0171 100644 --- a/clamd/session.c +++ b/clamd/session.c @@ -24,6 +24,7 @@ #endif #include +#include #include #include #ifdef HAVE_UNISTD_H @@ -66,47 +67,64 @@ #include "session.h" #include "thrmgr.h" -#ifndef HAVE_FDPASSING -#define FEATURE_FDPASSING 0 -#else -#define FEATURE_FDPASSING 1 -#endif - static struct { const char *cmd; const size_t len; enum commands cmdtype; - int need_arg; - int support_old; - int enabled; + bool need_arg; + bool support_old; } commands[] = { - {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}, - {CMD5, sizeof(CMD5) - 1, COMMAND_PING, 0, 1, 0}, - {CMD6, sizeof(CMD6) - 1, COMMAND_CONTSCAN, 1, 1, 0}, + {"SCAN", sizeof("SCAN") - 1, COMMAND_SCAN, true, true}, + {"QUIT", sizeof("QUIT") - 1, COMMAND_SHUTDOWN, false, true}, + {"RELOAD", sizeof("RELOAD") - 1, COMMAND_RELOAD, false, true}, + {"PING", sizeof("PING") - 1, COMMAND_PING, false, true}, + {"CONTSCAN", sizeof("CONTSCAN") - 1, COMMAND_CONTSCAN, true, true}, + {"OPTSCAN", sizeof("OPTSCAN") - 1, COMMAND_OPTSCAN, true, true}, /* must be before VERSION, because they share common prefix! */ - {CMD18, sizeof(CMD18) - 1, COMMAND_COMMANDS, 0, 0, 1}, - {CMD7, sizeof(CMD7) - 1, COMMAND_VERSION, 0, 1, 1}, - {CMD10, sizeof(CMD10) - 1, COMMAND_END, 0, 0, 1}, - {CMD11, sizeof(CMD11) - 1, COMMAND_SHUTDOWN, 0, 1, 1}, - {CMD13, sizeof(CMD13) - 1, COMMAND_MULTISCAN, 1, 1, 1}, - {CMD14, sizeof(CMD14) - 1, COMMAND_FILDES, 0, 1, FEATURE_FDPASSING}, - {CMD15, sizeof(CMD15) - 1, COMMAND_STATS, 0, 0, 1}, - {CMD16, sizeof(CMD16) - 1, COMMAND_IDSESSION, 0, 0, 1}, - {CMD17, sizeof(CMD17) - 1, COMMAND_INSTREAM, 0, 0, 1}, - {CMD19, sizeof(CMD19) - 1, COMMAND_DETSTATSCLEAR, 0, 1, 1}, - {CMD20, sizeof(CMD20) - 1, COMMAND_DETSTATS, 0, 1, 1}, - {CMD21, sizeof(CMD21) - 1, COMMAND_ALLMATCHSCAN, 1, 0, 1}}; - -enum commands parse_command(const char *cmd, const char **argument, int oldstyle) + {"VERSIONCOMMANDS", sizeof("VERSIONCOMMANDS") - 1, COMMAND_COMMANDS, false, false}, + {"VERSION", sizeof("VERSION") - 1, COMMAND_VERSION, false, true}, + {"END", sizeof("END") - 1, COMMAND_END, false, false}, + {"SHUTDOWN", sizeof("SHUTDOWN") - 1, COMMAND_SHUTDOWN, false, true}, + {"MULTISCAN", sizeof("MULTISCAN") - 1, COMMAND_MULTISCAN, true, true}, + {"FILDES", sizeof("FILDES") - 1, COMMAND_FILDES, false, true}, + {"STATS", sizeof("STATS") - 1, COMMAND_STATS, false, true}, + {"IDSESSION", sizeof("IDSESSION") - 1, COMMAND_IDSESSION, false, false}, + {"INSTREAM", sizeof("INSTREAM") - 1, COMMAND_INSTREAM, false, false}, + {"DETSTATSCLEAR", sizeof("DETSTATSCLEAR") - 1, COMMAND_DETSTATSCLEAR, false, true}, + {"DETSTATS", sizeof("DETSTATS") - 1, COMMAND_DETSTATS, false, true}, + {"ALLMATCHSCAN", sizeof("ALLMATCHSCAN") - 1, COMMAND_ALLMATCHSCAN, true, false}}; + +/** + * @brief Identify the command type, parse out the argument, and verify that commands have arguments if needed, etc. + * + * @param cmd The command, which may be like: INSTREAM\nFILEDATATOSCAN... + * @param cmd_len The length of the command string. + * @param [out] argument The argument. E.g. "FILEDATATOSCAN..." + * @param [out] argument_len The length in bytes of the argument. + * @param oldstyle A boolean indicating if the command was sent without a 'z' or 'n' prefix. + * @return enum commands + */ +enum commands parse_command(const char *cmd, size_t cmd_len, const char **argument, size_t *argument_len, int oldstyle) { size_t i; + + // First initialize the argument to NULL in case there isn't one. *argument = NULL; + *argument_len = 0; + + // Look through all the commands and find one that matches what was sent to us. for (i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { const size_t len = commands[i].len; + + // Does the command sent match one of the known commands? if (!strncmp(cmd, commands[i].cmd, len)) { + // Yes! Found a matching command. + + // The argument is everything after the command. const char *arg = cmd + len; + + // Verify that commands which need an argument have an argument. + // And that commands which don't need an argument do not have an argument. if (commands[i].need_arg) { if (!*arg) { /* missing argument */ logg(LOGG_DEBUG_NV, "Command %s missing argument!\n", commands[i].cmd); @@ -120,10 +138,16 @@ enum commands parse_command(const char *cmd, const char **argument, int oldstyle } *argument = NULL; } + + // Verify that commands sent without a 'z' or 'n' prefix (aka "old-style" commands) are allowed + // to be used this way. + // Some commands MUST have the z or n prefix. if (oldstyle && !commands[i].support_old) { logg(LOGG_DEBUG_NV, "Command sent as old-style when not supported: %s\n", commands[i].cmd); return COMMAND_UNKNOWN; } + + *argument_len = cmd_len - commands[i].len; return commands[i].cmdtype; } } @@ -142,27 +166,22 @@ int conn_reply_single(const client_conn_t *conn, const char *path, const char *s return mdprintf(conn->sd, "%s%c", status, conn->term); } -int conn_reply(const client_conn_t *conn, const char *path, - const char *msg, const char *status) +int conn_reply(const client_conn_t *conn, const char *path, const char *msg, const char *status) { if (conn->id) { if (path) - return mdprintf(conn->sd, "%u: %s: %s %s%c", conn->id, path, msg, - status, conn->term); - return mdprintf(conn->sd, "%u: %s %s%c", conn->id, msg, status, - conn->term); + return mdprintf(conn->sd, "%u: %s: %s %s%c", conn->id, path, msg, status, conn->term); + return mdprintf(conn->sd, "%u: %s %s%c", conn->id, msg, status, conn->term); } if (path) return mdprintf(conn->sd, "%s: %s %s%c", path, msg, status, conn->term); return mdprintf(conn->sd, "%s %s%c", msg, status, conn->term); } -int conn_reply_virus(const client_conn_t *conn, const char *file, - const char *virname) +int conn_reply_virus(const client_conn_t *conn, const char *file, const char *virname) { if (conn->id) { - return mdprintf(conn->sd, "%u: %s: %s FOUND%c", conn->id, file, virname, - conn->term); + return mdprintf(conn->sd, "%u: %s: %s FOUND%c", conn->id, file, virname, conn->term); } return mdprintf(conn->sd, "%s: %s FOUND%c", file, virname, conn->term); } @@ -298,7 +317,7 @@ int command(client_conn_t *conn, int *virus) conn_reply_error(conn, "FILDES: didn't receive file descriptor."); return 1; } else { - ret = scanfd(conn, NULL, engine, &options, opts, desc, 0); + ret = scanfd(conn, NULL, engine, &options, opts, desc, 0, &scandata); if (ret == CL_VIRUS) { *virus = 1; ret = 0; @@ -329,7 +348,7 @@ int command(client_conn_t *conn, int *virus) return 0; case COMMAND_INSTREAMSCAN: thrmgr_setactivetask(NULL, "INSTREAM"); - ret = scanfd(conn, NULL, engine, &options, opts, desc, 1); + ret = scanfd(conn, NULL, engine, &options, opts, desc, 1, &scandata); if (ret == CL_VIRUS) { *virus = 1; ret = 0; @@ -411,12 +430,21 @@ static int dispatch_command(client_conn_t *conn, enum commands cmd, const char * int ret = 0; int bulk; client_conn_t *dup_conn = (client_conn_t *)malloc(sizeof(struct client_conn_tag)); - if (!dup_conn) { logg(LOGG_ERROR, "Can't allocate memory for client_conn\n"); return -1; } + memcpy(dup_conn, conn, sizeof(*conn)); + + dup_conn->options = (struct cl_scan_options*)malloc(sizeof(struct cl_scan_options)); + if (!dup_conn->options) { + logg(LOGG_ERROR, "Can't allocate memory for client_conn options\n"); + return -1; + } + + memcpy(dup_conn->options, conn->options, sizeof(struct cl_scan_options)); + dup_conn->cmdtype = cmd; if (cl_engine_addref(dup_conn->engine)) { logg(LOGG_ERROR, "cl_engine_addref() failed\n"); @@ -467,6 +495,7 @@ static int dispatch_command(client_conn_t *conn, enum commands cmd, const char * } if (ret) { cl_engine_free(dup_conn->engine); + free(dup_conn->options); free(dup_conn); } return ret; diff --git a/clamd/session.h b/clamd/session.h index 60c6778a35..30f201bbe6 100644 --- a/clamd/session.h +++ b/clamd/session.h @@ -22,33 +22,6 @@ #ifndef __SESSION_H #define __SESSION_H -#define CMD1 "SCAN" -/* #define CMD2 "RAWSCAN" */ /* removed, was deprecated */ -#define CMD3 "QUIT" -#define CMD4 "RELOAD" -#define CMD5 "PING" -#define CMD6 "CONTSCAN" -#define CMD7 "VERSION" -/* #define CMD8 "STREAM" */ /* removed, was deprecated */ -/* #define CMD9 "SESSION" */ /* removed, was deprecated */ -#define CMD10 "END" -#define CMD11 "SHUTDOWN" -/* #define CMD12 "FD" */ -#define CMD13 "MULTISCAN" -#define CMD14 "FILDES" -#define CMD15 "STATS" -#define CMD16 "IDSESSION" -#define CMD17 "INSTREAM" -#define CMD18 "VERSIONCOMMANDS" -#define CMD19 "DETSTATSCLEAR" -#define CMD20 "DETSTATS" - -#define CMD21 "ALLMATCHSCAN" - -#define CMD22 "GET / HTTP/1.1" -#define CMD23 "GET / HTTP/2" -#define CMD24 "" - // libclamav #include "clamav.h" @@ -80,9 +53,7 @@ enum commands { COMMAND_MULTISCANFILE, COMMAND_INSTREAMSCAN, COMMAND_ALLMATCHSCAN, - COMMAND_SYNACK, - COMMAND_ACK, - COMMAND_GOPHER + COMMAND_OPTSCAN, }; typedef struct client_conn_tag { @@ -103,7 +74,7 @@ typedef struct client_conn_tag { } client_conn_t; int command(client_conn_t *conn, int *virus); -enum commands parse_command(const char *cmd, const char **argument, int oldstyle); +enum commands parse_command(const char *cmd, size_t cmd_len, const char **argument, size_t *argument_len, int oldstyle); int execute_or_dispatch_command(client_conn_t *conn, enum commands command, const char *argument); int conn_reply(const client_conn_t *conn, const char *path, const char *msg, const char *status); diff --git a/clamdscan/client.c b/clamdscan/client.c index 665847162d..93c73e8d86 100644 --- a/clamdscan/client.c +++ b/clamdscan/client.c @@ -307,7 +307,7 @@ static char *makeabs(const char *basepath) /* Recursively scans a path with the given scantype * Returns non zero for serious errors, zero otherwise */ -static int client_scan(const char *file, int scantype, int *infected, int *err, int maxlevel, int session, int flags) +static int client_scan(const char *file, scantype_t scantype, struct cl_scan_options *options, int *infected, int *err, int maxlevel, int session, int flags) { int ret; char *real_path = NULL; @@ -329,9 +329,9 @@ static int client_scan(const char *file, int scantype, int *infected, int *err, if (!fullpath) return 0; if (!session) - ret = serial_client_scan(fullpath, scantype, infected, err, maxlevel, flags); + ret = serial_client_scan(fullpath, scantype, options, infected, err, maxlevel, flags); else - ret = parallel_client_scan(fullpath, scantype, infected, err, maxlevel, flags); + ret = parallel_client_scan(fullpath, scantype, options, infected, err, maxlevel, flags); free(fullpath); return ret; } @@ -391,8 +391,10 @@ int reload_clamd_database(const struct optstruct *opts) int client(const struct optstruct *opts, int *infected, int *err) { - int remote, scantype, session = 0, errors = 0, scandash = 0, maxrec, flags = 0; + int remote, session = 0, errors = 0, scandash = 0, maxrec, flags = 0; const char *fname; + scantype_t scantype; + struct cl_scan_options options = {0}; if (optget(opts, "wait")->enabled) { int16_t ping_result = ping_clamd(opts); @@ -417,12 +419,17 @@ int client(const struct optstruct *opts, int *infected, int *err) if (remote || scandash) { scantype = STREAM; session = optget(opts, "multiscan")->enabled; - } else if (optget(opts, "multiscan")->enabled) + } else if (optget(opts, "multiscan")->enabled) { scantype = MULTI; - else if (optget(opts, "allmatch")->enabled) - scantype = ALLMATCH; - else + } else { scantype = CONT; + } + + // Get scan options + if (optget(opts, "allmatch")->enabled) { + logg(LOGG_DEBUG, "ClamClient: client setup to scan in all-match mode\n"); + options.general |= CL_SCAN_GENERAL_ALLMATCHES; + } maxrec = optget(clamdopts, "MaxDirectoryRecursion")->numarg; maxstream = optget(clamdopts, "StreamMaxLength")->numarg; @@ -443,7 +450,7 @@ int client(const struct optstruct *opts, int *infected, int *err) return 2; } if ((sb.st_mode & S_IFMT) != S_IFREG) scantype = STREAM; - if ((sockd = dconnect(clamdopts)) >= 0 && (ret = dsresult(sockd, scantype, NULL, &ret, NULL, clamdopts)) >= 0) + if ((sockd = dconnect(clamdopts)) >= 0 && (ret = dsresult(sockd, scantype, &options, NULL, &ret, NULL, clamdopts)) >= 0) *infected = ret; else errors = 1; @@ -457,7 +464,7 @@ int client(const struct optstruct *opts, int *infected, int *err) logg(LOGG_ERROR, "Scanning from standard input requires \"-\" to be the only file argument\n"); continue; } - errors += client_scan(fname, scantype, infected, err, maxrec, session, flags); + errors += client_scan(fname, scantype, &options, infected, err, maxrec, session, flags); /* this may be too strict if(errors >= 10) { logg(LOGG_ERROR, "Too many errors\n"); @@ -479,7 +486,7 @@ int client(const struct optstruct *opts, int *infected, int *err) } #endif else { - errors = client_scan("", scantype, infected, err, maxrec, session, flags); + errors = client_scan("", scantype, &options, infected, err, maxrec, session, flags); } return *infected ? 1 : (errors ? 2 : 0); } diff --git a/clamdscan/proto.c b/clamdscan/proto.c index c3965d2eea..b9622b3fa1 100644 --- a/clamdscan/proto.c +++ b/clamdscan/proto.c @@ -78,7 +78,8 @@ static int ftw_chkpath(const char *path, struct cli_ftw_cbdata *data) /* Used by serial_callback() */ struct client_serial_data { int infected; - int scantype; + scantype_t scantype; + struct cl_scan_options *options; int printok; int files; int errors; @@ -148,7 +149,7 @@ static cl_error_t serial_callback(STATBUF *sb, char *filename, const char *path, c->errors++; goto done; } - ret = dsresult(sockd, c->scantype, f, &c->printok, &c->errors, clamdopts); + ret = dsresult(sockd, c->scantype, c->options, f, &c->printok, &c->errors, clamdopts); closesocket(sockd); if (ret < 0) { c->errors++; @@ -171,7 +172,7 @@ static cl_error_t serial_callback(STATBUF *sb, char *filename, const char *path, /* Non-IDSESSION handler * Returns non zero for serious errors, zero otherwise */ -int serial_client_scan(char *file, int scantype, int *infected, int *err, int maxlevel, int flags) +int serial_client_scan(char *file, scantype_t scantype, struct cl_scan_options *options, int *infected, int *err, int maxlevel, int flags) { struct cli_ftw_cbdata data; struct client_serial_data cdata; @@ -182,6 +183,7 @@ int serial_client_scan(char *file, int scantype, int *infected, int *err, int ma cdata.errors = 0; cdata.printok = printinfected ^ 1; cdata.scantype = scantype; + cdata.options = options; data.data = &cdata; ftw = cli_ftw(file, flags, maxlevel ? maxlevel : INT_MAX, serial_callback, &data, ftw_chkpath); @@ -204,7 +206,8 @@ struct client_parallel_data { int infected; int files; int errors; - int scantype; + scantype_t scantype; + struct cl_scan_options *options; int sockd; int lastid; int printok; @@ -212,7 +215,7 @@ struct client_parallel_data { unsigned int id; const char *file; struct SCANID *next; - } * ids; + } *ids; }; /* Sends a proper scan request to clamd and parses its replies @@ -357,11 +360,15 @@ static cl_error_t parallel_callback(STATBUF *sb, char *filename, const char *pat switch (c->scantype) { #ifdef HAVE_FD_PASSING case FILDES: - res = send_fdpass(c->sockd, filename); + res = send_fdpass(c->sockd, filename, c->options); break; #endif case STREAM: - res = send_stream(c->sockd, filename, clamdopts); + res = send_stream(c->sockd, filename, clamdopts, c->options); + break; + + default: + // Ignore other cases (MULTI and CONT) break; } if (res <= 0) { @@ -397,7 +404,7 @@ static cl_error_t parallel_callback(STATBUF *sb, char *filename, const char *pat /* IDSESSION handler * Returns non zero for serious errors, zero otherwise */ -int parallel_client_scan(char *file, int scantype, int *infected, int *err, int maxlevel, int flags) +int parallel_client_scan(char *file, scantype_t scantype, struct cl_scan_options *options, int *infected, int *err, int maxlevel, int flags) { struct cli_ftw_cbdata data; struct client_parallel_data cdata; @@ -417,6 +424,7 @@ int parallel_client_scan(char *file, int scantype, int *infected, int *err, int cdata.files = 0; cdata.errors = 0; cdata.scantype = scantype; + cdata.options = options; cdata.lastid = 0; cdata.ids = NULL; cdata.printok = printinfected ^ 1; diff --git a/clamdscan/proto.h b/clamdscan/proto.h index e928f7cd04..ccec9b76e3 100644 --- a/clamdscan/proto.h +++ b/clamdscan/proto.h @@ -23,6 +23,6 @@ #define PROTO_H #include "misc.h" -int serial_client_scan(char *file, int scantype, int *infected, int *err, int maxlevel, int flags); -int parallel_client_scan(char *file, int scantype, int *infected, int *err, int maxlevel, int flags); +int serial_client_scan(char *file, scantype_t scantype, struct cl_scan_options *options, int *infected, int *err, int maxlevel, int flags); +int parallel_client_scan(char *file, scantype_t scantype, struct cl_scan_options *options, int *infected, int *err, int maxlevel, int flags); #endif diff --git a/clamonacc/clamonacc.c b/clamonacc/clamonacc.c index 2c6b886542..94e60efc58 100644 --- a/clamonacc/clamonacc.c +++ b/clamonacc/clamonacc.c @@ -103,6 +103,7 @@ int main(int argc, char **argv) const struct optstruct *opts; const struct optstruct *opt; const struct optstruct *clamdopts; + struct cl_scan_options options = {0}; struct onas_context *ctx; int ret = 0; @@ -147,6 +148,8 @@ int main(int argc, char **argv) } ctx->clamdopts = clamdopts; + ctx->options = &options; + /* Make sure we're good to begin spinup */ ret = startup_checks(ctx); if (ret) { diff --git a/clamonacc/clamonacc.h b/clamonacc/clamonacc.h index 1a0f3e9bbb..79053e0b2d 100644 --- a/clamonacc/clamonacc.h +++ b/clamonacc/clamonacc.h @@ -24,6 +24,7 @@ // libclamav #include "clamav.h" +#include "clamdcom.h" #ifndef ONAS_DEBUG #define ONAS_DEBUG @@ -59,7 +60,8 @@ struct onas_context { uint64_t sizelimit; uint64_t extinfo; - int scantype; + scantype_t scantype; + struct cl_scan_options *options; int isremote; int session; int timeout; diff --git a/clamonacc/client/client.c b/clamonacc/client/client.c index f7cddc33fc..fd5c3b058e 100644 --- a/clamonacc/client/client.c +++ b/clamonacc/client/client.c @@ -444,14 +444,16 @@ cl_error_t onas_setup_client(struct onas_context **ctx) } else if (optget(opts, "multiscan")->enabled) { logg(LOGG_DEBUG, "ClamClient: client setup to scan in multiscan mode\n"); (*ctx)->scantype = MULTI; - } else if (optget(opts, "allmatch")->enabled) { - logg(LOGG_DEBUG, "ClamClient: client setup to scan in all-match mode\n"); - (*ctx)->scantype = ALLMATCH; } else { logg(LOGG_DEBUG, "ClamClient: client setup for continuous scanning\n"); (*ctx)->scantype = CONT; } + if (optget(opts, "allmatch")->enabled) { + logg(LOGG_DEBUG, "ClamClient: client setup to scan in all-match mode\n"); + (*ctx)->options->general |= CL_SCAN_GENERAL_ALLMATCHES; + } + (*ctx)->maxstream = optget((*ctx)->clamdopts, "StreamMaxLength")->numarg; return CL_SUCCESS; @@ -517,19 +519,20 @@ int onas_get_clamd_version(struct onas_context **ctx) /** * @brief kick off scanning and return results * - * @param tcpaddr string string which refers to either the TCPaddress or the local socket to connect to - * @param portnum the port to use in case of TCP connection, set to 0 if connecting to a local socket - * @param scantype the type of scan to perform, e.g. fdpass, stream - * @param maxstream the max streamsize (in bytes) allowed across the socket per file - * @param fname the name of the file to be scanned - * @param fd the file descriptor for the file to be scanned, often (but not always) this is held by fanotify - * @param timeout time in ms to allow curl before timing out connection attempts - * @param sb variable to store and pass all of our stat info on the file so we don't have to access it multiple times (triggering multiple events) - * @param infected return variable indicating whether daemon returned with an infected verdict or not - * @param err return variable passed to the daemon protocol interface indicating how many things went wrong in the course of scanning - * @param ret_code return variable passed to the daemon protocol interface indicating last known issue or success + * @param tcpaddr string string which refers to either the TCPaddress or the local socket to connect to + * @param portnum the port to use in case of TCP connection, set to 0 if connecting to a local socket + * @param scantype the type of scan to perform, e.g. fdpass, stream, contscan, mutliscan + * @param options any additional scan options to pass, e.g. allmatch + * @param maxstream the max streamsize (in bytes) allowed across the socket per file + * @param fname the name of the file to be scanned + * @param fd the file descriptor for the file to be scanned, often (but not always) this is held by fanotify + * @param timeout time in ms to allow curl before timing out connection attempts + * @param sb variable to store and pass all of our stat info on the file so we don't have to access it multiple times (triggering multiple events) + * @param infected return variable indincating whether daemon returned with an infected verdict or not + * @param err return variable passed to the daemon protocol interface indicating how many things went wrong in the course of scanning + * @param ret_code return variable passed to the daemon protocol interface indicating last known issue or success */ -int onas_client_scan(const char *tcpaddr, int64_t portnum, int32_t scantype, uint64_t maxstream, const char *fname, int fd, int64_t timeout, STATBUF sb, int *infected, int *err, cl_error_t *ret_code) +int onas_client_scan(const char *tcpaddr, int64_t portnum, scantype_t scantype, struct cl_scan_options *options, uint64_t maxstream, const char *fname, int fd, int64_t timeout, STATBUF sb, int *infected, int *err, cl_error_t *ret_code) { CURL *curl = NULL; CURLcode curlcode = CURLE_OK; @@ -564,7 +567,7 @@ int onas_client_scan(const char *tcpaddr, int64_t portnum, int32_t scantype, uin disconnected = false; } - if ((ret = onas_dsresult(curl, scantype, maxstream, fname, fd, timeout, &ret, err, ret_code)) >= 0) { + if ((ret = onas_dsresult(curl, scantype, options, maxstream, fname, fd, timeout, &ret, err, ret_code)) >= 0) { *infected = ret; } else { logg(LOGG_DEBUG, "ClamClient: connection could not be established ... return code %d\n", *ret_code); diff --git a/clamonacc/client/client.h b/clamonacc/client/client.h index e27ca11e92..1ad05bd587 100644 --- a/clamonacc/client/client.h +++ b/clamonacc/client/client.h @@ -31,7 +31,7 @@ #define ONAS_DEFAULT_PING_ATTEMPTS 31 void onas_print_server_version(struct onas_context **ctx); -int onas_client_scan(const char *tcpaddr, int64_t portnum, int32_t scantype, uint64_t maxstream, const char *fname, int fd, int64_t timeout, STATBUF sb, int *infected, int *err, cl_error_t *ret_code); +int onas_client_scan(const char *tcpaddr, int64_t portnum, uint32_t scantype, struct cl_scan_options *options, uint64_t maxstream, const char *fname, int fd, int64_t timeout, STATBUF sb, int *infected, int *err, cl_error_t *ret_code); CURLcode onas_curl_init(CURL **curl, const char *ipaddr, int64_t port, int64_t timeout); int onas_get_clamd_version(struct onas_context **ctx); cl_error_t onas_setup_client(struct onas_context **ctx); diff --git a/clamonacc/client/protocol.c b/clamonacc/client/protocol.c index 30580ed7cf..158248ea3a 100644 --- a/clamonacc/client/protocol.c +++ b/clamonacc/client/protocol.c @@ -68,7 +68,7 @@ #include "client.h" #include "socket.h" -static const char *scancmd[] = {"CONTSCAN", "MULTISCAN", "INSTREAM", "FILDES", "ALLMATCHSCAN"}; +static const char *scancmd[] = {"CONTSCAN", "MULTISCAN", "INSTREAM", "FILDES"}; /* Issues an INSTREAM command to clamd and streams the given file * Returns >0 on success, 0 soft fail, -1 hard fail */ @@ -79,8 +79,7 @@ static int onas_send_stream(CURL *curl, const char *filename, int fd, int64_t ti int ret = 1; int close_flag = 0; STATBUF statbuf; - uint64_t bytesRead = 0; - const char zINSTREAM[] = "zINSTREAM"; + uint64_t bytesRead = 0; if (-1 == fd) { if (NULL == filename) { @@ -112,7 +111,7 @@ static int onas_send_stream(CURL *curl, const char *filename, int fd, int64_t ti goto strm_out; } - if (onas_sendln(curl, zINSTREAM, sizeof(zINSTREAM), timeout)) { + if (onas_sendln(curl, "INSTREAM", sizeof("INSTREAM"), timeout)) { ret = -1; goto strm_out; } @@ -160,9 +159,8 @@ static int onas_send_fdpass(int sockd, int fd) struct msghdr msg; struct cmsghdr *cmsg; unsigned char fdbuf[CMSG_SPACE(sizeof(int))]; - const char zFILDES[] = "zFILDES"; - if (sendln(sockd, zFILDES, sizeof(zFILDES))) { + if (sendln(sockd, "FILDES", sizeof("FILDES"))) { return -1; } @@ -232,10 +230,11 @@ static int onas_fdpass(const char *filename, int fd, int sockd) * This is used only in non IDSESSION mode * Returns the number of infected files or -1 on error * NOTE: filename may be NULL for STREAM scantype. */ -int onas_dsresult(CURL *curl, int scantype, uint64_t maxstream, const char *filename, int fd, int64_t timeout, int *printok, int *errors, cl_error_t *ret_code) +int onas_dsresult(CURL *curl, scantype_t scantype, struct cl_scan_options *options, uint64_t maxstream, const char *filename, int fd, int64_t timeout, int *printok, int *errors, cl_error_t *ret_code) { int infected = 0, len = 0, beenthere = 0; char *bol, *eol; + char *scan_command = NULL; struct onas_rcvln rcv; STATBUF sb; int sockd = -1; @@ -252,8 +251,9 @@ int onas_dsresult(CURL *curl, int scantype, uint64_t maxstream, const char *file switch (scantype) { case MULTI: - case CONT: - case ALLMATCH: + case CONT: { + const char *allmatch = ""; + if (!filename) { logg(LOGG_INFO, "Filename cannot be NULL for MULTISCAN or CONTSCAN.\n"); if (ret_code) { @@ -262,7 +262,12 @@ int onas_dsresult(CURL *curl, int scantype, uint64_t maxstream, const char *file infected = -1; goto done; } - len = strlen(filename) + strlen(scancmd[scantype]) + 3; + + if (options->general & CL_SCAN_GENERAL_ALLMATCHES) { + allmatch = "ALLMATCH "; + } + + len = strlen("zOPTSCAN ") + strlen(allmatch) + strlen(scancmd[scantype]) + 1 + strlen(filename) + 1; if (!(bol = malloc(len))) { logg(LOGG_ERROR, "Cannot allocate a command buffer: %s\n", strerror(errno)); if (ret_code) { @@ -271,7 +276,7 @@ int onas_dsresult(CURL *curl, int scantype, uint64_t maxstream, const char *file infected = -1; goto done; } - sprintf(bol, "z%s %s", scancmd[scantype], filename); + sprintf(scan_command, "zOPTSCAN %s%s %s", allmatch, scancmd[scantype], filename); if (onas_sendln(curl, bol, len, timeout)) { if (ret_code) { *ret_code = CL_EWRITE; @@ -282,7 +287,7 @@ int onas_dsresult(CURL *curl, int scantype, uint64_t maxstream, const char *file } free(bol); break; - + } case STREAM: /* NULL filename safe in send_stream() */ len = onas_send_stream(curl, filename, fd, timeout, maxstream); @@ -355,7 +360,7 @@ int onas_dsresult(CURL *curl, int scantype, uint64_t maxstream, const char *file *(eol - 7) = 0; *printok = 0; - if (scantype != ALLMATCH) { + if (!options->general & CL_SCAN_GENERAL_ALLMATCHES) { infected++; } else { if (filename != NULL && strcmp(filename, last_filename)) { @@ -458,8 +463,13 @@ int onas_dsresult(CURL *curl, int scantype, uint64_t maxstream, const char *file } done: + if (NULL != scan_command) { + free(scan_command); + } + if (sockd > 0) { closesocket(sockd); } + return infected; } diff --git a/clamonacc/client/protocol.h b/clamonacc/client/protocol.h index 225860ef7e..d91c9175fd 100644 --- a/clamonacc/client/protocol.h +++ b/clamonacc/client/protocol.h @@ -27,5 +27,5 @@ #include "misc.h" #include "../clamonacc.h" -int onas_dsresult(CURL *curl, int scantype, uint64_t maxstream, const char *filename, int fd, int64_t timeout, int *printok, int *errors, cl_error_t *ret_code); +int onas_dsresult(CURL *curl, scantype_t scantype, struct cl_scan_options *options, uint64_t maxstream, const char *filename, int fd, int64_t timeout, int *printok, int *errors, cl_error_t *ret_code); #endif diff --git a/clamonacc/scan/thread.c b/clamonacc/scan/thread.c index 974d150593..46724fa6b8 100644 --- a/clamonacc/scan/thread.c +++ b/clamonacc/scan/thread.c @@ -127,7 +127,7 @@ static cl_error_t onas_scan_safe(struct onas_scan_event *event_data, const char pthread_mutex_lock(&onas_scan_lock); - ret = onas_client_scan(event_data->tcpaddr, event_data->portnum, event_data->scantype, event_data->maxstream, + ret = onas_client_scan(event_data->tcpaddr, event_data->portnum, event_data->scantype, event_data->options, event_data->maxstream, fname, fd, event_data->timeout, sb, infected, err, ret_code); pthread_mutex_unlock(&onas_scan_lock); @@ -400,6 +400,7 @@ cl_error_t onas_map_context_info_to_event_data(struct onas_context *ctx, struct } (*event_data)->scantype = ctx->scantype; + (*event_data)->options = ctx->options; (*event_data)->timeout = ctx->timeout; (*event_data)->maxstream = ctx->maxstream; (*event_data)->fan_fd = ctx->fan_fd; diff --git a/clamonacc/scan/thread.h b/clamonacc/scan/thread.h index 1e5912237e..bc021396d2 100644 --- a/clamonacc/scan/thread.h +++ b/clamonacc/scan/thread.h @@ -60,7 +60,8 @@ struct onas_scan_event { #endif uint8_t retry_attempts; uint64_t sizelimit; - int32_t scantype; + uint32_t scantype; + struct cl_scan_options *options; int64_t maxstream; int64_t timeout; uint8_t bool_opts; diff --git a/common/clamdcom.c b/common/clamdcom.c index 54c4d8c716..90aa527ad7 100644 --- a/common/clamdcom.c +++ b/common/clamdcom.c @@ -50,7 +50,7 @@ struct sockaddr_un nixsock; #endif -static const char *scancmd[] = {"CONTSCAN", "MULTISCAN", "INSTREAM", "FILDES", "ALLMATCHSCAN"}; +static const char *scancmd[] = {"CONTSCAN", "MULTISCAN", "INSTREAM", "FILDES"}; /* Sends bytes over a socket * Returns 0 on success */ @@ -172,7 +172,7 @@ int chkpath(const char *path, struct optstruct *clamdopts) #ifdef HAVE_FD_PASSING /* Issues a FILDES command and pass a FD to clamd * Returns >0 on success, 0 soft fail, -1 hard fail */ -int send_fdpass(int sockd, const char *filename) +int send_fdpass(int sockd, const char *filename, struct cl_scan_options *options) { struct iovec iov[1]; struct msghdr msg; @@ -180,16 +180,24 @@ int send_fdpass(int sockd, const char *filename) unsigned char fdbuf[CMSG_SPACE(sizeof(int))]; char dummy[] = ""; int fd; - const char zFILDES[] = "zFILDES"; + const char *cmd; if (filename) { if ((fd = open(filename, O_RDONLY)) < 0) { logg(LOGG_INFO, "%s: Failed to open file\n", filename); return 0; } - } else + } else { fd = 0; - if (sendln(sockd, zFILDES, sizeof(zFILDES))) { + } + + if (options->general & CL_SCAN_GENERAL_ALLMATCHES) { + cmd = "zOPTSCAN ALLMATCH FILDES "; + } else { + cmd = "zOPTSCAN FILDES "; + } + + if (sendln(sockd, cmd, strlen(cmd) + 1)) { close(fd); return -1; } @@ -218,12 +226,12 @@ int send_fdpass(int sockd, const char *filename) /* Issues an INSTREAM command to clamd and streams the given file * Returns >0 on success, 0 soft fail, -1 hard fail */ -int send_stream(int sockd, const char *filename, struct optstruct *clamdopts) +int send_stream(int sockd, const char *filename, struct optstruct *clamdopts, struct cl_scan_options *options) { uint32_t buf[BUFSIZ / sizeof(uint32_t)]; int fd, len; unsigned long int todo = optget(clamdopts, "StreamMaxLength")->numarg; - const char zINSTREAM[] = "zINSTREAM"; + const char *cmd; if (filename) { if ((fd = safe_open(filename, O_RDONLY | O_BINARY)) < 0) { @@ -235,7 +243,13 @@ int send_stream(int sockd, const char *filename, struct optstruct *clamdopts) fd = 0; } - if (sendln(sockd, zINSTREAM, sizeof(zINSTREAM))) { + if (options->general & CL_SCAN_GENERAL_ALLMATCHES) { + cmd = "zOPTSCAN ALLMATCH INSTREAM "; + } else { + cmd = "zOPTSCAN INSTREAM "; + } + + if (sendln(sockd, cmd, strlen(cmd) + 1)) { close(fd); return -1; } @@ -334,9 +348,10 @@ int dconnect(struct optstruct *clamdopts) * This is used only in non IDSESSION mode * Returns the number of infected files or -1 on error * NOTE: filename may be NULL for STREAM scantype. */ -int dsresult(int sockd, int scantype, const char *filename, int *printok, int *errors, struct optstruct *clamdopts) +int dsresult(int sockd, scantype_t scantype, struct cl_scan_options *options, const char *filename, int *printok, int *errors, struct optstruct *clamdopts) { int infected = 0, len = 0, beenthere = 0; + char *scan_command = NULL; char *bol, *eol; struct RCVLN rcv; STATBUF sb; @@ -351,36 +366,44 @@ int dsresult(int sockd, int scantype, const char *filename, int *printok, int *e switch (scantype) { case MULTI: - case CONT: - case ALLMATCH: + case CONT: { + const char *allmatch = ""; + if (!filename) { logg(LOGG_INFO, "Filename cannot be NULL for MULTISCAN or CONTSCAN.\n"); infected = -1; goto done; } - len = strlen(filename) + strlen(scancmd[scantype]) + 3; - if (!(bol = malloc(len))) { + + if (options->general & CL_SCAN_GENERAL_ALLMATCHES) { + allmatch = "ALLMATCH "; + } + + len = strlen("zOPTSCAN ") + strlen(allmatch) + strlen(scancmd[scantype]) + 1 + strlen(filename) + 1; + if (!(scan_command = malloc(len))) { logg(LOGG_ERROR, "Cannot allocate a command buffer: %s\n", strerror(errno)); infected = -1; goto done; } - sprintf(bol, "z%s %s", scancmd[scantype], filename); - if (sendln(sockd, bol, len)) { - free(bol); + + sprintf(scan_command, "zOPTSCAN %s%s %s", allmatch, scancmd[scantype], filename); + if (sendln(sockd, scan_command, len)) { infected = -1; goto done; } - free(bol); - break; + free(scan_command); + scan_command = NULL; + break; + } case STREAM: /* NULL filename safe in send_stream() */ - len = send_stream(sockd, filename, clamdopts); + len = send_stream(sockd, filename, clamdopts, options); break; #ifdef HAVE_FD_PASSING case FILDES: /* NULL filename safe in send_fdpass() */ - len = send_fdpass(sockd, filename); + len = send_fdpass(sockd, filename, options); break; #endif } @@ -425,7 +448,8 @@ int dsresult(int sockd, int scantype, const char *filename, int *printok, int *e *(eol - 7) = 0; if (printok) *printok = 0; - if (scantype != ALLMATCH) { + + if (!options->general & CL_SCAN_GENERAL_ALLMATCHES) { infected++; } else { if (filename != NULL && strcmp(filename, last_filename)) { @@ -479,5 +503,9 @@ int dsresult(int sockd, int scantype, const char *filename, int *printok, int *e } done: + if (NULL != scan_command) { + free(scan_command); + } + return infected; } diff --git a/common/clamdcom.h b/common/clamdcom.h index 34f13b01e1..b8e4f83f48 100644 --- a/common/clamdcom.h +++ b/common/clamdcom.h @@ -32,14 +32,13 @@ #include "misc.h" -enum { +typedef enum { CONT, MULTI, STREAM, FILDES, - ALLMATCH, - MAX_SCANTYPE = ALLMATCH -}; + MAX_SCANTYPE = FILDES +} scantype_t; struct RCVLN { char buf[PATH_MAX + 1024]; /* FIXME must match that in clamd - bb1349 */ @@ -59,9 +58,9 @@ int recvln(struct RCVLN *s, char **rbol, char **reol); int chkpath(const char *path, struct optstruct *clamdopts); #ifdef HAVE_FD_PASSING -int send_fdpass(int sockd, const char *filename); +int send_fdpass(int sockd, const char *filename, struct cl_scan_options *options); #endif -int send_stream(int sockd, const char *filename, struct optstruct *clamdopts); +int send_stream(int sockd, const char *filename, struct optstruct *clamdopts, struct cl_scan_options *options); int dconnect(struct optstruct *clamdopts); -int dsresult(int sockd, int scantype, const char *filename, int *printok, int *errors, struct optstruct *clamdopts); +int dsresult(int sockd, scantype_t scantype, struct cl_scan_options *options, const char *filename, int *printok, int *errors, struct optstruct *clamdopts); #endif diff --git a/common/scanmem.c b/common/scanmem.c index 593e189acb..9f63b6c357 100644 --- a/common/scanmem.c +++ b/common/scanmem.c @@ -532,7 +532,7 @@ int dump_pe(const char *filename, PROCESSENTRY32 ProcStruct, int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *info) { int fd; - int scantype; + scantype_t scantype; int ret = CL_CLEAN; const char *virname = NULL; @@ -548,8 +548,6 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf scantype = STREAM; else if (optget(info->opts, "multiscan")->enabled) scantype = MULTI; - else if (optget(info->opts, "allmatch")->enabled) - scantype = ALLMATCH; else scantype = CONT; @@ -557,7 +555,7 @@ int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *inf info->errors++; return -1; } - if (dsresult(sock, scantype, filename, NULL, &info->errors, clamdopts) > 0) { + if (dsresult(sock, scantype, info->options, filename, NULL, &info->errors, clamdopts) > 0) { info->ifiles++; ret = CL_VIRUS; } diff --git a/libclamav/others_common.c b/libclamav/others_common.c index 65f3740669..df76c0a15b 100644 --- a/libclamav/others_common.c +++ b/libclamav/others_common.c @@ -691,7 +691,7 @@ cl_error_t cli_ftw(char *path, int flags, int maxdepth, cli_ftw_cb callback, str if (((flags & CLI_FTW_TRIM_SLASHES) || pathchk) && path[0] && path[1]) { char *pathend; - /* trim slashes so that dir and dir/ behave the same when + /* trim slashes so that dir and dir/ behave the same when * they are symlinks, and we are not following symlinks */ #ifndef _WIN32 while (path[0] == *PATHSEP && path[1] == *PATHSEP) path++; diff --git a/unit_tests/check_clamd.c b/unit_tests/check_clamd.c index 78b90b637c..cc34f6f366 100644 --- a/unit_tests/check_clamd.c +++ b/unit_tests/check_clamd.c @@ -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 PING CONTSCAN OPTSCAN VERSIONCOMMANDS VERSION END SHUTDOWN MULTISCAN FILDES STATS IDSESSION INSTREAM DETSTATSCLEAR DETSTATS ALLMATCHSCAN" enum idsession_support { IDS_OK, /* accepted */ diff --git a/unit_tests/clamd_test.py b/unit_tests/clamd_test.py index 96623a67fc..c149c233c1 100644 --- a/unit_tests/clamd_test.py +++ b/unit_tests/clamd_test.py @@ -472,7 +472,7 @@ def test_clamd_08_VirusEvent(self): def test_clamd_09_clamdscan_ExcludePath(self): ''' - Verify that ExcudePath works and does not cause other on works as expected. + Verify that ExcudePath works as expected. We'll use valgrind on clamdscan instead of clamd for this one, if enabled as a regression for clamdscan memory leak fixes. @@ -485,9 +485,9 @@ def test_clamd_09_clamdscan_ExcludePath(self): (TC.path_tmp / 'beta').mkdir() (TC.path_tmp / 'charlie').mkdir() - shutil.copy(str(TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe'), str(TC.path_tmp / 'alpha' / 'a_found')) # This should be found (first) - shutil.copy(str(TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe'), str(TC.path_tmp / 'beta' / 'b_excluded')) # This one should be excluded - shutil.copy(str(TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe'), str(TC.path_tmp / 'charlie' / 'c_found')) # This one should still be found after excluding the previous + shutil.copy(str(TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe'), str(TC.path_tmp / 'alpha' / 'a_found')) # This should be found (first) + shutil.copy(str(TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe'), str(TC.path_tmp / 'beta' / 'b_excluded')) # This one should be excluded + shutil.copy(str(TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.exe'), str(TC.path_tmp / 'charlie' / 'c_found')) # This one should still be found after excluding the previous with TC.clamd_config.open('a') as config: exclude_path = 'beta' @@ -496,7 +496,7 @@ def test_clamd_09_clamdscan_ExcludePath(self): ExcludePath {} '''.format(exclude_path)) - self.start_clamd(use_valgrind=False) + self.start_clamd(use_valgrind=True) poll = self.proc.poll() assert poll == None # subprocess is alive if poll() returns None @@ -515,7 +515,7 @@ def test_clamd_09_clamdscan_ExcludePath(self): self.run_clamdscan('{}'.format(TC.path_tmp), expected_ec=1, expected_out=expected_out, unexpected_out=unexpected_out, - use_valgrind=True) + use_valgrind=False) def test_clamd_10_allmatch_not_sticky(self): '''