diff --git a/clamdscan/proto.c b/clamdscan/proto.c index 8c7f3b5..c85199c 100644 --- a/clamdscan/proto.c +++ b/clamdscan/proto.c @@ -346,6 +346,7 @@ struct client_parallel_data { } *ids; unsigned int action_sources; unsigned int max_action_sources; + int restarts; }; /* Sends a proper scan request to clamd and parses its replies @@ -436,6 +437,101 @@ static void free_scanids(struct client_parallel_data *c) } } +#define MAX_SESSION_RESTARTS 5 + +/* Re-establish the IDSESSION after clamd unexpectedly closed the + * connection, and re-send every outstanding (unanswered) request. + * The head of the old outstanding list is the most recently sent + * request, i.e. the best suspect for having triggered the disconnect, + * so it is logged for diagnostic purposes. + * Returns 0 on success, nonzero if the session could not be restored + * (restart budget exhausted or reconnection failed). */ +static int session_restart(struct client_parallel_data *c) +{ + static const char zIDSESSION[] = "zIDSESSION"; + struct SCANID *outstanding; + struct SCANID *cur; + unsigned int n = 0; + + if (++c->restarts > MAX_SESSION_RESTARTS) { + logg(LOGG_ERROR, "Connection to clamd lost %d times, giving up\n", + MAX_SESSION_RESTARTS); + return 1; + } + + outstanding = c->ids; + c->ids = NULL; + c->lastid = 0; + + closesocket(c->sockd); + if ((c->sockd = dconnect(clamdopts)) < 0) + return 1; + if (sendln(c->sockd, zIDSESSION, sizeof(zIDSESSION))) { + closesocket(c->sockd); + return 1; + } + + for (cur = outstanding; cur; cur = cur->next) n++; + logg(LOGG_WARNING, + "Connection to clamd lost (restart %d/%d); last request before " + "disconnect: %s; re-sending %u outstanding request(s)\n", + c->restarts, MAX_SESSION_RESTARTS, + outstanding ? outstanding->file : "(none)", n); + + while ((cur = outstanding)) { + int res = 0; + outstanding = cur->next; + switch (c->scantype) { +#ifdef HAVE_FD_PASSING + case FILDES: + res = (NULL != cur->action_source) + ? send_fdpass_fd(c->sockd, cur->action_source->scan_fd) + : send_fdpass(c->sockd, cur->file); + break; +#endif + case STREAM: + res = (NULL != cur->action_source) + ? send_stream_fd_action(c->sockd, cur->action_source->scan_fd, + cur->action_source->display_path, clamdopts) + : send_stream(c->sockd, cur->file, clamdopts); + break; + } + if (res > 0) { + /* Re-queued under a fresh id in the new session. */ + cur->id = ++c->lastid; + cur->next = c->ids; + c->ids = cur; + continue; + } + if (res == 0) { + /* Local failure (e.g. file vanished): count and drop. */ + logg(LOGG_ERROR, "Failed to re-send %s after reconnect\n", cur->file); + c->errors++; + free((void *)cur->file); + if (NULL != cur->action_source) { + action_source_close(cur->action_source); + free(cur->action_source); + if (c->action_sources > 0) + c->action_sources--; + } + free(cur); + continue; + } + /* res < 0: the new connection died as well. Stash everything + * (both the not-yet-resent tail and the already-resent entries; + * duplicates are renumbered and resent, which is harmless) and + * try again within the restart budget. */ + cur->next = outstanding; + while ((cur = outstanding)) { + outstanding = cur->next; + cur->next = c->ids; + c->ids = cur; + } + return session_restart(c); + } + return 0; +} + /* FTW callback for scanning in IDSESSION mode * Returns SUCCESS on success, CL_EXXX or BREAK on error */ static cl_error_t parallel_callback(STATBUF *sb, char *filename, const char *path, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data) @@ -494,8 +590,10 @@ static cl_error_t parallel_callback(STATBUF *sb, char *filename, const char *pat if (action) { while (c->action_sources >= c->max_action_sources) { if (dspresult(c)) { - status = CL_BREAK; - goto done; + if (session_restart(c)) { + status = CL_BREAK; + goto done; + } } } @@ -531,28 +629,42 @@ static cl_error_t parallel_callback(STATBUF *sb, char *filename, const char *pat } if (FD_ISSET(c->sockd, &rfds)) { if (dspresult(c)) { - status = CL_BREAK; - goto done; - } else - continue; + if (session_restart(c)) { + status = CL_BREAK; + goto done; + } + } + continue; } if (FD_ISSET(c->sockd, &wfds)) break; } - switch (c->scantype) { + while (1) { + switch (c->scantype) { #ifdef HAVE_FD_PASSING - case FILDES: - res = (NULL != action_source) ? send_fdpass_fd(c->sockd, action_source->scan_fd) : send_fdpass(c->sockd, scan_path); - break; + case FILDES: + res = (NULL != action_source) ? send_fdpass_fd(c->sockd, action_source->scan_fd) : send_fdpass(c->sockd, scan_path); + break; #endif - case STREAM: - res = (NULL != action_source) ? send_stream_fd_action(c->sockd, action_source->scan_fd, action_source->display_path, clamdopts) : send_stream(c->sockd, scan_path, clamdopts); + case STREAM: + res = (NULL != action_source) ? send_stream_fd_action(c->sockd, action_source->scan_fd, action_source->display_path, clamdopts) : send_stream(c->sockd, scan_path, clamdopts); + break; + } + if (res >= 0) break; + /* Connection-level send failure: try to restore the session, + * then retry sending the current file. */ + if (session_restart(c)) { + c->printok = 0; + c->errors++; + status = CL_BREAK; + goto done; + } } - if (res <= 0) { + if (res == 0) { c->printok = 0; c->errors++; - status = res ? CL_BREAK : CL_SUCCESS; + status = CL_SUCCESS; goto done; } @@ -619,6 +731,7 @@ int parallel_client_scan(char *file, int scantype, int *infected, int *err, int cdata.printok = printinfected ^ 1; cdata.action_sources = 0; cdata.max_action_sources = get_max_action_sources(); + cdata.restarts = 0; client_walk_policy_init(&cdata.walk_policy, file); data.data = &cdata; @@ -633,7 +746,14 @@ int parallel_client_scan(char *file, int scantype, int *infected, int *err, int } sendln(cdata.sockd, zEND, sizeof(zEND)); - while (cdata.ids && !dspresult(&cdata)) continue; + while (cdata.ids) { + if (dspresult(&cdata)) { + if (session_restart(&cdata)) + break; + /* New session: terminate it too so remaining replies flush. */ + sendln(cdata.sockd, zEND, sizeof(zEND)); + } + } closesocket(cdata.sockd); *infected += cdata.infected;