/* * rspamd_http.c - Rspamd dlfunc for Exim * https://mta.org.ua/exim-4.94-conf/dlfunc/rspamd_http/rspamd_http.c * * Copyright (C) 2011-2020 Victor Ustugov * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Based on examples from http://www.ols.es/exim/dlext/ by David Saez , * source code of exim by Philip Hazel , * source code of exiscan by Tom Kistner 2003 - 2015 * source code of exim by The Exim Maintainers 2016 - 2018 * source code of dlfunc-json by Alexey Savelyev */ // // warn set acl_m0 = ${dlfunc{/usr/local/libexec/exim/exim-dlfunc.so}{rspamd_http}\ // {127.0.0.1 11333}{defer_ok}} // #include #include #include #include #include #include #include #include "cJSON.h" #include "cJSON.c" //#include "local_scan.h" //#include "macros.h" #include "exim.h" //# define string_copy(s) string_copy_function(s) //# define string_copyn(s, n) string_copyn_function((s), (n)) //# define string_copy_taint(s, t) string_copy_taint_function((s), (t)) extern uschar * string_copy_function(const uschar *); extern uschar * string_copyn_function(const uschar *, int n); extern uschar * string_copy_taint_function(const uschar *, BOOL tainted); #define string_sprintf(fmt, ...) \ string_sprintf_trc(fmt, US __FUNCTION__, __LINE__, __VA_ARGS__) extern uschar *string_sprintf_trc(const char *, const uschar *, unsigned, ...) ALMOST_PRINTF(1,4); extern uschar *tod_stamp(int); //extern BOOL split_spool_directory; /* TRUE to use multiple subdirs */ //extern uschar *spool_directory; /* Name of spool directory */ //extern uschar message_subdir[]; /* Subdirectory for messages */ /***************************************************************************** * Configuration settings: * *****************************************************************************/ // for rspamd 0.6.7+ with additional request header Hostname #define WITH_PROTO_RSPAMC_1_3_WITH_HOSTNAME_HEADER #define RSPAMD_TIMEOUT 90 //------------------------------------------------------------------------- int rspamd_http(uschar **yield, int argc, uschar *argv[]) { uschar *arg_socket_addr; uschar *arg_defer_ok; int defer_ok; int rspamd_command; char tcp_addr[15]; int tcp_port; FILE *mbox_file = NULL; // unsigned long mbox_size; off_t mbox_size; uschar *s, *p; header_line *my_header, *header_new, *header_last, *tmp_headerlist; header_line *last_received = NULL; uschar *address; uschar *helo; uschar *sender_host_name; char mbox_path[512]; int max_len, len; client_conn_ctx rspamd_cctx = {.sock = -1}; struct hostent *he; struct in_addr in; int result; #ifndef NO_POLL_H struct pollfd pollfd; // patch posted by Erik ? for OS X and applied by PH #else fd_set select_fd; // /patch posted by Erik ? for OS X and applied by PH #endif int offset; #define SPAMD_BUFFER_SIZE 32600 uschar * spamd_buffer = NULL; #define SPAMD_BUFFER2_SIZE 32600 uschar * spamd_buffer2 = NULL; time_t start; size_t read, wrote; int i, j, c; uschar * errstr; uschar *tls_version; uschar *tls_cipher; const char *json_answer; arg_socket_addr = argv[0]; arg_defer_ok = argv[1]; if (argc < 2) { defer_ok = 0; } else if ( (strcmpic(arg_defer_ok,US"1") == 0) || (strcmpic(arg_defer_ok,US"yes") == 0) || (strcmpic(arg_defer_ok,US"true") == 0) || (strcmpic(arg_defer_ok,US"defer_ok") == 0) ) { defer_ok = 1; } else { defer_ok = 0; } debug_printf(" defer_ok: %d\n", defer_ok); if ((arg_socket_addr == NULL) || (arg_socket_addr[0] == 0)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: Socket address expected"); *yield = string_copy(US"rspamd dlfunc: Socket address expected"); goto RETURN_DEFER; } // get message body stream if (split_spool_directory == 0) { sprintf(mbox_path, "%s/input/%s-D", spool_directory, message_id); } else { sprintf(mbox_path, "%s/input/%s/%s-D", spool_directory, message_subdir, message_id); } debug_printf(" Open spool file: %s\n", mbox_path); mbox_file = fopen(mbox_path,"rb"); if (!mbox_file) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: Unable to spool message '%s'", mbox_path); *yield = string_sprintf("rspamd dlfunc: Unable to spool message '%s'", mbox_path); return(defer_ok ? OK : ERROR); } (void)fseek(mbox_file, 0, SEEK_END); mbox_size = ftell(mbox_file); debug_printf(" Total spool file size: %ld\n", (long)mbox_size); mbox_size -= SPOOL_DATA_START_OFFSET; debug_printf(" Spool file size: %ld\n", (long)mbox_size); debug_printf(" fseek %d, %d\n", SPOOL_DATA_START_OFFSET, SEEK_SET); (void)fseek(mbox_file, SPOOL_DATA_START_OFFSET, SEEK_SET); start = time(NULL); // socket does not start with '/' -> network socket if ((arg_socket_addr[0] != '/') && (sscanf(CS arg_socket_addr, "%s %u", tcp_addr, &tcp_port) != 2 )) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: Invalid rspamd address: '%s'", arg_socket_addr); *yield = string_sprintf("rspamd dlfunc: Invalid rspamd address: '%s'", arg_socket_addr); goto RETURN_DEFER; } debug_printf(" Try to connect to rspamd on socket: %s\n", arg_socket_addr); if ((rspamd_cctx.sock = ip_streamsocket(arg_socket_addr, &errstr, 5, NULL)) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: %s", errstr); *yield = string_sprintf("rspamd dlfunc: %s", errstr); goto RETURN_DEFER; }; (void)fcntl(rspamd_cctx.sock, F_SETFL, O_NONBLOCK); // now we are connected to rspamd on rspamd_cctx.sock // spamd_buffer2 = store_get(SPAMD_BUFFER2_SIZE, is_tainted(address)); spamd_buffer2 = store_get(SPAMD_BUFFER2_SIZE, TRUE); memset(spamd_buffer2, 0, SPAMD_BUFFER2_SIZE); // address = expand_string(US"${if def:received_for{$received_for}}"); address = expand_string(US"${received_for}"); if (!address || !*address) address = expand_string(US"${recipients}"); if (!address || !*address) address = expand_string(US"${local_part}@${domain}"); (void)string_format(spamd_buffer2, SPAMD_BUFFER2_SIZE, "Return-path: <%s>\n%sDelivery-date: %s\n", ((sender_address && *sender_address) ? sender_address : (uschar *)""), ((address && *address) ? string_sprintf("Envelope-To: %s\n", address) : (uschar *)""), tod_stamp(tod_full) ); offset = Ustrlen(spamd_buffer2); // create links to original headers list tmp_headerlist = NULL; header_last = NULL; for (my_header = header_list; my_header; my_header = my_header->next) { if ((my_header->type != '*') && (my_header->type != htype_old)) { header_new = store_get(sizeof(header_line), FALSE); header_new->text = my_header->text; header_new->slen = my_header->slen; header_new->type = my_header->type; header_new->next = NULL; //debug_printf(" create a copy of header item: '%s'\n", header_new->text); if (tmp_headerlist == NULL) tmp_headerlist = header_new; if (header_last != NULL) header_last->next = header_new; header_last = header_new; } } // headers removed by acl_check_data if (acl_removed_headers != NULL) { for (my_header = tmp_headerlist; my_header != NULL; my_header = my_header->next) { uschar *list; list = acl_removed_headers; int sep = ':'; // This is specified as a colon-separated list uschar buffer[128]; while ((s = string_nextinlist((const uschar **)&list, &sep, buffer, sizeof(buffer))) != NULL) { int len = Ustrlen(s); if (header_testname(my_header, s, len, FALSE)) { //debug_printf(" header removed by acl_check_data: '%s'; '%s'\n", s, my_header->text); my_header->type = htype_old; } } } } // headers added by acl_check_data my_header = acl_added_headers; while (my_header != NULL) { //debug_printf(" header added by acl_check_data: '%s'\n", my_header->text); header_new = store_get(sizeof(header_line), FALSE); //// header_new->text = string_copyn(my_header->text, my_header->slen); // header_new->text = store_get(my_header->slen, FALSE); // memcpy(header_new->text, my_header->text, my_header->slen); header_new->text = my_header->text; header_new->slen = my_header->slen; switch(my_header->type) { case htype_add_top: // add header at top header_new->next = tmp_headerlist; tmp_headerlist = header_new; break; case htype_add_rec: // add header after Received: if (last_received == NULL) { last_received = tmp_headerlist; while (!header_testname(last_received, US"Received", 8, FALSE)) last_received = last_received->next; while (last_received->next != NULL && header_testname(last_received->next, US"Received", 8, FALSE)) last_received = last_received->next; } header_new->next = last_received->next; last_received->next = header_new; break; case htype_add_rfc: // add header before any header which is NOT Received: or Resent- last_received = tmp_headerlist; while ( (last_received->next != NULL) && ( (header_testname(last_received->next, US"Received", 8, FALSE)) || (header_testname_incomplete(last_received->next, US"Resent-", 7, FALSE)) ) ) last_received = last_received->next; // last_received now points to the last Received: or Resent-* header // in an uninterrupted chain of those header types (seen from the beginning // of all headers. Our current header must follow it. header_new->next = last_received->next; last_received->next = header_new; break; default: // htype_add_bot // add header at bottom header_new->next = NULL; header_last->next = header_new; break; } if (header_new->next == NULL) header_last = header_new; my_header = my_header->next; } // copy all the headers to data buffer my_header = tmp_headerlist; while (my_header) { if ((my_header->type != '*') && (my_header->type != htype_old)) { max_len = SPAMD_BUFFER2_SIZE - offset - 1; len = my_header->slen; if (len > max_len) len = max_len; Ustrncpy(spamd_buffer2 + offset, my_header->text, len); offset += len; //debug_printf(" copy header item: '%s'\n", my_header->text); } //// if (my_header->text) store_release(my_header->text); header_last = my_header; my_header = my_header->next; // if (header_last) store_release(header_last); } // s = string_copy(US"\r\n"); s = string_copy(US"\n"); max_len = SPAMD_BUFFER2_SIZE - offset - 1; len = Ustrlen(s); if (len > max_len) len = max_len; Ustrncpy(spamd_buffer2 + offset, s, len); offset += len; debug_printf(" Headers size: %d\n", offset); mbox_size += offset; debug_printf(" Total message size: %ld\n", (long)mbox_size); // spamd_buffer = store_get(SPAMD_BUFFER_SIZE, is_tainted(address)); spamd_buffer = store_get(SPAMD_BUFFER_SIZE, TRUE); memset(spamd_buffer, 0, SPAMD_BUFFER_SIZE); // copy request to buffer string_format(spamd_buffer, SPAMD_BUFFER_SIZE, "POST /checkv2 HTTP/1.0\r\nContent-Length: " OFF_T_FMT "\r\nMessage-Length: " OFF_T_FMT "\r\nPass: all\r\nQueue-Id: %s\r\nFrom: %s\r\nRecipient-Number: %d\r\n", mbox_size, mbox_size, message_id, sender_address, recipients_count); for (i = 0; i < recipients_count; i ++) string_format(spamd_buffer+Ustrlen(spamd_buffer), SPAMD_BUFFER_SIZE-Ustrlen(spamd_buffer), "Rcpt: %s\r\n", recipients_list[i].address); if ((helo = expand_string(US"$sender_helo_name")) != NULL && *helo != '\0') string_format(spamd_buffer+Ustrlen(spamd_buffer), SPAMD_BUFFER_SIZE-Ustrlen(spamd_buffer), "Helo: %s\r\n", helo); #ifdef WITH_PROTO_RSPAMC_1_3_WITH_HOSTNAME_HEADER if ((sender_host_name = expand_string(US"$sender_host_name")) != NULL && *sender_host_name != '\0') string_format(spamd_buffer+Ustrlen(spamd_buffer), SPAMD_BUFFER_SIZE-Ustrlen(spamd_buffer), "Hostname: %s\r\n", sender_host_name); #endif if (sender_host_address != NULL) string_format(spamd_buffer+Ustrlen(spamd_buffer), SPAMD_BUFFER_SIZE-Ustrlen(spamd_buffer), "IP: %s\r\n", sender_host_address); if (authenticated_id != NULL) string_format(spamd_buffer+Ustrlen(spamd_buffer), SPAMD_BUFFER_SIZE-Ustrlen(spamd_buffer), "User: %s\r\n", authenticated_id); if ((tls_version = expand_string (US"${extract{1}{:}{$tls_cipher}}")) != NULL && *tls_version != '\0') string_format (spamd_buffer + Ustrlen (spamd_buffer), sizeof (spamd_buffer) - Ustrlen (spamd_buffer), "TLS-Version: %s\r\n", tls_version); if ((tls_cipher = expand_string (US"${extract{2}{:}{$tls_cipher}}")) != NULL && *tls_cipher != '\0') string_format (spamd_buffer + Ustrlen (spamd_buffer), sizeof (spamd_buffer) - Ustrlen (spamd_buffer), "TLS-Cipher: %s\r\n", tls_cipher); string_format(spamd_buffer+Ustrlen(spamd_buffer), SPAMD_BUFFER_SIZE-Ustrlen(spamd_buffer), "\r\n%s", ""); debug_printf(" Send to socket: %s", spamd_buffer); // send our request if (send(rspamd_cctx.sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: rspamd send failed: %s", strerror(errno)); *yield = string_sprintf("rspamd dlfunc: rspamd send failed: %s", strerror(errno)); goto RETURN_DEFER; } // // now send the data buffer and spool file // debug_printf("sending data block\n"); debug_printf(" Send to socket: %s", spamd_buffer2); wrote = send(rspamd_cctx.sock, spamd_buffer2, Ustrlen(spamd_buffer2), 0); if (wrote == -1) goto WRITE_FAILED; debug_printf(" wrote to socket %ld bytes\n", wrote); // // Note: poll() is not supported in OSX 10.2. // #ifndef NO_POLL_H pollfd.fd = rspamd_cctx.sock; pollfd.events = POLLOUT; #endif (void)fcntl(rspamd_cctx.sock, F_SETFL, O_NONBLOCK); do { read = fread(spamd_buffer,1,SPAMD_BUFFER_SIZE,mbox_file); if (read < SPAMD_BUFFER_SIZE) spamd_buffer[read] = 0; debug_printf(" Read from spool file: %s", spamd_buffer); if (read > 0) { offset = 0; again: #ifndef NO_POLL_H result = poll(&pollfd, 1, 1000); // patch posted by Erik ? for OS X and applied by PH #else select_tv.tv_sec = 1; select_tv.tv_usec = 0; FD_ZERO(&select_fd); FD_SET(rspamd_cctx.sock, &select_fd); result = select(rspamd_cctx.sock+1, NULL, &select_fd, NULL, &select_tv); // /patch posted by Erik ? for OS X and applied by PH */ #endif if (result == -1 && errno == EINTR) goto again; else if (result < 1) { if (result == -1) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: %s on rspamd socket", strerror(errno)); *yield = string_sprintf("rspamd dlfunc: %s on rspamd socket", strerror(errno)); } else { if (time(NULL) - start < RSPAMD_TIMEOUT) goto again; log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: timed out writing rspamd socket"); *yield = string_copy(US"rspamd dlfunc: timed out writing rspamd socket"); } goto RETURN_DEFER; } wrote = send(rspamd_cctx.sock, spamd_buffer + offset, read - offset, 0); //debug_printf(" Send to socket %ld bytes: %s", (long)read - (long)offset, spamd_buffer + offset); debug_printf(" wrote to rspamd socket %ld bytes\n", (long)wrote); if (wrote == -1) goto WRITE_FAILED; if (offset + wrote != read) { offset += wrote; goto again; } } } while (!feof(mbox_file) && !ferror(mbox_file)); if (ferror(mbox_file)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: error reading spool file: %s", strerror(errno)); *yield = string_sprintf("rspamd dlfunc: error reading spool file: %s", strerror(errno)); goto RETURN_DEFER; } /* read rspamd response using what's left of the timeout. */ debug_printf("read rspamd response using what's left of the timeout (%ld sec)\n", RSPAMD_TIMEOUT - time(NULL) + start); memset(spamd_buffer, 0, SPAMD_BUFFER_SIZE); offset = 0; while ((i = ip_recv(&rspamd_cctx, spamd_buffer + offset, SPAMD_BUFFER_SIZE - offset - 1, RSPAMD_TIMEOUT + start)) > 0 ) { debug_printf(" read %d bytes from socket\n", i); offset += i; } spamd_buffer[offset] = '\0'; /* guard byte */ debug_printf(" total read %d bytes from socket\n", offset); /* error handling */ if((i <= 0) && (errno != 0)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: error reading from rspamd socket: %s", strerror(errno)); *yield = string_sprintf("rspamd dlfunc: error reading from rspamd socket: %s", strerror(errno)); goto RETURN_DEFER; } debug_printf("read from socket: %s\n", spamd_buffer); if (rspamd_cctx.sock > 0) { (void)close(rspamd_cctx.sock); rspamd_cctx.sock = 0; } if (mbox_file != NULL) { (void)fclose(mbox_file); mbox_file = NULL; } // check HTTP response code char *crlf_pos = strstr((const char *)spamd_buffer, "\r\n"); if (crlf_pos == NULL) crlf_pos = strstr((const char *)spamd_buffer, "\n"); if (strstr((const char *)spamd_buffer, "HTTP/1.1 200 OK") == NULL && strstr((const char *)spamd_buffer, "HTTP/1.0 200 OK") == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: HTTP return code != 200: %s", spamd_buffer); *yield = string_sprintf("rspamd dlfunc: HTTP return code != 200: %s", spamd_buffer); if (crlf_pos != NULL) *crlf_pos = '\r'; goto RETURN_DEFER; } if (crlf_pos != NULL) *crlf_pos = '\r'; // check HTTP response body crlf_pos = strstr((const char *)spamd_buffer, "\r\n\r\n"); if (crlf_pos == NULL) crlf_pos = strstr((const char *)spamd_buffer, "\n\n"); if (crlf_pos == NULL) { // log_write(0, LOG_MAIN|LOG_PANIC, // "rspamd dlfunc: A body of HTTP response not found: %s", spamd_buffer); log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: A body of HTTP response not found"); *yield = string_sprintf("rspamd dlfunc: A body of HTTP response not found: %s", spamd_buffer); goto RETURN_DEFER; } json_answer = (const char *)string_sprintf("%s", crlf_pos + 4); // parse JSON cJSON *json = NULL; json = cJSON_Parse(json_answer); if (!json) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, JSON: %s", json_answer); *yield = string_sprintf("rspamd dlfunc: JSON parse error, JSON: %s", json_answer); goto RETURN_DEFER; } // get is_skipped cJSON *is_skipped = cJSON_GetObjectItem(json, "is_skipped"); if (is_skipped != NULL) { if (!cJSON_IsBool(is_skipped)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, is_skipped is not a boolean"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, is_skipped is not a boolean"); goto RETURN_DEFER; } *yield = string_sprintf("is_skipped=%d", (cJSON_IsTrue(is_skipped) ? 1 : 0)); } // get score cJSON *score = cJSON_GetObjectItem(json, "score"); if (score == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, score not found"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, score not found"); goto RETURN_DEFER; } if (!cJSON_IsNumber(score)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, score is not a number"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, score is not a number"); goto RETURN_DEFER; } // get required_score cJSON *required_score = cJSON_GetObjectItem(json, "required_score"); if (required_score == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, required_score not found"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, required_score not found"); goto RETURN_DEFER; } if (!cJSON_IsNumber(required_score)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, required_score is not a number"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, required_score is not a number"); goto RETURN_DEFER; } *yield = string_sprintf("%s score=%.2f required_score=%.2f score100=%d required_score100=%d", *yield, score->valuedouble, required_score->valuedouble, score->valuedouble * 100, required_score->valuedouble * 100); // get action cJSON *action = cJSON_GetObjectItem(json, "action"); if (action == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, action not found"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, action not found"); goto RETURN_DEFER; } if (!cJSON_IsString(action)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, action is not a string"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, action is not a string"); goto RETURN_DEFER; } *yield = string_sprintf ("%s action=\"%s\"", *yield, action->valuestring); // get scan time cJSON *time_real = cJSON_GetObjectItem(json, "time_real"); if (time_real != NULL) { if (!cJSON_IsNumber(time_real)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, time_real is not a number"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, time_real is not a number"); goto RETURN_DEFER; } *yield = string_sprintf ("%s time_real=%.2f", *yield, time_real->valuedouble); } cJSON *time_virtual = cJSON_GetObjectItem(json, "time_virtual"); if (time_virtual != NULL) { if (!cJSON_IsNumber(time_virtual)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, time_virtual is not a number"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, time_virtual is not a number"); goto RETURN_DEFER; } *yield = string_sprintf ("%s time_virtual=%.2f", *yield, time_virtual->valuedouble); } // get message-id cJSON *message_id = cJSON_GetObjectItem(json, "message-id"); if (message_id != NULL) { if (!cJSON_IsString(message_id)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, message-id is not a string"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, message-id is not a string"); goto RETURN_DEFER; } *yield = string_sprintf ("%s message-id=\"%s\"", *yield, message_id->valuestring); } // get messages cJSON *message = NULL; cJSON *messages = cJSON_GetObjectItem(json, "messages"); if (messages != NULL) { if (!cJSON_IsObject(messages)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, messages is not a list"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, messages is not a list"); goto RETURN_DEFER; } c = cJSON_GetArraySize(messages); for (i = 0; i < c; i++) { message = cJSON_GetArrayItem(messages, i); if (message == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, could not get message number %d", i + 1); *yield = string_sprintf("rspamd dlfunc: JSON parse error, could not get message number %d", i + 1); goto RETURN_DEFER; } if (!cJSON_IsString(message)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, message number %d is not a atring", i + 1); *yield = string_sprintf("rspamd dlfunc: JSON parse error, message number %d is not a atring", i + 1); goto RETURN_DEFER; } *yield = string_sprintf ("%s%s%s%s", *yield, (i == 0 ? " messages=\"" : ""), message->valuestring, (i < c - 1 ? "\n" : "\"")); } } // get emails cJSON *email = NULL; cJSON *emails = cJSON_GetObjectItem(json, "emails"); if (emails != NULL) { if (!cJSON_IsArray(emails)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, emails is not an array"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, emails is not an array"); goto RETURN_DEFER; } c = cJSON_GetArraySize(emails); for (i = 0; i < c; i++) { email = cJSON_GetArrayItem(emails, i); if (email == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, could not get email number %d", i + 1); *yield = string_sprintf("rspamd dlfunc: JSON parse error, could not get email number %d", i + 1); goto RETURN_DEFER; } if (!cJSON_IsString(email)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, email number %d is not a atring", i + 1); *yield = string_sprintf("rspamd dlfunc: JSON parse error, email number %d is not a atring", i + 1); goto RETURN_DEFER; } *yield = string_sprintf ("%s%s%s%s", *yield, (i == 0 ? " emails=\"" : ""), email->valuestring, (i < c - 1 ? "\n" : "\"")); } } // get urls cJSON *url = NULL; cJSON *urls = cJSON_GetObjectItem(json, "urls"); if (urls != NULL) { if (!cJSON_IsArray(urls)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, urls is not an array"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, urls is not an array"); goto RETURN_DEFER; } c = cJSON_GetArraySize(urls); for (i = 0; i < c; i++) { url = cJSON_GetArrayItem(urls, i); if (url == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, could not get url number %d", i + 1); *yield = string_sprintf("rspamd dlfunc: JSON parse error, could not get url number %d", i + 1); goto RETURN_DEFER; } if (!cJSON_IsString(url)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, url number %d is not a atring", i + 1); *yield = string_sprintf("rspamd dlfunc: JSON parse error, url number %d is not a atring", i + 1); goto RETURN_DEFER; } *yield = string_sprintf ("%s%s%s%s", *yield, (i == 0 ? " urls=\"" : ""), url->valuestring, (i < c - 1 ? "\n" : "\"")); } } // get symbols cJSON *symbol = NULL; cJSON *symbol_name = NULL; cJSON *symbol_score = NULL; cJSON *symbol_description = NULL; cJSON *symbol_options = NULL; cJSON *option = NULL; cJSON *symbols = cJSON_GetObjectItem(json, "symbols"); if (symbols != NULL) { if (!cJSON_IsObject(symbols)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, symbols is not a list"); *yield = string_copy(US"rspamd dlfunc: JSON parse error, symbols is not a list"); goto RETURN_DEFER; } *yield = string_sprintf("%s symbols=\"", *yield); int first_symbol = 1; for (i = 0; i < cJSON_GetArraySize(symbols); i++) { symbol = cJSON_GetArrayItem(symbols, i); if (symbol == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, could not get symbol number %d", i); *yield = string_sprintf("rspamd dlfunc: JSON parse error, could not get symbol number %d", i); goto RETURN_DEFER; } if (!cJSON_IsObject(symbol)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, symbol number %d is not a list", i); *yield = string_sprintf("rspamd dlfunc: JSON parse error, symbol number %d is not a list", i); goto RETURN_DEFER; } symbol_name = cJSON_GetObjectItem(symbol, "name"); symbol_score = cJSON_GetObjectItem(symbol, "score"); symbol_description = cJSON_GetObjectItem(symbol, "description"); if ((symbol_name != NULL) && (symbol_score != NULL) && (cJSON_IsString(symbol_name)) && (cJSON_IsNumber(symbol_score))) { *yield = string_sprintf("%s%s%6.2f %-20s%s%s", *yield, (first_symbol ? "" : "\n"), symbol_score->valuedouble, symbol_name->valuestring, (((symbol_description != NULL) && (cJSON_IsString(symbol_description))) ? " " : ""), (((symbol_description != NULL) && (cJSON_IsString(symbol_description))) ? symbol_description->valuestring : "")); first_symbol = 0; } symbol_options = cJSON_GetObjectItem(symbol, "options"); if (symbol_options != NULL) { if (!cJSON_IsArray(symbol_options)) { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: JSON parse error, options of symbol %s is not an array", symbol_name->valuestring); *yield = string_sprintf("rspamd dlfunc: JSON parse error, options of symbol %s is not an array", symbol_name->valuestring); goto RETURN_DEFER; } c = cJSON_GetArraySize(symbol_options); for (j = 0; j < c; j++) { option = cJSON_GetArrayItem (symbol_options, j); if ((option != NULL) && (cJSON_IsString(option))) { *yield = string_sprintf("%s%s%s%s", *yield, (j == 0 ? " [" : ""), option->valuestring, (j < c - 1 ? ", " : "]")); } } } } *yield = string_sprintf("%s\"", *yield); } return OK; /* Come here if any call to read_response, other than a response after the data phase, failed. Analyse the error, and if isn't too bad, send a QUIT command. Wait for the response with a short timeout, so we don't wind up this process before the far end has had time to read the QUIT. */ WRITE_FAILED: { log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: %s on rspamd socket", strerror(errno)); *yield = string_sprintf("rspamd dlfunc: %s on rspamd socket", strerror(errno)); goto RETURN_DEFER; } RETURN_DEFER: { if (rspamd_cctx.sock > 0) { (void)close(rspamd_cctx.sock); rspamd_cctx.sock = 0; } if (mbox_file != NULL) { (void)fclose(mbox_file); mbox_file = NULL; } return(defer_ok ? OK : ERROR); } return OK; }