// // Copyright (c) Victor Ustugov 2006-2008 // http://mta.org.ua/exim-4.68-conf/dlfunc/aveserver/aveserver.c // License: GPLv2 // // // based on examples 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}{aveserver}\ // {/var/run/aveserver}{defer_ok}} // //#include "local_scan.h" //#include "macros.h" #include "exim.h" #include #include #include #include #include #include #include //extern BOOL split_spool_directory; /* TRUE to use multiple subdirs */ //extern uschar *spool_directory; /* Name of spool directory */ //extern uschar message_subdir[]; /* Subdirectory for messages */ #define AVESERVER_TIMEOUT 120 #ifndef WITH_CONTENT_SCAN #define MAX_TMP_CREATE_FAILS 10 const char temp_dir[] = "/var/spool/exim/aveserver"; #endif //------------------------------------------------------------------------- char *read_response(FILE *aveserver_file, char *aveserver_buffer, int aveserver_buffer_size, int timeout) { char *rc; int save_errno; aveserver_buffer[0] = 0; // In case nothing gets read sigalrm_seen = FALSE; alarm(timeout); rc = Ufgets(aveserver_buffer, aveserver_buffer_size, aveserver_file); save_errno = errno; alarm(0); errno = save_errno; return(rc); } //------------------------------------------------------------------------- BOOL write_command(int fd, char *format, ...) { int count, rc; va_list ap; va_start(ap, format); if (!string_vformat(big_buffer, big_buffer_size, CS format, ap)) { errno = ERRNO_SMTPFORMAT; return FALSE; } va_end(ap); count = Ustrlen(big_buffer); debug_printf(" write to file>> %s", big_buffer); rc = write(fd, big_buffer, count); big_buffer[count-2] = 0; /* remove \r\n for debug and error message */ if (rc > 0) return TRUE; debug_printf(" write failed: %s\n", strerror(errno)); return FALSE; } //------------------------------------------------------------------------- int aveserver(uschar **yield, int argc, uschar *argv[]) { char *arg_socket_addr; char *arg_defer_ok; int defer_ok; char tcp_addr[15]; int tcp_port; FILE *aveserver_file = NULL; #ifdef WITH_CONTENT_SCAN 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; header_line *my_headerlist; int i; #endif unsigned long mbox_size; uschar *s, *p; int aveserver_sock = 0; struct hostent *he; struct in_addr in; struct sockaddr_un server; char mbox_path[512]; #ifndef NO_POLL_H int result; struct pollfd pollfd; #endif int offset; uschar aveserver_buffer[32600]; time_t start; size_t read, wrote; int 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 < 2) { log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: Bad number of arguments: %d", argc); *yield = string_sprintf("aveserver 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, "aveserver dlfunc: Socket address expected"); *yield = string_sprintf("aveserver dlfunc: Socket address expected"); goto RETURN_DEFER; } // get message body stream #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) { *yield = string_copy((uschar *)"aveserver dlfunc: Unable to spool message"); goto RETURN_DEFER; } (void)fclose(mbox_file); mbox_file = -1; sprintf(mbox_path, "%s/scan/%s/%s.eml", spool_directory, message_id, message_id); #else if (split_spool_directory == 0) { sprintf(spool_path, "%s/input/%s-D", spool_directory, message_id); } else { sprintf(spool_path, "%s/input/%s/%s-D", spool_directory, message_subdir, 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, "aveserver dlfunc: Unable to spool message"); *yield = string_copy((uschar *)"aveserver dlfunc: DEFER: Unable to spool message"); goto RETURN_DEFER; } (void)fseek(spool_file, 0, SEEK_END); mbox_size = ftell(spool_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(spool_file, SPOOL_DATA_START_OFFSET, SEEK_SET); for (i = 0; i < MAX_TMP_CREATE_FAILS; 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, 0640)) > 0) break; if (errno != EEXIST) { // Unexpected error debug_printf("Unable create temporary file: %d\n", errno); log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: Unable create temporary file %s: %d", mbox_path, errno); *yield = string_sprintf("aveserver 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, "aveserver dlfunc: Unable create temporary file %s", mbox_path); *yield = string_sprintf("aveserver dlfunc: DEFER: Unable create temporary file %s", mbox_path); goto RETURN_DEFER; } 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(aveserver_buffer, 1, sizeof(aveserver_buffer) - 1, spool_file); aveserver_buffer[read_count] = 0; //debug_printf("Read %d bytes from file: %s", read_count, aveserver_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, aveserver_buffer); again: wrote_count = write(mbox_file, aveserver_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, "aveserver dlfunc: error reading spool file: %s", strerror(errno)); *yield = string_sprintf("aveserver 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 start = time(NULL); if ((aveserver_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: Unable to acquire socket (%s)", strerror(errno)); *yield = string_sprintf("aveserver 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(aveserver_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: Unable to connect to UNIX socket %s (%s)", socket, strerror(errno) ); *yield = string_sprintf("aveserver 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 aveserver on aveserver_sock aveserver_file = fdopen(aveserver_sock, "rb"); aveserver_buffer[0] = 0; // In case nothing gets read p = read_response(aveserver_file, aveserver_buffer, sizeof(aveserver_buffer) - 1, AVESERVER_TIMEOUT - time(NULL) + start); if (p == NULL) goto RESPONSE_FAILED; debug_printf(" aveserver response: %s\n", p); if (aveserver_buffer[0] == '4') { *yield = string_sprintf("aveserver dlfunc: aveserver defer: %s", aveserver_buffer); goto RETURN_DEFER; } else if (aveserver_buffer[0] == '2') { if (!write_command(aveserver_sock, "SCAN bPQRSTUW %s\r\n", mbox_path)) goto WRITE_FAILED; p = read_response(aveserver_file, aveserver_buffer, sizeof(aveserver_buffer) - 1, AVESERVER_TIMEOUT - time(NULL) + start); if (p == NULL) goto RESPONSE_FAILED; debug_printf(" aveserver response: %s\n", p); if (aveserver_buffer[0] == '4') { *yield = string_sprintf("aveserver dlfunc: aveserver defer: %s", aveserver_buffer); goto RETURN_DEFER; } else if (aveserver_buffer[0] == '3') { offset = Ustrlen(aveserver_buffer); p = read_response(aveserver_file, aveserver_buffer + offset, sizeof(aveserver_buffer) - 1 - offset, AVESERVER_TIMEOUT - time(NULL) + start); if (p == NULL) goto RESPONSE_FAILED; debug_printf(" aveserver response: %s\n", p); if (aveserver_buffer[offset] == '4') { *yield = string_sprintf("aveserver dlfunc: aveserver defer: %s", aveserver_buffer[offset]); goto RETURN_DEFER; } else if (aveserver_buffer[offset] != '2') { *yield = string_sprintf("aveserver dlfunc: aveserver failed: %s", aveserver_buffer[offset]); goto RESPONSE_FAILED; } } else if (aveserver_buffer[0] != '2') { *yield = string_sprintf("aveserver dlfunc: aveserver failed: %s", aveserver_buffer); goto RESPONSE_FAILED; } } else { *yield = string_sprintf("aveserver dlfunc: aveserver failed: %s", aveserver_buffer); goto RESPONSE_FAILED; } *yield = string_sprintf("AVESERVER answer: %s", aveserver_buffer); if (write_command(aveserver_sock, "QUIT\r\n")) { p = read_response(aveserver_file, aveserver_buffer, sizeof(aveserver_buffer) - 1, AVESERVER_TIMEOUT - time(NULL) + start); if (p != NULL) debug_printf(" aveserver response: %s\n", p); } if (aveserver_file >= 0) (void)fclose(aveserver_file); if (aveserver_sock >= 0) (void)close(aveserver_sock); #ifdef WITH_CONTENT_SCAN // if (mbox_file >= 0) (void)fclose(mbox_file); if (mbox_file != -1) (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 return OK; RESPONSE_FAILED: { int code; int save_errno; int more_errno; uschar message_buffer[256]; uschar *message; save_errno = errno; message = &message_buffer[0]; if (check_response(&save_errno, more_errno, aveserver_buffer, &code, &message)) { if (write_command(aveserver_sock, "QUIT\r\n")) read_response(aveserver_file, aveserver_buffer, sizeof(aveserver_buffer), AVESERVER_TIMEOUT - time(NULL) + start); } log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: %s", message); *yield = string_sprintf("aveserver dlfunc: %s", message); goto RETURN_DEFER; } #ifndef WITH_CONTENT_SCAN FILE_WRITE_FAILED: { log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: Unable to write to temporary file %s", mbox_path); *yield = string_sprintf("aveserver dlfunc: DEFER: Unable to write to temporary file %s", mbox_path); goto RETURN_DEFER; } #endif WRITE_FAILED: { if (errno == ERRNO_CHHEADER_FAIL) { log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: Failed to expand headers_add or headers_remove: %s", expand_string_message); *yield = string_sprintf("aveserver dlfunc: Failed to expand headers_add or headers_remove: %s", expand_string_message); } else if (errno == ERRNO_FILTER_FAIL) { log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: Filter process failure"); *yield = string_sprintf("aveserver dlfunc: Filter process failure"); } else if (errno == ERRNO_WRITEINCOMPLETE) { log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: Failed repeatedly to write data"); *yield = string_sprintf("aveserver dlfunc: Failed repeatedly to write data"); } else if (errno == ERRNO_SMTPFORMAT) { log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: overlong command generated"); *yield = string_sprintf("aveserver dlfunc: overlong command generated"); } else { log_write(0, LOG_MAIN|LOG_PANIC, "aveserver dlfunc: write failed: error %d: %s", errno, strerror(errno)); *yield = string_sprintf("aveserver dlfunc: write failed: error %d: %s", errno, strerror(errno)); } goto RETURN_DEFER; } RETURN_DEFER: { if (aveserver_file >= 0) (void)fclose(aveserver_file); if (aveserver_sock >= 0) (void)close(aveserver_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 return(defer_ok ? OK : ERROR); } return OK; }