// // Copyright (c) Victor Ustugov 2008 // http://mta.org.ua/exim-4.68-conf/dlfunc/milter/milter.c // License: GPLv2 // // // based on examples from http://www.ols.es/exim/dlext/ by David Saez , // source code of exim by Philip Hazel , // source code of exiscan by Tom Kistner // and contributed source code from libspawner http://developer.berlios.de/projects/libspawner // // // warn set acl_m0 = ${dlfunc{/usr/local/libexec/exim/exim-dlfunc.so}{milter}\ // {filter, S=inet:3333@localhost}{defer_ok}} // // warn set acl_m0 = ${dlfunc{/usr/local/libexec/exim/exim-dlfunc.so}{milter}\ // {spamassassin, S=unix:/var/run/spamass-milter/spamass-milter.sock}{defer_ok}} // //#include "local_scan.h" //#include "macros.h" #include "exim.h" #include #include "spawner.h" #include "protocol.h" int spool_file = -1; int local_scan_error; int local_scan_quarantine; //extern uschar *message_id; /* Internal id of message being handled */ //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 */ int local_scan_version_major(void) { return LOCAL_SCAN_ABI_VERSION_MAJOR; } int local_scan_version_minor(void) { return LOCAL_SCAN_ABI_VERSION_MINOR; } int local_scan_version(void) { return 1; } void local_scan_add_rcpt(spawner* sp, char* rcpt) { (void) sp; log_write(0, LOG_MAIN, "milter dlfunc: Adding recipient %s.", rcpt); receive_add_recipient(US rcpt, -1); return; } void local_scan_del_rcpt(spawner* sp, char* rcpt) { (void) sp; log_write(0, LOG_MAIN, "milter dlfunc: Removing recipient %s.", rcpt); receive_remove_recipient(US rcpt); return; } void local_scan_add_header(spawner* sp, char* hn, char* hb) { (void) sp; debug_printf("milter dlfunc: Adding \"%s\" message header: %s\n", hn, hb); log_write(0, LOG_MAIN, "milter dlfunc: Adding \"%s\" message header", hn); header_add(' ', "%s: %s\n", hn, hb); return; } void local_scan_change_header(spawner* sp, int index, char* hn, char* hb) { (void) sp; header_line *h; int i=0; debug_printf("milter dlfunc: Changing \"%s\" message header: %s\n", hn, hb); log_write(0, LOG_MAIN, "milter dlfunc: Changing \"%s\" message header", hn); if (strlen(hb) == 0) header_remove(index, US hb); else { /* Find header */ for (h=header_list; h != NULL; h=h->next) if(header_testname(h, US hn, strlen(hn), TRUE)) { i++; if (i==index) break; } if(i!=index) { log_write(0, LOG_MAIN, "\"%s\" header (%i) not found.", hn, index); return; } h->text = string_sprintf("%s: %s\n", hn, hb); h->slen=strlen(CS h->text); } return; } void local_scan_replace_body(spawner* sp, int size, char* chunk) { (void) sp; int r; int s=size; log_write(0, LOG_MAIN, "milter dlfunc: Replacing message body."); /* If this is the first call of replace_body, truncate the body file */ if ( lseek(spool_file, 0, SEEK_CUR ) == SPOOL_DATA_START_OFFSET) ftruncate(spool_file, SPOOL_DATA_START_OFFSET); while( s>0 ) { r=write(spool_file, chunk, s); if (r > 0) s-=r; else { log_write(0, LOG_MAIN | LOG_PANIC, "milter dlfunc: Error writing chunk to body file: %s.", strerror(errno)); local_scan_error=SPAWNER_TEMPFAIL; } } return; } void local_scan_do_quarantine(spawner* sp, char* reason) { (void) sp; log_write(0, LOG_MAIN, "milter dlfunc: Quarantining message ($s).", reason); local_scan_quarantine = 1; return; } int milter(uschar **yield, int argc, uschar *argv[]) { char *arg_socket_addr; char *arg_defer_ok; int defer_ok; spawner* sp = NULL; int protocol; int i, r; struct header_line *my_header; char *hn; char *hv; char spool_path[512]; 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("milter dlfunc: defer_ok: %d\n", defer_ok); if (argc < 2) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: Bad number of arguments: %d", argc); *yield = string_sprintf("milter 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, "milter dlfunc: Milter socket address expected"); *yield = string_sprintf("milter dlfunc: Milter socket address expected"); goto RETURN_DEFER; } debug_printf("spawner_init() result: %x\n", sp); sp = spawner_init(); if (sp == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: spawner_init failed"); *yield = string_sprintf("milter dlfunc: spawner_init failed"); goto RETURN_DEFER; } spawner_callback_add_rcpt(sp, &local_scan_add_rcpt); spawner_callback_del_rcpt(sp, &local_scan_del_rcpt); spawner_callback_add_header(sp, &local_scan_add_header); spawner_callback_change_header(sp, &local_scan_change_header); spawner_callback_replace_body(sp, &local_scan_replace_body); spawner_callback_quarantine(sp, &local_scan_do_quarantine); log_write(0, LOG_MAIN, "milter dlfunc: Connecting to %s...", arg_socket_addr); spawner_set_spec(sp, arg_socket_addr); debug_printf("spawner_set_spec(sp, '%s')\n", arg_socket_addr); // spawner_set_strict_state(sp, 0); //debug_printf("spawner_set_strict_state(sp, 0)\n"); debug_printf("spawner_open(sp) returned: %d\n", r); r = spawner_open(sp); if (r != SPAWNER_OK) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: spawner_open failed"); *yield = string_sprintf("milter dlfunc: spawner_open failed"); goto RETURN_DEFER; } if (sender_host_address != NULL) { protocol = (strchr(CS sender_host_address, ':') != NULL ? AF_INET6 : AF_INET); debug_printf("spawner_new_connection(sp, '%s', %d, %d, '%s') result: %d\n", CS sender_host_name, protocol, sender_host_port, CS sender_host_name, r); r = spawner_new_connection(sp, CS sender_host_name, protocol, sender_host_port, CS sender_host_address); } else { debug_printf("spawner_new_connection(sp, NULL, AF_UNSPEC, 0, NULL) result: %d\n", r); r = spawner_new_connection(sp, NULL, AF_UNSPEC, 0, NULL); } if (r < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: spawner_new_connection failed"); *yield = string_sprintf("milter dlfunc: spawner_new_connection failed"); goto RETURN_DEFER; } debug_printf("spawner_helo(sp, '%s') result: %d\n", CS sender_helo_name, r); r = spawner_helo(sp, CS sender_helo_name); if (r < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: spawner_helo failed"); *yield = string_sprintf("milter dlfunc: spawner_helo failed"); goto RETURN_DEFER; } debug_printf("spawner_set_from(sp, '%s', NULL) result: %d\n", CS sender_address, r); r = spawner_set_from(sp, CS sender_address, NULL); if (r < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: spawner_set_from failed"); *yield = string_sprintf("milter dlfunc: spawner_set_from failed"); goto RETURN_DEFER; } for ( i = 0; i < recipients_count; i++) { debug_printf("spawner_add_rcpt(sp, '%s', NULL) result: %d\n", CS recipients_list[i].address, r); r = spawner_add_rcpt(sp, CS recipients_list[i].address, NULL); if (r < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: spawner_add_rcpt failed (%s)", recipients_list[i].address); *yield = string_sprintf("milter dlfunc: spawner_add_rcpt failed (%s)", recipients_list[i].address); goto RETURN_DEFER; } } /* debug_printf("spawner_send_macros(sp, SMFIC_MAIL, \"i\", '%s', NULL);\n", message_id); r = spawner_send_macros(sp, SMFIC_MAIL, "i", message_id, NULL); if (r < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: spawner_send_macros failed"); *yield = string_sprintf("milter dlfunc: spawner_send_macros failed"); goto RETURN_DEFER; } */ for (my_header = header_list; my_header; my_header = my_header->next ) { if ((my_header->type != '*') && (my_header->type != htype_old)) { hn = strdup(CS my_header->text); if (hn == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: memory allocation error"); *yield = string_sprintf("milter dlfunc: memory allocation error"); goto RETURN_DEFER; } hv = strchr(CS hn, ':'); if (hv != NULL) { *hv = 0; hv++; while(isspace(*hv)) hv++; } debug_printf("spawner_send_header(sp, '%s', NULL) result: %d\n", CS hn, CS hv); r = spawner_send_header(sp, CS hn, CS hv); free(hn); if (r < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: spawner_send_header failed"); *yield = string_sprintf("milter dlfunc: spawner_send_header failed"); goto RETURN_DEFER; } } } debug_printf("spawner_send_eoh(sp)\n"); r = spawner_send_eoh(sp); if (r < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: spawner_send_eoh failed"); *yield = string_sprintf("milter dlfunc: spawner_send_eoh failed"); goto RETURN_DEFER; } 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 = open(spool_path, O_RDWR, 0640); if (spool_file <= 0) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: Unable to spool message: %d", errno); *yield = string_sprintf("milter dlfunc: Unable to spool message: %d", errno); goto RETURN_DEFER; } debug_printf("lseek %d, %d\n", SPOOL_DATA_START_OFFSET, SEEK_SET); (void)lseek(spool_file, SPOOL_DATA_START_OFFSET, SEEK_SET); debug_printf("spawner_send_body_fd(sp, spool_file)\n"); r = spawner_send_body_fd(sp, spool_file); (void)lseek(spool_file, SPOOL_DATA_START_OFFSET, SEEK_SET); if (r < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: spawner_send_body_fd failed"); *yield = string_sprintf("milter dlfunc: spawner_send_body_fd failed"); goto RETURN_DEFER; } debug_printf("Close spool file: %s\n", spool_path); (void)close(spool_file); spool_file = -1; if (local_scan_error) r = local_scan_error; switch (r) { case SPAWNER_CONTINUE: case SPAWNER_ACCEPT: log_write(0, LOG_MAIN, "milter dlfunc: Message accepted"); if (local_scan_quarantine) { *yield = string_sprintf("milter dlfunc: QUARANTINE"); } else { *yield = string_sprintf("milter dlfunc: ACCEPT"); } break; case SPAWNER_TEMPFAIL: log_write(0, LOG_MAIN, "milter dlfunc: Message rejected (temporary)"); *yield = string_sprintf("milter dlfunc: DEFER"); goto RETURN_DEFER; case SPAWNER_REJECT: log_write(0, LOG_MAIN, "milter dlfunc: Message rejected (permanent)"); *yield = string_sprintf("milter dlfunc: REJECT"); break; case SPAWNER_DISCARD: log_write(0, LOG_MAIN, "milter dlfunc: Message discarded"); recipients_count = 0; *yield = string_sprintf("milter dlfunc: DISCARD"); break; default: log_write(0, LOG_MAIN|LOG_PANIC, "milter dlfunc: Unexpected answer from mail filter: %d. Message rejected (temporary)", r); *yield = string_sprintf("milter dlfunc: Unexpected answer from mail filter: %d. Message rejected (temporary)", r); goto RETURN_DEFER; } if (sp != NULL) { r = spawner_close(sp); free(sp); } return OK; RETURN_DEFER: { if (sp != NULL) { r = spawner_close(sp); free(sp); } if (spool_file > 0) (void)close(spool_file); if ((*yield == NULL) || (*yield[0] == '\0')) *yield = string_sprintf("milter dlfunc: DEFER"); return(defer_ok ? OK : ERROR); } return OK; }