+ 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
+ blob cmd_str;
+
+ /*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));
+
+ /* Set up the very first data we will be sending */
+ if (!use_scan_command)
+#ifdef WITH_OLD_CLAMAV_STREAM
+ { cmd_str.data = US"STREAM\n"; cmd_str.len = 7; }
+#else
+ { cmd_str.data = US"zINSTREAM"; cmd_str.len = 10; }
+#endif
+ else
+ {
+ cmd_str.data = string_sprintf("SCAN %s\n", eml_filename);
+ cmd_str.len = Ustrlen(cmd_str.data);
+ }
+
+ /* 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_indent("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 (;;)
+ {
+ if ((sock = m_tcpsocket(cd->hostspec, cd->tcp_port,
+ &connhost, &errstr, &cmd_str)) >= 0)
+ {
+ /* Connection successfully established with a server */
+ hostname = cd->hostspec;
+ cmd_str.len = 0;
+ 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_indent(
+ "Malware scan: issuing %s old-style remote scan (PORT)\n",
+ scanner_name);
+
+ /* Pass the string to ClamAV (7 = "STREAM\n"), if not already sent */
+ if (cmd_str.len)
+ if (m_sock_send(sock, cmd_str.data, cmd_str.len, &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, NULL);
+ 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_indent(
+ "Malware scan: issuing %s new-style remote scan (zINSTREAM)\n",
+ scanner_name);
+
+ /* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */
+ if (cmd_str.len)
+ if (send(sock, cmd_str.data, cmd_str.len, 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