X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/69358f0206debf14a18c7e798e23133d304232b6..8e669ac162fe3b1040297f1d021de10778dce9d9:/src/src/receive.c diff --git a/src/src/receive.c b/src/src/receive.c index 0483bd5f5..7f814e64a 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -1,10 +1,10 @@ -/* $Cambridge: exim/src/src/receive.c,v 1.3 2004/10/19 11:04:26 ph10 Exp $ */ +/* $Cambridge: exim/src/src/receive.c,v 1.11 2005/02/17 11:58:26 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2004 */ +/* Copyright (c) University of Cambridge 1995 - 2005 */ /* See the file NOTICE for conditions of use and distribution. */ /* Code for receiving a message and setting up spool files. */ @@ -92,90 +92,55 @@ return /************************************************* -* Check space on spool and log partitions * +* Read space info for a partition * *************************************************/ -/* This function is called before accepting a message; if any thresholds are -set, it checks them. If a message_size is supplied, it checks that there is -enough space for that size plus the threshold - i.e. that the message won't -reduce the space to the threshold. Not all OS have statvfs(); for those that -don't, this function always returns TRUE. For some OS the old function and -struct name statfs is used; that is handled by a macro, defined in exim.h. +/* This function is called by receive_check_fs() below, and also by string +expansion for variables such as $spool_space. The field names for the statvfs +structure are macros, because not all OS have F_FAVAIL and it seems tidier to +have macros for F_BAVAIL and F_FILES as well. Some kinds of file system do not +have inodes, and they return -1 for the number available. + +Later: It turns out that some file systems that do not have the concept of +inodes return 0 rather than -1. Such systems should also return 0 for the total +number of inodes, so we require that to be greater than zero before returning +an inode count. Arguments: - msg_size the (estimated) size of an incoming message + isspool TRUE for spool partition, FALSE for log partition + inodeptr address of int to receive inode count; -1 if there isn't one -Returns: FALSE if there isn't enough space, or if the information cannot - be obtained - TRUE if no check was done or there is enough space +Returns: available on-root space, in kilobytes + -1 for log partition if there isn't one + +All values are -1 if the STATFS functions are not available. */ -BOOL -receive_check_fs(int msg_size) +int +receive_statvfs(BOOL isspool, int *inodeptr) { #ifdef HAVE_STATFS -BOOL rc = TRUE; struct STATVFS statbuf; +uschar *path; +uschar *name; +uschar buffer[1024]; -memset(&statbuf, 0, sizeof(statbuf)); - -/* The field names are macros, because not all OS have F_FAVAIL and it seems -tidier to have macros for F_BAVAIL and F_FILES as well. Some kinds of file -server do not have inodes, and they return -1 for the number available, so we -do the check only when this field is non-negative. - -Later: It turns out that some file systems that do not have the concept of -inodes return 0 rather than -1. Such systems should also return 0 for the total -number of inodes, so we require that to be greater than zero before doing the -test. */ +/* The spool directory must always exist. */ -if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0) +if (isspool) { - if (STATVFS(CS spool_directory, &statbuf) != 0) - { - log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat " - "spool directory %s: %s", spool_directory, strerror(errno)); - smtp_closedown(US"spool directory problem"); - exim_exit(EXIT_FAILURE); - } - - /* check_spool_space is held in K because disks are getting huge */ - - if (statbuf.F_BAVAIL < (unsigned long) - ((((double)check_spool_space) * 1024.0 + (double)msg_size) / - (double)statbuf.F_FRSIZE) - || - (statbuf.F_FILES > 0 && - statbuf.F_FAVAIL >= 0 && - statbuf.F_FAVAIL < check_spool_inodes)) - rc = FALSE; - - DEBUG(D_receive) - debug_printf("spool directory %s space = %d blocks; inodes = %d; " - "check_space = %dK (%d blocks); inodes = %d; msg_size = %d (%d blocks)\n", - spool_directory, (int)statbuf.F_BAVAIL, (int)statbuf.F_FAVAIL, - check_spool_space, - (int)(((double)check_spool_space * 1024.0) / (double)statbuf.F_FRSIZE), - check_spool_inodes, msg_size, (int)(msg_size / statbuf.F_FRSIZE)); - - if (!rc) - { - log_write(0, LOG_MAIN, "spool directory space check failed: space=%d " - "inodes=%d", (int)statbuf.F_BAVAIL, (int)statbuf.F_FAVAIL); - return FALSE; - } + path = spool_directory; + name = US"spool"; } /* Need to cut down the log file path to the directory, and to ignore any appearance of "syslog" in it. */ -if (check_log_space > 0 || check_log_inodes > 0) +else { - uschar *path; int sep = ':'; /* Not variable - outside scripts use */ - uschar *cp; uschar *p = log_file_path; - uschar buffer[1024]; + name = US"log"; /* An empty log_file_path means "use the default". This is the same as an empty item in a list. */ @@ -186,50 +151,117 @@ if (check_log_space > 0 || check_log_inodes > 0) if (Ustrcmp(path, "syslog") != 0) break; } - if (path == NULL) return TRUE; /* No log files, so no problem */ + if (path == NULL) /* No log files */ + { + *inodeptr = -1; + return -1; + } - /* An empty string means use the default */ + /* An empty string means use the default, which is in the spool directory. + But don't just use the spool directory, as it is possible that the log + subdirectory has been symbolically linked elsewhere. */ if (path[0] == 0) - path = string_sprintf("%s/log/%%slog", spool_directory); - - if ((cp = Ustrrchr(path, '/')) == NULL) { - DEBUG(D_receive) debug_printf("cannot find slash in %s\n", path); - return FALSE; + sprintf(CS buffer, CS"%s/log", CS spool_directory); + path = buffer; + } + else + { + uschar *cp; + if ((cp = Ustrrchr(path, '/')) != NULL) *cp = 0; } - *cp = 0; + } + +/* We now have the patch; do the business */ + +memset(&statbuf, 0, sizeof(statbuf)); + +if (STATVFS(CS path, &statbuf) != 0) + { + log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat " + "%s directory %s: %s", name, spool_directory, strerror(errno)); + smtp_closedown(US"spool or log directory problem"); + exim_exit(EXIT_FAILURE); + } + +*inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1; + +/* Disks are getting huge. Take care with computing the size in kilobytes. */ + +return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0); + +/* Unable to find partition sizes in this environment. */ + +#else +*inodeptr = -1; +return -1; +#endif +} - if (STATVFS(CS path, &statbuf) != 0) + + + +/************************************************* +* Check space on spool and log partitions * +*************************************************/ + +/* This function is called before accepting a message; if any thresholds are +set, it checks them. If a message_size is supplied, it checks that there is +enough space for that size plus the threshold - i.e. that the message won't +reduce the space to the threshold. Not all OS have statvfs(); for those that +don't, this function always returns TRUE. For some OS the old function and +struct name statfs is used; that is handled by a macro, defined in exim.h. + +Arguments: + msg_size the (estimated) size of an incoming message + +Returns: FALSE if there isn't enough space, or if the information cannot + be obtained + TRUE if no check was done or there is enough space +*/ + +BOOL +receive_check_fs(int msg_size) +{ +int space, inodes; + +if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0) + { + space = receive_statvfs(TRUE, &inodes); + + DEBUG(D_receive) + debug_printf("spool directory space = %dK inodes = %d " + "check_space = %dK inodes = %d msg_size = %d\n", + space, inodes, check_spool_space, check_spool_inodes, msg_size); + + if ((space >= 0 && space < check_spool_space) || + (inodes >= 0 && inodes < check_spool_inodes)) { - log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat " - "log directory %s: %s", path, strerror(errno)); - smtp_closedown(US"log directory problem"); - exim_exit(EXIT_FAILURE); + log_write(0, LOG_MAIN, "spool directory space check failed: space=%d " + "inodes=%d", space, inodes); + return FALSE; } + } - if (statbuf.F_BAVAIL < (unsigned long) - (((double)check_log_space * 1024.0) / (double)statbuf.F_FRSIZE) - || - statbuf.F_FAVAIL < check_log_inodes) rc = FALSE; +if (check_log_space > 0 || check_log_inodes > 0) + { + space = receive_statvfs(FALSE, &inodes); DEBUG(D_receive) - debug_printf("log directory %s space = %d blocks; inodes = %d; " - "check_space = %dK (%d blocks); inodes = %d\n", - path, (int)statbuf.F_BAVAIL, (int)statbuf.F_FAVAIL, - check_log_space, - (int)(((double)check_log_space * 1024.0) / (double)statbuf.F_FRSIZE), - check_log_inodes); - - if (!rc) + debug_printf("log directory space = %dK inodes = %d " + "check_space = %dK inodes = %d\n", + space, inodes, check_log_space, check_log_inodes); + + if ((space >= 0 && space < check_log_space) || + (inodes >= 0 && inodes < check_log_inodes)) { log_write(0, LOG_MAIN, "log directory space check failed: space=%d " - "inodes=%d", (int)statbuf.F_BAVAIL, (int)statbuf.F_FAVAIL); + "inodes=%d", space, inodes); return FALSE; } } -#endif return TRUE; } @@ -395,7 +427,7 @@ if (smtp_input) } else { - if (filter_test == NULL) + if (filter_test == FTEST_NONE) { fprintf(stderr, "\nexim: %s received - message abandoned\n", (sig == SIGTERM)? "SIGTERM" : "SIGINT"); @@ -438,6 +470,11 @@ if (recipients_count >= recipients_list_max) recipients_list[recipients_count].address = recipient; recipients_list[recipients_count].pno = pno; +#ifdef EXPERIMENTAL_BRIGHTMAIL +recipients_list[recipients_count].bmi_optin = bmi_current_optin; +/* reset optin string pointer for next recipient */ +bmi_current_optin = NULL; +#endif recipients_list[recipients_count++].errors_to = NULL; } @@ -886,6 +923,21 @@ for (h = acl_warn_headers; h != NULL; h = next) DEBUG(D_receive|D_acl) debug_printf(" (after Received:)"); break; + case htype_add_rfc: + /* add header before any header which is NOT Received: or Resent- */ + last_received = header_list; + 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. */ + h->next = last_received->next; + last_received->next = h; + DEBUG(D_receive|D_acl) debug_printf(" (before any non-Received: or Resent-*: header)"); + break; + default: h->next = NULL; header_last->next = h; @@ -1063,6 +1115,7 @@ BOOL yield = FALSE; BOOL resents_exist = FALSE; uschar *resent_prefix = US""; uschar *blackholed_by = NULL; +uschar *blackhole_log_msg = US""; flock_t lock_data; error_block *bad_addresses = NULL; @@ -1476,18 +1529,18 @@ for (;;) if (domain == 0 && newsender[0] != 0) newsender = rewrite_address_qualify(newsender, FALSE); - if (filter_test != NULL || receive_check_set_sender(newsender)) + if (filter_test != FTEST_NONE || receive_check_set_sender(newsender)) { sender_address = newsender; - if (trusted_caller || filter_test != NULL) + if (trusted_caller || filter_test != FTEST_NONE) { authenticated_sender = NULL; originator_name = US""; sender_local = FALSE; } - if (filter_test != NULL) + if (filter_test != FTEST_NONE) printf("Sender taken from \"From \" line\n"); } } @@ -1627,7 +1680,7 @@ if (smtp_input && (receive_feof)()) /* If this is a filter test run and no headers were read, output a warning in case there is a mistake in the test message. */ -if (filter_test != NULL && header_list->next == NULL) +if (filter_test != FTEST_NONE && header_list->next == NULL) printf("Warning: no message headers read\n"); @@ -1749,7 +1802,7 @@ for (h = header_list->next; h != NULL; h = h->next) otherwise set. However, remove any <> that surround the address because the variable doesn't have these. */ - if (filter_test != NULL) + if (filter_test != FTEST_NONE) { uschar *start = h->text + 12; uschar *end = start + Ustrlen(start); @@ -2346,7 +2399,7 @@ DEBUG(D_receive) testing mode, that is all this function does. Return TRUE if the message ended with a dot. */ -if (filter_test != NULL) +if (filter_test != FTEST_NONE) { process_info[process_info_len] = 0; return message_ended == END_DOT; @@ -2684,6 +2737,130 @@ else if (smtp_input && !smtp_batched_input) { + +#ifdef WITH_CONTENT_SCAN + /* MIME ACL hook */ + if (acl_smtp_mime != NULL && recipients_count > 0) + { + FILE *mbox_file; + uschar rfc822_file_path[2048]; + unsigned long mbox_size; + header_line *my_headerlist; + uschar *user_msg, *log_msg; + int mime_part_count_buffer = -1; + + memset(CS rfc822_file_path,0,2048); + + /* check if it is a MIME message */ + my_headerlist = header_list; + while (my_headerlist != NULL) { + /* skip deleted headers */ + if (my_headerlist->type == '*') { + my_headerlist = my_headerlist->next; + continue; + }; + if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0) { + DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n"); + goto DO_MIME_ACL; + }; + my_headerlist = my_headerlist->next; + }; + + DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n"); + goto NO_MIME_ACL; + + DO_MIME_ACL: + /* make sure the eml mbox file is spooled up */ + mbox_file = spool_mbox(&mbox_size); + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected."); + Uunlink(spool_name); + unspool_mbox(); + smtp_respond(451, TRUE, US"temporary local problem"); + message_id[0] = 0; /* Indicate no message accepted */ + smtp_reply = US""; /* Indicate reply already sent */ + goto TIDYUP; /* Skip to end of function */ + }; + + mime_is_rfc822 = 0; + + MIME_ACL_CHECK: + mime_part_count = -1; + rc = mime_acl_check(mbox_file, NULL, &user_msg, &log_msg); + fclose(mbox_file); + + if (Ustrlen(rfc822_file_path) > 0) { + mime_part_count = mime_part_count_buffer; + + if (unlink(CS rfc822_file_path) == -1) { + log_write(0, LOG_PANIC, + "acl_smtp_mime: can't unlink RFC822 spool file, skipping."); + goto END_MIME_ACL; + }; + }; + + /* check if we must check any message/rfc822 attachments */ + if (rc == OK) { + uschar temp_path[1024]; + int n; + struct dirent *entry; + DIR *tempdir; + + snprintf(CS temp_path, 1024, "%s/scan/%s", spool_directory, message_id); + + tempdir = opendir(CS temp_path); + n = 0; + do { + entry = readdir(tempdir); + if (entry == NULL) break; + if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0) { + snprintf(CS rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name); + debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path); + break; + }; + } while (1); + closedir(tempdir); + + if (entry != NULL) { + mbox_file = Ufopen(rfc822_file_path,"r"); + if (mbox_file == NULL) { + log_write(0, LOG_PANIC, + "acl_smtp_mime: can't open RFC822 spool file, skipping."); + unlink(CS rfc822_file_path); + goto END_MIME_ACL; + }; + /* set RFC822 expansion variable */ + mime_is_rfc822 = 1; + mime_part_count_buffer = mime_part_count; + goto MIME_ACL_CHECK; + }; + }; + + END_MIME_ACL: + add_acl_headers(US"MIME"); + if (rc == DISCARD) + { + recipients_count = 0; + blackholed_by = US"MIME ACL"; + } + else if (rc != OK) + { + Uunlink(spool_name); + unspool_mbox(); + if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) + smtp_yield = FALSE; /* No more messsages after dropped connection */ + smtp_reply = US""; /* Indicate reply already sent */ + message_id[0] = 0; /* Indicate no message accepted */ + goto TIDYUP; /* Skip to end of function */ + }; + } + + NO_MIME_ACL: +#endif /* WITH_CONTENT_SCAN */ + + if (acl_smtp_data != NULL && recipients_count > 0) { uschar *user_msg, *log_msg; @@ -2693,10 +2870,15 @@ else { recipients_count = 0; blackholed_by = US"DATA ACL"; + if (log_msg != NULL) + blackhole_log_msg = string_sprintf(": %s", log_msg); } else if (rc != OK) { Uunlink(spool_name); +#ifdef WITH_CONTENT_SCAN + unspool_mbox(); +#endif if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0) smtp_yield = FALSE; /* No more messsages after dropped connection */ smtp_reply = US""; /* Indicate reply already sent */ @@ -2717,13 +2899,14 @@ else { recipients_count = 0; blackholed_by = US"non-SMTP ACL"; + if (log_msg != NULL) blackhole_log_msg = string_sprintf(": %s", log_msg); } else if (rc != OK) { Uunlink(spool_name); log_write(0, LOG_MAIN|LOG_REJECT, "F=<%s> rejected by non-SMTP ACL: %s", sender_address, log_msg); - if (user_msg == NULL) user_msg = US"local configuration problem"; + if (user_msg == NULL) user_msg = US"local configuration problem"; if (smtp_batched_input) { moan_smtp_batch(NULL, "%d %s", 550, user_msg); @@ -2747,6 +2930,10 @@ else enable_dollar_recipients = FALSE; } +#ifdef WITH_CONTENT_SCAN +unspool_mbox(); +#endif + /* The final check on the message is to run the scan_local() function. The version supplied with Exim always accepts, but this is a hook for sysadmins to supply their own checking code. The local_scan() function is run even when all @@ -2917,6 +3104,14 @@ signal(SIGINT, SIG_IGN); deliver_firsttime = TRUE; +#ifdef EXPERIMENTAL_BRIGHTMAIL +if (bmi_run == 1) { + /* rewind data file */ + lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET); + bmi_verdicts = bmi_process_message(header_list, data_fd); +}; +#endif + /* Update the timstamp in our Received: header to account for any time taken by an ACL or by local_scan(). The new time is the time that all reception processing is complete. */ @@ -3203,12 +3398,21 @@ if (smtp_input) { if (smtp_reply == NULL) { - smtp_printf("250 OK id=%s\r\n", message_id); + if (fake_reject) + smtp_respond(550,TRUE,fake_reject_text); + else + smtp_printf("250 OK id=%s\r\n", message_id); if (host_checking) fprintf(stdout, "\n**** SMTP testing: that is not a real message id!\n\n"); } - else if (smtp_reply[0] != 0) smtp_printf("%.1024s\r\n", smtp_reply); + else if (smtp_reply[0] != 0) + { + if (fake_reject && (smtp_reply[0] == '2')) + smtp_respond(550,TRUE,fake_reject_text); + else + smtp_printf("%.1024s\r\n", smtp_reply); + } } /* For batched SMTP, generate an error message on failure, and do @@ -3229,7 +3433,7 @@ if (blackholed_by != NULL) uschar *detail = (local_scan_data != NULL)? string_printing(local_scan_data) : string_sprintf("(%s discarded recipients)", blackholed_by); - log_write(0, LOG_MAIN, "=> blackhole %s", detail); + log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg); log_write(0, LOG_MAIN, "Completed"); message_id[0] = 0; }