// // 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); #ifndef WITH_CONTENT_SCAN const char temp_dir[] = "/var/spool/spamooborona"; #endif //------------------------------------------------------------------------- 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]; #ifdef WITH_CONTENT_SCAN unsigned long mbox_size; FILE *mbox_file = NULL; #else 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_headerlist; #endif 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 #ifdef WITH_CONTENT_SCAN sprintf(mbox_path, "%s/scan/%s/%s.eml", spool_directory, message_id, message_id); debug_printf("Use spooled file %s\n", mbox_path); // 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, "so dlfunc: Unable to spool message"); *yield = string_sprintf("so dlfunc: DEFER: Unable to spool message"); goto RETURN_DEFER; } // if (fclose(mbox_file) == 0) mbox_file = NULL; #else 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; for (my_headerlist = header_list; my_headerlist; my_headerlist = my_headerlist->next) { if (my_headerlist->type != '*') { debug_printf("Write to temporary file: %s", my_headerlist->text); if (write(mbox_file, my_headerlist->text, my_headerlist->slen) != my_headerlist->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; 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; #endif 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; #ifndef WITH_CONTENT_SCAN 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; } #endif 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); #ifdef WITH_CONTENT_SCAN if (mbox_file > 0) (void)fclose(mbox_file); #else 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; } #endif 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); #ifdef WITH_CONTENT_SCAN if (mbox_file > 0) (void)fclose(mbox_file); #else 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; } #endif 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; }