Browse code

Fix clamd INSTREAM handling inside IDSESSION (bb #1564).

git-svn: trunk@5049

Török Edvin authored on 2009/04/20 23:26:48
Showing 3 changed files
... ...
@@ -1,3 +1,8 @@
1
+Mon Apr 20 17:20:27 EEST 2009 (edwin)
2
+-------------------------------------
3
+ * clamd/server-th.c, unit_tests/check_clamd.c: Fix clamd INSTREAM
4
+ handling inside IDSESSION (bb #1564).
5
+
1 6
 Fri Apr 17 18:23:44 CEST 2009 (acab)
2 7
 ------------------------------------
3 8
  * clamav-milter/clamav-milter.c: spam syslog with start events (bb#1557)
... ...
@@ -641,7 +641,7 @@ static int handle_stream(client_conn_t *conn, struct fd_buf *buf, const struct o
641 641
 		    pos = 4;
642 642
 		    memmove (buf->buffer, &buf->buffer[pos], buf->off - pos);
643 643
 		    buf->off -= pos;
644
-		    pos = 0;
644
+		    *ppos = 0;
645 645
 		    buf->id++;
646 646
 		    return 0;
647 647
 		}
... ...
@@ -651,6 +651,7 @@ static int handle_stream(client_conn_t *conn, struct fd_buf *buf, const struct o
651 651
 		     (unsigned long)buf->chunksize, (unsigned long)buf->quota);
652 652
 		conn_reply_error(conn, "INSTREAM size limit exceeded.");
653 653
 		*error = 1;
654
+		*ppos = pos;
654 655
 		return -1;
655 656
 	    } else {
656 657
 		buf->quota -= buf->chunksize;
... ...
@@ -153,42 +153,52 @@ static void commands_teardown(void)
153 153
 #define VERSION_REPLY "ClamAV "REPO_VERSION""VERSION_SUFFIX
154 154
 
155 155
 #define VCMDS_REPLY VERSION_REPLY"| COMMANDS: SCAN QUIT RELOAD PING CONTSCAN VERSIONCOMMANDS VERSION STREAM END SHUTDOWN MULTISCAN FILDES STATS IDSESSION INSTREAM"
156
+
157
+enum idsession_support {
158
+    IDS_OK, /* accepted */
159
+    IDS_REJECT,
160
+    /* after sending this message, clamd will reply,  then accept 
161
+     * no further commands, but still reply to all active commands */
162
+    IDS_END /* the END command */
163
+};
164
+
156 165
 static struct basic_test {
157 166
     const char *command;
158 167
     const char *extra;
159 168
     const char *reply;
160 169
     int support_old;
161 170
     int skiproot;
171
+    enum idsession_support ids;
162 172
 } basic_tests[] = {
163
-    {"PING", NULL, "PONG", 1, 0},
164
-    {"RELOAD", NULL, "RELOADING", 1, 0},
165
-    {"VERSION", NULL, VERSION_REPLY, 1, 0},
166
-    {"VERSIONCOMMANDS", NULL, VCMDS_REPLY, 0, 0},
167
-    {"SCAN "SCANFILE, NULL, FOUNDREPLY, 1, 0},
168
-    {"SCAN "CLEANFILE, NULL, CLEANREPLY, 1, 0},
169
-    {"CONTSCAN "SCANFILE, NULL, FOUNDREPLY, 1, 0},
170
-    {"CONTSCAN "CLEANFILE, NULL, CLEANREPLY, 1, 0},
171
-    {"MULTISCAN "SCANFILE, NULL, FOUNDREPLY, 1, 0},
172
-    {"MULTISCAN "CLEANFILE, NULL, CLEANREPLY, 1, 0},
173
+    {"PING", NULL, "PONG", 1, 0, IDS_OK},
174
+    {"RELOAD", NULL, "RELOADING", 1, 0, IDS_REJECT},
175
+    {"VERSION", NULL, VERSION_REPLY, 1, 0, IDS_OK},
176
+    {"VERSIONCOMMANDS", NULL, VCMDS_REPLY, 0, 0, IDS_REJECT},
177
+    {"SCAN "SCANFILE, NULL, FOUNDREPLY, 1, 0, IDS_OK},
178
+    {"SCAN "CLEANFILE, NULL, CLEANREPLY, 1, 0, IDS_OK},
179
+    {"CONTSCAN "SCANFILE, NULL, FOUNDREPLY, 1, 0, IDS_REJECT},
180
+    {"CONTSCAN "CLEANFILE, NULL, CLEANREPLY, 1, 0, IDS_REJECT},
181
+    {"MULTISCAN "SCANFILE, NULL, FOUNDREPLY, 1, 0, IDS_REJECT},
182
+    {"MULTISCAN "CLEANFILE, NULL, CLEANREPLY, 1, 0, IDS_REJECT},
173 183
     /* unknown commnads */
174
-    {"RANDOM", NULL, UNKNOWN_REPLY, 1, 0},
184
+    {"RANDOM", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
175 185
     /* commands invalid as first */
176
-    {"END", NULL, UNKNOWN_REPLY, 1, 0},
186
+    {"END", NULL, UNKNOWN_REPLY, 1, 0, IDS_END},
177 187
     /* commands for nonexistent files */
178
-    {"SCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0},
179
-    {"CONTSCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0},
180
-    {"MULTISCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0},
188
+    {"SCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_OK},
189
+    {"CONTSCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_REJECT},
190
+    {"MULTISCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_REJECT},
181 191
     /* commands for access denied files */
182
-    {"SCAN "ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1},
183
-    {"CONTSCAN "ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1},
184
-    {"MULTISCAN "ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1},
192
+    {"SCAN "ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_OK},
193
+    {"CONTSCAN "ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_REJECT},
194
+    {"MULTISCAN "ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_REJECT},
185 195
     /* commands with invalid/missing arguments */
186
-    {"SCAN", NULL, UNKNOWN_REPLY, 1, 0},
187
-    {"CONTSCAN", NULL, UNKNOWN_REPLY, 1, 0},
188
-    {"MULTISCAN", NULL, UNKNOWN_REPLY, 1, 0},
196
+    {"SCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
197
+    {"CONTSCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
198
+    {"MULTISCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
189 199
     /* commands with invalid data */
190
-    {"INSTREAM", "\xff\xff\xff\xff", "INSTREAM size limit exceeded. ERROR", 0, 0}, /* too big chunksize */
191
-    {"FILDES", "X", "No file descriptor received. ERROR", 1, 0}, /* FILDES w/o ancillary data */
200
+    {"INSTREAM", "\xff\xff\xff\xff", "INSTREAM size limit exceeded. ERROR", 0, 0, IDS_REJECT}, /* too big chunksize */
201
+    {"FILDES", "X", "No file descriptor received. ERROR", 1, 0, IDS_REJECT}, /* FILDES w/o ancillary data */
192 202
 };
193 203
 
194 204
 static void *recvpartial(int sd, size_t *len, int partial)
... ...
@@ -204,7 +214,6 @@ static void *recvpartial(int sd, size_t *len, int partial)
204 204
 	    buf = realloc(buf, *len);
205 205
 	    fail_unless(!!buf, "Cannot realloc buffer\n");
206 206
 	}
207
-
208 207
 	rc = recv(sd, buf + off, BUFSIZ, 0);
209 208
 	fail_unless_fmt(rc != -1, "recv() failed: %s\n", strerror(errno));
210 209
 	off += rc;
... ...
@@ -308,6 +317,7 @@ END_TEST
308 308
 #endif
309 309
 
310 310
 #define EXPECT_INSTREAM "stream: ClamAV-Test-File.UNOFFICIAL FOUND\n"
311
+#define EXPECT_INSTREAM0 "stream: ClamAV-Test-File.UNOFFICIAL FOUND"
311 312
 
312 313
 #define STATS_REPLY "POOLS: 1\n\nSTATE: VALID PRIMARY\n"
313 314
 START_TEST (test_stats)
... ...
@@ -335,16 +345,11 @@ START_TEST (test_stats)
335 335
 }
336 336
 END_TEST
337 337
 
338
-START_TEST (test_instream)
338
+static size_t prepare_instream(char *buf, size_t off, size_t buflen)
339 339
 {
340
-    int fd, nread, rc;
341 340
     struct stat stbuf;
341
+    int fd, nread;
342 342
     uint32_t chunk;
343
-    void *recvdata;
344
-    size_t len, expect_len;
345
-    char buf[4096] = "nINSTREAM\n";
346
-    size_t off = strlen(buf);
347
-
348 343
     fail_unless_fmt(stat(SCANFILE, &stbuf) != -1, "stat failed for %s: %s", SCANFILE, strerror(errno));
349 344
 
350 345
     fd = open(SCANFILE, O_RDONLY);
... ...
@@ -353,14 +358,26 @@ START_TEST (test_instream)
353 353
     chunk = htonl(stbuf.st_size);
354 354
     memcpy(&buf[off], &chunk, sizeof(chunk));
355 355
     off += 4;
356
-    nread = read(fd, &buf[off], sizeof(buf)-off-4);
357
-    fail_unless_fmt(nread == stbuf.st_size, "read failed: %s\n", strerror(errno));
356
+    nread = read(fd, &buf[off], buflen-off-4);
357
+    fail_unless_fmt(nread == stbuf.st_size, "read failed: %d != %d, %s\n", nread, stbuf.st_size, strerror(errno));
358 358
     off += nread;
359 359
     buf[off++]=0;
360 360
     buf[off++]=0;
361 361
     buf[off++]=0;
362 362
     buf[off++]=0;
363 363
     close(fd);
364
+    return off;
365
+}
366
+
367
+START_TEST (test_instream)
368
+{
369
+    void *recvdata;
370
+    size_t len, expect_len;
371
+    char buf[4096] = "nINSTREAM\n";
372
+    size_t off = strlen(buf);
373
+    int rc;
374
+
375
+    off = prepare_instream(buf, off, sizeof(buf));
364 376
 
365 377
     conn_setup();
366 378
     fail_unless((size_t)send(sockd, buf, off, 0) == off, "send() failed: %s\n", strerror(errno));
... ...
@@ -713,6 +730,110 @@ START_TEST (test_stream)
713 713
 }
714 714
 END_TEST
715 715
 
716
+#define END_CMD "zEND"
717
+#define INSTREAM_CMD "zINSTREAM"
718
+static void test_idsession_commands(int split, int instream)
719
+{
720
+    char buf[20480];
721
+    size_t i, len=0, j=0;
722
+    char *recvdata;
723
+    char *p = buf;
724
+    const char *replies[2 + sizeof(basic_tests)/sizeof(basic_tests[0])];
725
+
726
+    /* test all commands that must be accepted inside an IDSESSION */
727
+    for (i=0;i < sizeof(basic_tests)/sizeof(basic_tests[0]); i++) {
728
+	const struct basic_test *test = &basic_tests[i];
729
+	if (test->ids == IDS_OK) {
730
+	    fail_unless(p+strlen(test->command)+2 < buf+sizeof(buf), "Buffer too small");
731
+	    *p++ = 'z';
732
+	    strcpy(p, test->command);
733
+	    p += strlen(test->command);
734
+	    *p++ = '\0';
735
+	    if (test->extra) {
736
+		fail_unless(p+strlen(test->extra) < buf+sizeof(buf), "Buffer too small");
737
+		strcpy(p, test->extra);
738
+		p += strlen(test->extra);
739
+	    }
740
+	    replies[j++] = test->reply;
741
+	}
742
+	if (instream && test->ids == IDS_END) {
743
+	    uint32_t chunk;
744
+	    int fd;
745
+	    /* IDS_END - in middle of other commands, perfect for inserting
746
+	     * INSTREAM */
747
+	    fail_unless(p+sizeof(INSTREAM_CMD)+544< buf+sizeof(buf), "Buffer too small");
748
+	    memcpy(p, INSTREAM_CMD, sizeof(INSTREAM_CMD));
749
+	    p += sizeof(INSTREAM_CMD);
750
+	    p += prepare_instream(p, 0, 552);
751
+	    replies[j++] = EXPECT_INSTREAM0;
752
+	    fail_unless(p+sizeof(INSTREAM_CMD)+16388< buf+sizeof(buf), "Buffer too small");
753
+	    memcpy(p, INSTREAM_CMD, sizeof(INSTREAM_CMD));
754
+	    p += sizeof(INSTREAM_CMD);
755
+	    chunk=htonl(16384);
756
+	    memcpy(p, &chunk, 4);
757
+	    p+=4;
758
+	    memset(p, 0x5a, 16384);
759
+	    p += 16384;
760
+	    *p++='\0';
761
+	    *p++='\0';
762
+	    *p++='\0';
763
+	    *p++='\0';
764
+	    replies[j++] = "stream: OK";
765
+	}
766
+    }
767
+    fail_unless(p+sizeof(END_CMD) < buf+sizeof(buf), "Buffer too small");
768
+    memcpy(p, END_CMD, sizeof(END_CMD));
769
+    p += sizeof(END_CMD);
770
+
771
+    if (split) {
772
+	/* test corner-cases: 1-byte sends */
773
+	for (i=0;i<p-buf;i++)
774
+	    fail_unless((size_t)send(sockd, &buf[i], 1, 0) == 1, "send() failed: %u, %s\n", i, strerror(errno));
775
+    } else {
776
+	fail_unless((size_t)send(sockd, buf, p-buf, 0) == p-buf,"send() failed: %s\n", strerror(errno));
777
+    }
778
+    recvdata = recvfull(sockd, &len);
779
+    p = recvdata;
780
+    for (i=0;i < sizeof(basic_tests)/sizeof(basic_tests[0]); i++) {
781
+	const struct basic_test *test = &basic_tests[i];
782
+	if (test->ids == IDS_OK) {
783
+	    unsigned id;
784
+	    char *q = strchr(p, ':');
785
+	    fail_unless_fmt(!!q, "No ID in reply: %s\n", p);
786
+	    *q = '\0';
787
+	    fail_unless_fmt(sscanf(p, "%u", &id) == 1,"Wrong ID in reply: %s\n", p);
788
+	    fail_unless(id > 0, "ID cannot be zero");
789
+	    fail_unless_fmt(id <= j, "ID too big: %u, max: %u\n", id, j);
790
+	    q += 2;
791
+	    fail_unless_fmt(!strcmp(q, replies[id-1]),
792
+			    "Wrong ID reply for ID %u: %s, expected %s\n",
793
+			    id,
794
+			    q, replies[id-1]);
795
+	    p = q + strlen(q)+1;
796
+	}
797
+    }
798
+    free(recvdata);
799
+    conn_teardown();
800
+}
801
+
802
+#define ID_CMD "zIDSESSION"
803
+START_TEST(test_idsession)
804
+{
805
+    conn_setup();
806
+    fail_unless_fmt((size_t)send(sockd, ID_CMD, sizeof(ID_CMD), 0) == sizeof(ID_CMD),
807
+		    "send() failed: %s\n", strerror(errno));
808
+    test_idsession_commands(0, 0);
809
+    conn_setup();
810
+    fail_unless_fmt((size_t)send(sockd, ID_CMD, sizeof(ID_CMD), 0) == sizeof(ID_CMD),
811
+		    "send() failed: %s\n", strerror(errno));
812
+    test_idsession_commands(1, 0);
813
+    conn_setup();
814
+    fail_unless_fmt((size_t)send(sockd, ID_CMD, sizeof(ID_CMD), 0) == sizeof(ID_CMD),
815
+		    "send() failed: %s\n", strerror(errno));
816
+    test_idsession_commands(0, 1);
817
+}
818
+END_TEST
819
+
716 820
 static Suite *test_clamd_suite(void)
717 821
 {
718 822
     Suite *s = suite_create("clamd");
... ...
@@ -728,6 +849,7 @@ static Suite *test_clamd_suite(void)
728 728
     tcase_add_test(tc_commands, test_stats);
729 729
     tcase_add_test(tc_commands, test_instream);
730 730
     tcase_add_test(tc_commands, test_stream);
731
+    tcase_add_test(tc_commands, test_idsession);
731 732
     tc_stress = tcase_create("clamd stress test");
732 733
     suite_add_tcase(s, tc_stress);
733 734
     tcase_set_timeout(tc_stress, 20);