- uschar scanrequest[1024];
- int sockData, clam_fd, result;
- unsigned int fsize;
-
- if ((clamd_options = string_nextinlist(&av_scanner_work, &sep,
- clamd_options_buffer,
- sizeof(clamd_options_buffer))) == NULL) {
- /* no options supplied, use default options */
- clamd_options = clamd_options_default;
- }
- if ((clamd_options2 = string_nextinlist(&av_scanner_work, &sep,
- clamd_options2_buffer,
- sizeof(clamd_options2_buffer))) == NULL) {
- clamd_options2 = clamd_options2_default;
- }
-
- /* socket does not start with '/' -> network socket */
- if (*clamd_options != '/') {
-
- /* extract host and port part */
- if( sscanf(CS clamd_options, "%s %u", hostname, &port) != 2 ) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: invalid socket '%s'", clamd_options);
- return DEFER;
- };
-
- /* Lookup the host */
- if((he = gethostbyname(CS hostname)) == 0) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: failed to lookup host '%s'", hostname);
- return DEFER;
- }
-
- in = *(struct in_addr *) he->h_addr_list[0];
-
- /* Open the ClamAV Socket */
- if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: unable to acquire socket (%s)",
- strerror(errno));
- return DEFER;
- }
-
- if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
- close(sock);
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: connection to %s, port %u failed (%s)",
- inet_ntoa(in), port, strerror(errno));
- return DEFER;
- }
-
- if (strcmpic(clamd_options2,US"local") == 0) {
-
- /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
-
- snprintf(CS file_name,1024,"SCAN %s/scan/%s\n", spool_directory, message_id);
-
- if (send(sock, file_name, Ustrlen(file_name), 0) < 0) {
- close(sock);
- log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
- strerror(errno));
- return DEFER;
- }
- } else {
-
- /* Pass the string to ClamAV (7 = "STREAM\n") */
-
- if (send(sock, "STREAM\n", 7, 0) < 0) {
- close(sock);
- log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
- strerror(errno));
- return DEFER;
- }
- memset(av_buffer2, 0, sizeof(av_buffer2));
- bread = read(sock, av_buffer2, sizeof(av_buffer2));
-
- if (bread < 0) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: unable to read PORT from socket (%s)",
- strerror(errno));
- return DEFER;
- }
-
- if (bread == sizeof(av_buffer)) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: buffer too small");
- return DEFER;
- }
-
- if (!(*av_buffer2)) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: ClamAV returned null");
- return DEFER;
- }
-
- av_buffer2[bread] = '\0';
- if( sscanf(CS av_buffer2, "PORT %hu\n", &port) != 1 ) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: Expected port information from clamd, got '%s'", av_buffer2);
- return DEFER;
- };
-
- if ( (sockData = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: unable to acquire socket (%s)",
- strerror(errno));
- return DEFER;
- }
-
- if (ip_connect(sockData, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) {
- close(sockData);
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: connection to %s, port %u failed (%s)",
- inet_ntoa(in), port, strerror(errno));
- return DEFER;
- }
-
- snprintf(CS scanrequest, 1024,CS"%s/scan/%s/%s.eml",
- spool_directory, message_id, message_id);
-
- /* calc file size */
- clam_fd = open(CS scanrequest, O_RDONLY);
- if (clam_fd == -1) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: can't open spool file %s: %s",
- scanrequest, strerror(errno));
- return DEFER;
- }
- fsize = lseek(clam_fd, 0, SEEK_END);
- if (fsize == -1) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: can't seek spool file %s: %s",
- scanrequest, strerror(errno));
- return DEFER;
- }
- lseek(clam_fd, 0, SEEK_SET);
-
- clamav_fbuf = (uschar *) malloc (fsize);
- if (!clamav_fbuf) {
- close(sockData);
- close(clam_fd);
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: unable to allocate memory %u for file (%s)",
- fsize, scanrequest);
- return DEFER;
- }
-
- result = read (clam_fd, clamav_fbuf, fsize);
- if (result == -1) {
- close(sockData);
- close(clam_fd);
- free(clamav_fbuf);
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: can't read spool file %s: %s",
- scanrequest, strerror(errno));
- return DEFER;
- }
- close(clam_fd);
-
- /* send file body to socket */
- if (send(sockData, clamav_fbuf, fsize, 0) < 0) {
- close(sockData);
- free(clamav_fbuf);
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: unable to send file body to socket (%s:%u)", hostname, port);
- return DEFER;
- }
- free(clamav_fbuf);
- close(sockData);
- }
- }
- else {
- /* open the local socket */
- if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: unable to acquire socket (%s)",
- strerror(errno));
- return DEFER;
- }
-
- server.sun_family = AF_UNIX;
- Ustrcpy(server.sun_path, clamd_options);
-
- if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
- close(sock);
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: clamd: unable to connect to UNIX socket %s (%s)",
- clamd_options, strerror(errno) );
- return DEFER;
- }
- }
-
- /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
-
- snprintf(CS file_name,1024,"SCAN %s/scan/%s\n", spool_directory, message_id);
-
- if (send(sock, file_name, Ustrlen(file_name), 0) < 0) {
- close(sock);
- log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)",
- strerror(errno));
- return DEFER;
- }
-
- /*
- We're done sending, close socket for writing.
-
- One user reported that clamd 0.70 does not like this any more ...
-
- */
-
- /* shutdown(sock, SHUT_WR); */
-
+ int clam_fd, result;
+ off_t fsize;
+ unsigned int fsize_uint;
+ BOOL use_scan_command = FALSE;
+ clamd_address * cv[MAX_CLAMD_SERVERS];
+ int num_servers = 0;
+#ifdef WITH_OLD_CLAMAV_STREAM
+ unsigned int port;
+ uschar av_buffer2[1024];
+ int sockData;
+#else
+ uint32_t send_size, send_final_zeroblock;
+#endif
+
+ /*XXX if unixdomain socket, only one server supported. Needs fixing;
+ there's no reason we should not mix local and remote servers */
+
+ if (*scanner_options == '/')
+ {
+ clamd_address * cd;
+ const uschar * sublist;
+ int subsep = ' ';
+
+ /* Local file; so we def want to use_scan_command and don't want to try
+ * passing IP/port combinations */
+ use_scan_command = TRUE;
+ cd = (clamd_address *) store_get(sizeof(clamd_address));
+
+ /* extract socket-path part */
+ sublist = scanner_options;
+ cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0);
+
+ /* parse options */
+ if (clamd_option(cd, sublist, &subsep) != OK)
+ return m_errlog_defer(scanent, NULL,
+ string_sprintf("bad option '%s'", scanner_options));
+ cv[0] = cd;
+ }
+ else
+ {
+ /* Go through the rest of the list of host/port and construct an array
+ * of servers to try. The first one is the bit we just passed from
+ * scanner_options so process that first and then scan the remainder of
+ * the address buffer */
+ do
+ {
+ clamd_address * cd;
+ const uschar * sublist;
+ int subsep = ' ';
+ uschar * s;
+
+ /* The 'local' option means use the SCAN command over the network
+ * socket (ie common file storage in use) */
+ /*XXX we could accept this also as a local option? */
+ if (strcmpic(scanner_options, US"local") == 0)
+ {
+ use_scan_command = TRUE;
+ continue;
+ }
+
+ cd = (clamd_address *) store_get(sizeof(clamd_address));
+
+ /* extract host and port part */
+ sublist = scanner_options;
+ if (!(cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0)))
+ {
+ (void) m_errlog_defer(scanent, NULL,
+ string_sprintf("missing address: '%s'", scanner_options));
+ continue;
+ }
+ if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0)))
+ {
+ (void) m_errlog_defer(scanent, NULL,
+ string_sprintf("missing port: '%s'", scanner_options));
+ continue;
+ }
+ cd->tcp_port = atoi(CS s);
+
+ /* parse options */
+ /*XXX should these options be common over scanner types? */
+ if (clamd_option(cd, sublist, &subsep) != OK)
+ return m_errlog_defer(scanent, NULL,
+ string_sprintf("bad option '%s'", scanner_options));
+
+ cv[num_servers++] = cd;
+ if (num_servers >= MAX_CLAMD_SERVERS)
+ {
+ (void) m_errlog_defer(scanent, NULL,
+ US"More than " MAX_CLAMD_SERVERS_S " clamd servers "
+ "specified; only using the first " MAX_CLAMD_SERVERS_S );
+ break;
+ }
+ } while ((scanner_options = string_nextinlist(&av_scanner_work, &sep,
+ NULL, 0)));
+
+ /* check if we have at least one server */
+ if (!num_servers)
+ return m_errlog_defer(scanent, NULL,
+ US"no useable server addresses in malware configuration option.");
+ }
+
+ /* See the discussion of response formats below to see why we really
+ don't like colons in filenames when passing filenames to ClamAV. */
+ if (use_scan_command && Ustrchr(eml_filename, ':'))
+ return m_errlog_defer(scanent, NULL,
+ string_sprintf("local/SCAN mode incompatible with" \
+ " : in path to email filename [%s]", eml_filename));
+
+ /* We have some network servers specified */
+ if (num_servers)
+ {
+ /* Confirmed in ClamAV source (0.95.3) that the TCPAddr option of clamd
+ * only supports AF_INET, but we should probably be looking to the
+ * future and rewriting this to be protocol-independent anyway. */
+
+ while (num_servers > 0)
+ {
+ int i = random_number( num_servers );
+ clamd_address * cd = cv[i];
+
+ DEBUG(D_acl) debug_printf("trying server name %s, port %u\n",
+ cd->hostspec, cd->tcp_port);
+
+ /* Lookup the host. This is to ensure that we connect to the same IP
+ * on both connections (as one host could resolve to multiple ips) */
+ for (;;)
+ {
+ sock= m_tcpsocket(cd->hostspec, cd->tcp_port, &connhost, &errstr);
+ if (sock >= 0)
+ {
+ /* Connection successfully established with a server */
+ hostname = cd->hostspec;
+ break;
+ }
+ if (cd->retry <= 0) break;
+ while (cd->retry > 0) cd->retry = sleep(cd->retry);
+ }
+ if (sock >= 0)
+ break;
+
+ (void) m_errlog_defer(scanent, CUS callout_address, errstr);
+
+ /* Remove the server from the list. XXX We should free the memory */
+ num_servers--;
+ for (; i < num_servers; i++)
+ cv[i] = cv[i+1];
+ }
+
+ if (num_servers == 0)
+ return m_errlog_defer(scanent, NULL, US"all servers failed");
+ }
+ else
+ for (;;)
+ {
+ if ((sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0)
+ {
+ hostname = cv[0]->hostspec;
+ break;
+ }
+ if (cv[0]->retry <= 0)
+ return m_errlog_defer(scanent, CUS callout_address, errstr);
+ while (cv[0]->retry > 0) cv[0]->retry = sleep(cv[0]->retry);
+ }
+
+ /* have socket in variable "sock"; command to use is semi-independent of
+ * the socket protocol. We use SCAN if is local (either Unix/local
+ * domain socket, or explicitly told local) else we stream the data.
+ * How we stream the data depends upon how we were built. */
+
+ if (!use_scan_command)
+ {
+#ifdef WITH_OLD_CLAMAV_STREAM
+ /* "STREAM\n" command, get back a "PORT <N>\n" response, send data to
+ * that port on a second connection; then in the scan-method-neutral
+ * part, read the response back on the original connection. */
+
+ DEBUG(D_acl) debug_printf(
+ "Malware scan: issuing %s old-style remote scan (PORT)\n",
+ scanner_name);
+
+ /* Pass the string to ClamAV (7 = "STREAM\n") */
+ if (m_sock_send(sock, US"STREAM\n", 7, &errstr) < 0)
+ return m_errlog_defer(scanent, CUS callout_address, errstr);
+
+ memset(av_buffer2, 0, sizeof(av_buffer2));
+ bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), tmo-time(NULL));
+
+ if (bread < 0)
+ return m_errlog_defer_3(scanent, CUS callout_address,
+ string_sprintf("unable to read PORT from socket (%s)",
+ strerror(errno)),
+ sock);
+
+ if (bread == sizeof(av_buffer2))
+ return m_errlog_defer_3(scanent, CUS callout_address,
+ "buffer too small", sock);
+
+ if (!(*av_buffer2))
+ return m_errlog_defer_3(scanent, CUS callout_address,
+ "ClamAV returned null", sock);
+
+ av_buffer2[bread] = '\0';
+ if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 )
+ return m_errlog_defer_3(scanent, CUS callout_address,
+ string_sprintf("Expected port information from clamd, got '%s'",
+ av_buffer2),
+ sock);
+
+ sockData = m_tcpsocket(connhost.address, port, NULL, &errstr);
+ if (sockData < 0)
+ return m_errlog_defer_3(scanent, CUS callout_address, errstr, sock);
+
+# define CLOSE_SOCKDATA (void)close(sockData)
+#else /* WITH_OLD_CLAMAV_STREAM not defined */
+ /* New protocol: "zINSTREAM\n" followed by a sequence of <length><data>
+ chunks, <n> a 4-byte number (network order), terminated by a zero-length
+ chunk. */
+
+ DEBUG(D_acl) debug_printf(
+ "Malware scan: issuing %s new-style remote scan (zINSTREAM)\n",
+ scanner_name);
+
+ /* Pass the string to ClamAV (10 = "zINSTREAM\0") */
+ if (send(sock, "zINSTREAM", 10, 0) < 0)
+ return m_errlog_defer_3(scanent, CUS hostname,
+ string_sprintf("unable to send zINSTREAM to socket (%s)",
+ strerror(errno)),
+ sock);
+
+# define CLOSE_SOCKDATA /**/
+#endif
+
+ /* calc file size */
+ if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0)
+ {
+ int err = errno;
+ CLOSE_SOCKDATA;
+ return m_errlog_defer_3(scanent, NULL,
+ string_sprintf("can't open spool file %s: %s",
+ eml_filename, strerror(err)),
+ sock);
+ }
+ if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0)
+ {
+ int err = errno;
+ CLOSE_SOCKDATA; (void)close(clam_fd);
+ return m_errlog_defer_3(scanent, NULL,
+ string_sprintf("can't seek spool file %s: %s",
+ eml_filename, strerror(err)),
+ sock);
+ }
+ fsize_uint = (unsigned int) fsize;
+ if ((off_t)fsize_uint != fsize)
+ {
+ CLOSE_SOCKDATA; (void)close(clam_fd);
+ return m_errlog_defer_3(scanent, NULL,
+ string_sprintf("seeking spool file %s, size overflow",
+ eml_filename),
+ sock);
+ }
+ lseek(clam_fd, 0, SEEK_SET);
+
+ if (!(clamav_fbuf = US malloc(fsize_uint)))
+ {
+ CLOSE_SOCKDATA; (void)close(clam_fd);
+ return m_errlog_defer_3(scanent, NULL,
+ string_sprintf("unable to allocate memory %u for file (%s)",
+ fsize_uint, eml_filename),
+ sock);
+ }
+
+ if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0)
+ {
+ int err = errno;
+ free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd);
+ return m_errlog_defer_3(scanent, NULL,
+ string_sprintf("can't read spool file %s: %s",
+ eml_filename, strerror(err)),
+ sock);
+ }
+ (void)close(clam_fd);
+
+ /* send file body to socket */
+#ifdef WITH_OLD_CLAMAV_STREAM
+ if (send(sockData, clamav_fbuf, fsize_uint, 0) < 0)
+ {
+ free(clamav_fbuf); CLOSE_SOCKDATA;
+ return m_errlog_defer_3(scanent, NULL,
+ string_sprintf("unable to send file body to socket (%s:%u)",
+ hostname, port),
+ sock);
+ }
+#else
+ send_size = htonl(fsize_uint);
+ send_final_zeroblock = 0;
+ if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
+ (send(sock, clamav_fbuf, fsize_uint, 0) < 0) ||
+ (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
+ {
+ free(clamav_fbuf);
+ return m_errlog_defer_3(scanent, NULL,
+ string_sprintf("unable to send file body to socket (%s)", hostname),
+ sock);
+ }
+#endif
+
+ free(clamav_fbuf);
+
+ CLOSE_SOCKDATA;
+#undef CLOSE_SOCKDATA
+ }
+ else
+ { /* use scan command */
+ /* Send a SCAN command pointing to a filename; then in the then in the
+ * scan-method-neutral part, read the response back */
+
+/* ================================================================= */
+
+ /* Prior to the reworking post-Exim-4.72, this scanned a directory,
+ which dates to when ClamAV needed us to break apart the email into the
+ MIME parts (eg, with the now deprecated demime condition coming first).
+ Some time back, ClamAV gained the ability to deconstruct the emails, so
+ doing this would actually have resulted in the mail attachments being
+ scanned twice, in the broken out files and from the original .eml.
+ Since ClamAV now handles emails (and has for quite some time) we can
+ just use the email file itself. */
+ /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
+ file_name = string_sprintf("SCAN %s\n", eml_filename);
+
+ DEBUG(D_acl) debug_printf(
+ "Malware scan: issuing %s local-path scan [%s]\n",
+ scanner_name, scanner_options);
+
+ if (send(sock, file_name, Ustrlen(file_name), 0) < 0)
+ return m_errlog_defer_3(scanent, CUS callout_address,
+ string_sprintf("unable to write to socket (%s)", strerror(errno)),
+ sock);
+
+ /* Do not shut down the socket for writing; a user report noted that
+ * clamd 0.70 does not react well to this. */
+ }
+ /* Commands have been sent, no matter which scan method or connection
+ * type we're using; now just read the result, independent of method. */
+