clamav/unit_tests/check_clamd.c

879 lines
27 KiB
C
Raw Normal View History

2009-02-17 19:40:22 +00:00
/*
* Unit tests for clamd.
*
2020-01-03 15:44:07 -05:00
* Copyright (C) 2013-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
* Copyright (C) 2009-2013 Sourcefire, Inc.
2009-02-17 19:40:22 +00:00
*
* Authors: Török Edvin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <check.h>
Add CMake build tooling This patch adds experimental-quality CMake build tooling. The libmspack build required a modification to use "" instead of <> for header #includes. This will hopefully be included in the libmspack upstream project when adding CMake build tooling to libmspack. Removed use of libltdl when using CMake. Flex & Bison are now required to build. If -DMAINTAINER_MODE, then GPERF is also required, though it currently doesn't actually do anything. TODO! I found that the autotools build system was generating the lexer output but not actually compiling it, instead using previously generated (and manually renamed) lexer c source. As a consequence, changes to the .l and .y files weren't making it into the build. To resolve this, I removed generated flex/bison files and fixed the tooling to use the freshly generated files. Flex and bison are now required build tools. On Windows, this adds a dependency on the winflexbison package, which can be obtained using Chocolatey or may be manually installed. CMake tooling only has partial support for building with external LLVM library, and no support for the internal LLVM (to be removed in the future). I.e. The CMake build currently only supports the bytecode interpreter. Many files used include paths relative to the top source directory or relative to the current project, rather than relative to each build target. Modern CMake support requires including internal dependency headers the same way you would external dependency headers (albeit with "" instead of <>). This meant correcting all header includes to be relative to the build targets and not relative to the workspace. For example, ... ```c include "../libclamav/clamav.h" include "clamd/clamd_others.h" ``` ... becomes: ```c // libclamav include "clamav.h" // clamd include "clamd_others.h" ``` Fixes header name conflicts by renaming a few of the files. Converted the "shared" code into a static library, which depends on libclamav. The ironically named "shared" static library provides features common to the ClamAV apps which are not required in libclamav itself and are not intended for use by downstream projects. This change was required for correct modern CMake practices but was also required to use the automake "subdir-objects" option. This eliminates warnings when running autoreconf which, in the next version of autoconf & automake are likely to break the build. libclamav used to build in multiple stages where an earlier stage is a static library containing utils required by the "shared" code. Linking clamdscan and clamdtop with this libclamav utils static lib allowed these two apps to function without libclamav. While this is nice in theory, the practical gains are minimal and it complicates the build system. As such, the autotools and CMake tooling was simplified for improved maintainability and this feature was thrown out. clamdtop and clamdscan now require libclamav to function. Removed the nopthreads version of the autotools libclamav_internal_utils static library and added pthread linking to a couple apps that may have issues building on some platforms without it, with the intention of removing needless complexity from the source. Kept the regular version of libclamav_internal_utils.la though it is no longer used anywhere but in libclamav. Added an experimental doxygen build option which attempts to build clamav.h and libfreshclam doxygen html docs. The CMake build tooling also may build the example program(s), which isn't a feature in the Autotools build system. Changed C standard to C90+ due to inline linking issues with socket.h when linking libfreshclam.so on Linux. Generate common.rc for win32. Fix tabs/spaces in shared Makefile.am, and remove vestigial ifndef from misc.c. Add CMake files to the automake dist, so users can try the new CMake tooling w/out having to build from a git clone. clamonacc changes: - Renamed FANOTIFY macro to HAVE_SYS_FANOTIFY_H to better match other similar macros. - Added a new clamav-clamonacc.service systemd unit file, based on the work of ChadDevOps & Aaron Brighton. - Added missing clamonacc man page. Updates to clamdscan man page, add missing options. Remove vestigial CL_NOLIBCLAMAV definitions (all apps now use libclamav). Rename Windows mspack.dll to libmspack.dll so all ClamAV-built libraries have the lib-prefix with Visual Studio as with CMake.
2020-08-13 00:25:34 -07:00
// libclamav
#include "clamav.h"
#include "version.h"
// shared
#include "fdpassing.h"
#include "checks_common.h"
static int sockd;
#define SOCKET "clamd-test.socket"
static void conn_setup_mayfail(int may)
{
int rc;
struct sockaddr_un nixsock;
memset((void *)&nixsock, 0, sizeof(nixsock));
nixsock.sun_family = AF_UNIX;
strncpy(nixsock.sun_path, SOCKET, sizeof(nixsock.sun_path));
sockd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockd == -1 && (may && (errno == EMFILE || errno == ENFILE)))
return;
ck_assert_msg(sockd != -1, "Unable to create socket: %s\n", strerror(errno));
2013-02-28 14:39:19 -05:00
rc = connect(sockd, (struct sockaddr *)&nixsock, (socklen_t)sizeof(nixsock));
ck_assert_msg(rc != -1, "Unable to connect(): %s\n", strerror(errno));
signal(SIGPIPE, SIG_IGN);
}
static void conn_setup(void)
{
conn_setup_mayfail(0);
}
static int conn_tcp(int port)
{
struct sockaddr_in server;
int rc;
int sd = socket(AF_INET, SOCK_STREAM, 0);
ck_assert_msg(sd != -1, "Unable to create socket: %s\n", strerror(errno));
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
2013-02-28 14:39:19 -05:00
rc = connect(sd, (struct sockaddr *)&server, (socklen_t)sizeof(server));
ck_assert_msg(rc != -1, "Unable to connect(): %s\n", strerror(errno));
return sd;
}
static void conn_teardown(void)
{
if (sockd != -1)
close(sockd);
}
#ifndef REPO_VERSION
#define REPO_VERSION VERSION
#endif
#define SCANFILE BUILDDIR "/../test/clam.exe"
#define FOUNDREPLY SCANFILE ": ClamAV-Test-File.UNOFFICIAL FOUND"
/* some clean file */
#define CLEANFILE SRCDIR "/Makefile.am"
#define CLEANREPLY CLEANFILE ": OK"
#define UNKNOWN_REPLY "UNKNOWN COMMAND"
#define NONEXISTENT "/nonexistent\vfilename"
#define NONEXISTENT_REPLY NONEXISTENT ": lstat() failed: No such file or directory. ERROR"
#define ACCDENIED BUILDDIR "/accdenied"
#define ACCDENIED_REPLY ACCDENIED ": Access denied. ERROR"
static int isroot = 0;
static void commands_setup(void)
{
const char *nonempty = "NONEMPTYFILE";
int fd = open(NONEXISTENT, O_RDONLY);
if (fd != -1) close(fd);
ck_assert_msg(fd == -1, "Nonexistent file exists!\n");
fd = open(ACCDENIED, O_CREAT | O_WRONLY, S_IWUSR);
ck_assert_msg(fd != -1,
2020-07-24 08:32:47 -07:00
"Failed to create file for access denied tests: %s\n", strerror(errno));
ck_assert_msg(fchmod(fd, S_IWUSR) != -1,
2020-07-24 08:32:47 -07:00
"Failed to chmod: %s\n", strerror(errno));
/* must not be empty file */
ck_assert_msg((size_t)write(fd, nonempty, strlen(nonempty)) == strlen(nonempty),
2020-07-24 08:32:47 -07:00
"Failed to write into testfile: %s\n", strerror(errno));
close(fd);
/* skip access denied tests when run as root, as root will ignore
* permissions */
if (!geteuid())
isroot = 1;
}
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 STREAM END SHUTDOWN MULTISCAN FILDES STATS IDSESSION INSTREAM DETSTATSCLEAR DETSTATS ALLMATCHSCAN"
enum idsession_support {
IDS_OK, /* accepted */
IDS_REJECT,
2020-01-03 15:44:07 -05:00
/* after sending this message, clamd will reply, then accept
* no further commands, but still reply to all active commands */
IDS_END /* the END command */
};
static struct basic_test {
const char *command;
const char *extra;
const char *reply;
int support_old;
int skiproot;
enum idsession_support ids;
} basic_tests[] = {
{"PING", NULL, "PONG", 1, 0, IDS_OK},
{"RELOAD", NULL, "RELOADING", 1, 0, IDS_REJECT},
{"VERSION", NULL, VERSION_REPLY, 1, 0, IDS_OK},
{"VERSIONCOMMANDS", NULL, VCMDS_REPLY, 0, 0, IDS_REJECT},
{"SCAN " SCANFILE, NULL, FOUNDREPLY, 1, 0, IDS_OK},
{"SCAN " CLEANFILE, NULL, CLEANREPLY, 1, 0, IDS_OK},
{"CONTSCAN " SCANFILE, NULL, FOUNDREPLY, 1, 0, IDS_REJECT},
{"CONTSCAN " CLEANFILE, NULL, CLEANREPLY, 1, 0, IDS_REJECT},
{"MULTISCAN " SCANFILE, NULL, FOUNDREPLY, 1, 0, IDS_REJECT},
{"MULTISCAN " CLEANFILE, NULL, CLEANREPLY, 1, 0, IDS_REJECT},
Spelling Adjustments (#30) * spelling: accessed * spelling: alignment * spelling: amalgamated * spelling: answers * spelling: another * spelling: acquisition * spelling: apitid * spelling: ascii * spelling: appending * spelling: appropriate * spelling: arbitrary * spelling: architecture * spelling: asynchronous * spelling: attachments * spelling: argument * spelling: authenticode * spelling: because * spelling: boundary * spelling: brackets * spelling: bytecode * spelling: calculation * spelling: cannot * spelling: changes * spelling: check * spelling: children * spelling: codegen * spelling: commands * spelling: container * spelling: concatenated * spelling: conditions * spelling: continuous * spelling: conversions * spelling: corresponding * spelling: corrupted * spelling: coverity * spelling: crafting * spelling: daemon * spelling: definition * spelling: delivered * spelling: delivery * spelling: delimit * spelling: dependencies * spelling: dependency * spelling: detection * spelling: determine * spelling: disconnects * spelling: distributed * spelling: documentation * spelling: downgraded * spelling: downloading * spelling: endianness * spelling: entities * spelling: especially * spelling: empty * spelling: expected * spelling: explicitly * spelling: existent * spelling: finished * spelling: flexibility * spelling: flexible * spelling: freshclam * spelling: functions * spelling: guarantee * spelling: hardened * spelling: headaches * spelling: heighten * spelling: improper * spelling: increment * spelling: indefinitely * spelling: independent * spelling: inaccessible * spelling: infrastructure Conflicts: docs/html/node68.html * spelling: initializing * spelling: inited * spelling: instream * spelling: installed * spelling: initialization * spelling: initialize * spelling: interface * spelling: intrinsics * spelling: interpreter * spelling: introduced * spelling: invalid * spelling: latency * spelling: lawyers * spelling: libclamav * spelling: likelihood * spelling: loop * spelling: maximum * spelling: million * spelling: milliseconds * spelling: minimum * spelling: minzhuan * spelling: multipart * spelling: misled * spelling: modifiers * spelling: notifying * spelling: objects * spelling: occurred * spelling: occurs * spelling: occurrences * spelling: optimization * spelling: original * spelling: originated * spelling: output * spelling: overridden * spelling: parenthesis * spelling: partition * spelling: performance * spelling: permission * spelling: phishing * spelling: portions * spelling: positives * spelling: preceded * spelling: properties * spelling: protocol * spelling: protos * spelling: quarantine * spelling: recursive * spelling: referring * spelling: reorder * spelling: reset * spelling: resources * spelling: resume * spelling: retrieval * spelling: rewrite * spelling: sanity * spelling: scheduled * spelling: search * spelling: section * spelling: separator * spelling: separated * spelling: specify * spelling: special * spelling: statement * spelling: streams * spelling: succession * spelling: suggests * spelling: superfluous * spelling: suspicious * spelling: synonym * spelling: temporarily * spelling: testfiles * spelling: transverse * spelling: turkish * spelling: typos * spelling: unable * spelling: unexpected * spelling: unexpectedly * spelling: unfinished * spelling: unfortunately * spelling: uninitialized * spelling: unlocking * spelling: unnecessary * spelling: unpack * spelling: unrecognized * spelling: unsupported * spelling: usable * spelling: wherever * spelling: wishlist * spelling: white * spelling: infrastructure * spelling: directories * spelling: overridden * spelling: permission * spelling: yesterday * spelling: initialization * spelling: intrinsics * space adjustment for spelling changes * minor modifications by klin
2018-02-21 15:00:59 -05:00
/* unknown commands */
{"RANDOM", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
/* commands invalid as first */
{"END", NULL, UNKNOWN_REPLY, 1, 0, IDS_END},
/* commands for nonexistent files */
{"SCAN " NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_OK},
{"CONTSCAN " NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_REJECT},
{"MULTISCAN " NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_REJECT},
/* commands for access denied files */
{"SCAN " ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_OK},
{"CONTSCAN " ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_REJECT},
{"MULTISCAN " ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_REJECT},
/* commands with invalid/missing arguments */
{"SCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
{"CONTSCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
{"MULTISCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
/* commands with invalid data */
{"INSTREAM", "\xff\xff\xff\xff", "INSTREAM size limit exceeded. ERROR", 0, 0, IDS_REJECT}, /* too big chunksize */
{"FILDES", "X", "No file descriptor received. ERROR", 1, 0, IDS_REJECT}, /* FILDES w/o ancillary data */
};
static void *recvpartial(int sd, size_t *len, int partial)
{
char *buf = NULL;
size_t off = 0;
int rc;
*len = 0;
do {
if (off + BUFSIZ > *len) {
*len += BUFSIZ + 1;
buf = realloc(buf, *len);
ck_assert_msg(!!buf, "Cannot realloc buffer\n");
}
rc = recv(sd, buf + off, BUFSIZ, 0);
ck_assert_msg(rc != -1, "recv() failed: %s\n", strerror(errno));
off += rc;
} while (rc && (!partial || !memchr(buf, '\0', off)));
*len = off;
2009-02-16 18:27:03 +00:00
buf[*len] = '\0';
return buf;
}
static void *recvfull(int sd, size_t *len)
{
return recvpartial(sd, len, 0);
}
static void test_command(const char *cmd, size_t len, const char *extra, const char *expect, size_t expect_len)
{
void *recvdata;
ssize_t rc;
rc = send(sockd, cmd, len, 0);
ck_assert_msg((size_t)rc == len, "Unable to send(): %s\n", strerror(errno));
if (extra) {
rc = send(sockd, extra, strlen(extra), 0);
ck_assert_msg((size_t)rc == strlen(extra), "Unable to send() extra for %s: %s\n", cmd, strerror(errno));
}
shutdown(sockd, SHUT_WR);
recvdata = recvfull(sockd, &len);
ck_assert_msg(len == expect_len, "Reply has wrong size: %lu, expected %lu, reply: %s, expected: %s\n",
2020-07-24 08:32:47 -07:00
len, expect_len, recvdata, expect);
rc = memcmp(recvdata, expect, expect_len);
ck_assert_msg(!rc, "Wrong reply for command %s: |%s|, expected: |%s|\n", cmd, recvdata, expect);
free(recvdata);
}
START_TEST(test_basic_commands)
{
struct basic_test *test = &basic_tests[_i];
char nsend[BUFSIZ], nreply[BUFSIZ];
if (test->skiproot && isroot)
return;
/* send nCOMMAND */
snprintf(nreply, sizeof(nreply), "%s\n", test->reply);
snprintf(nsend, sizeof(nsend), "n%s\n", test->command);
conn_setup();
test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
conn_teardown();
/* send zCOMMAND */
snprintf(nsend, sizeof(nsend), "z%s", test->command);
conn_setup();
test_command(nsend, strlen(nsend) + 1, test->extra, test->reply, strlen(test->reply) + 1);
conn_teardown();
}
END_TEST
START_TEST(test_compat_commands)
{
/* test sending the command the "old way" */
struct basic_test *test = &basic_tests[_i];
char nsend[BUFSIZ], nreply[BUFSIZ];
if (test->skiproot && isroot)
return;
if (!test->support_old) {
snprintf(nreply, sizeof(nreply), "UNKNOWN COMMAND\n");
test->extra = NULL;
} else {
snprintf(nreply, sizeof(nreply), "%s\n", test->reply);
}
/* one command = one packet, no delimiter */
if (!test->extra) {
conn_setup();
test_command(test->command, strlen(test->command), test->extra, nreply, strlen(nreply));
conn_teardown();
}
/* one packet, \n delimited command, followed by "extra" if needed */
snprintf(nsend, sizeof(nsend), "%s\n", test->command);
conn_setup();
test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
conn_teardown();
if (!test->extra) {
/* FILDES won't support this, because it expects
* strlen("FILDES\n") characters, then 1 character and the FD. */
/* one packet, \r\n delimited command, followed by "extra" if needed */
snprintf(nsend, sizeof(nsend), "%s\r\n", test->command);
conn_setup();
test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
conn_teardown();
}
}
END_TEST
#define EXPECT_INSTREAM "stream: ClamAV-Test-File.UNOFFICIAL FOUND\n"
#define EXPECT_INSTREAM0 "stream: ClamAV-Test-File.UNOFFICIAL FOUND"
#define STATS_REPLY "POOLS: 1\n\nSTATE: VALID PRIMARY\n"
START_TEST(test_stats)
{
char *recvdata;
size_t len = strlen("nSTATS\n");
int rc;
conn_setup();
rc = send(sockd, "nSTATS\n", len, 0);
ck_assert_msg((size_t)rc == len, "Unable to send(): %s\n", strerror(errno));
recvdata = recvfull(sockd, &len);
ck_assert_msg(len > strlen(STATS_REPLY), "Reply has wrong size: %lu, minimum %lu, reply: %s\n",
2020-07-24 08:32:47 -07:00
len, strlen(STATS_REPLY), recvdata);
if (len > strlen(STATS_REPLY))
len = strlen(STATS_REPLY);
rc = strncmp(recvdata, STATS_REPLY, len);
ck_assert_msg(rc == 0, "Wrong reply: %s\n", recvdata);
free(recvdata);
conn_teardown();
}
END_TEST
static size_t prepare_instream(char *buf, size_t off, size_t buflen)
{
STATBUF stbuf;
int fd, nread;
uint32_t chunk;
ck_assert_msg(CLAMSTAT(SCANFILE, &stbuf) != -1, "stat failed for %s: %s", SCANFILE, strerror(errno));
fd = open(SCANFILE, O_RDONLY);
ck_assert_msg(fd != -1, "open failed: %s\n", strerror(errno));
chunk = htonl(stbuf.st_size);
memcpy(&buf[off], &chunk, sizeof(chunk));
off += 4;
nread = read(fd, &buf[off], buflen - off - 4);
ck_assert_msg(nread == stbuf.st_size, "read failed: %d != %d, %s\n", nread, stbuf.st_size, strerror(errno));
off += nread;
buf[off++] = 0;
buf[off++] = 0;
buf[off++] = 0;
buf[off++] = 0;
close(fd);
return off;
}
START_TEST(test_instream)
{
void *recvdata;
size_t len, expect_len;
char buf[4096] = "nINSTREAM\n";
size_t off = strlen(buf);
int rc;
off = prepare_instream(buf, off, sizeof(buf));
conn_setup();
ck_assert_msg((size_t)send(sockd, buf, off, 0) == off, "send() failed: %s\n", strerror(errno));
recvdata = recvfull(sockd, &len);
expect_len = strlen(EXPECT_INSTREAM);
ck_assert_msg(len == expect_len, "Reply has wrong size: %lu, expected %lu, reply: %s\n",
2020-07-24 08:32:47 -07:00
len, expect_len, recvdata);
rc = memcmp(recvdata, EXPECT_INSTREAM, expect_len);
ck_assert_msg(!rc, "Wrong reply for command INSTREAM: |%s|, expected: |%s|\n", recvdata, EXPECT_INSTREAM);
free(recvdata);
conn_teardown();
}
END_TEST
static int sendmsg_fd(int sockd, const char *mesg, size_t msg_len, int fd, int singlemsg)
2009-02-16 18:27:03 +00:00
{
struct msghdr msg;
struct cmsghdr *cmsg;
unsigned char fdbuf[CMSG_SPACE(sizeof(int))];
char dummy[BUFSIZ];
2009-02-16 18:27:03 +00:00
struct iovec iov[1];
int rc;
if (!singlemsg) {
/* send FILDES\n and then a single character + ancillary data */
dummy[0] = '\0';
iov[0].iov_base = dummy;
iov[0].iov_len = 1;
} else {
/* send single message with ancillary data */
ck_assert_msg(msg_len < sizeof(dummy) - 1, "message too large");
memcpy(dummy, mesg, msg_len);
dummy[msg_len] = '\0';
iov[0].iov_base = dummy;
iov[0].iov_len = msg_len + 1;
}
2009-02-16 18:27:03 +00:00
memset(&msg, 0, sizeof(msg));
msg.msg_control = fdbuf;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
2009-02-16 18:27:03 +00:00
msg.msg_controllen = CMSG_LEN(sizeof(int));
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
2009-02-16 18:27:03 +00:00
*(int *)CMSG_DATA(cmsg) = fd;
if (!singlemsg) {
rc = send(sockd, mesg, msg_len, 0);
if (rc == -1)
return rc;
}
return sendmsg(sockd, &msg, 0);
}
static void tst_fildes(const char *cmd, size_t len, int fd,
const char *expect, size_t expect_len, int closefd, int singlemsg)
{
char *recvdata, *p;
int rc;
2009-02-16 18:27:03 +00:00
conn_setup();
ck_assert_msg(sendmsg_fd(sockd, cmd, len, fd, singlemsg) != -1,
2020-07-24 08:32:47 -07:00
"Failed to sendmsg: %s\n", strerror(errno));
2009-02-16 18:27:03 +00:00
if (closefd)
close(fd);
2009-02-16 18:27:03 +00:00
recvdata = recvfull(sockd, &len);
p = strchr(recvdata, ':');
2009-02-16 18:27:03 +00:00
ck_assert_msg(!!p, "Reply doesn't contain ':' : %s\n", recvdata);
2009-02-16 18:27:03 +00:00
*p++ = '\0';
ck_assert_msg(sscanf(recvdata, "fd[%u]", &rc) == 1, "Reply doesn't contain fd: %s\n", recvdata);
2009-02-16 18:27:03 +00:00
len -= p - recvdata;
ck_assert_msg(len == expect_len, "Reply has wrong size: %lu, expected %lu, reply: %s, expected: %s\n",
2020-07-24 08:32:47 -07:00
len, expect_len, p, expect);
2009-02-16 18:27:03 +00:00
rc = memcmp(p, expect, expect_len);
ck_assert_msg(!rc, "Wrong reply for command %s: |%s|, expected: |%s|\n", cmd, p, expect);
2009-02-16 18:27:03 +00:00
free(recvdata);
conn_teardown();
}
#define FOUNDFDREPLY " ClamAV-Test-File.UNOFFICIAL FOUND"
#define CLEANFDREPLY " OK"
static struct cmds {
const char *cmd;
const char term;
const char *file;
const char *reply;
} fildes_cmds[] =
{
{"FILDES", '\n', SCANFILE, FOUNDFDREPLY},
{"nFILDES", '\n', SCANFILE, FOUNDFDREPLY},
{"zFILDES", '\0', SCANFILE, FOUNDFDREPLY},
{"FILDES", '\n', CLEANFILE, CLEANFDREPLY},
{"nFILDES", '\n', CLEANFILE, CLEANFDREPLY},
{"zFILDES", '\0', CLEANFILE, CLEANFDREPLY}};
START_TEST(test_fildes)
2009-02-16 18:27:03 +00:00
{
char nreply[BUFSIZ], nsend[BUFSIZ];
int fd = open(SCANFILE, O_RDONLY);
int closefd = 0;
int singlemsg = 0;
const struct cmds *cmd;
size_t nreply_len, nsend_len;
switch (_i & 3) {
case 0:
closefd = 0;
singlemsg = 0;
break;
case 1:
closefd = 1;
singlemsg = 0;
break;
case 2:
closefd = 0;
singlemsg = 1;
break;
case 3:
closefd = 1;
singlemsg = 1;
break;
}
2009-02-16 18:27:03 +00:00
cmd = &fildes_cmds[_i / 4];
nreply_len = snprintf(nreply, sizeof(nreply), "%s%c", cmd->reply, cmd->term);
nsend_len = snprintf(nsend, sizeof(nsend), "%s%c", cmd->cmd, cmd->term);
2009-02-16 18:27:03 +00:00
fd = open(cmd->file, O_RDONLY);
ck_assert_msg(fd != -1, "Failed to open: %s\n", strerror(errno));
2009-02-16 18:27:03 +00:00
tst_fildes(nsend, nsend_len, fd, nreply, nreply_len, closefd, singlemsg);
2009-02-16 18:27:03 +00:00
if (!closefd) {
2020-01-03 15:44:07 -05:00
/* closefd:
* 1 - close fd right after sending
* 0 - close fd after receiving reply */
close(fd);
}
}
END_TEST
2009-02-16 18:27:03 +00:00
START_TEST(test_fildes_many)
{
const char idsession[] = "zIDSESSION";
2009-04-20 14:26:53 +00:00
int dummyfd, i, killed = 0;
conn_setup();
dummyfd = open(SCANFILE, O_RDONLY);
ck_assert_msg(dummyfd != -1, "failed to open %s: %s\n", SCANFILE, strerror(errno));
ck_assert_msg(send(sockd, idsession, sizeof(idsession), 0) == sizeof(idsession), "send IDSESSION failed\n");
for (i = 0; i < 1024; i++) {
if (sendmsg_fd(sockd, "zFILDES", sizeof("zFILDES"), dummyfd, 1) == -1) {
killed = 1;
break;
}
}
close(dummyfd);
if (send(sockd, "zEND", sizeof("zEND"), 0) == -1) {
killed = 1;
}
conn_teardown();
conn_setup();
test_command("zPING", sizeof("zPING"), NULL, "PONG", 5);
conn_teardown();
2009-02-16 18:27:03 +00:00
}
END_TEST
START_TEST(test_fildes_unwanted)
{
char *recvdata;
size_t len;
int dummyfd;
conn_setup();
dummyfd = open(SCANFILE, O_RDONLY);
/* send a 'zVERSION\0' including the ancillary data.
* The \0 is from the extra char needed when sending ancillary data */
ck_assert_msg(sendmsg_fd(sockd, "zIDSESSION", strlen("zIDSESSION"), dummyfd, 1) != -1,
2020-07-24 08:32:47 -07:00
"sendmsg failed: %s\n", strerror(errno));
recvdata = recvfull(sockd, &len);
ck_assert_msg(!strcmp(recvdata, "1: PROTOCOL ERROR: ancillary data sent without FILDES. ERROR"),
2020-07-24 08:32:47 -07:00
"Wrong reply: %s\n", recvdata);
free(recvdata);
close(dummyfd);
conn_teardown();
}
END_TEST
START_TEST(test_idsession_stress)
{
char buf[BUFSIZ];
size_t i;
char *data, *p;
size_t len;
conn_setup();
ck_assert_msg(send(sockd, "zIDSESSION", sizeof("zIDSESSION"), 0) == sizeof("zIDSESSION"),
2020-07-24 08:32:47 -07:00
"send() failed: %s\n", strerror(errno));
for (i = 0; i < 1024; i++) {
snprintf(buf, sizeof(buf), "%u", (unsigned)(i + 1));
ck_assert_msg(send(sockd, "zVERSION", sizeof("zVERSION"), 0) == sizeof("zVERSION"),
2020-07-24 08:32:47 -07:00
"send failed: %s\n", strerror(errno));
data = recvpartial(sockd, &len, 1);
p = strchr(data, ':');
ck_assert_msg(!!p, "wrong VERSION reply (%u): %s\n", i, data);
*p++ = '\0';
ck_assert_msg(*p == ' ', "wrong VERSION reply (%u): %s\n", i, p);
*p++ = '\0';
ck_assert_msg(!strcmp(p, VERSION_REPLY), "wrong VERSION reply: %s\n", data);
ck_assert_msg(!strcmp(data, buf), "wrong IDSESSION id: %s\n", data);
free(data);
}
conn_teardown();
}
END_TEST
#define TIMEOUT_REPLY "TIMED OUT WAITING FOR COMMAND\n"
START_TEST(test_connections)
{
int rc;
2009-04-20 14:26:53 +00:00
int i;
struct rlimit rlim;
int *sock;
int nf, maxfd = 0;
ck_assert_msg(getrlimit(RLIMIT_NOFILE, &rlim) != -1,
2020-07-24 08:32:47 -07:00
"Failed to get RLIMIT_NOFILE: %s\n", strerror(errno));
nf = rlim.rlim_cur - 5;
sock = malloc(sizeof(int) * nf);
ck_assert_msg(!!sock, "malloc failed\n");
for (i = 0; i < nf; i++) {
/* just open connections, and let them time out */
conn_setup_mayfail(1);
if (sockd == -1) {
nf = i;
break;
}
sock[i] = sockd;
if (sockd > maxfd)
maxfd = sockd;
}
rc = fork();
ck_assert_msg(rc != -1, "fork() failed: %s\n", strerror(errno));
if (rc == 0) {
char dummy;
int ret;
fd_set rfds;
FD_ZERO(&rfds);
for (i = 0; i < nf; i++) {
FD_SET(sock[i], &rfds);
}
while (1) {
ret = select(maxfd + 1, &rfds, NULL, NULL, NULL);
if (ret < 0)
break;
for (i = 0; i < nf; i++) {
if (FD_ISSET(sock[i], &rfds)) {
if (recv(sock[i], &dummy, 1, 0) == 0) {
close(sock[i]);
FD_CLR(sock[i], &rfds);
}
}
}
}
free(sock);
exit(0);
} else {
for (i = 0; i < nf; i++) {
close(sock[i]);
}
free(sock);
/* now see if clamd is able to do anything else */
for (i = 0; i < 10; i++) {
conn_setup();
test_command("RELOAD", sizeof("RELOAD") - 1, NULL, "RELOADING\n", sizeof("RELOADING\n") - 1);
conn_teardown();
}
}
}
END_TEST
START_TEST(test_stream)
{
char buf[BUFSIZ];
char *recvdata;
size_t len;
unsigned port;
int streamsd, infd, nread;
infd = open(SCANFILE, O_RDONLY);
ck_assert_msg(infd != -1, "open failed: %s\n", strerror(errno));
conn_setup();
ck_assert_msg(
send(sockd, "zSTREAM", sizeof("zSTREAM"), 0) == sizeof("zSTREAM"),
"send failed: %s\n", strerror(errno));
recvdata = recvpartial(sockd, &len, 1);
ck_assert_msg(sscanf(recvdata, "PORT %u\n", &port) == 1,
2020-07-24 08:32:47 -07:00
"Wrong stream reply: %s\n", recvdata);
free(recvdata);
streamsd = conn_tcp(port);
do {
nread = read(infd, buf, sizeof(buf));
if (nread > 0)
ck_assert_msg(send(streamsd, buf, nread, 0) == nread,
2020-07-24 08:32:47 -07:00
"send failed: %s\n", strerror(errno));
} while (nread > 0 || (nread == -1 && errno == EINTR));
ck_assert_msg(nread != -1, "read failed: %s\n", strerror(errno));
close(infd);
close(streamsd);
recvdata = recvfull(sockd, &len);
ck_assert_msg(!strcmp(recvdata, "stream: ClamAV-Test-File.UNOFFICIAL FOUND"),
2020-07-24 08:32:47 -07:00
"Wrong reply: %s\n", recvdata);
free(recvdata);
conn_teardown();
}
END_TEST
#define END_CMD "zEND"
#define INSTREAM_CMD "zINSTREAM"
static void test_idsession_commands(int split, int instream)
{
char buf[20480];
size_t i, len = 0, j = 0;
char *recvdata;
char *p = buf;
const char *replies[2 + sizeof(basic_tests) / sizeof(basic_tests[0])];
/* test all commands that must be accepted inside an IDSESSION */
for (i = 0; i < sizeof(basic_tests) / sizeof(basic_tests[0]); i++) {
const struct basic_test *test = &basic_tests[i];
if (test->skiproot && isroot)
continue;
if (test->ids == IDS_OK) {
ck_assert_msg(p + strlen(test->command) + 2 < buf + sizeof(buf), "Buffer too small");
*p++ = 'z';
strcpy(p, test->command);
p += strlen(test->command);
*p++ = '\0';
if (test->extra) {
ck_assert_msg(p + strlen(test->extra) < buf + sizeof(buf), "Buffer too small");
strcpy(p, test->extra);
p += strlen(test->extra);
}
replies[j++] = test->reply;
}
if (instream && test->ids == IDS_END) {
uint32_t chunk;
/* IDS_END - in middle of other commands, perfect for inserting
* INSTREAM */
ck_assert_msg(p + sizeof(INSTREAM_CMD) + 544 < buf + sizeof(buf), "Buffer too small");
memcpy(p, INSTREAM_CMD, sizeof(INSTREAM_CMD));
p += sizeof(INSTREAM_CMD);
p += prepare_instream(p, 0, 552);
replies[j++] = EXPECT_INSTREAM0;
ck_assert_msg(p + sizeof(INSTREAM_CMD) + 16388 < buf + sizeof(buf), "Buffer too small");
memcpy(p, INSTREAM_CMD, sizeof(INSTREAM_CMD));
p += sizeof(INSTREAM_CMD);
chunk = htonl(16384);
memcpy(p, &chunk, 4);
p += 4;
memset(p, 0x5a, 16384);
p += 16384;
*p++ = '\0';
*p++ = '\0';
*p++ = '\0';
*p++ = '\0';
replies[j++] = "stream: OK";
}
}
ck_assert_msg(p + sizeof(END_CMD) < buf + sizeof(buf), "Buffer too small");
memcpy(p, END_CMD, sizeof(END_CMD));
p += sizeof(END_CMD);
if (split) {
/* test corner-cases: 1-byte sends */
for (i = 0; i < (size_t)(p - buf); i++)
ck_assert_msg((size_t)send(sockd, &buf[i], 1, 0) == 1, "send() failed: %u, %s\n", i, strerror(errno));
} else {
ck_assert_msg(send(sockd, buf, p - buf, 0) == p - buf, "send() failed: %s\n", strerror(errno));
}
recvdata = recvfull(sockd, &len);
p = recvdata;
for (i = 0; i < sizeof(basic_tests) / sizeof(basic_tests[0]); i++) {
const struct basic_test *test = &basic_tests[i];
if (test->skiproot && isroot)
continue;
if (test->ids == IDS_OK) {
unsigned id;
char *q = strchr(p, ':');
ck_assert_msg(!!q, "No ID in reply: %s\n", p);
*q = '\0';
ck_assert_msg(sscanf(p, "%u", &id) == 1, "Wrong ID in reply: %s\n", p);
ck_assert_msg(id > 0, "ID cannot be zero");
ck_assert_msg(id <= j, "ID too big: %u, max: %u\n", id, j);
q += 2;
ck_assert_msg(!strcmp(q, replies[id - 1]),
2020-07-24 08:32:47 -07:00
"Wrong ID reply for ID %u: %s, expected %s\n",
id,
q, replies[id - 1]);
p = q + strlen(q) + 1;
}
}
free(recvdata);
conn_teardown();
}
#define ID_CMD "zIDSESSION"
START_TEST(test_idsession)
{
conn_setup();
ck_assert_msg((size_t)send(sockd, ID_CMD, sizeof(ID_CMD), 0) == sizeof(ID_CMD),
2020-07-24 08:32:47 -07:00
"send() failed: %s\n", strerror(errno));
test_idsession_commands(0, 0);
conn_setup();
ck_assert_msg((size_t)send(sockd, ID_CMD, sizeof(ID_CMD), 0) == sizeof(ID_CMD),
2020-07-24 08:32:47 -07:00
"send() failed: %s\n", strerror(errno));
test_idsession_commands(1, 0);
conn_setup();
ck_assert_msg((size_t)send(sockd, ID_CMD, sizeof(ID_CMD), 0) == sizeof(ID_CMD),
2020-07-24 08:32:47 -07:00
"send() failed: %s\n", strerror(errno));
test_idsession_commands(0, 1);
}
END_TEST
static Suite *test_clamd_suite(void)
{
Suite *s = suite_create("clamd");
TCase *tc_commands, *tc_stress;
tc_commands = tcase_create("clamd commands");
suite_add_tcase(s, tc_commands);
tcase_add_unchecked_fixture(tc_commands, commands_setup, commands_teardown);
tcase_add_loop_test(tc_commands, test_basic_commands, 0, sizeof(basic_tests) / sizeof(basic_tests[0]));
tcase_add_loop_test(tc_commands, test_compat_commands, 0, sizeof(basic_tests) / sizeof(basic_tests[0]));
tcase_add_loop_test(tc_commands, test_fildes, 0, 4 * sizeof(fildes_cmds) / sizeof(fildes_cmds[0]));
2009-02-16 18:27:03 +00:00
tcase_add_test(tc_commands, test_stats);
tcase_add_test(tc_commands, test_instream);
tcase_add_test(tc_commands, test_stream);
tcase_add_test(tc_commands, test_idsession);
tc_stress = tcase_create("clamd stress test");
suite_add_tcase(s, tc_stress);
tcase_set_timeout(tc_stress, 20);
tcase_add_test(tc_stress, test_fildes_many);
tcase_add_test(tc_stress, test_idsession_stress);
tcase_add_test(tc_stress, test_fildes_unwanted);
#ifndef C_BSD
/* FreeBSD and Darwin: connect() says connection refused on both
* tcp/unix sockets, if I too quickly connect ~193 times, even if
* listen backlog is higher.
* Don't run this test on BSD for now */
tcase_add_test(tc_stress, test_connections);
#endif
return s;
}
int main(void)
{
int nf;
Suite *s = test_clamd_suite();
SRunner *sr = srunner_create(s);
srunner_set_log(sr, BUILDDIR "/test-clamd.log");
srunner_run_all(sr, CK_NORMAL);
nf = srunner_ntests_failed(sr);
srunner_free(sr);
return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}