Browse code

introduce timeouts for send(), this is needed for IDSESSION to work reliably, since a buggy client implementation may get stuck in send(), and then clamd gets stuck in send() -> deadlock. To avoid this we need nonblocking sockets, and (low) timeouts on send. Add more tests for clamd protocol, including a stress test for IDSESSION.

git-svn: trunk@4798

Török Edvin authored on 2009/02/17 03:27:08
Showing 9 changed files
... ...
@@ -1,3 +1,12 @@
1
+Mon Feb 16 20:36:01 EET 2009 (edwin)
2
+------------------------------------
3
+ * clamd/, libclamav/, shared/, unit_tests/: introduce timeouts for
4
+ send(), this is needed for IDSESSION to work reliably, since a buggy
5
+ client implementation may get stuck in send(), and then clamd gets
6
+ stuck in send() -> deadlock. To avoid this we need nonblocking
7
+ sockets, and (low) timeouts on send.  Add more tests for clamd protocol,
8
+ including a stress test for IDSESSION.
9
+
1 10
 Mon Feb 16 16:15:21 EET 2009 (edwin)
2 11
 ------------------------------------
3 12
  * unit_tests/check_clamd.c: test for FILDES
... ...
@@ -190,7 +190,8 @@ int scan_callback(struct stat *sb, char *filename, const char *msg, enum cli_ftw
190 190
     }
191 191
 
192 192
     if (access(filename, R_OK)) {
193
-	conn_reply(scandata->conn, filename, "Access denied.", "ERROR");
193
+	if (conn_reply(scandata->conn, filename, "Access denied.", "ERROR") == -1)
194
+	    return CL_ETIMEOUT;
194 195
 	logg("*Access denied: %s\n", filename);
195 196
 	scandata->errors++;
196 197
 	free(filename);
... ...
@@ -202,14 +203,21 @@ int scan_callback(struct stat *sb, char *filename, const char *msg, enum cli_ftw
202 202
     ret = cl_scanfile(filename, &virname, &scandata->scanned, scandata->engine, scandata->options);
203 203
     thrmgr_setactivetask(NULL, NULL);
204 204
 
205
+    if (thrmgr_group_need_terminate(scandata->conn->group)) {
206
+	logg("*Client disconnected while scanjob was active\n");
207
+	return ret == CL_ETIMEOUT ? ret : CL_BREAK;
208
+    }
209
+
205 210
     if (ret == CL_VIRUS) {
206 211
 	scandata->infected++;
207
-	conn_reply(scandata->conn, filename, virname, "FOUND");
212
+	if (conn_reply(scandata->conn, filename, virname, "FOUND") == -1)
213
+	    return CL_ETIMEOUT;
208 214
 	logg("~%s: %s FOUND\n", filename, virname);
209 215
 	virusaction(filename, virname, scandata->opts);
210 216
     } else if (ret != CL_CLEAN) {
211 217
 	scandata->errors++;
212
-	conn_reply(scandata->conn, filename, cl_strerror(ret), "ERROR");
218
+	if (conn_reply(scandata->conn, filename, cl_strerror(ret), "ERROR") == -1)
219
+	    return CL_ETIMEOUT;
213 220
 	logg("~%s: %s ERROR\n", filename, cl_strerror(ret));
214 221
     } else if (logok) {
215 222
 	logg("~%s: OK\n", filename);
... ...
@@ -219,10 +227,6 @@ int scan_callback(struct stat *sb, char *filename, const char *msg, enum cli_ftw
219 219
     if(ret == CL_EMEM) /* stop scanning */
220 220
 	return ret;
221 221
 
222
-    if (thrmgr_group_need_terminate(scandata->conn->group)) {
223
-	logg("^Client disconnected while scanjob was active\n");
224
-	return CL_BREAK;
225
-    }
226 222
     if (type == TYPE_SCAN) {
227 223
 	/* virus -> break */
228 224
 	return ret;
... ...
@@ -246,8 +250,9 @@ int scanfd(const int fd, const client_conn_t *conn, unsigned long int *scanned,
246 246
 	else
247 247
 	    snprintf(fdstr, sizeof(fdstr), "fd[%d]", fd);
248 248
 	if(fstat(fd, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) {
249
-		conn_reply(conn, fdstr, "Not a regular file", "ERROR");
250 249
 		logg("%s: Not a regular file. ERROR\n", fdstr);
250
+		if (conn_reply(conn, fdstr, "Not a regular file", "ERROR") == -1)
251
+		    return CL_ETIMEOUT;
251 252
 		return -1;
252 253
 	}
253 254
 
... ...
@@ -255,15 +260,23 @@ int scanfd(const int fd, const client_conn_t *conn, unsigned long int *scanned,
255 255
 	ret = cl_scandesc(fd, &virname, scanned, engine, options);
256 256
 	thrmgr_setactivetask(NULL, NULL);
257 257
 
258
+	if (thrmgr_group_need_terminate(conn->group)) {
259
+	    logg("*Client disconnected while scanjob was active\n");
260
+	    return ret == CL_ETIMEOUT ? ret : CL_BREAK;
261
+	}
262
+
258 263
 	if(ret == CL_VIRUS) {
259
-		conn_reply(conn, fdstr, virname, "FOUND");
264
+		if (conn_reply(conn, fdstr, virname, "FOUND") == -1)
265
+		    ret = CL_ETIMEOUT;
260 266
 		logg("%s: %s FOUND\n", fdstr, virname);
261 267
 		virusaction(fdstr, virname, opts);
262 268
 	} else if(ret != CL_CLEAN) {
263
-		conn_reply(conn, fdstr, cl_strerror(ret), "ERROR");
269
+		if (conn_reply(conn, fdstr, cl_strerror(ret), "ERROR") == -1)
270
+		    ret = CL_ETIMEOUT;
264 271
 		logg("%s: %s ERROR\n", fdstr, cl_strerror(ret));
265 272
 	} else {
266
-		conn_reply_single(conn, fdstr, "OK");
273
+		if (conn_reply_single(conn, fdstr, "OK") == CL_ETIMEOUT)
274
+		    ret = CL_ETIMEOUT;
267 275
 		if(logok)
268 276
 			logg("%s: OK\n", fdstr);
269 277
 	}
... ...
@@ -43,6 +43,7 @@
43 43
 #include <unistd.h>
44 44
 #endif
45 45
 
46
+#include <fcntl.h>
46 47
 #include <arpa/inet.h>
47 48
 #include "libclamav/clamav.h"
48 49
 
... ...
@@ -358,7 +359,20 @@ static void *acceptloop_th(void *arg)
358 358
 	    pthread_mutex_unlock(&exit_mutex);
359 359
 
360 360
 	    if (new_sd >= 0) {
361
-		int ret;
361
+		int ret, flags;
362
+
363
+#ifdef F_GETFL
364
+		flags = fcntl(new_sd, F_GETFL, 0);
365
+		if (flags != -1) {
366
+		    if (fcntl(new_sd, F_SETFL, flags | O_NONBLOCK) == -1) {
367
+			logg("^Can't set socket to nonblocking mode, errno %d\n",
368
+			     errno);
369
+		    }
370
+		} else {
371
+			logg("^Can't get socket flags, errno %d\n", errno);
372
+		}
373
+#endif
374
+
362 375
 		pthread_mutex_lock(&recv_fds->buf_mutex);
363 376
 		ret = fds_add(recv_fds, new_sd, 0);
364 377
 		pthread_mutex_unlock(&recv_fds->buf_mutex);
... ...
@@ -882,6 +896,11 @@ int recvloop_th(int *socketds, unsigned nsockets, struct cl_engine *engine, unsi
882 882
 			}
883 883
 			error = 1;
884 884
 		    }
885
+		    if (thrmgr_group_need_terminate(conn.group)) {
886
+			logg("*RECVTH: have to terminate group\n");
887
+			error = CL_ETIMEOUT;
888
+			break;
889
+		    }
885 890
 		    if (error || !conn.group || rc) {
886 891
 			if (rc && thrmgr_group_finished(conn.group, EXIT_OK)) {
887 892
 			    logg("*RECVTH: closing conn, group finished\n");
... ...
@@ -1023,7 +1042,7 @@ int recvloop_th(int *socketds, unsigned nsockets, struct cl_engine *engine, unsi
1023 1023
 			buf->off = 0;
1024 1024
 		    }
1025 1025
 		}
1026
-		if (error) {
1026
+		if (error && error != CL_ETIMEOUT) {
1027 1027
 		    conn_reply_error(&conn, "Error processing command.");
1028 1028
 		}
1029 1029
 	    }
... ...
@@ -1036,7 +1055,8 @@ int recvloop_th(int *socketds, unsigned nsockets, struct cl_engine *engine, unsi
1036 1036
 		    }
1037 1037
 		    buf->dumpfd = -1;
1038 1038
 		}
1039
-		if (thrmgr_group_terminate(buf->group)) {
1039
+		thrmgr_group_terminate(buf->group);
1040
+		if (thrmgr_group_finished(buf->group, EXIT_ERROR)) {
1040 1041
 		    logg("*RECVTH: shutting down socket after error\n");
1041 1042
 		    shutdown(buf->fd, 2);
1042 1043
 		    closesocket(buf->fd);
... ...
@@ -1055,7 +1075,8 @@ int recvloop_th(int *socketds, unsigned nsockets, struct cl_engine *engine, unsi
1055 1055
 	    for (i=0;i < fds->nfds; i++) {
1056 1056
 		if (fds->buf[i].fd == -1)
1057 1057
 		    continue;
1058
-		if (thrmgr_group_terminate(fds->buf[i].group)) {
1058
+		thrmgr_group_terminate(fds->buf[i].group);
1059
+		if (thrmgr_group_finished(fds->buf[i].group, EXIT_ERROR)) {
1059 1060
 		    shutdown(fds->buf[i].fd, 2);
1060 1061
 		    closesocket(fds->buf[i].fd);
1061 1062
 		    fds->buf[i].fd = -1;
... ...
@@ -183,7 +183,7 @@ int command(client_conn_t *conn, int *virus)
183 183
     jobgroup_t *group = NULL;
184 184
 
185 185
     if (thrmgr_group_need_terminate(conn->group)) {
186
-	logg("^Client disconnected while command was active\n");
186
+	logg("*Client disconnected while command was active\n");
187 187
 	if (conn->scanfd != -1)
188 188
 	    close(conn->scanfd);
189 189
 	return 1;
... ...
@@ -235,11 +235,14 @@ int command(client_conn_t *conn, int *virus)
235 235
 		conn_reply_error(conn, "FILDES: didn't receive file descriptor.");
236 236
 	    else {
237 237
 		ret = scanfd(conn->scanfd, conn, NULL, engine, options, opts, desc, 0);
238
-		if (ret == CL_VIRUS)
238
+		if (ret == CL_VIRUS) {
239 239
 		    *virus = 1;
240
-		if (ret == CL_EMEM) {
240
+		} else if (ret == CL_EMEM) {
241 241
 		    if(optget(opts, "ExitOnOOM")->enabled)
242 242
 			ret = -1;
243
+		} else if (ret == CL_ETIMEOUT) {
244
+			thrmgr_group_terminate(conn->group);
245
+			ret = 1;
243 246
 		} else
244 247
 		    ret = 0;
245 248
 	    }
... ...
@@ -269,11 +272,14 @@ int command(client_conn_t *conn, int *virus)
269 269
 	    return 0;
270 270
 	case COMMAND_INSTREAMSCAN:
271 271
 	    ret = scanfd(conn->scanfd, conn, NULL, engine, options, opts, desc, 1);
272
-	    if (ret == CL_VIRUS)
272
+	    if (ret == CL_VIRUS) {
273 273
 		*virus = 1;
274
-	    if (ret == CL_EMEM) {
274
+	    } else if (ret == CL_EMEM) {
275 275
 		if(optget(opts, "ExitOnOOM")->enabled)
276 276
 		    ret = -1;
277
+	    } else if (ret == CL_ETIMEOUT) {
278
+		thrmgr_group_terminate(conn->group);
279
+		ret = 1;
277 280
 	    } else
278 281
 		ret = 0;
279 282
 	    if (ftruncate(conn->scanfd, 0) == -1) {
... ...
@@ -292,7 +298,8 @@ int command(client_conn_t *conn, int *virus)
292 292
 	flags |= CLI_FTW_FOLLOW_DIR_SYMLINK;
293 293
     if (optget(opts, "FollowFileSymlinks")->enabled)
294 294
 	flags |= CLI_FTW_FOLLOW_FILE_SYMLINK;
295
-    if (cli_ftw(conn->filename, flags,  maxdirrec ? maxdirrec : INT_MAX, scan_callback, &data) == CL_EMEM) 
295
+    ret = cli_ftw(conn->filename, flags,  maxdirrec ? maxdirrec : INT_MAX, scan_callback, &data);
296
+    if (ret == CL_EMEM)
296 297
 	if(optget(opts, "ExitOnOOM")->enabled)
297 298
 	    return -1;
298 299
     if (scandata.group && conn->cmdtype == COMMAND_MULTISCAN) {
... ...
@@ -304,9 +311,13 @@ int command(client_conn_t *conn, int *virus)
304 304
     }
305 305
 
306 306
     if (ok + error == total && (error != total)) {
307
-	conn_reply_single(conn, conn->filename, "OK");
307
+	if (conn_reply_single(conn, conn->filename, "OK") == -1)
308
+	    ret = CL_ETIMEOUT;
308 309
     }
309 310
     *virus = total - (ok + error);
311
+
312
+    if (ret == CL_ETIMEOUT)
313
+	thrmgr_group_terminate(conn->group);
310 314
     return error;
311 315
 }
312 316
 
... ...
@@ -828,18 +828,13 @@ int thrmgr_group_need_terminate(jobgroup_t *group)
828 828
     return ret;
829 829
 }
830 830
 
831
-/* returns
832
- *  0 - flag set, jobs still active
833
- *  1 - last job exited */
834
-int thrmgr_group_terminate(jobgroup_t *group)
831
+void thrmgr_group_terminate(jobgroup_t *group)
835 832
 {
836
-    if (thrmgr_group_finished(group, EXIT_ERROR))
837
-	return 1;
838
-    /* we are not last active job, now
839
-     * the last active job will free resources */
840
-    pthread_mutex_lock(&group->mutex);
841
-    group->force_exit = 1;
842
-    pthread_mutex_unlock(&group->mutex);
843
-
844
-    return 0;
833
+    if (group) {
834
+	/* we may not be the last active job, now
835
+	 * the last active job will free resources */
836
+	pthread_mutex_lock(&group->mutex);
837
+	group->force_exit = 1;
838
+	pthread_mutex_unlock(&group->mutex);
839
+    }
845 840
 }
... ...
@@ -101,7 +101,7 @@ int thrmgr_group_dispatch(threadpool_t *threadpool, jobgroup_t *group, void *use
101 101
 void thrmgr_group_waitforall(jobgroup_t *group, unsigned *ok, unsigned *error, unsigned *total);
102 102
 int thrmgr_group_finished(jobgroup_t *group, enum thrmgr_exit exitc);
103 103
 int thrmgr_group_need_terminate(jobgroup_t *group);
104
-int thrmgr_group_terminate(jobgroup_t *group);
104
+void thrmgr_group_terminate(jobgroup_t *group);
105 105
 jobgroup_t *thrmgr_group_new(void);
106 106
 int thrmgr_printstats(int outfd);
107 107
 void thrmgr_setactivetask(const char *filename, const char* command);
... ...
@@ -57,6 +57,7 @@ typedef enum {
57 57
     CL_ETMPDIR,
58 58
     CL_EMAP,
59 59
     CL_EMEM,
60
+    CL_ETIMEOUT,
60 61
 
61 62
     /* internal (not reported outside libclamav) */
62 63
     CL_BREAK,
... ...
@@ -47,6 +47,10 @@
47 47
 #include <sys/types.h>
48 48
 #endif
49 49
 
50
+#ifdef HAVE_SYS_SELECT_H
51
+#include <sys/select.h>
52
+#endif
53
+
50 54
 #if defined(USE_SYSLOG) && !defined(C_AIX)
51 55
 #include <syslog.h>
52 56
 #endif
... ...
@@ -88,7 +92,7 @@ short logg_syslog;
88 88
 #endif
89 89
 
90 90
 short int mprintf_disabled = 0, mprintf_verbose = 0, mprintf_quiet = 0,
91
-	  mprintf_stdout = 0, mprintf_nowarn = 0;
91
+	  mprintf_stdout = 0, mprintf_nowarn = 0, mprintf_send_timeout = 100;
92 92
 
93 93
 #define ARGLEN(args, str, len)			    \
94 94
 {						    \
... ...
@@ -127,7 +131,7 @@ int mdprintf(int desc, const char *str, ...)
127 127
 {
128 128
 	va_list args;
129 129
 	char buffer[512], *abuffer = NULL, *buff;
130
-	int bytes, len, ret;
130
+	int bytes, todo, len, ret;
131 131
 
132 132
 
133 133
     ARGLEN(args, str, len);
... ...
@@ -156,12 +160,36 @@ int mdprintf(int desc, const char *str, ...)
156 156
     if((size_t) bytes >= len)
157 157
 	bytes = len - 1;
158 158
 
159
-    ret = send(desc, buff, bytes, 0);
159
+    todo = bytes;
160
+    while (todo > 0) {
161
+	ret = send(desc, buff, bytes, 0);
162
+	if (ret < 0) {
163
+	    struct timeval tv;
164
+	    if (errno != EWOULDBLOCK)
165
+		break;
166
+	    tv.tv_sec = 0;
167
+	    tv.tv_usec = mprintf_send_timeout*1000;
168
+	    do {
169
+		fd_set wfds;
170
+		FD_ZERO(&wfds);
171
+		FD_SET(desc, &wfds);
172
+		ret = select(desc+1, NULL, &wfds, NULL, &tv);
173
+	    } while (ret < 0 && errno == EINTR);
174
+	    if (!ret) {
175
+		/* timed out */
176
+		ret = -1;
177
+		break;
178
+	    }
179
+	} else {
180
+	    todo -= ret;
181
+	    buff += ret;
182
+	}
183
+    }
160 184
 
161 185
     if(len > sizeof(buffer))
162 186
 	free(abuffer);
163 187
 
164
-    return bytes;
188
+    return ret < 0 ? -1 : bytes;
165 189
 }
166 190
 
167 191
 void logg_close(void)
... ...
@@ -162,7 +162,7 @@ static void test_command(const char *cmd, size_t len, const char *extra, const c
162 162
 
163 163
     if (extra) {
164 164
 	rc = send(sockd, extra, strlen(extra), 0);
165
-	fail_unless_fmt((size_t)rc == strlen(extra), "Unable to send(): %s\n", strerror(errno));
165
+	fail_unless_fmt((size_t)rc == strlen(extra), "Unable to send() extra for %s: %s\n", cmd, strerror(errno));
166 166
     }
167 167
 
168 168
     recvdata = recvfull(sockd, &len);
... ...
@@ -215,11 +215,15 @@ START_TEST (test_compat_commands)
215 215
     test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
216 216
     conn_teardown();
217 217
 
218
-    /* one packet, \r\n delimited command, followed by "extra" if needed */
219
-    snprintf(nsend, sizeof(nsend), "%s\r\n", test->command);
220
-    conn_setup();
221
-    test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
222
-    conn_teardown();
218
+    if (!test->extra) {
219
+	/* FILDES won't support this, because it expects
220
+	 * strlen("FILDES\n") characters, then 1 character and the FD. */
221
+	/* one packet, \r\n delimited command, followed by "extra" if needed */
222
+	snprintf(nsend, sizeof(nsend), "%s\r\n", test->command);
223
+	conn_setup();
224
+	test_command(nsend, strlen(nsend), test->extra, nreply, strlen(nreply));
225
+	conn_teardown();
226
+    }
223 227
 }
224 228
 END_TEST
225 229
 
... ...
@@ -295,22 +299,28 @@ START_TEST (test_instream)
295 295
 }
296 296
 END_TEST
297 297
 
298
-static void tst_fildes(const char *cmd, size_t len, int fd,
299
-			const char *expect, size_t expect_len, int closefd)
298
+static int sendmsg_fd(int sockd, const char *mesg, size_t msg_len, int fd, int singlemsg)
300 299
 {
301 300
     struct msghdr msg;
302 301
     struct cmsghdr *cmsg;
303 302
     unsigned char fdbuf[CMSG_SPACE(sizeof(int))];
304
-    char dummy[] = "";
305
-    off_t pos;
303
+    char dummy[BUFSIZ];
306 304
     struct iovec iov[1];
307
-    char *recvdata, *p;
308 305
     int rc;
309 306
 
310
-    conn_setup();
311
-
312
-    iov[0].iov_base = dummy;
313
-    iov[0].iov_len = 1;
307
+    if (!singlemsg) {
308
+	/* send FILDES\n and then a single character + ancillary data */
309
+	dummy[0] = '\0';
310
+	iov[0].iov_base = dummy;
311
+	iov[0].iov_len = 1;
312
+    } else {
313
+	/* send single message with ancillary data */
314
+	fail_unless(msg_len < sizeof(dummy)-1);
315
+	memcpy(dummy, mesg, msg_len);
316
+	dummy[msg_len] = '\0';
317
+	iov[0].iov_base = dummy;
318
+	iov[0].iov_len = msg_len + 1;
319
+    }
314 320
 
315 321
     memset(&msg, 0, sizeof(msg));
316 322
     msg.msg_control = fdbuf;
... ...
@@ -324,10 +334,25 @@ static void tst_fildes(const char *cmd, size_t len, int fd,
324 324
     cmsg->cmsg_type = SCM_RIGHTS;
325 325
     *(int *)CMSG_DATA(cmsg) = fd;
326 326
 
327
-    fail_unless_fmt(write(sockd, cmd, len) == len, "Failed to write: %s\n", strerror(errno));
327
+    if (!singlemsg) {
328
+	rc = send(sockd, mesg, msg_len, 0);
329
+	if (rc == -1)
330
+	    return rc;
331
+    }
332
+
333
+    return sendmsg(sockd, &msg, 0);
334
+}
335
+
336
+static void tst_fildes(const char *cmd, size_t len, int fd,
337
+			const char *expect, size_t expect_len, int closefd, int singlemsg)
338
+{
339
+    off_t pos;
340
+    char *recvdata, *p;
341
+    int rc;
328 342
 
329
-    rc = sendmsg(sockd, &msg, 0);
330
-    fail_unless_fmt(rc != -1, "Failed to sendmsg: %s\n", strerror(errno));
343
+    conn_setup();
344
+    fail_unless_fmt(sendmsg_fd(sockd, cmd, len, fd, singlemsg) != -1,
345
+		     "Failed to sendmsg: %s\n", strerror(errno));
331 346
 
332 347
     if (closefd)
333 348
 	close(fd);
... ...
@@ -353,48 +378,110 @@ static void tst_fildes(const char *cmd, size_t len, int fd,
353 353
 #define FOUNDFDREPLY " ClamAV-Test-File.UNOFFICIAL FOUND"
354 354
 #define CLEANFDREPLY " OK"
355 355
 
356
+static struct cmds {
357
+    const char *cmd;
358
+    const char term;
359
+    const char *file;
360
+    const char *reply;
361
+} fildes_cmds[] =
362
+{
363
+    {"FILDES", '\n', SCANFILE, FOUNDFDREPLY},
364
+    {"nFILDES", '\n', SCANFILE, FOUNDFDREPLY},
365
+    {"zFILDES", '\0', SCANFILE, FOUNDFDREPLY},
366
+    {"FILDES", '\n', CLEANFILE, CLEANFDREPLY},
367
+    {"nFILDES", '\n', CLEANFILE, CLEANFDREPLY},
368
+    {"zFILDES", '\0', CLEANFILE, CLEANFDREPLY}
369
+};
370
+
356 371
 START_TEST (test_fildes)
357 372
 {
358
-    char nreply[BUFSIZ];
373
+    char nreply[BUFSIZ], nsend[BUFSIZ];
359 374
     int fd = open(SCANFILE, O_RDONLY);
375
+    int closefd;
376
+    int singlemsg;
377
+    size_t i;
378
+    const struct cmds *cmd;
379
+    size_t nreply_len, nsend_len;
380
+
381
+    switch (_i&3) {
382
+	case 0:
383
+	    closefd = 0;
384
+	    singlemsg = 0;
385
+	    break;
386
+	case 1:
387
+	    closefd = 1;
388
+	    singlemsg = 0;
389
+	    break;
390
+	case 2:
391
+	    closefd = 0;
392
+	    singlemsg = 1;
393
+	    break;
394
+	case 3:
395
+	    closefd = 1;
396
+	    singlemsg = 1;
397
+	    break;
398
+    }
360 399
 
361
-    snprintf(nreply, sizeof(nreply), "%s\n", FOUNDFDREPLY);
400
+    cmd = &fildes_cmds[_i/4];
401
+    nreply_len = snprintf(nreply, sizeof(nreply), "%s%c", cmd->reply, cmd->term);
402
+    nsend_len = snprintf(nsend, sizeof(nsend), "%s%c", cmd->cmd, cmd->term);
362 403
 
404
+    fd = open(cmd->file, O_RDONLY);
363 405
     fail_unless_fmt(fd != -1, "Failed to open: %s\n", strerror(errno));
364 406
 
365
-    tst_fildes("FILDES\n", strlen("FILDES\n"), fd, nreply, strlen(nreply), 0);
366
-    tst_fildes("nFILDES\n", strlen("nFILDES\n"), fd, nreply, strlen(nreply), 0);
367
-    tst_fildes("zFILDES", sizeof("zFILDES"), fd, FOUNDFDREPLY, sizeof(FOUNDFDREPLY), 0);
407
+    tst_fildes(nsend, nsend_len, fd, nreply, nreply_len, closefd, singlemsg);
368 408
 
369
-    close(fd);
409
+    if (!closefd) {
410
+	/* closefd: 
411
+	 *  1 - close fd right after sending
412
+	 *  0 - close fd after receiving reply */
413
+	close(fd);
414
+    }
415
+}
416
+END_TEST
370 417
 
371
-    snprintf(nreply, sizeof(nreply), "%s\n", CLEANFDREPLY);
418
+START_TEST (test_fildes_many)
419
+{
420
+    const char idsession[] = "zIDSESSION";
421
+    int dummyfd, dummycleanfd, i, killed = 0;
372 422
 
373
-    fd = open(CLEANFILE, O_RDONLY);
374
-    fail_unless_fmt(fd != -1, "Failed to open: %s\n", strerror(errno));
423
+    conn_setup();
424
+    dummyfd = open(SCANFILE, O_RDONLY);
425
+    fail_unless_fmt(dummyfd != -1, "failed to open %s: %s\n", SCANFILE, strerror(errno));
426
+
427
+    fail_unless_fmt(send(sockd, idsession, sizeof(idsession), 0) == sizeof(idsession), "send IDSESSION failed\n");
428
+    for (i=0; i < 2048; i++) {
429
+	if (sendmsg_fd(sockd, "zFILDES", sizeof("zFILDES"), dummyfd, 1) == -1) {
430
+	    killed = 1;
431
+	    break;
432
+	}
433
+    }
375 434
 
376
-    tst_fildes("FILDES\n", strlen("FILDES\n"), fd, nreply, strlen(nreply), 0);
377
-    tst_fildes("nFILDES\n", strlen("nFILDES\n"), fd, nreply, strlen(nreply), 0);
378
-    tst_fildes("zFILDES", sizeof("zFILDES"), fd, CLEANFDREPLY, sizeof(CLEANFDREPLY), 0);
435
+    fail_unless(killed, "Clamd did not kill connection when overloaded!\n");
379 436
 
380
-    snprintf(nreply, sizeof(nreply), "%s\n", FOUNDFDREPLY);
381
-    fd = open(SCANFILE, O_RDONLY);
382
-    fail_unless_fmt(fd != -1, "Failed to open: %s\n", strerror(errno));
383
-    tst_fildes("FILDES\n", strlen("FILDES\n"), fd, nreply, strlen(nreply), 1);
437
+    close(dummyfd);
438
+    conn_teardown();
384 439
 }
385 440
 END_TEST
386 441
 
387 442
 static Suite *test_clamd_suite(void)
388 443
 {
389 444
     Suite *s = suite_create("clamd");
390
-    TCase *tc_commands = tcase_create("clamd commands");
445
+    TCase *tc_commands, *tc_stress;
446
+
447
+    tc_commands = tcase_create("clamd commands");
391 448
     suite_add_tcase(s, tc_commands);
392 449
     tcase_add_unchecked_fixture(tc_commands, commands_setup, commands_teardown);
393 450
     tcase_add_loop_test(tc_commands, test_basic_commands, 0, sizeof(basic_tests)/sizeof(basic_tests[0]));
394 451
     tcase_add_loop_test(tc_commands, test_compat_commands, 0, sizeof(basic_tests)/sizeof(basic_tests[0]));
395
-    tcase_add_test(tc_commands, test_fildes);
452
+    tcase_add_loop_test(tc_commands, test_fildes, 0, 4*sizeof(fildes_cmds)/sizeof(fildes_cmds[0]));
396 453
     tcase_add_test(tc_commands, test_stats);
397 454
     tcase_add_test(tc_commands, test_instream);
455
+
456
+    tc_stress = tcase_create("clamd stress test");
457
+    suite_add_tcase(s, tc_stress);
458
+    tcase_add_test(tc_stress, test_fildes_many);
459
+
398 460
     return s;
399 461
 }
400 462