// // 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" #ifndef WITH_CONTENT_SCAN extern uschar *tod_stamp(int); //extern uschar *spool_directory; #endif /* 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; #ifdef WITH_CONTENT_SCAN unsigned long mbox_size; int mbox_delimiter_skiped = 0; uschar *p; #else header_line *my_headerlist; uschar mbox_path[512]; #endif 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"); } #ifdef WITH_CONTENT_SCAN debug_printf("Open spool file\n"); // make sure the eml mbox file is spooled up mbox_file = spool_mbox(&mbox_size); if (!mbox_file) { log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: Unable to spool message"); *yield = string_copy((uschar *)"kas3 dlfunc: Unable to spool message"); goto RETURN_DEFER; } 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; } #else 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; my_headerlist = header_list; while (my_headerlist != NULL) { if (my_headerlist->type != '*') { debug_printf("Send header. Iterating a, %c %d %s|%d\n", my_headerlist->type, my_headerlist->slen, my_headerlist->text, my_headerlist->text[my_headerlist->slen-1]); if ((status = sts_send_body(&s, my_headerlist->text, my_headerlist->slen, STS_REINIT_TIMEOUT)) == NULL) { // failure. finish proceed log_write(0, LOG_MAIN|LOG_PANIC, "kas3 dlfunc: NULL status after sending header %s", my_headerlist->text); *yield = string_sprintf("kas3 dlfunc: DEFER: NULL status after sending header %s", my_headerlist->text); goto RETURN_DEFER; } else { // check status and finish if need debug_printf("Header '%s' sent to filter\n", my_headerlist->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_headerlist == header_last) { debug_printf("This was last header\n"); break; } my_headerlist = my_headerlist->next; } debug_printf("Headers sent\n"); 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("Enter sent\n"); #endif while ((cnt = fread(buf, 1, sizeof(buf) - 1, mbox_file)) > 0) { buf[cnt] = '\0'; #ifdef WITH_CONTENT_SCAN p = buf; if (mbox_delimiter_skiped == 0) { debug_printf("Try to find unix mailbox delimiter\n"); p = Ustrstr(buf, "From "); if (p == buf) { debug_printf("unix mailbox delimiter found\n"); p = Ustrstr(buf, "\n"); if (p != NULL) { p++; cnt -= p - buf; } } } mbox_delimiter_skiped = 1; debug_printf("Send %d bytes to filter\n", cnt); status = sts_send_body(&s, p, cnt, STS_REINIT_TIMEOUT); debug_printf("Sent to filter:\n%s", p); #else 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); #endif 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; }