// // based on source code from http://www.ols.es/exim/dlext/ by David Saez , // and local_scan.c from Yandex spamoborona http://so.yandex.ru // // warn set acl_m0 = ${dlfunc{/usr/local/libexec/exim/exim-dlfunc.so}{spamoborona}\ // {/var/run/sp-daemon.sock}{defer_ok}} // //#include "local_scan.h" //#include "macros.h" #include "exim.h" #include #include #include #include #include #include #include #include //#define SO_TIMEOUT 120 #define READ_FAIL(x) ((x) < 0) #define MAX_SIZE_SO 64 * 1024 #define SO_FAILURE_HDR "X-SO-Flag" #define ERR_WRITE 53 #define ERR_READ 54 extern uschar *tod_stamp(int); const char temp_dir[] = "/var/spool/spamooborona"; //------------------------------------------------------------------------- int FakeSMTPCommand (int sock, char *command, char *value) { char sCommand[1024]; char answ [3]; int Len; sprintf(sCommand, "%s %s", command, value); debug_printf("Send to socket >> %s\n", sCommand); if (send(sock, sCommand, strlen(sCommand), 0) != (int)strlen(sCommand)) { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: socket sending '%s' error %d", sCommand, errno); // *yield = string_sprintf("so dlfunc: DEFER: socket sending '%s' error %d", sCommand, errno); return ERR_WRITE; } memset(answ, '\0', sizeof (answ)); Len = read(sock, answ, sizeof(answ)); if (READ_FAIL(Len)) { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: read() error %d, len=%d", errno, Len); // *yield = string_sprintf("so dlfunc: DEFER: read() error %d, len=%d", errno, Len); return ERR_WRITE; } debug_printf("Read from to socket << %s\n", answ); if (strncmp (answ, "OK", 2) != 0) { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: server did not confirm, answ=%s", answ); // *yield = string_sprintf("so dlfunc: DEFER: server did not confirm, answ=%s", answ); return ERR_WRITE; // Cannot read message error code } return OK; } //------------------------------------------------------------------------- char *read_response(FILE *daemon_file, char *daemon_buffer, int daemon_buffer_size, int timeout) { char *rc; int save_errno; daemon_buffer[0] = 0; // In case nothing gets read sigalrm_seen = FALSE; alarm(timeout); rc = Ufgets(daemon_buffer, daemon_buffer_size, daemon_file); save_errno = errno; alarm(0); errno = save_errno; return(rc); } //------------------------------------------------------------------------- int spamoborona(uschar **yield, int argc, uschar *argv[]) { char *arg_socket_addr; char *arg_defer_ok; int defer_ok; int so_sock = 0; FILE *so_file = NULL; char mbox_path[512]; int mbox_created = 0; int mbox_file = -1; FILE *spool_file = NULL; char spool_path[512]; static int miRand = 1; uschar *s; header_line *my_header, *header_new, *header_last, *tmp_headerlist; header_line *last_received = NULL; uschar *str; struct sockaddr_un server; uschar so_buffer[32600]; uschar *address; int i, rejected; char RejectStr[4096]; int offset, read_count, wrote_count; 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 (argc < 1) { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: Bad number of arguments: %d", argc); *yield = string_sprintf("so 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, "so dlfunc: Socket address expected"); *yield = string_sprintf("so dlfunc: DEFER: Socket address expected"); goto RETURN_DEFER; } // get message body stream sprintf(spool_path, "%s/input/%s-D", spool_directory, message_id); debug_printf("Open spool file: %s\n", spool_path); spool_file = fopen(spool_path, "rb"); if (!spool_file) { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: Unable to spool message"); *yield = string_copy((uschar *)"so dlfunc: DEFER: Unable to spool message"); return(defer_ok ? OK : ERROR); } debug_printf("fseek %d, %d\n", SPOOL_DATA_START_OFFSET, SEEK_SET); (void)fseek(spool_file, SPOOL_DATA_START_OFFSET, SEEK_SET); for (i = 0; i < 10; i++) { sprintf(mbox_path, "%s/%s-%8.8d-%3.3d-%6.6d", temp_dir, message_id, getpid(), miRand++, time(NULL)); umask ((mode_t) 0); debug_printf("Create temporary file %s\n", mbox_path); if ((mbox_file = open(mbox_path, O_RDWR | O_EXCL | O_CREAT, 0666)) > 0) break; if (errno != EEXIST) { /* Unexpected error */ debug_printf("Unable create temporary file: %d\n", errno); log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: Unable create temporary file %s: %d", mbox_path, errno); *yield = string_sprintf("so dlfunc: DEFER: Unable create temporary file %s: %d", mbox_path, errno); goto RETURN_DEFER; } /* Else, for some reason this file exists. Try again after a short sleep */ sleep(1); } if (mbox_file <= 0) { debug_printf("Unable create temporary file\n"); log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: Unable create temporary file %s", mbox_path); *yield = string_sprintf("so dlfunc: DEFER: Unable create temporary file %s", mbox_path); goto RETURN_DEFER; } address = expand_string(US"${sender_address}"); if (address && *address) { s = string_sprintf("Return-path: <%s>\n", address); debug_printf("Write to temporary file: %s", s); if (write(mbox_file, s, strlen(s)) != strlen(s)) goto FILE_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) { s = string_sprintf("Envelope-To: %s\n", address); debug_printf("Write to temporary file: %s", s); if (write(mbox_file, s, strlen(s)) != strlen(s)) goto FILE_WRITE_FAILED; } s = string_sprintf("Delivery-date: %s\n", tod_stamp(tod_full)); debug_printf("Write to temporary file: %s", s); if (write(mbox_file, s, strlen(s)) != strlen(s)) goto FILE_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; } for (my_header = tmp_headerlist; my_header; my_header = my_header->next) { if (my_header->type != '*') { debug_printf("Write to temporary file: %s", my_header->text); if (write(mbox_file, my_header->text, my_header->slen) != my_header->slen) goto FILE_WRITE_FAILED; } } s = string_sprintf("\r\n"); debug_printf("Write to temporary file: %s", s); if (write(mbox_file, s, strlen(s)) != strlen(s)) goto FILE_WRITE_FAILED; debug_printf("Headers wrote\n"); do { read_count = fread(so_buffer, 1, sizeof(so_buffer) - 1, spool_file); so_buffer[read_count] = 0; //debug_printf("Read %d bytes from file: %s", read_count, so_buffer); debug_printf("Read %d bytes from spool file\n", read_count); if (read_count > 0) { offset = 0; //debug_printf("Write %d bytes to temporary file\n", wrote_count, so_buffer); again: wrote_count = write(mbox_file, so_buffer + offset, read_count - offset); debug_printf("Wrote %d bytes to temporary file\n", wrote_count); if (wrote_count == -1) goto FILE_WRITE_FAILED; if (offset + wrote_count < read_count) { offset += wrote_count; goto again; } } } while (!feof(spool_file) && !ferror(spool_file)); if (ferror(spool_file)) { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: error reading spool file: %s", strerror(errno)); *yield = string_sprintf("so dlfunc: error reading spool file: %s", strerror(errno)); goto RETURN_DEFER; } debug_printf("Save temporary file: %s\n", mbox_path); (void)close(mbox_file); mbox_file = -1; mbox_created = 1; debug_printf("Close spool file: %s\n", spool_path); (void)fclose(spool_file); spool_file = NULL; if ((so_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: Unable to acquire socket (%s)", strerror(errno)); *yield = string_sprintf("so dlfunc: DEFER: Unable to acquire socket (%s)", strerror(errno)); goto RETURN_DEFER; } server.sun_family = AF_UNIX; if (Ustrlen(arg_socket_addr) > sizeof(server.sun_path) -1) { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: UNIX socket name %s too long", arg_socket_addr); *yield = string_sprintf("so dlfunc: DEFER: UNIX socket name %s too long", arg_socket_addr); goto RETURN_DEFER; } Ustrcpy(server.sun_path, arg_socket_addr); if (connect(so_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: Unable to connect to UNIX socket %s (%s)", socket, strerror(errno)); *yield = string_sprintf("so dlfunc: DEFER: Unable to connect to UNIX socket %s (%s)", socket, strerror(errno)); goto RETURN_DEFER; } debug_printf("Use UNIX Domain socket %s\n", arg_socket_addr); so_file = fdopen(so_sock, "rb"); // sender IP and hostname if (FakeSMTPCommand(so_sock, "CONNECT", (char *)expand_string(US"${sender_host_name} [${sender_host_address}]")) != OK) goto WRITE_FAILED; // envelope from address = expand_string(US"${sender_address}"); if (FakeSMTPCommand(so_sock, "MAILFROM", ((address == NULL) || (strlen(address) == 0)) ? "MAILER-DAEMON" : (char *)address) != OK) goto WRITE_FAILED; // envelope rcpto for (i = 0; i < recipients_count; i++) { if (FakeSMTPCommand(so_sock, "RCPTTO", recipients_list[i].address) != OK) goto WRITE_FAILED; } if (FakeSMTPCommand(so_sock, "DATA", mbox_path) != OK) goto WRITE_FAILED; if (FakeSMTPCommand(so_sock, ".", "") != OK) goto WRITE_FAILED; memset (so_buffer, '\0', sizeof(so_buffer)); { int Len; int answer_size; char answ[4096]; char *strP, *tok, *tmp, *tmp2; memset (RejectStr, '\0', sizeof(RejectStr)); Len = read(so_sock, answ, sizeof(answ) - 1); debug_printf("Read from socket: %s\n", answ); if (strncmp(answ, "SODAEMON ", 9) == 0) { strP = (char *)answ; for (tok = (char *)strtok (strP, "\n"); tok; tok = (char *)strtok (NULL, "\n")) { // signature always goes first if (strncmp (tok, "SODAEMON ", 9) == 0) { if (sscanf (tok, "%*s %d", &answer_size) == 1) { if (answer_size == 0) { // empty reply debug_printf("Zero answer size, check skiped\n"); Ustrcpy(so_buffer + Ustrlen(so_buffer), SO_FAILURE_HDR); Ustrcpy(so_buffer + Ustrlen(so_buffer), ": SKIP\n"); goto RETURN_OK; } else { if (answer_size > sizeof (answ) - 1) { debug_printf("daemon answer too long (%d bytes)\n", answer_size); log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: daemon answer too long (%d bytes)", answer_size); *yield = string_sprintf("so dlfunc: DEFER: daemon answer too long (%d bytes)", answer_size); goto RETURN_DEFER; } } } continue; } // reject or accept flag if (strncmp (tok, "REJECT ", 7) == 0) { sscanf (tok, "%*s %d", &rejected); continue; } // reject string if (strncmp (tok, "REJECTSTR ", 10) == 0) { if (rejected) { tmp = strchr(tok, ' '); tmp2 = tmp; if ((tmp2 != NULL) && (tmp2[0] == ' ')) tmp2++; if ((tmp2 == NULL) || (Ustrlen(tmp2) == 0)) { Ustrcpy(RejectStr, "Spam message rejected"); } else { Ustrcpy(RejectStr, tmp2); } } continue; } // spam flag if (strncmp (tok, "SPAM ", 5) == 0) { continue; } // spam label if (strncmp (tok, "SPAMSTR ", 8) == 0) { continue; } Ustrcpy(so_buffer + Ustrlen(so_buffer), tok); Ustrcpy(so_buffer + Ustrlen(so_buffer), "\n"); } } else { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: wrong signature in answer: %s", answ); *yield = string_sprintf("so dlfunc: DEFER: wrong signature in answer: %s", answ); goto RETURN_DEFER; } } goto RETURN_OK; FILE_WRITE_FAILED: { log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: Unable to write to temporary file %s", mbox_path); *yield = string_sprintf("so dlfunc: DEFER: Unable to write to temporary file %s", mbox_path); goto RETURN_DEFER; } WRITE_FAILED: { // log_write(0, LOG_MAIN|LOG_PANIC, // "so dlfunc: %s on so socket", strerror(errno)); *yield = string_sprintf("so dlfunc: DEFER: %s on so socket", strerror(errno)); goto RETURN_DEFER; } RESPONSE_FAILED: { int code; int save_errno; int more_errno; uschar message_buffer[256]; uschar *message; save_errno = errno; message = &message_buffer[0]; log_write(0, LOG_MAIN|LOG_PANIC, "so dlfunc: %s", message); *yield = string_sprintf("so dlfunc: DEFER: %s", message); goto RETURN_DEFER; } RETURN_DEFER: { if (so_file > 0) (void)fclose(so_file); if (so_sock > 0) (void)close(so_sock); if (spool_file > 0) (void)fclose(spool_file); if (mbox_file > 0) { (void)close(mbox_file); mbox_file = -1; } if (mbox_created > 0) { debug_printf("Remove temporary file %s\n", mbox_path); unlink(mbox_path); mbox_created = 0; } if ((*yield == NULL) || (*yield[0] == '\0')) *yield = string_sprintf("so dlfunc: DEFER"); return(defer_ok ? OK : ERROR); } RETURN_OK: { if (so_file > 0) (void)fclose(so_file); if (so_sock > 0) (void)close(so_sock); if (spool_file > 0) (void)fclose(spool_file); if (mbox_file > 0) { (void)close(mbox_file); mbox_file = -1; } if (mbox_created > 0) { debug_printf("Remove temporary file %s\n", mbox_path); unlink(mbox_path); mbox_created = 0; } if (rejected) { *yield = string_sprintf("so dlfunc: REJECT: %s\n%s", RejectStr, so_buffer); } else { *yield = string_sprintf("so dlfunc: ACCEPT\n%s", so_buffer); } // if ((*yield == NULL) || (*yield[0] == '\0')) // *yield = string_sprintf("so dlfunc: ACCEPT"); return OK; } return OK; }