// // based on source code from http://www.ols.es/exim/dlext/ by David Saez , // source code of exim by Philip Hazel // and source code of exiscan by Tom Kistner // // warn set acl_m0 = ${dlfunc{/usr/local/libexec/exim/exim-dlfunc.so}{spamd}\ // {127.0.0.1 783}{defer_ok}{spamd}{report}} // //#include "local_scan.h" //#include "macros.h" #include "exim.h" #include #include #include #include #include #include #include #define SPAMD_TIMEOUT 120 #define SPAM_COMMAND_REPORT 1 #define SPAM_COMMAND_PROCESS 2 extern uschar *tod_stamp(int); //------------------------------------------------------------------------- int spamd(uschar **yield, int argc, uschar *argv[]) { char *arg_socket_addr; char *arg_defer_ok; char *arg_user_name; char *arg_spamd_command; int defer_ok; int spamd_command; char tcp_addr[15]; int tcp_port; FILE *mbox_file = NULL; unsigned long mbox_size; uschar *s, *p; header_line *my_header, *header_new, *header_last, *tmp_headerlist; header_line *last_received = NULL; uschar *address; char mbox_path[512]; uschar spamd_buffer2[32600]; int max_len, len; int spamd_sock = 0; struct hostent *he; struct in_addr in; struct sockaddr_un server; #ifndef NO_POLL_H int result; struct pollfd pollfd; #endif int offset; uschar spamd_buffer[32600]; time_t start; size_t read, wrote; int i; arg_socket_addr = argv[0]; arg_defer_ok = argv[1]; arg_user_name = argv[2]; arg_spamd_command = argv[3]; if (argc < 3) { 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 < 3) { log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: Bad number of arguments: %d", argc); *yield = string_sprintf("spamd dlfunc: 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, "spamd dlfunc: Socket address expected"); *yield = string_sprintf("spamd dlfunc: Socket address expected"); goto RETURN_DEFER; } if ((arg_user_name == NULL) || (arg_user_name[0] == 0)) { log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: Username expected"); *yield = string_sprintf("spamd dlfunc: Username expected"); goto RETURN_DEFER; } if ((arg_spamd_command == NULL) || (arg_spamd_command[0] == 0)) { spamd_command = SPAM_COMMAND_REPORT; } else if (strcmpic(arg_spamd_command,US"process") == 0) { spamd_command = SPAM_COMMAND_PROCESS; } else { spamd_command = SPAM_COMMAND_REPORT; } debug_printf(" SPAMD command: %s\n", (spamd_command == SPAM_COMMAND_REPORT ? "REPORT" : "PROCESS")); // get message body stream 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) { *yield = string_copy((uschar *)"dspam dlfunc: Unable to spool message"); return(defer_ok ? OK : ERROR); } (void)fseek(mbox_file, 0, SEEK_END); mbox_size = ftell(mbox_file); debug_printf(" Total spool file size: %d\n", mbox_size); mbox_size -= SPOOL_DATA_START_OFFSET; debug_printf(" Spool file size: %d\n", 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] != '/') { if (sscanf(CS arg_socket_addr, "%s %u", tcp_addr, &tcp_port) != 2 ) { log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: Invalid spamd address: '%s'", arg_socket_addr); *yield = string_sprintf("spamd dlfunc: Invalid spamd address: '%s'", arg_socket_addr); goto RETURN_DEFER; } /* Lookup the host */ if((he = gethostbyname(CS tcp_addr)) == 0) { log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: failed to lookup host '%s'", tcp_addr); *yield = string_sprintf("spamd dlfunc: failed to lookup host '%s'", tcp_addr); goto RETURN_DEFER; } in = *(struct in_addr *) he->h_addr_list[0]; /* contact a spamd */ if ((spamd_sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: TCP socket creation failed: %s", strerror(errno)); *yield = string_sprintf("spamd dlfunc: TCP socket creation failed: %s", strerror(errno)); goto RETURN_DEFER; }; if (ip_connect(spamd_sock, AF_INET, (uschar*)inet_ntoa(in), tcp_port, 5) < 0) { if (spamd_sock >= 0) (void)close(spamd_sock); log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: connection to %s, port %u failed: %s", tcp_addr, tcp_port, strerror(errno)); *yield = string_sprintf("spamd dlfunc: connection to %s, port %u failed: %s", tcp_addr, tcp_port, strerror(errno)); goto RETURN_DEFER; } debug_printf(" Use TCP socket %s:%d\n", tcp_addr, tcp_port); } else { if ((spamd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: Unable to acquire socket (%s)", strerror(errno)); *yield = string_sprintf("spamd dlfunc: Unable to acquire socket (%s)", strerror(errno)); goto RETURN_DEFER; } server.sun_family = AF_UNIX; Ustrcpy(server.sun_path, arg_socket_addr); if (connect(spamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: Unable to connect to UNIX socket %s (%s)", socket, strerror(errno) ); *yield = string_sprintf("spamd dlfunc: 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); } // now we are connected to spamd on spamd_sock memset(spamd_buffer2, 0, sizeof(spamd_buffer2)); offset = 0; address = expand_string(US"${sender_address}"); if (address && *address) { s = string_sprintf("Return-path: <%s>\n", address); max_len = sizeof(spamd_buffer2) - offset - 1; len = Ustrlen(s); if (len > max_len) len = max_len; Ustrncpy(spamd_buffer2 + offset, s, len); offset += len; } // 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); max_len = sizeof(spamd_buffer2) - offset - 1; len = Ustrlen(s); if (len > max_len) len = max_len; Ustrncpy(spamd_buffer2 + offset, s, len); offset += len; } s = string_sprintf("Delivery-date: %s\n", tod_stamp(tod_full)); max_len = sizeof(spamd_buffer2) - offset - 1; len = Ustrlen(s); if (len > max_len) len = max_len; Ustrncpy(spamd_buffer2 + offset, s, len); offset += len; // 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 ((s = string_nextinlist(&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)); 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; } // copy all the headers to data buffer my_header = tmp_headerlist; while (my_header) { if (my_header->type != htype_old) { max_len = sizeof(spamd_buffer2) - 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_sprintf("\r\n"); max_len = sizeof(spamd_buffer2) - 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: %d\n", mbox_size); // copy request to buffer string_format(spamd_buffer, sizeof(spamd_buffer), "%s SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n", (spamd_command == SPAM_COMMAND_PROCESS ? "PROCESS" : "REPORT"), arg_user_name, mbox_size); // send our request if (send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: spamd send failed: %s", strerror(errno)); goto RETURN_DEFER; } /* * now send the data buffer and spool file */ Ustrcpy(big_buffer, "sending data block"); debug_printf("sending data block\n"); debug_printf(" Send to socket: %s", spamd_buffer2); wrote = send(spamd_sock, spamd_buffer2, strlen(spamd_buffer2), 0); if (wrote == -1) goto WRITE_FAILED; /* * Note: poll() is not supported in OSX 10.2. */ #ifndef NO_POLL_H pollfd.fd = spamd_sock; pollfd.events = POLLOUT; #endif // (void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK); do { read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file); if (read < sizeof(spamd_buffer)) spamd_buffer[read] = 0; //debug_printf(" Read from file: %s", spamd_buffer); if (read > 0) { offset = 0; again: #ifndef NO_POLL_H result = poll(&pollfd, 1, 1000); if (result == -1 && errno == EINTR) continue; else if (result < 1) { if (result == -1) log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: %s on spamd socket", strerror(errno)); else { if (time(NULL) - start < SPAMD_TIMEOUT) goto again; log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: timed out writing spamd socket"); *yield = string_sprintf("spamd dlfunc: timed out writing spamd socket"); } goto RETURN_DEFER; } #endif wrote = send(spamd_sock, spamd_buffer + offset, read - offset, 0); //debug_printf(" Send to socket %d bytes: %s", read - offset, spamd_buffer + offset); 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, "spamd dlfunc: error reading spool file: %s", strerror(errno)); *yield = string_sprintf("spamd dlfunc: error reading spool file: %s", strerror(errno)); goto RETURN_DEFER; } /* read spamd response using what's left of the timeout. */ memset(spamd_buffer, 0, sizeof(spamd_buffer)); offset = 0; while((i = ip_recv(spamd_sock, spamd_buffer + offset, sizeof(spamd_buffer) - offset - 1, SPAMD_TIMEOUT - time(NULL) + start)) > 0 ) { offset += i; } /* error handling */ if((i <= 0) && (errno != 0)) { log_write(0, LOG_MAIN|LOG_PANIC, "spamd dlfunc: error reading from spamd socket: %s", strerror(errno)); *yield = string_sprintf("spamd dlfunc: error reading from spamd socket: %s", strerror(errno)); goto RETURN_DEFER; } if (spamd_command == SPAM_COMMAND_PROCESS) { s = Ustrstr(spamd_buffer, "\n\r\n"); if (s == NULL) s = Ustrstr(spamd_buffer, "\n\n"); p = s + 1; if (p != NULL) { s = Ustrstr(p, "\n\r\n"); if (s == NULL) s = Ustrstr(p, "\n\n"); if (s != NULL) s[1] = '\0'; } } //debug_printf("read from socket: %s", spamd_buffer); if (spamd_sock >= 0) (void)close(spamd_sock); if (mbox_file >= 0) (void)fclose(mbox_file); *yield = string_sprintf("SPAMD answer: %s", spamd_buffer); 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, "spamd dlfunc: %s on spamd socket", strerror(errno)); *yield = string_sprintf("spamd dlfunc: %s on spamd 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, "spamd dlfunc: %s", message); *yield = string_sprintf("spamd dlfunc: %s", message); goto RETURN_DEFER; } RETURN_DEFER: { if (spamd_sock >= 0) (void)close(spamd_sock); if (mbox_file >= 0) (void)fclose(mbox_file); return(defer_ok ? OK : ERROR); } return OK; }