// // based on source code from http://www.ols.es/exim/dlext/ by David Saez , // and kas_exim.c from KAS 3.0.242 by Kaspersky Lab // // warn set acl_m0 = ${dlfunc{/usr/local/libexec/exim/exim-dlfunc.so}{kas3}\ // {tcp:127.0.0.1:2277}{defer_ok}{$qualify_domain}} // //#include "local_scan.h" #include "exim.h" #include #include #include #include #include #include #include #include #include "spamtest.h" #include "msgstore.h" extern uschar *tod_stamp(int); //extern uschar *spool_directory; /* Default values for configuration options */ #define KAS_CONNECT_TIMEOUT 60000 #define KAS_DEFAULT_DOMAIN "" #define KAS_DEFER_OK 0 #define KAS_DATA_TIMEOUT 60000 #define KAS_CONNECT_TO "tcp:127.0.0.1:2277" int check_results(spamtest_session_t *s, spamtest_status_t *status, int *res, uschar **return_text); int proceed_accept(spamtest_status_t *status, uschar **return_text); //------------------------------------------------------------------------- int kas3(uschar **yield, int argc, uschar *argv[]) { char *arg_socket_addr; char *arg_defer_ok; char *arg_default_domain; int defer_ok; char default_domain[64] = KAS_DEFAULT_DOMAIN; spamtest_session_t s; int spamtest_session_flag = 0; spamtest_status_t *status = NULL; int errorcode; uschar *sender_address; uschar *sender_host_address; int rcpt_sent = 0; unsigned long cnt = 0; int totalcnt = 0; int i; int res = 0; uschar buf[32600]; char *ctmp; FILE *mbox_file = NULL; uschar *address; header_line *my_header, *header_new, *header_last, *tmp_headerlist; header_line *last_received = NULL; uschar *str; uschar mbox_path[512]; arg_socket_addr = argv[0]; arg_defer_ok = argv[1]; arg_default_domain = argv[2]; if (argc < 2) { defer_ok = KAS_DEFER_OK; } 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 (argc < 3) { log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: Bad number of arguments: %d", argc); *yield = string_sprintf("kas3 dlfunc: DEFER: Bad number of arguments: %d", argc); goto RETURN_DEFER; } if ((arg_socket_addr == NULL) || (arg_socket_addr[0] == 0)) { log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: Spamtest access address not defined, cannot process mail by spam filtering"); *yield = string_sprintf("kas3 dlfunc: DEFER: Spamtest access address not defined, cannot process mail by spam filtering"); goto RETURN_DEFER; } if (arg_default_domain == NULL) arg_default_domain = (char *)&default_domain; bzero(&s,sizeof(s)); s.access_address = arg_socket_addr; s.rw_timeout = KAS_DATA_TIMEOUT; s.connect_timeout = KAS_CONNECT_TIMEOUT; s.mail_domain = arg_default_domain; debug_printf("Spam test session: %s %s %d %d\n", s.access_address, s.mail_domain, s.connect_timeout, s.rw_timeout); if ((errorcode = sts_init(&s)) < 0 ) { log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: Unable to connect to %s (%s)", s.access_address, sts_strerror(errorcode)); *yield = string_sprintf("kas3 dlfunc: DEFER: Unable to connect to %s (%s)", s.access_address, sts_strerror(errorcode)); goto RETURN_DEFER; }; spamtest_session_flag = 1; if ( ! s.should_store_message ) { /* SMTP mode, blackholing message because filter will send message */ //recipients_count = 0; log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: Filter in SMTP mode, Incorrect situation."); *yield = string_sprintf("kas3 dlfunc: DEFER: Filter in SMTP mode, Incorrect situation."); goto RETURN_DEFER; } sender_address = expand_string(US"${sender_address}"); if (sender_address != NULL && sender_address[0] != '\0') { debug_printf("Send sender address: %s\n", sender_address); if ((errorcode = sts_set_sender(&s, sender_address)) != STS_ERR_NO_ERR ) { log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: Unable to pass sender address %s (%d)", sender_address, errorcode); *yield = string_sprintf("kas3 dlfunc: DEFER: Unable to pass sender address %s(%d)", sender_address, errorcode); goto RETURN_DEFER; } } sender_host_address = expand_string(US"${sender_host_address}"); if (sender_host_address != NULL && sender_host_address[0] != '\0') { debug_printf("Send sender host address: %s\n", sender_host_address); if ((errorcode = sts_set_relay(&s, sender_host_address)) != STS_ERR_NO_ERR) { log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: Unable to pass relay IP %s (%d)", sender_host_address, errorcode); if (errorcode == STS_ERR_OUT_OF_MEMORY) { *yield = string_sprintf("kas3 dlfunc: DEFER: Unable to pass relay IP %s (%d)", sender_host_address, errorcode); goto RETURN_DEFER; } } } rcpt_sent = 0; for (i = 0; i < recipients_count; i++) { if (recipients_list[i].address != NULL && recipients_list[i].address[0] != '\0') { debug_printf("Send recipient address: %s\n", recipients_list[i].address); if ((errorcode = sts_add_recipient(&s,recipients_list[i].address)) != STS_ERR_NO_ERR) { log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: Unable to pass recipient '%s' to filter",recipients_list[i].address); } else { rcpt_sent++; } } } if (rcpt_sent == 0) { debug_printf("Recipients not sent to filter. Continue\n"); } sprintf(mbox_path, "%s/input/%s-D", spool_directory, 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, "kas3 dlfunc: Unable to spool message"); *yield = string_copy((uschar *)"kas3 dlfunc: DEFER: Unable to spool message"); goto RETURN_DEFER; } debug_printf("fseek %d, %d\n", SPOOL_DATA_START_OFFSET, SEEK_SET); (void)fseek(mbox_file, SPOOL_DATA_START_OFFSET, SEEK_SET); address = expand_string(US"${sender_address}"); if (address && *address) { sprintf(buf, "Return-path: <%s>\n", address); debug_printf("Send to socket: %s", buf); if ((status = sts_send_body(&s, buf, strlen(buf), STS_REINIT_TIMEOUT)) == NULL) goto WRITE_FAILED; } // 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}"); if (address && *address) { sprintf(buf, "Envelope-To: %s\n", address); debug_printf("Send to socket: %s", buf); if ((status = sts_send_body(&s, buf, strlen(buf), STS_REINIT_TIMEOUT)) == NULL) goto WRITE_FAILED; } sprintf(buf, "Delivery-date: %s\n", tod_stamp(tod_full)); debug_printf("Send to socket: %s", buf); if ((status = sts_send_body(&s, buf, strlen(buf), STS_REINIT_TIMEOUT)) == NULL) goto WRITE_FAILED; // create a copy of 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)); header_new->text = string_copyn(my_header->text, my_header->slen); 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 ((str = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) { int len = Ustrlen(str); if (header_testname(my_header, str, 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)); header_new->text = string_copyn(my_header->text, my_header->slen); 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; } my_header = tmp_headerlist; while (my_header != NULL) { if ((my_header->type != '*') && (my_header->type != htype_old)) { debug_printf("Send header. Iterating a, %c %d %s|%d\n", my_header->type, my_header->slen, my_header->text, my_header->text[my_header->slen-1]); if ((status = sts_send_body(&s, my_header->text, my_header->slen, STS_REINIT_TIMEOUT)) == NULL) { // failure. finish proceed log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: NULL status after sending header %s", my_header->text); *yield = string_sprintf("kas3 dlfunc: DEFER: NULL status after sending header %s", my_header->text); goto RETURN_DEFER; } else { // check status and finish if need debug_printf("Header '%s' sent to filter\n", my_header->text); if (check_results(&s, status, &res, yield) > 0) { if (res == LOCAL_SCAN_TEMPREJECT) goto RETURN_DEFER; else if (res == LOCAL_SCAN_ACCEPT) goto RETURN_OK; else if (res == LOCAL_SCAN_REJECT) goto RETURN_ERROR; } } } if (my_header == header_last) break; my_header = my_header->next; } debug_printf("Send empty string\n"); if ((status = sts_send_body(&s,"\n", 1, STS_REINIT_TIMEOUT)) == NULL) { // failure. finish proceed log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: NULL status after sending empty string (end of headers)"); *yield = string_sprintf("kas3 dlfunc: DEFER: NULL status after sending empty string (end of headers)"); goto RETURN_DEFER; } else { // check status and finish if need debug_printf("Empty string (end of headers) sent to filter\n"); if ( check_results(&s, status, &res, yield) > 0 ) { if (res == LOCAL_SCAN_TEMPREJECT) goto RETURN_DEFER; else if (res == LOCAL_SCAN_ACCEPT) goto RETURN_OK; else if (res == LOCAL_SCAN_REJECT) goto RETURN_ERROR; } } debug_printf("Headers sent\n"); while ((cnt = fread(buf, 1, sizeof(buf) - 1, mbox_file)) > 0) { buf[cnt] = '\0'; debug_printf("Send %d bytes to filter\n", cnt); status = sts_send_body(&s, buf, cnt, STS_REINIT_TIMEOUT); debug_printf("Sent to filter:\n%s", buf); if (status == NULL) { // failure. finish proceed log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: NULL status after sending part of body"); *yield = string_copy((uschar *)"kas3 dlfunc: DEFER: NULL status after sending part of body"); goto RETURN_DEFER; } else { // check status and finish if need debug_printf("Part of body sent to filter\n"); if ( check_results(&s, status, &res, yield) > 0 ) { if (res == LOCAL_SCAN_TEMPREJECT) goto RETURN_DEFER; else if (res == LOCAL_SCAN_ACCEPT) goto RETURN_OK; else if (res == LOCAL_SCAN_REJECT) goto RETURN_ERROR; } } totalcnt += cnt; } debug_printf("Total sent %d bytes of message\n",totalcnt); status = sts_body_end(&s, STS_FLUSH_DATA); debug_printf("Body sent\n"); if (check_results(&s, status, &res, yield) > 0 ){ if (res == LOCAL_SCAN_TEMPREJECT) goto RETURN_DEFER; else if (res == LOCAL_SCAN_ACCEPT) goto RETURN_OK; else if (res == LOCAL_SCAN_REJECT) goto RETURN_ERROR; } debug_printf("EOF\n"); goto RETURN_OK; WRITE_FAILED: { // failure. finish proceed log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: NULL status after sending part of body"); *yield = string_copy((uschar *)"kas3 dlfunc: DEFER: NULL status after sending part of body"); goto RETURN_DEFER; } RETURN_DEFER: { if (mbox_file != NULL) (void)fclose(mbox_file); if (spamtest_session_flag) sts_close(&s); if ((*yield == NULL) || (*yield[0] == '\0')) *yield = string_sprintf("kas3 dlfunc: DEFER"); return(defer_ok ? OK : ERROR); } RETURN_ERROR: { if (mbox_file != NULL) (void)fclose(mbox_file); if (spamtest_session_flag) sts_close(&s); if ((*yield == NULL) || (*yield[0] == '\0')) *yield = string_sprintf("kas3 dlfunc: REJECT"); // return(ERROR); return(OK); } RETURN_OK: { if (mbox_file != NULL) (void)fclose(mbox_file); if (spamtest_session_flag) sts_close(&s); if ((*yield == NULL) || (*yield[0] == '\0')) *yield = string_sprintf("kas3 dlfunc: ACCEPT"); return(OK); } return OK; } //------------------------------------------------------------------------- int check_results(spamtest_session_t *s, spamtest_status_t *status, int *res, uschar **return_text) { debug_printf("Check results started\n"); switch (status->status) { case STS_SMTP_ACCEPT: case STS_BLACKHOLE: debug_printf("\tSMTP_ACCEPT or BLACK_HOLE\n"); recipients_count = 0; sts_close(s); *res = LOCAL_SCAN_ACCEPT; return 1; case STS_REJECT: debug_printf("\tREJECT\n"); *return_text = string_sprintf("kas3 dlfunc: REJECT: %s", (uschar *)status->reason); *res = LOCAL_SCAN_REJECT; return 1; case STS_FILTER_ERROR: debug_printf("\tFILTER_ERROR\n"); *return_text = string_copy((uschar *)"kas3 dlfunc: DEFER: filter error"); *res = LOCAL_SCAN_TEMPREJECT; return 1; case STS_SENDERROR: debug_printf("\tSENDERROR\n"); *return_text = string_copy((uschar *)"kas3 dlfunc: DEFER: I/O or other error"); *res = LOCAL_SCAN_TEMPREJECT; return 1; case STS_ACCEPT: debug_printf("\tACCEPT\n"); if (proceed_accept(status, return_text) != 0) *res = LOCAL_SCAN_TEMPREJECT; else *res = LOCAL_SCAN_ACCEPT; sts_close(s); return 1; case STS_CONTINUE: debug_printf("\tCONTINUE\n"); return 0; default: debug_printf("\tUnknown status: %d\n", status->status); return 0; } } //------------------------------------------------------------------------- extern int recipients_list_max; int proceed_accept(spamtest_status_t *status, uschar **return_text) { spamtest_status_t *st; message_status_t *mst; int i; int rcptn; int good_rcpt_cnt = 0,k; int found = 0; char buf[40960]; debug_printf("Accept_message started\n"); st = glue_actions(status); if (st==NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: glue_actions return NULL"); *return_text = string_copy((uschar *)"kas3 dlfunc: DEFER: glue_actions return NULL"); return -1; } mst = &st->ms_array[0] ; rcptn = rcptlist_count(mst->rcpts); for (k = 0; k < rcptn; k++) if (strlen(rcptlist_getn(mst->rcpts, k)) > 0) good_rcpt_cnt++; if (good_rcpt_cnt <1) { debug_printf("Empty recipient list\n"); log_write(0, LOG_MAIN, "kas3 dlfunc: Empty recipient list"); recipients_count = 0; return 0; } if ((mst != NULL) && (mst->action == STS_ACTION_CHANGE)) { debug_printf("spamtest status returned STS_ACTION_CHANGE for message\n"); bzero(&buf,sizeof(buf)); debug_printf("Actions count = %d\n", mst->action_count); for (i = 0; i < mst->action_count; i++) { int hdrlen; debug_printf("action type: %d\n", mst->act_array[i].type); hdrlen = (mst->act_array[i].header == NULL ? 0 : strlen(mst->act_array[i].header)); if (mst->act_array[i].value != NULL) hdrlen += strlen(mst->act_array[i].value) + 3; switch (mst->act_array[i].type) { case STS_HEADER_DEL: debug_printf("Delete header %s\n", mst->act_array[i].header); break; case STS_HEADER_ADD: debug_printf("Add value to header %s %s\n", mst->act_array[i].header,mst->act_array[i].value); if (hdrlen < sizeof(buf) - Ustrlen(buf)) { Ustrcpy(buf + Ustrlen(buf), mst->act_array[i].header); Ustrcpy(buf + Ustrlen(buf), ": "); Ustrcpy(buf + Ustrlen(buf), mst->act_array[i].value); Ustrcpy(buf + Ustrlen(buf), "\n"); } break; case STS_HEADER_PREPEND: debug_printf("Prepend value to header %s %s\n", mst->act_array[i].header, mst->act_array[i].value); if (hdrlen < sizeof(buf) - Ustrlen(buf)) { Ustrcpy(buf + Ustrlen(buf), mst->act_array[i].header); Ustrcpy(buf + Ustrlen(buf), ": "); Ustrcpy(buf + Ustrlen(buf), mst->act_array[i].value); Ustrcpy(buf + Ustrlen(buf), "\n"); } break; case STS_HEADER_CHG: debug_printf("Change header %s %s\n", mst->act_array[i].header,mst->act_array[i].value); if (hdrlen < sizeof(buf) - Ustrlen(buf)) { Ustrcpy(buf + Ustrlen(buf), mst->act_array[i].header); Ustrcpy(buf + Ustrlen(buf), ": "); Ustrcpy(buf + Ustrlen(buf), mst->act_array[i].value); Ustrcpy(buf + Ustrlen(buf), "\n"); } break; case STS_HEADER_NEW: debug_printf("New header %s: %s\n", mst->act_array[i].header, mst->act_array[i].value); if (hdrlen < sizeof(buf) - Ustrlen(buf)) { Ustrcpy(buf + Ustrlen(buf), mst->act_array[i].header); Ustrcpy(buf + Ustrlen(buf), ": "); Ustrcpy(buf + Ustrlen(buf), mst->act_array[i].value); Ustrcpy(buf + Ustrlen(buf), "\n"); } break; default: log_write(0, LOG_MAIN, "kas3 dlfunc: Invalid action type %d",mst->act_array[i].type); } debug_printf("buf: '%s'\n", buf); } *return_text = string_sprintf((uschar *)"kas3 dlfunc: ACCEPT: %s", buf); } else if ((mst != NULL) && (mst->action == STS_ACTION_BOUNCE)) { // bounce message log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: spamtest status returned STS_ACTION_BOUNCE for message, but we don't support this status"); *return_text = string_copy((uschar *)"kas3 dlfunc: DEFER: spamtest status returned STS_ACTION_BOUNCE for message, but we don't support this status"); return -1; } else { if ( mst != NULL ) { log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: Invalid action %d", mst->action); *return_text = string_sprintf("kas3 dlfunc: DEFER: Invalid action %d", mst->action); } else { log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: mst is NULL"); *return_text = string_sprintf("kas3 dlfunc: DEFER: mst is NULL"); } return -1; } debug_printf("Message accepted\n"); return 0; }