X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/803628d5b0b175dfa78f0b19b35213ace01207b1..bd8fbe3606d80e5a3fc02fe71b521146c6938448:/src/src/receive.c diff --git a/src/src/receive.c b/src/src/receive.c index 32154792e..48cdff88c 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -37,7 +37,7 @@ the file. (When SMTP input is occurring, different functions are used by changing the pointer variables.) */ int -stdin_getc(void) +stdin_getc(unsigned lim) { return getc(stdin); } @@ -124,6 +124,7 @@ receive_statvfs(BOOL isspool, int *inodeptr) { #ifdef HAVE_STATFS struct STATVFS statbuf; +struct stat dummy; uschar *path; uschar *name; uschar buffer[1024]; @@ -180,12 +181,18 @@ else 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); - } + if (stat(CS path, &dummy) == -1 && errno == ENOENT) + { /* Can happen on first run after installation */ + *inodeptr = -1; + return -1; + } + else + { + log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat " + "%s directory %s: %s", name, path, strerror(errno)); + smtp_closedown(US"spool or log directory problem"); + exim_exit(EXIT_FAILURE); + } *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1; @@ -193,9 +200,9 @@ if (STATVFS(CS path, &statbuf) != 0) return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0); +#else /* Unable to find partition sizes in this environment. */ -#else *inodeptr = -1; return -1; #endif @@ -619,7 +626,7 @@ if (!dot_ends) { register int last_ch = '\n'; - for (; (ch = (receive_getc)()) != EOF; last_ch = ch) + for (; (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF; last_ch = ch) { if (ch == 0) body_zerocount++; if (last_ch == '\r' && ch != '\n') @@ -661,7 +668,7 @@ if (!dot_ends) ch_state = 1; -while ((ch = (receive_getc)()) != EOF) +while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF) { if (ch == 0) body_zerocount++; switch (ch_state) @@ -777,9 +784,9 @@ read_message_data_smtp(FILE *fout) { int ch_state = 0; int ch; -register int linelength = 0; +int linelength = 0; -while ((ch = (receive_getc)()) != EOF) +while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF) { if (ch == 0) body_zerocount++; switch (ch_state) @@ -867,7 +874,7 @@ while ((ch = (receive_getc)()) != EOF) message_size++; linelength++; - if (fout != NULL) + if (fout) { if (fputc(ch, fout) == EOF) return END_WERROR; if (message_size > thismessage_size_limit) return END_SIZE; @@ -876,7 +883,7 @@ while ((ch = (receive_getc)()) != EOF) (void) cutthrough_put_nl(); else { - uschar c= ch; + uschar c = ch; (void) cutthrough_puts(&c, 1); } } @@ -890,6 +897,63 @@ return END_EOF; +/* Variant of the above read_message_data_smtp() specialised for RFC 3030 +CHUNKING. We assume that the incoming has proper CRLF, so only have to scan +for and strip CR. On the downside there are more protocol reasons to stop. + +Arguments: + fout a FILE to which to write the message; NULL if skipping + +Returns: One of the END_xxx values indicating why it stopped reading +*/ + +static int +read_message_bdat_smtp(FILE *fout) +{ +int ch; +int linelength = 0; + +for (;;) switch (ch = bdat_getc(GETC_BUFFER_UNLIMITED)) + { + case EOF: return END_EOF; + case EOD: return END_DOT; + case ERR: return END_PROTOCOL; + + case '\r': + body_linecount++; + if (linelength > max_received_linelength) + max_received_linelength = linelength; + linelength = -1; + break; + + case 0: + body_zerocount++; + /*FALLTHROUGH*/ + default: + message_size++; + linelength++; + if (fout) + { + if (fputc(ch, fout) == EOF) return END_WERROR; + if (message_size > thismessage_size_limit) return END_SIZE; + } +#ifdef notyet + if(ch == '\n') + (void) cutthrough_put_nl(); + else + { + uschar c = ch; + (void) cutthrough_puts(&c, 1); + } +#endif + break; + } +/*NOTREACHED*/ +} + + + + /************************************************* * Swallow SMTP message * *************************************************/ @@ -906,6 +970,7 @@ Returns: nothing void receive_swallow_smtp(void) { +/*XXX CHUNKING: not enough. read chunks until RSET? */ if (message_ended >= END_NOTENDED) message_ended = read_message_data_smtp(NULL); } @@ -928,6 +993,7 @@ handle_lost_connection(uschar *s) { log_write(L_lost_incoming_connection | L_smtp_connection, LOG_MAIN, "%s lost while reading message data%s", smtp_get_connection_info(), s); +smtp_notquit_exit(US"connection-lost", NULL, NULL); return US"421 Lost incoming connection"; } @@ -1557,8 +1623,10 @@ message_linecount = body_linecount = body_zerocount = max_received_linelength = 0; #ifndef DISABLE_DKIM -/* Call into DKIM to set up the context. */ -if (smtp_input && !smtp_batched_input && !dkim_disable_verify) dkim_exim_verify_init(); +/* Call into DKIM to set up the context. In CHUNKING mode +we clear the dot-stuffing flag */ +if (smtp_input && !smtp_batched_input && !dkim_disable_verify) + dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED); #endif #ifdef EXPERIMENTAL_DMARC @@ -1614,7 +1682,7 @@ next->text. */ for (;;) { - int ch = (receive_getc)(); + int ch = (receive_getc)(GETC_BUFFER_UNLIMITED); /* If we hit EOF on a SMTP connection, it's an error, since incoming SMTP must have a correct "." terminator. */ @@ -1693,10 +1761,10 @@ for (;;) if (ptr == 0 && ch == '.' && (smtp_input || dot_ends)) { - ch = (receive_getc)(); + ch = (receive_getc)(GETC_BUFFER_UNLIMITED); if (ch == '\r') { - ch = (receive_getc)(); + ch = (receive_getc)(GETC_BUFFER_UNLIMITED); if (ch != '\n') { receive_ungetc(ch); @@ -1727,7 +1795,7 @@ for (;;) if (ch == '\r') { - ch = (receive_getc)(); + ch = (receive_getc)(GETC_BUFFER_UNLIMITED); if (ch == '\n') { if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE; @@ -1822,7 +1890,7 @@ for (;;) if (ch != EOF) { - int nextch = (receive_getc)(); + int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED); if (nextch == ' ' || nextch == '\t') { next->text[ptr++] = nextch; @@ -2832,6 +2900,14 @@ if (filter_test != FTEST_NONE) return message_ended == END_DOT; } +/*XXX CHUNKING: need to cancel cutthrough under BDAT, for now. In future, +think more if it could be handled. Cannot do onward CHUNKING unless +inbound is, but inbound chunking ought to be ok with outbound plain. +Could we do onward CHUNKING given inbound CHUNKING? +*/ +if (chunking_state > CHUNKING_OFFERED) + cancel_cutthrough_connection("chunking active"); + /* Cutthrough delivery: We have to create the Received header now rather than at the end of reception, so the timestamp behaviour is a change to the normal case. @@ -2929,7 +3005,9 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) { if (smtp_input) { - message_ended = read_message_data_smtp(data_file); + message_ended = chunking_state > CHUNKING_OFFERED + ? read_message_bdat_smtp(data_file) + : read_message_data_smtp(data_file); receive_linecount++; /* The terminating "." line */ } else message_ended = read_message_data(data_file); @@ -2937,51 +3015,64 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) receive_linecount += body_linecount; /* For BSMTP errors mainly */ message_linecount += body_linecount; - /* Handle premature termination of SMTP */ - - if (smtp_input && message_ended == END_EOF) + switch (message_ended) { - Uunlink(spool_name); /* Lose data file when closed */ - cancel_cutthrough_connection("sender closed connection"); - message_id[0] = 0; /* Indicate no message accepted */ - smtp_reply = handle_lost_connection(US""); - smtp_yield = FALSE; - goto TIDYUP; /* Skip to end of function */ - } + /* Handle premature termination of SMTP */ - /* Handle message that is too big. Don't use host_or_ident() in the log - message; we want to see the ident value even for non-remote messages. */ + case END_EOF: + if (smtp_input) + { + Uunlink(spool_name); /* Lose data file when closed */ + cancel_cutthrough_connection("sender closed connection"); + message_id[0] = 0; /* Indicate no message accepted */ + smtp_reply = handle_lost_connection(US""); + smtp_yield = FALSE; + goto TIDYUP; /* Skip to end of function */ + } + break; - if (message_ended == END_SIZE) - { - Uunlink(spool_name); /* Lose the data file when closed */ - cancel_cutthrough_connection("mail too big"); - if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */ + /* Handle message that is too big. Don't use host_or_ident() in the log + message; we want to see the ident value even for non-remote messages. */ - log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: " - "message too big: read=%d max=%d", - sender_address, - (sender_fullhost == NULL)? "" : " H=", - (sender_fullhost == NULL)? US"" : sender_fullhost, - (sender_ident == NULL)? "" : " U=", - (sender_ident == NULL)? US"" : sender_ident, - message_size, - thismessage_size_limit); + case END_SIZE: + Uunlink(spool_name); /* Lose the data file when closed */ + cancel_cutthrough_connection("mail too big"); + if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */ - if (smtp_input) - { - smtp_reply = US"552 Message size exceeds maximum permitted"; - message_id[0] = 0; /* Indicate no message accepted */ - goto TIDYUP; /* Skip to end of function */ - } - else - { - fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET); - give_local_error(ERRMESS_TOOBIG, - string_sprintf("message too big (max=%d)", thismessage_size_limit), - US"message rejected: ", error_rc, data_file, header_list); - /* Does not return */ - } + log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: " + "message too big: read=%d max=%d", + sender_address, + (sender_fullhost == NULL)? "" : " H=", + (sender_fullhost == NULL)? US"" : sender_fullhost, + (sender_ident == NULL)? "" : " U=", + (sender_ident == NULL)? US"" : sender_ident, + message_size, + thismessage_size_limit); + + if (smtp_input) + { + smtp_reply = US"552 Message size exceeds maximum permitted"; + message_id[0] = 0; /* Indicate no message accepted */ + goto TIDYUP; /* Skip to end of function */ + } + else + { + fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET); + give_local_error(ERRMESS_TOOBIG, + string_sprintf("message too big (max=%d)", thismessage_size_limit), + US"message rejected: ", error_rc, data_file, header_list); + /* Does not return */ + } + break; + + /* Handle bad BDAT protocol sequence */ + + case END_PROTOCOL: + Uunlink(spool_name); /* Lose the data file when closed */ + cancel_cutthrough_connection("sender protocol error"); + smtp_reply = US""; /* Response already sent */ + message_id[0] = 0; /* Indicate no message accepted */ + goto TIDYUP; /* Skip to end of function */ } } @@ -3164,9 +3255,8 @@ user_msg = NULL; enable_dollar_recipients = TRUE; if (recipients_count == 0) - { - blackholed_by = recipients_discarded? US"MAIL ACL" : US"RCPT ACL"; - } + blackholed_by = recipients_discarded ? US"MAIL ACL" : US"RCPT ACL"; + else { /* Handle interactive SMTP messages */ @@ -3182,18 +3272,15 @@ else dkim_exim_verify_finish(); /* Check if we must run the DKIM ACL */ - if ((acl_smtp_dkim != NULL) && - (dkim_verify_signers != NULL) && - (dkim_verify_signers[0] != '\0')) + if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers) { uschar *dkim_verify_signers_expanded = expand_string(dkim_verify_signers); - if (dkim_verify_signers_expanded == NULL) - { + if (!dkim_verify_signers_expanded) log_write(0, LOG_MAIN|LOG_PANIC, "expansion of dkim_verify_signers option failed: %s", expand_string_message); - } + else { int sep = 0; @@ -3202,28 +3289,23 @@ else uschar *seen_items = NULL; int seen_items_size = 0; int seen_items_offset = 0; - uschar itembuf[256]; /* Default to OK when no items are present */ rc = OK; - while ((item = string_nextinlist(&ptr, &sep, - itembuf, - sizeof(itembuf)))) + while ((item = string_nextinlist(&ptr, &sep, NULL, 0))) { /* Prevent running ACL for an empty item */ - if (!item || (item[0] == '\0')) continue; + if (!item || !*item) continue; /* Only run ACL once for each domain or identity, no matter how often it appears in the expanded list. */ if (seen_items) { uschar *seen_item = NULL; - uschar seen_item_buf[256]; const uschar *seen_items_list = seen_items; BOOL seen_this_item = FALSE; while ((seen_item = string_nextinlist(&seen_items_list, &sep, - seen_item_buf, - sizeof(seen_item_buf)))) + NULL, 0))) if (Ustrcmp(seen_item,item) == 0) { seen_this_item = TRUE; @@ -3656,7 +3738,7 @@ if (bmi_run == 1) } #endif -/* Update the timstamp in our Received: header to account for any time taken by +/* Update the timestamp 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. */ @@ -3741,7 +3823,7 @@ string as required. Since we commonly want to add two items at a time, use a macro to simplify the coding. We log the arrival of a new message while the file is still locked, just in case the machine is *really* fast, and delivers it first! Include any message id that is in the message - since the syntax of a -message id is actually an addr-spec, we can use the parse routine to canonicize +message id is actually an addr-spec, we can use the parse routine to canonicalize it. */ size = 256; @@ -3783,7 +3865,7 @@ if (sender_host_authenticated) #ifndef DISABLE_PRDR if (prdr_requested) - s = string_append(s, &size, &sptr, 1, US" PRDR"); + s = string_catn(s, &size, &sptr, US" PRDR", 5); #endif #ifdef SUPPORT_PROXY @@ -3791,6 +3873,9 @@ if (proxy_session && LOGGING(proxy)) s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address); #endif +if (chunking_state > CHUNKING_OFFERED) + s = string_catn(s, &size, &sptr, US" K", 2); + sprintf(CS big_buffer, "%d", msg_size); s = string_append(s, &size, &sptr, 2, US" S=", big_buffer); @@ -3939,17 +4024,17 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket && if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0) { - int c = (receive_getc)(); + int c = (receive_getc)(GETC_BUFFER_UNLIMITED); if (c != EOF) (receive_ungetc)(c); else { - uschar *msg = US"SMTP connection lost after final dot"; + smtp_notquit_exit(US"connection-lost", NULL, NULL); smtp_reply = US""; /* No attempt to send a response */ smtp_yield = FALSE; /* Nothing more on this connection */ /* Re-use the log line workspace */ sptr = 0; - s = string_cat(s, &size, &sptr, msg); + s = string_cat(s, &size, &sptr, US"SMTP connection lost after final dot"); s = add_host_info_for_log(s, &size, &sptr); s[sptr] = 0; log_write(0, LOG_MAIN, "%s", s); @@ -3973,8 +4058,9 @@ for this message. */ Send dot onward. If accepted, wipe the spooled files, log as delivered and accept the sender's dot (below). - If rejected: copy response to sender, wipe the spooled files, log approriately. - If temp-reject: accept to sender, keep the spooled files. + If rejected: copy response to sender, wipe the spooled files, log appropriately. + If temp-reject: normally accept to sender, keep the spooled file - unless defer=pass + in which case pass temp-reject back to initiator and dump the files. Having the normal spool files lets us do data-filtering, and store/forward on temp-reject. @@ -3990,13 +4076,17 @@ if(cutthrough.fd >= 0) cutthrough_done = ACCEPTED; break; /* message_id needed for SMTP accept below */ + case '4': /* Temp-reject. Keep spoolfiles and accept, unless defer-pass mode. + ... for which, pass back the exact error */ + if (cutthrough.defer_pass) smtp_reply = string_copy_malloc(msg); + /*FALLTRHOUGH*/ + default: /* Unknown response, or error. Treat as temp-reject. */ - case '4': /* Temp-reject. Keep spoolfiles and accept. */ cutthrough_done = TMP_REJ; /* Avoid the usual immediate delivery attempt */ break; /* message_id needed for SMTP accept below */ case '5': /* Perm-reject. Do the same to the source. Dump any spoolfiles */ - smtp_reply= msg; /* Pass on the exact error */ + smtp_reply = string_copy_malloc(msg); /* Pass on the exact error */ cutthrough_done = PERM_REJ; break; } @@ -4093,8 +4183,15 @@ if (smtp_input) /* Default OK response */ + else if (chunking_state > CHUNKING_OFFERED) + { + smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", + chunking_datasize, message_size+message_linecount, message_id); + chunking_state = CHUNKING_OFFERED; + } 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"); @@ -4103,27 +4200,37 @@ if (smtp_input) /* smtp_reply is set non-empty */ else if (smtp_reply[0] != 0) - { if (fake_response != OK && (smtp_reply[0] == '2')) smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE, fake_response_text); else smtp_printf("%.1024s\r\n", smtp_reply); - } switch (cutthrough_done) { - case ACCEPTED: log_write(0, LOG_MAIN, "Completed");/* Delivery was done */ + case ACCEPTED: + log_write(0, LOG_MAIN, "Completed");/* Delivery was done */ case PERM_REJ: - { /* Delete spool files */ - Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D")); - Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H")); - Uunlink(spool_fname(US"msglog", message_subdir, message_id, US"")); - } - case TMP_REJ: message_id[0] = 0; /* Prevent a delivery from starting */ - default:break; + /* Delete spool files */ + Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D")); + Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H")); + Uunlink(spool_fname(US"msglog", message_subdir, message_id, US"")); + message_id[0] = 0; /* Prevent a delivery from starting */ + break; + + case TMP_REJ: + if (cutthrough.defer_pass) + { + Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D")); + Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H")); + Uunlink(spool_fname(US"msglog", message_subdir, message_id, US"")); + } + message_id[0] = 0; /* Prevent a delivery from starting */ + default: + break; } cutthrough.delivery = FALSE; + cutthrough.defer_pass = FALSE; } /* For batched SMTP, generate an error message on failure, and do