+/* get sum of weights */
+for (weights = 0, i = 0; i < num_servers; i++)
+ {
+ sd = spamds[i];
+ if (!sd->is_failed && sd->priority == pri) weights += sd->weight;
+ }
+if (weights == 0) /* all servers failed */
+ return -1;
+
+for (long rnd = random_number(weights), i = 0; i < num_servers; i++)
+ {
+ sd = spamds[i];
+ if (!sd->is_failed && sd->priority == pri)
+ if ((rnd -= sd->weight) < 0)
+ return i;
+ }
+
+log_write(0, LOG_MAIN|LOG_PANIC,
+ "%s unknown error (memory/cpu corruption?)", loglabel);
+return -1;
+}
+
+
+int
+spam(const uschar **listptr)
+{
+int sep = 0;
+const uschar *list = *listptr;
+uschar *user_name;
+unsigned long mbox_size;
+FILE *mbox_file;
+client_conn_ctx spamd_cctx = {.sock = -1};
+uschar spamd_buffer[32600];
+int i, j, offset, result;
+uschar spamd_version[8];
+uschar spamd_short_result[8];
+uschar spamd_score_char;
+double spamd_threshold, spamd_score, spamd_reject_score;
+int spamd_report_offset;
+uschar *p,*q;
+int override = 0;
+time_t start;
+size_t read, wrote;
+uschar *spamd_address_work;
+spamd_address_container * sd;
+
+/* stop compiler warning */
+result = 0;
+
+/* find the username from the option list */
+if (!(user_name = string_nextinlist(&list, &sep, NULL, 0)))
+ {
+ /* no username given, this means no scanning should be done */
+ return FAIL;
+ }
+
+/* if username is "0" or "false", do not scan */
+if (Ustrcmp(user_name, "0") == 0 || strcmpic(user_name, US"false") == 0)
+ return FAIL;
+
+/* if there is an additional option, check if it is "true" */
+if (strcmpic(list,US"true") == 0)
+ /* in that case, always return true later */
+ override = 1;
+
+/* expand spamd_address if needed */
+if (*spamd_address != '$')
+ spamd_address_work = spamd_address;
+else if (!(spamd_address_work = expand_string(spamd_address)))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "%s spamd_address starts with $, but expansion failed: %s",
+ loglabel, expand_string_message);
+ return DEFER;
+ }
+
+DEBUG(D_acl) debug_printf_indent("spamd: addrlist '%s'\n", spamd_address_work);
+
+/* check if previous spamd_address was expanded and has changed. dump cached results if so */
+if ( spam_ok
+ && prev_spamd_address_work != NULL
+ && Ustrcmp(prev_spamd_address_work, spamd_address_work) != 0
+ )
+ spam_ok = 0;
+
+/* if we scanned for this username last time, just return */
+if (spam_ok && Ustrcmp(prev_user_name, user_name) == 0)
+ return override ? OK : spam_rc;
+
+/* make sure the eml mbox file is spooled up */
+
+if (!(mbox_file = spool_mbox(&mbox_size, NULL, NULL)))
+ { /* error while spooling */
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "%s error while creating mbox spool file", loglabel);
+ return DEFER;
+ }
+
+start = time(NULL);
+
+ {
+ int num_servers = 0;
+ int current_server;
+ uschar * address;
+ const uschar * spamd_address_list_ptr = spamd_address_work;
+ spamd_address_container * spamd_address_vector[32];
+
+ /* Check how many spamd servers we have
+ and register their addresses */
+ sep = 0; /* default colon-sep */
+ while ((address = string_nextinlist(&spamd_address_list_ptr, &sep, NULL, 0)))
+ {
+ const uschar * sublist;
+ int sublist_sep = -(int)' '; /* default space-sep */
+ unsigned args;
+ uschar * s;
+
+ DEBUG(D_acl) debug_printf_indent("spamd: addr entry '%s'\n", address);
+ sd = store_get(sizeof(spamd_address_container), GET_UNTAINTED);
+
+ for (sublist = address, args = 0, spamd_param_init(sd);
+ (s = string_nextinlist(&sublist, &sublist_sep, NULL, 0));
+ args++
+ )
+ {
+ DEBUG(D_acl) debug_printf_indent("spamd: addr parm '%s'\n", s);
+ switch (args)
+ {
+ case 0: sd->hostspec = s;
+ if (*s == '/') args++; /* local; no port */
+ break;
+ case 1: sd->hostspec = string_sprintf("%s %s", sd->hostspec, s);
+ break;
+ default: spamd_param(s, sd);
+ break;
+ }
+ }
+ if (args < 2)
+ {
+ log_write(0, LOG_MAIN,
+ "%s warning - invalid spamd address: '%s'", loglabel, address);
+ continue;
+ }
+
+ spamd_address_vector[num_servers] = sd;
+ if (++num_servers > 31)
+ break;