Browse code

add more unit tests. Make handling of old-style commands compatible with old clamd: if they have a \n that will delimit the command. If multiscan of a single file encounters errors, don't reply OK too.

git-svn: trunk@4796

Török Edvin authored on 2009/02/17 03:26:58
Showing 4 changed files
... ...
@@ -1,3 +1,9 @@
1
+Mon Feb 16 14:08:50 EET 2009 (edwin)
2
+------------------------------------
3
+ * clamd/server-th.c, clamd/session.c, unit_tests/check_clamd.c: add
4
+ more unit tests.  Make handling of old-style commands compatible
5
+ with old clamd: if they have a \n that will delimit the command.
6
+
1 7
 Mon Feb 16 17:59:57 CET 2009 (tk)
2 8
 ---------------------------------
3 9
  * libclamav, sigtool: fix handling of long signatures (bb#1395)
... ...
@@ -240,6 +240,12 @@ static struct cl_engine *reload_db(struct cl_engine *engine, unsigned int dbopti
240 240
     return engine;
241 241
 }
242 242
 
243
+/*
244
+ * zCOMMANDS are delimited by \0
245
+ * nCOMMANDS are delimited by \n
246
+ * Old-style non-prefixed commands are one packet, optionally delimited by \n,
247
+ * with trailing \r|\n ignored
248
+ */
243 249
 static const char *get_cmd(struct fd_buf *buf, size_t off, size_t *len, char *term)
244 250
 {
245 251
     unsigned char *pos;
... ...
@@ -269,10 +275,18 @@ static const char *get_cmd(struct fd_buf *buf, size_t off, size_t *len, char *te
269 269
 	    return buf->buffer + off + 1;
270 270
 	default:
271 271
 	    /* one packet = one command */
272
-	    *len = buf->off - off;
273
-	    buf->buffer[buf->off] = '\0';
274
-	    cli_chomp(buf->buffer + off);
275
-	    return buf->buffer + off;
272
+	    if (off)
273
+		return NULL;
274
+	    pos = memchr(buf->buffer, '\n', buf->off);
275
+	    if (pos) {
276
+		*len = pos - buf->buffer;
277
+		*pos = '\0';
278
+	    } else {
279
+		*len = buf->off;
280
+		buf->buffer[buf->off] = '\0';
281
+	    }
282
+	    cli_chomp(buf->buffer);
283
+	    return buf->buffer;
276 284
     }
277 285
 }
278 286
 
... ...
@@ -832,6 +846,7 @@ int recvloop_th(int *socketds, unsigned nsockets, struct cl_engine *engine, unsi
832 832
 		conn.quota = buf->quota;
833 833
 		conn.filename = buf->dumpname;
834 834
 		conn.mode = buf->mode;
835
+		conn.term = buf->term;
835 836
 		/* Parse & dispatch commands */
836 837
 		while ((conn.mode == MODE_COMMAND) &&
837 838
 		       (cmd = get_cmd(buf, pos, &cmdlen, &term)) != NULL) {
... ...
@@ -980,8 +995,9 @@ int recvloop_th(int *socketds, unsigned nsockets, struct cl_engine *engine, unsi
980 980
 			    if (buf->chunksize > buf->quota) {
981 981
 				logg("^INSTREAM: Size limit reached, (requested: %lu, max: %lu)\n", 
982 982
 				     (unsigned long)buf->chunksize, (unsigned long)buf->quota);
983
-				conn_reply_error(&conn, "INSTREAM size limit exceeded. ERROR");
983
+				conn_reply_error(&conn, "INSTREAM size limit exceeded.");
984 984
 				error = 1;
985
+				break;
985 986
 			    } else {
986 987
 				buf->quota -= buf->chunksize;
987 988
 			    }
... ...
@@ -227,7 +227,7 @@ int command(client_conn_t *conn, int *virus)
227 227
 	    /* callback freed it */
228 228
 	    conn->filename = NULL;
229 229
 	    *virus = scandata.infected;
230
-	    return 0;
230
+	    return scandata.errors > 0 ? scandata.errors : 0;
231 231
 	case COMMAND_FILDES:
232 232
 	    thrmgr_setactivetask(NULL, "FILDES");
233 233
 #ifdef HAVE_FD_PASSING
... ...
@@ -56,16 +56,77 @@ static void conn_teardown(void)
56 56
 #define SCANFILE BUILDDIR"/../test/clam.exe"
57 57
 #define FOUNDREPLY SCANFILE": ClamAV-Test-File.UNOFFICIAL FOUND"
58 58
 
59
+/* some clean file */
60
+#define CLEANFILE SRCDIR"/Makefile.am"
61
+#define CLEANREPLY CLEANFILE": OK"
62
+#define UNKNOWN_REPLY "UNKNOWN COMMAND"
63
+
64
+#define NONEXISTENT "/nonexistent\vfilename"
65
+#define NONEXISTENT_REPLY NONEXISTENT": lstat() failed: No such file or directory. ERROR"
66
+
67
+#define ACCDENIED BUILDDIR"/accdenied"
68
+#define ACCDENIED_REPLY ACCDENIED": Access denied. ERROR"
69
+
70
+static void commands_setup(void)
71
+{
72
+    const char *nonempty = "NONEMPTYFILE";
73
+    int fd = open(NONEXISTENT, O_RDONLY);
74
+    int rc;
75
+    if (fd != -1) close(fd);
76
+    fail_unless(fd == -1, "Nonexistent file exists!\n");
77
+
78
+    fd = open(ACCDENIED, O_CREAT | O_WRONLY, S_IWUSR);
79
+    fail_unless_fmt(fd != -1,
80
+		    "Failed to create file for access denied tests: %s\n", strerror(errno));
81
+
82
+
83
+    fail_unless_fmt(fchmod(fd,  S_IWUSR) != -1,
84
+		    "Failed to chmod: %s\n", strerror(errno));
85
+    /* must not be empty file */
86
+    fail_unless_fmt(write(fd, nonempty, strlen(nonempty)) == strlen(nonempty),
87
+		    "Failed to write into testfile: %s\n", strerror(errno));
88
+    close(fd);
89
+}
90
+
91
+static void commands_teardown(void)
92
+{
93
+    int rc = unlink(ACCDENIED);
94
+    fail_unless_fmt(rc != -1, "Failed to unlink access denied testfile: %s\n", strerror(errno));
95
+}
96
+
59 97
 static struct basic_test {
60 98
     const char *command;
99
+    const char *extra;
61 100
     const char *reply;
62 101
 } basic_tests[] = {
63
-    {"PING", "PONG"},
64
-    {"RELOAD","RELOADING"},
65
-    {"VERSION", "ClamAV "REPO_VERSION""VERSION_SUFFIX},
66
-    {"SCAN "SCANFILE, FOUNDREPLY},
67
-    {"CONTSCAN "SCANFILE, FOUNDREPLY},
68
-    {"MULTISCAN "SCANFILE, FOUNDREPLY},
102
+    {"PING", NULL, "PONG"},
103
+    {"RELOAD", NULL, "RELOADING"},
104
+    {"VERSION", NULL, "ClamAV "REPO_VERSION""VERSION_SUFFIX},
105
+    {"SCAN "SCANFILE, NULL, FOUNDREPLY},
106
+    {"SCAN "CLEANFILE, NULL, CLEANREPLY},
107
+    {"CONTSCAN "SCANFILE, NULL, FOUNDREPLY},
108
+    {"CONTSCAN "CLEANFILE, NULL, CLEANREPLY},
109
+    {"MULTISCAN "SCANFILE, NULL, FOUNDREPLY},
110
+    {"MULTISCAN "CLEANFILE, NULL, CLEANREPLY},
111
+    /* unknown commnads */
112
+    {"RANDOM", NULL, UNKNOWN_REPLY},
113
+    /* commands invalid as first */
114
+    {"END", NULL, UNKNOWN_REPLY},
115
+    /* commands for nonexistent files */
116
+    {"SCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY},
117
+    {"CONTSCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY},
118
+    {"MULTISCAN "NONEXISTENT, NULL, NONEXISTENT_REPLY},
119
+    /* commands for access denied files */
120
+    {"SCAN "ACCDENIED, NULL, ACCDENIED_REPLY},
121
+    {"CONTSCAN "ACCDENIED, NULL, ACCDENIED_REPLY},
122
+    {"MULTISCAN "ACCDENIED, NULL, ACCDENIED_REPLY},
123
+    /* commands with invalid/missing arguments */
124
+    {"SCAN", NULL, UNKNOWN_REPLY},
125
+    {"CONTSCAN", NULL, UNKNOWN_REPLY},
126
+    {"MULTISCAN", NULL, UNKNOWN_REPLY},
127
+    /* commands with invalid data */
128
+    {"INSTREAM", "\xff\xff\xff\xff", "INSTREAM size limit exceeded. ERROR"}, /* too big chunksize */
129
+    {"FILDES", "X", "No file descriptor received. ERROR"}, /* FILDES w/o ancillary data */
69 130
 };
70 131
 
71 132
 static void *recvfull(int sd, size_t *len)
... ...
@@ -91,13 +152,18 @@ static void *recvfull(int sd, size_t *len)
91 91
     return buf;
92 92
 }
93 93
 
94
-static void test_command(const char *cmd, size_t len, const char *expect, size_t expect_len)
94
+static void test_command(const char *cmd, size_t len, const char *extra, const char *expect, size_t expect_len)
95 95
 {
96 96
     void *recvdata;
97 97
     ssize_t rc;
98 98
     rc = send(sockd, cmd, len, 0);
99 99
     fail_unless_fmt((size_t)rc == len, "Unable to send(): %s\n", strerror(errno));
100 100
 
101
+    if (extra) {
102
+	rc = send(sockd, extra, strlen(extra), 0);
103
+	fail_unless_fmt((size_t)rc == strlen(extra), "Unable to send(): %s\n", strerror(errno));
104
+    }
105
+
101 106
     recvdata = recvfull(sockd, &len);
102 107
 
103 108
     fail_unless_fmt(len == expect_len, "Reply has wrong size: %lu, expected %lu, reply: %s\n",
... ...
@@ -112,22 +178,46 @@ START_TEST (test_basic_commands)
112 112
 {
113 113
     struct basic_test *test = &basic_tests[_i];
114 114
     char nsend[BUFSIZ], nreply[BUFSIZ];
115
-    /* send the command the "old way" */
116
-    conn_setup();
117
-    snprintf(nreply, sizeof(nreply), "%s\n", test->reply);
118
-    test_command(test->command, strlen(test->command), nreply, strlen(nreply));
119
-    conn_teardown();
120 115
 
121 116
     /* send nCOMMAND */
122
-    conn_setup();
117
+    snprintf(nreply, sizeof(nreply), "%s\n", test->reply);
123 118
     snprintf(nsend, sizeof(nsend), "n%s\n", test->command);
124
-    test_command(nsend, strlen(nsend), nreply, strlen(nreply));
119
+    conn_setup();
120
+    test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
125 121
     conn_teardown();
126 122
 
127 123
     /* send zCOMMAND */
128
-    conn_setup();
129 124
     snprintf(nsend, sizeof(nsend), "z%s", test->command);
130
-    test_command(nsend, strlen(nsend)+1, test->reply, strlen(test->reply)+1);
125
+    conn_setup();
126
+    test_command(nsend, strlen(nsend)+1, test->extra, test->reply, strlen(test->reply)+1);
127
+    conn_teardown();
128
+}
129
+END_TEST
130
+
131
+START_TEST (test_compat_commands)
132
+{
133
+    /* test sending the command the "old way" */
134
+    struct basic_test *test = &basic_tests[_i];
135
+    char nsend[BUFSIZ], nreply[BUFSIZ];
136
+
137
+    snprintf(nreply, sizeof(nreply), "%s\n", test->reply);
138
+    if (!test->extra) {
139
+	/* one command = one packet, no delimiter */
140
+	conn_setup();
141
+	test_command(test->command, strlen(test->command), test->extra, nreply, strlen(nreply));
142
+	conn_teardown();
143
+    }
144
+
145
+    /* one packet, \n delimited command, followed by "extra" if needed */
146
+    snprintf(nsend, sizeof(nsend), "%s\n", test->command);
147
+    conn_setup();
148
+    test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
149
+    conn_teardown();
150
+
151
+    /* one packet, \r\n delimited command, followed by "extra" if needed */
152
+    snprintf(nsend, sizeof(nsend), "%s\r\n", test->command);
153
+    conn_setup();
154
+    test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
131 155
     conn_teardown();
132 156
 }
133 157
 END_TEST
... ...
@@ -204,12 +294,15 @@ START_TEST (tc_instream)
204 204
 }
205 205
 END_TEST
206 206
 
207
+
207 208
 static Suite *test_clamd_suite(void)
208 209
 {
209 210
     Suite *s = suite_create("clamd");
210 211
     TCase *tc_commands = tcase_create("clamd commands");
211 212
     suite_add_tcase(s, tc_commands);
213
+    tcase_add_unchecked_fixture(tc_commands, commands_setup, commands_teardown);
212 214
     tcase_add_loop_test(tc_commands, test_basic_commands, 0, sizeof(basic_tests)/sizeof(basic_tests[0]));
215
+    tcase_add_loop_test(tc_commands, test_compat_commands, 0, sizeof(basic_tests)/sizeof(basic_tests[0]));
213 216
     tcase_add_test(tc_commands, tc_instream);
214 217
     tcase_add_test(tc_commands, tc_stats);
215 218
     return s;