source: clamav/trunk/unit_tests/check_clamd.c@ 319

Last change on this file since 319 was 319, checked in by Yuri Dario, 14 years ago

clamav: update trunk to 0.97.

File size: 25.8 KB
Line 
1/*
2 * Unit tests for clamd.
3 *
4 * Copyright (C) 2009 Sourcefire, Inc.
5 *
6 * Authors: Török Edvin
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
21 */
22#if HAVE_CONFIG_H
23#include "clamav-config.h"
24#endif
25#include "shared/fdpassing.h"
26#include <arpa/inet.h>
27#include <netinet/in.h>
28#include <fcntl.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <errno.h>
33#include <signal.h>
34#ifdef HAVE_SYS_TYPES_H
35#include <sys/types.h>
36#endif
37#include <sys/socket.h>
38#include <sys/stat.h>
39#include <sys/un.h>
40#include <unistd.h>
41
42#include <sys/time.h>
43#include <sys/resource.h>
44#include <sys/select.h>
45#include <sys/time.h>
46#include <sys/types.h>
47#include <unistd.h>
48#include <check.h>
49#include "checks_common.h"
50#include "libclamav/version.h"
51#include "libclamav/cltypes.h"
52
53#ifdef __KLIBC__
54#define SHUT_WR 0
55#endif
56
57#ifdef CHECK_HAVE_LOOPS
58
59static int sockd;
60#define SOCKET "clamd-test.socket"
61static void conn_setup_mayfail(int may)
62{
63 int rc;
64 struct sockaddr_un nixsock;
65 memset((void *)&nixsock, 0, sizeof(nixsock));
66 nixsock.sun_family = AF_UNIX;
67 strncpy(nixsock.sun_path, SOCKET, sizeof(nixsock.sun_path));
68#ifdef C_OS2
69 if (strncmp(nixsock.sun_path,"\\socket\\",8))
70 sprintf(nixsock.sun_path,"\\socket\\%s",SOCKET);
71#endif
72
73 sockd = socket(AF_UNIX, SOCK_STREAM, 0);
74 if (sockd == -1 && (may && (errno == EMFILE || errno == ENFILE)))
75 return;
76 fail_unless_fmt(sockd != -1, "Unable to create socket: %s\n", strerror(errno));
77
78 rc = connect(sockd, (struct sockaddr *)&nixsock, sizeof(nixsock));
79 fail_unless_fmt(rc != -1, "Unable to connect(): %s\n", strerror(errno));
80
81 signal(SIGPIPE, SIG_IGN);
82}
83
84static void conn_setup(void)
85{
86 conn_setup_mayfail(0);
87}
88
89static int conn_tcp(int port)
90{
91 struct sockaddr_in server;
92 int rc;
93 int sd = socket(AF_INET, SOCK_STREAM, 0);
94 fail_unless_fmt(sd != -1, "Unable to create socket: %s\n", strerror(errno));
95
96 memset(&server, 0, sizeof(server));
97 server.sin_family = AF_INET;
98 server.sin_port = htons(port);
99 server.sin_addr.s_addr = inet_addr("127.0.0.1");
100
101 rc = connect(sd, (struct sockaddr *)&server, sizeof(server));
102 fail_unless_fmt(rc != -1, "Unable to connect(): %s\n", strerror(errno));
103 return sd;
104}
105
106static void conn_teardown(void)
107{
108 if (sockd != -1)
109 close(sockd);
110}
111
112#ifndef REPO_VERSION
113#define REPO_VERSION VERSION
114#endif
115
116#define SCANFILE BUILDDIR"/../test/clam.exe"
117#define FOUNDREPLY SCANFILE": ClamAV-Test-File.UNOFFICIAL FOUND"
118
119/* some clean file */
120#define CLEANFILE SRCDIR"/Makefile.am"
121#define CLEANREPLY CLEANFILE": OK"
122#define UNKNOWN_REPLY "UNKNOWN COMMAND"
123
124#define NONEXISTENT "/nonexistent\vfilename"
125
126#define NONEXISTENT_REPLY NONEXISTENT": lstat() failed: No such file or directory. ERROR"
127
128#define ACCDENIED BUILDDIR"/accdenied"
129#define ACCDENIED_REPLY ACCDENIED": Access denied. ERROR"
130static int isroot = 0;
131static void commands_setup(void)
132{
133 const char *nonempty = "NONEMPTYFILE";
134 int fd = open(NONEXISTENT, O_RDONLY);
135 if (fd != -1) close(fd);
136 fail_unless(fd == -1, "Nonexistent file exists!\n");
137
138 fd = open(ACCDENIED, O_CREAT | O_WRONLY, S_IWUSR);
139 fail_unless_fmt(fd != -1,
140 "Failed to create file for access denied tests: %s\n", strerror(errno));
141
142
143 fail_unless_fmt(fchmod(fd, S_IWUSR) != -1,
144 "Failed to chmod: %s\n", strerror(errno));
145 /* must not be empty file */
146 fail_unless_fmt((size_t)write(fd, nonempty, strlen(nonempty)) == strlen(nonempty),
147 "Failed to write into testfile: %s\n", strerror(errno));
148 close(fd);
149
150 /* skip access denied tests when run as root, as root will ignore
151 * permissions */
152 if (!geteuid())
153 isroot = 1;
154}
155
156static void commands_teardown(void)
157{
158}
159
160#define VERSION_REPLY "ClamAV "REPO_VERSION""VERSION_SUFFIX
161
162#define VCMDS_REPLY VERSION_REPLY"| COMMANDS: SCAN QUIT RELOAD PING CONTSCAN VERSIONCOMMANDS VERSION STREAM END SHUTDOWN MULTISCAN FILDES STATS IDSESSION INSTREAM DETSTATSCLEAR DETSTATS"
163
164enum idsession_support {
165 IDS_OK, /* accepted */
166 IDS_REJECT,
167 /* after sending this message, clamd will reply, then accept
168 * no further commands, but still reply to all active commands */
169 IDS_END /* the END command */
170};
171
172static struct basic_test {
173 const char *command;
174 const char *extra;
175 const char *reply;
176 int support_old;
177 int skiproot;
178 enum idsession_support ids;
179} basic_tests[] = {
180 {"PING", NULL, "PONG", 1, 0, IDS_OK},
181 {"RELOAD", NULL, "RELOADING", 1, 0, IDS_REJECT},
182 {"VERSION", NULL, VERSION_REPLY, 1, 0, IDS_OK},
183 {"VERSIONCOMMANDS", NULL, VCMDS_REPLY, 0, 0, IDS_REJECT},
184 {"SCAN "SCANFILE, NULL, FOUNDREPLY, 1, 0, IDS_OK},
185 {"SCAN "CLEANFILE, NULL, CLEANREPLY, 1, 0, IDS_OK},
186 {"CONTSCAN "SCANFILE, NULL, FOUNDREPLY, 1, 0, IDS_REJECT},
187 {"CONTSCAN "CLEANFILE, NULL, CLEANREPLY, 1, 0, IDS_REJECT},
188 {"MULTISCAN "SCANFILE, NULL, FOUNDREPLY, 1, 0, IDS_REJECT},
189 {"MULTISCAN "CLEANFILE, NULL, CLEANREPLY, 1, 0, IDS_REJECT},
190 /* unknown commnads */
191 {"RANDOM", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
192 /* commands invalid as first */
193 {"END", NULL, UNKNOWN_REPLY, 1, 0, IDS_END},
194 /* commands for nonexistent files */
195 {"SCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_OK},
196 {"CONTSCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_REJECT},
197 {"MULTISCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_REJECT},
198 /* commands for access denied files */
199 {"SCAN "ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_OK},
200 {"CONTSCAN "ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_REJECT},
201 {"MULTISCAN "ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_REJECT},
202 /* commands with invalid/missing arguments */
203 {"SCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
204 {"CONTSCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
205 {"MULTISCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
206 /* commands with invalid data */
207 {"INSTREAM", "\xff\xff\xff\xff", "INSTREAM size limit exceeded. ERROR", 0, 0, IDS_REJECT}, /* too big chunksize */
208 {"FILDES", "X", "No file descriptor received. ERROR", 1, 0, IDS_REJECT}, /* FILDES w/o ancillary data */
209};
210
211static void *recvpartial(int sd, size_t *len, int partial)
212{
213 char *buf = NULL;
214 size_t off = 0;
215 int rc;
216
217 *len = 0;
218 do {
219 if (off + BUFSIZ > *len) {
220 *len += BUFSIZ+1;
221 buf = realloc(buf, *len);
222 fail_unless(!!buf, "Cannot realloc buffer\n");
223 }
224 rc = recv(sd, buf + off, BUFSIZ, 0);
225 fail_unless_fmt(rc != -1, "recv() failed: %s\n", strerror(errno));
226 off += rc;
227 } while (rc && (!partial || !memchr(buf, '\0', off)));
228 *len = off;
229 buf[*len] = '\0';
230 return buf;
231}
232
233static void *recvfull(int sd, size_t *len)
234{
235 return recvpartial(sd, len, 0);
236}
237
238static void test_command(const char *cmd, size_t len, const char *extra, const char *expect, size_t expect_len)
239{
240 void *recvdata;
241 ssize_t rc;
242
243 rc = send(sockd, cmd, len, 0);
244 fail_unless_fmt((size_t)rc == len, "Unable to send(): %s\n", strerror(errno));
245
246 if (extra) {
247 rc = send(sockd, extra, strlen(extra), 0);
248 fail_unless_fmt((size_t)rc == strlen(extra), "Unable to send() extra for %s: %s\n", cmd, strerror(errno));
249 }
250 shutdown(sockd, SHUT_WR);
251 recvdata = recvfull(sockd, &len);
252
253 fail_unless_fmt(len == expect_len, "Reply has wrong size: %lu, expected %lu, reply: %s, expected: %s\n",
254 len, expect_len, recvdata, expect);
255
256 rc = memcmp(recvdata, expect, expect_len);
257 fail_unless_fmt(!rc, "Wrong reply for command %s: |%s|, expected: |%s|\n", cmd, recvdata, expect);
258 free(recvdata);
259
260}
261
262#ifdef CHECK_HAVE_LOOPS
263START_TEST (test_basic_commands)
264{
265 struct basic_test *test = &basic_tests[_i];
266 char nsend[BUFSIZ], nreply[BUFSIZ];
267
268 if (test->skiproot && isroot)
269 return;
270 /* send nCOMMAND */
271 snprintf(nreply, sizeof(nreply), "%s\n", test->reply);
272 snprintf(nsend, sizeof(nsend), "n%s\n", test->command);
273 conn_setup();
274 test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
275 conn_teardown();
276
277 /* send zCOMMAND */
278 snprintf(nsend, sizeof(nsend), "z%s", test->command);
279 conn_setup();
280 test_command(nsend, strlen(nsend)+1, test->extra, test->reply, strlen(test->reply)+1);
281 conn_teardown();
282}
283END_TEST
284
285START_TEST (test_compat_commands)
286{
287 /* test sending the command the "old way" */
288 struct basic_test *test = &basic_tests[_i];
289 char nsend[BUFSIZ], nreply[BUFSIZ];
290
291 if (test->skiproot && isroot)
292 return;
293
294 if (!test->support_old) {
295 snprintf(nreply, sizeof(nreply), "UNKNOWN COMMAND\n");
296 test->extra = NULL;
297 } else {
298 snprintf(nreply, sizeof(nreply), "%s\n", test->reply);
299 }
300 /* one command = one packet, no delimiter */
301 if (!test->extra) {
302 conn_setup();
303 test_command(test->command, strlen(test->command), test->extra, nreply, strlen(nreply));
304 conn_teardown();
305 }
306
307 /* one packet, \n delimited command, followed by "extra" if needed */
308 snprintf(nsend, sizeof(nsend), "%s\n", test->command);
309 conn_setup();
310 test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
311 conn_teardown();
312
313 if (!test->extra) {
314 /* FILDES won't support this, because it expects
315 * strlen("FILDES\n") characters, then 1 character and the FD. */
316 /* one packet, \r\n delimited command, followed by "extra" if needed */
317 snprintf(nsend, sizeof(nsend), "%s\r\n", test->command);
318 conn_setup();
319 test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
320 conn_teardown();
321 }
322}
323END_TEST
324#endif
325
326#define EXPECT_INSTREAM "stream: ClamAV-Test-File.UNOFFICIAL FOUND\n"
327#define EXPECT_INSTREAM0 "stream: ClamAV-Test-File.UNOFFICIAL FOUND"
328
329#define STATS_REPLY "POOLS: 1\n\nSTATE: VALID PRIMARY\n"
330START_TEST (test_stats)
331{
332 char *recvdata;
333 size_t len = strlen("nSTATS\n");
334 int rc;
335
336 conn_setup();
337 rc = send(sockd, "nSTATS\n", len, 0);
338 fail_unless_fmt((size_t)rc == len, "Unable to send(): %s\n", strerror(errno));
339
340 recvdata = recvfull(sockd, &len);
341
342 fail_unless_fmt(len > strlen(STATS_REPLY), "Reply has wrong size: %lu, minimum %lu, reply: %s\n",
343 len, strlen(STATS_REPLY), recvdata);
344
345 if (len > strlen(STATS_REPLY))
346 len = strlen(STATS_REPLY);
347 rc = strncmp(recvdata, STATS_REPLY, len);
348
349 fail_unless_fmt(rc == 0, "Wrong reply: %s\n", recvdata);
350 free(recvdata);
351 conn_teardown();
352}
353END_TEST
354
355static size_t prepare_instream(char *buf, size_t off, size_t buflen)
356{
357 struct stat stbuf;
358 int fd, nread;
359 uint32_t chunk;
360 fail_unless_fmt(stat(SCANFILE, &stbuf) != -1, "stat failed for %s: %s", SCANFILE, strerror(errno));
361
362 fd = open(SCANFILE, O_RDONLY);
363 fail_unless_fmt(fd != -1, "open failed: %s\n", strerror(errno));
364
365 chunk = htonl(stbuf.st_size);
366 memcpy(&buf[off], &chunk, sizeof(chunk));
367 off += 4;
368 nread = read(fd, &buf[off], buflen-off-4);
369 fail_unless_fmt(nread == stbuf.st_size, "read failed: %d != %d, %s\n", nread, stbuf.st_size, strerror(errno));
370 off += nread;
371 buf[off++]=0;
372 buf[off++]=0;
373 buf[off++]=0;
374 buf[off++]=0;
375 close(fd);
376 return off;
377}
378
379START_TEST (test_instream)
380{
381 void *recvdata;
382 size_t len, expect_len;
383 char buf[4096] = "nINSTREAM\n";
384 size_t off = strlen(buf);
385 int rc;
386
387 off = prepare_instream(buf, off, sizeof(buf));
388
389 conn_setup();
390 fail_unless((size_t)send(sockd, buf, off, 0) == off, "send() failed: %s\n", strerror(errno));
391
392 recvdata = recvfull(sockd, &len);
393
394 expect_len = strlen(EXPECT_INSTREAM);
395 fail_unless_fmt(len == expect_len, "Reply has wrong size: %lu, expected %lu, reply: %s\n",
396 len, expect_len, recvdata);
397
398 rc = memcmp(recvdata, EXPECT_INSTREAM, expect_len);
399 fail_unless_fmt(!rc, "Wrong reply for command INSTREAM: |%s|, expected: |%s|\n", recvdata, EXPECT_INSTREAM);
400 free(recvdata);
401
402 conn_teardown();
403}
404END_TEST
405
406static int sendmsg_fd(int sockd, const char *mesg, size_t msg_len, int fd, int singlemsg)
407{
408#ifndef __KLIBC__
409 struct msghdr msg;
410 struct cmsghdr *cmsg;
411 unsigned char fdbuf[CMSG_SPACE(sizeof(int))];
412 char dummy[BUFSIZ];
413 struct iovec iov[1];
414 int rc;
415
416 if (!singlemsg) {
417 /* send FILDES\n and then a single character + ancillary data */
418 dummy[0] = '\0';
419 iov[0].iov_base = dummy;
420 iov[0].iov_len = 1;
421 } else {
422 /* send single message with ancillary data */
423 fail_unless(msg_len < sizeof(dummy)-1, "message too large");
424 memcpy(dummy, mesg, msg_len);
425 dummy[msg_len] = '\0';
426 iov[0].iov_base = dummy;
427 iov[0].iov_len = msg_len + 1;
428 }
429
430 memset(&msg, 0, sizeof(msg));
431 msg.msg_control = fdbuf;
432 msg.msg_iov = iov;
433 msg.msg_iovlen = 1;
434 msg.msg_controllen = CMSG_LEN(sizeof(int));
435
436 cmsg = CMSG_FIRSTHDR(&msg);
437 cmsg->cmsg_len = CMSG_LEN(sizeof(int));
438 cmsg->cmsg_level = SOL_SOCKET;
439 cmsg->cmsg_type = SCM_RIGHTS;
440 *(int *)CMSG_DATA(cmsg) = fd;
441
442 if (!singlemsg) {
443 rc = send(sockd, mesg, msg_len, 0);
444 if (rc == -1)
445 return rc;
446 }
447
448 return sendmsg(sockd, &msg, 0);
449#endif // __KLIBC__
450}
451
452static void tst_fildes(const char *cmd, size_t len, int fd,
453 const char *expect, size_t expect_len, int closefd, int singlemsg)
454{
455 char *recvdata, *p;
456 int rc;
457
458 conn_setup();
459 fail_unless_fmt(sendmsg_fd(sockd, cmd, len, fd, singlemsg) != -1,
460 "Failed to sendmsg: %s\n", strerror(errno));
461
462 if (closefd)
463 close(fd);
464
465 recvdata = recvfull(sockd, &len);
466 p = strchr(recvdata, ':');
467
468 fail_unless_fmt(!!p, "Reply doesn't contain ':' : %s\n", recvdata);
469 *p++ = '\0';
470
471 fail_unless_fmt(sscanf(recvdata, "fd[%u]", &rc) == 1, "Reply doesn't contain fd: %s\n", recvdata);
472
473 len -= p - recvdata;
474 fail_unless_fmt(len == expect_len, "Reply has wrong size: %lu, expected %lu, reply: %s, expected: %s\n",
475 len, expect_len, p, expect);
476
477 rc = memcmp(p, expect, expect_len);
478 fail_unless_fmt(!rc, "Wrong reply for command %s: |%s|, expected: |%s|\n", cmd, p, expect);
479 free(recvdata);
480 conn_teardown();
481}
482
483#define FOUNDFDREPLY " ClamAV-Test-File.UNOFFICIAL FOUND"
484#define CLEANFDREPLY " OK"
485
486static struct cmds {
487 const char *cmd;
488 const char term;
489 const char *file;
490 const char *reply;
491} fildes_cmds[] =
492{
493 {"FILDES", '\n', SCANFILE, FOUNDFDREPLY},
494 {"nFILDES", '\n', SCANFILE, FOUNDFDREPLY},
495 {"zFILDES", '\0', SCANFILE, FOUNDFDREPLY},
496 {"FILDES", '\n', CLEANFILE, CLEANFDREPLY},
497 {"nFILDES", '\n', CLEANFILE, CLEANFDREPLY},
498 {"zFILDES", '\0', CLEANFILE, CLEANFDREPLY}
499};
500
501#ifdef CHECK_HAVE_LOOPS
502START_TEST (test_fildes)
503{
504 char nreply[BUFSIZ], nsend[BUFSIZ];
505 int fd = open(SCANFILE, O_RDONLY);
506 int closefd=0;
507 int singlemsg=0;
508 const struct cmds *cmd;
509 size_t nreply_len, nsend_len;
510
511 switch (_i&3) {
512 case 0:
513 closefd = 0;
514 singlemsg = 0;
515 break;
516 case 1:
517 closefd = 1;
518 singlemsg = 0;
519 break;
520 case 2:
521 closefd = 0;
522 singlemsg = 1;
523 break;
524 case 3:
525 closefd = 1;
526 singlemsg = 1;
527 break;
528 }
529
530 cmd = &fildes_cmds[_i/4];
531 nreply_len = snprintf(nreply, sizeof(nreply), "%s%c", cmd->reply, cmd->term);
532 nsend_len = snprintf(nsend, sizeof(nsend), "%s%c", cmd->cmd, cmd->term);
533
534 fd = open(cmd->file, O_RDONLY);
535 fail_unless_fmt(fd != -1, "Failed to open: %s\n", strerror(errno));
536
537 tst_fildes(nsend, nsend_len, fd, nreply, nreply_len, closefd, singlemsg);
538
539 if (!closefd) {
540 /* closefd:
541 * 1 - close fd right after sending
542 * 0 - close fd after receiving reply */
543 close(fd);
544 }
545}
546END_TEST
547#endif
548
549START_TEST (test_fildes_many)
550{
551 const char idsession[] = "zIDSESSION";
552 int dummyfd, i, killed = 0;
553 conn_setup();
554 dummyfd = open(SCANFILE, O_RDONLY);
555 fail_unless_fmt(dummyfd != -1, "failed to open %s: %s\n", SCANFILE, strerror(errno));
556
557 fail_unless_fmt(send(sockd, idsession, sizeof(idsession), 0) == sizeof(idsession), "send IDSESSION failed\n");
558 for (i=0;i<1024;i++) {
559 if (sendmsg_fd(sockd, "zFILDES", sizeof("zFILDES"), dummyfd, 1) == -1) {
560 killed = 1;
561 break;
562 }
563 }
564 close(dummyfd);
565 if (send(sockd, "zEND", sizeof("zEND"), 0) == -1) {
566 killed = 1;
567 }
568 conn_teardown();
569
570 conn_setup();
571 test_command("zPING", sizeof("zPING"), NULL, "PONG", 5);
572 conn_teardown();
573}
574END_TEST
575
576START_TEST (test_fildes_unwanted)
577{
578 char *recvdata;
579 size_t len;
580 int dummyfd;
581 conn_setup();
582 dummyfd = open(SCANFILE, O_RDONLY);
583
584 /* send a 'zVERSION\0' including the ancillary data.
585 * The \0 is from the extra char needed when sending ancillary data */
586 fail_unless_fmt(sendmsg_fd(sockd, "zIDSESSION", strlen("zIDSESSION"), dummyfd, 1) != -1,
587 "sendmsg failed: %s\n", strerror(errno));
588
589 recvdata = recvfull(sockd, &len);
590
591 fail_unless_fmt(!strcmp(recvdata,"1: PROTOCOL ERROR: ancillary data sent without FILDES. ERROR"),
592 "Wrong reply: %s\n", recvdata);
593
594 free(recvdata);
595 close(dummyfd);
596 conn_teardown();
597}
598END_TEST
599
600START_TEST (test_idsession_stress)
601{
602 char buf[BUFSIZ];
603 size_t i;
604 char *data, *p;
605 size_t len;
606
607 conn_setup();
608
609 fail_unless_fmt(send(sockd, "zIDSESSION", sizeof("zIDSESSION"), 0) == sizeof("zIDSESSION"),
610 "send() failed: %s\n", strerror(errno));
611 for (i=0;i < 1024; i++) {
612 snprintf(buf, sizeof(buf), "%u", (unsigned)(i+1));
613 fail_unless(send(sockd, "zVERSION", sizeof("zVERSION"), 0) == sizeof("zVERSION"),
614 "send failed: %s\n",strerror(errno));
615 data = recvpartial(sockd, &len, 1);
616 p = strchr(data, ':');
617 fail_unless_fmt(!!p, "wrong VERSION reply (%u): %s\n", i, data);
618 *p++ = '\0';
619 fail_unless_fmt(*p == ' ', "wrong VERSION reply (%u): %s\n", i, p);
620 *p++ = '\0';
621
622 fail_unless_fmt(!strcmp(p, VERSION_REPLY), "wrong VERSION reply: %s\n", data);
623 fail_unless_fmt(!strcmp(data, buf), "wrong IDSESSION id: %s\n", data);
624
625 free(data);
626 }
627
628 conn_teardown();
629}
630END_TEST
631
632#define TIMEOUT_REPLY "TIMED OUT WAITING FOR COMMAND\n"
633
634START_TEST (test_connections)
635{
636 int rc;
637 int i;
638 struct rlimit rlim;
639 int *sock;
640 int nf, maxfd=0;
641 fail_unless_fmt(getrlimit(RLIMIT_NOFILE, &rlim) != -1,
642 "Failed to get RLIMIT_NOFILE: %s\n", strerror(errno));
643 nf = rlim.rlim_cur - 5;
644 sock = malloc(sizeof(int)*nf);
645
646 fail_unless(!!sock, "malloc failed\n");
647
648 for (i=0;i<nf;i++) {
649 /* just open connections, and let them time out */
650 conn_setup_mayfail(1);
651 if (sockd == -1) {
652 nf = i;
653 break;
654 }
655 sock[i] = sockd;
656 if (sockd > maxfd)
657 maxfd = sockd;
658 }
659 rc = fork();
660 fail_unless(rc != -1, "fork() failed: %s\n", strerror(errno));
661 if (rc == 0) {
662 char dummy;
663 int ret;
664 fd_set rfds;
665 FD_ZERO(&rfds);
666 for (i=0;i<nf;i++) {
667 FD_SET(sock[i], &rfds);
668 }
669 while (1) {
670 ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
671 if (ret < 0)
672 break;
673 for (i=0;i<nf;i++) {
674 if (FD_ISSET(sock[i], &rfds)) {
675 if (recv(sock[i], &dummy, 1, 0) == 0) {
676 close(sock[i]);
677 FD_CLR(sock[i], &rfds);
678 }
679 }
680 }
681 }
682 free(sock);
683 exit(0);
684 } else {
685 for (i=0;i<nf;i++) {
686 close(sock[i]);
687 }
688 free(sock);
689 /* now see if clamd is able to do anything else */
690 for (i=0;i<10;i++) {
691 conn_setup();
692 test_command("RELOAD", sizeof("RELOAD")-1, NULL, "RELOADING\n", sizeof("RELOADING\n")-1);
693 conn_teardown();
694 }
695 }
696}
697END_TEST
698
699START_TEST (test_stream)
700{
701 char buf[BUFSIZ];
702 char *recvdata;
703 size_t len;
704 unsigned port;
705 int streamsd, infd, nread;
706
707 infd = open(SCANFILE, O_RDONLY);
708
709 fail_unless_fmt(infd != -1, "open failed: %s\n", strerror(errno));
710 conn_setup();
711 fail_unless_fmt(
712 send(sockd, "zSTREAM", sizeof("zSTREAM"), 0) == sizeof("zSTREAM"),
713 "send failed: %s\n", strerror(errno));
714 recvdata = recvpartial(sockd, &len, 1);
715 fail_unless_fmt (sscanf(recvdata, "PORT %u\n", &port) == 1,
716 "Wrong stream reply: %s\n", recvdata);
717
718 free(recvdata);
719 streamsd = conn_tcp(port);
720
721 do {
722 nread = read(infd, buf, sizeof(buf));
723 if (nread > 0)
724 fail_unless_fmt(send(streamsd, buf, nread, 0) == nread,
725 "send failed: %s\n", strerror(errno));
726 } while (nread > 0 || (nread == -1 && errno == EINTR));
727 fail_unless_fmt(nread != -1, "read failed: %s\n", strerror(errno));
728 close(infd);
729 close(streamsd);
730
731 recvdata = recvfull(sockd, &len);
732 fail_unless_fmt(!strcmp(recvdata,"stream: ClamAV-Test-File.UNOFFICIAL FOUND"),
733 "Wrong reply: %s\n", recvdata);
734 free(recvdata);
735
736 conn_teardown();
737}
738END_TEST
739
740#define END_CMD "zEND"
741#define INSTREAM_CMD "zINSTREAM"
742static void test_idsession_commands(int split, int instream)
743{
744 char buf[20480];
745 size_t i, len=0, j=0;
746 char *recvdata;
747 char *p = buf;
748 const char *replies[2 + sizeof(basic_tests)/sizeof(basic_tests[0])];
749
750 /* test all commands that must be accepted inside an IDSESSION */
751 for (i=0;i < sizeof(basic_tests)/sizeof(basic_tests[0]); i++) {
752 const struct basic_test *test = &basic_tests[i];
753 if (test->skiproot && isroot)
754 continue;
755 if (test->ids == IDS_OK) {
756 fail_unless(p+strlen(test->command)+2 < buf+sizeof(buf), "Buffer too small");
757 *p++ = 'z';
758 strcpy(p, test->command);
759 p += strlen(test->command);
760 *p++ = '\0';
761 if (test->extra) {
762 fail_unless(p+strlen(test->extra) < buf+sizeof(buf), "Buffer too small");
763 strcpy(p, test->extra);
764 p += strlen(test->extra);
765 }
766 replies[j++] = test->reply;
767 }
768 if (instream && test->ids == IDS_END) {
769 uint32_t chunk;
770 /* IDS_END - in middle of other commands, perfect for inserting
771 * INSTREAM */
772 fail_unless(p+sizeof(INSTREAM_CMD)+544< buf+sizeof(buf), "Buffer too small");
773 memcpy(p, INSTREAM_CMD, sizeof(INSTREAM_CMD));
774 p += sizeof(INSTREAM_CMD);
775 p += prepare_instream(p, 0, 552);
776 replies[j++] = EXPECT_INSTREAM0;
777 fail_unless(p+sizeof(INSTREAM_CMD)+16388< buf+sizeof(buf), "Buffer too small");
778 memcpy(p, INSTREAM_CMD, sizeof(INSTREAM_CMD));
779 p += sizeof(INSTREAM_CMD);
780 chunk=htonl(16384);
781 memcpy(p, &chunk, 4);
782 p+=4;
783 memset(p, 0x5a, 16384);
784 p += 16384;
785 *p++='\0';
786 *p++='\0';
787 *p++='\0';
788 *p++='\0';
789 replies[j++] = "stream: OK";
790 }
791 }
792 fail_unless(p+sizeof(END_CMD) < buf+sizeof(buf), "Buffer too small");
793 memcpy(p, END_CMD, sizeof(END_CMD));
794 p += sizeof(END_CMD);
795
796 if (split) {
797 /* test corner-cases: 1-byte sends */
798 for (i=0;i<(size_t)(p-buf);i++)
799 fail_unless((size_t)send(sockd, &buf[i], 1, 0) == 1, "send() failed: %u, %s\n", i, strerror(errno));
800 } else {
801 fail_unless(send(sockd, buf, p-buf, 0) == p-buf,"send() failed: %s\n", strerror(errno));
802 }
803 recvdata = recvfull(sockd, &len);
804 p = recvdata;
805 for (i=0;i < sizeof(basic_tests)/sizeof(basic_tests[0]); i++) {
806 const struct basic_test *test = &basic_tests[i];
807 if (test->skiproot && isroot)
808 continue;
809 if (test->ids == IDS_OK) {
810 unsigned id;
811 char *q = strchr(p, ':');
812 fail_unless_fmt(!!q, "No ID in reply: %s\n", p);
813 *q = '\0';
814 fail_unless_fmt(sscanf(p, "%u", &id) == 1,"Wrong ID in reply: %s\n", p);
815 fail_unless(id > 0, "ID cannot be zero");
816 fail_unless_fmt(id <= j, "ID too big: %u, max: %u\n", id, j);
817 q += 2;
818 fail_unless_fmt(!strcmp(q, replies[id-1]),
819 "Wrong ID reply for ID %u: %s, expected %s\n",
820 id,
821 q, replies[id-1]);
822 p = q + strlen(q)+1;
823 }
824 }
825 free(recvdata);
826 conn_teardown();
827}
828
829#define ID_CMD "zIDSESSION"
830START_TEST(test_idsession)
831{
832 conn_setup();
833 fail_unless_fmt((size_t)send(sockd, ID_CMD, sizeof(ID_CMD), 0) == sizeof(ID_CMD),
834 "send() failed: %s\n", strerror(errno));
835 test_idsession_commands(0, 0);
836 conn_setup();
837 fail_unless_fmt((size_t)send(sockd, ID_CMD, sizeof(ID_CMD), 0) == sizeof(ID_CMD),
838 "send() failed: %s\n", strerror(errno));
839 test_idsession_commands(1, 0);
840 conn_setup();
841 fail_unless_fmt((size_t)send(sockd, ID_CMD, sizeof(ID_CMD), 0) == sizeof(ID_CMD),
842 "send() failed: %s\n", strerror(errno));
843 test_idsession_commands(0, 1);
844}
845END_TEST
846
847static Suite *test_clamd_suite(void)
848{
849 Suite *s = suite_create("clamd");
850 TCase *tc_commands, *tc_stress;
851 tc_commands = tcase_create("clamd commands");
852 suite_add_tcase(s, tc_commands);
853 tcase_add_unchecked_fixture(tc_commands, commands_setup, commands_teardown);
854#ifdef CHECK_HAVE_LOOPS
855 tcase_add_loop_test(tc_commands, test_basic_commands, 0, sizeof(basic_tests)/sizeof(basic_tests[0]));
856 tcase_add_loop_test(tc_commands, test_compat_commands, 0, sizeof(basic_tests)/sizeof(basic_tests[0]));
857 tcase_add_loop_test(tc_commands, test_fildes, 0, 4*sizeof(fildes_cmds)/sizeof(fildes_cmds[0]));
858#endif
859 tcase_add_test(tc_commands, test_stats);
860 tcase_add_test(tc_commands, test_instream);
861 tcase_add_test(tc_commands, test_stream);
862 tcase_add_test(tc_commands, test_idsession);
863 tc_stress = tcase_create("clamd stress test");
864 suite_add_tcase(s, tc_stress);
865 tcase_set_timeout(tc_stress, 20);
866 tcase_add_test(tc_stress, test_fildes_many);
867 tcase_add_test(tc_stress, test_idsession_stress);
868 tcase_add_test(tc_stress, test_fildes_unwanted);
869#ifndef C_BSD
870 /* FreeBSD and Darwin: connect() says connection refused on both
871 * tcp/unix sockets, if I too quickly connect ~193 times, even if
872 * listen backlog is higher.
873 * Don't run this test on BSD for now */
874 tcase_add_test(tc_stress, test_connections);
875#endif
876 return s;
877}
878
879int main(void)
880{
881 int nf;
882 Suite *s = test_clamd_suite();
883 SRunner *sr = srunner_create(s);
884 srunner_set_log(sr, BUILDDIR"/test-clamd.log");
885 srunner_run_all(sr, CK_NORMAL);
886 nf = srunner_ntests_failed(sr);
887 srunner_free(sr);
888 return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
889}
890
891#else
892int main(void)
893{
894 puts("\n*** Check version too old, clamd tests not run!\n");
895 /* tell automake the test was skipped */
896 return 77;
897}
898#endif
Note: See TracBrowser for help on using the repository browser.