From 0de9945258c2e7910c35f715caf07c5e9270aa1b Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Tue, 12 May 2020 23:10:08 +0100 Subject: [PATCH] smtp transport: message_linelength_limit option. Bug 1684 --- doc/doc-docbook/spec.xfpt | 14 +++++++++++ doc/doc-txt/NewStuff | 3 +++ doc/doc-txt/OptionLists.txt | 1 + src/src/configure.default | 5 ---- src/src/deliver.c | 24 +++++++++++++++---- src/src/smtp_in.c | 8 +++---- src/src/spool_out.c | 6 ++--- src/src/transports/smtp.c | 19 +++++++++++++++ src/src/transports/smtp.h | 1 + test/confs/0588 | 42 +++++++++++++++++++++++++++++++++ test/log/0588 | 15 ++++++++++++ test/scripts/0000-Basic/0588 | 45 ++++++++++++++++++++++++++++++++++++ test/stdout/0572 | 1 + test/stdout/0588 | 40 ++++++++++++++++++++++++++++++++ 14 files changed, 207 insertions(+), 17 deletions(-) create mode 100644 test/confs/0588 create mode 100644 test/log/0588 create mode 100644 test/scripts/0000-Basic/0588 create mode 100644 test/stdout/0588 diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 30f5f2867..5c6955e58 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -25472,6 +25472,20 @@ so can cause parallel connections to the same host if &%remote_max_parallel%& permits this. +.new +.option message_linelength_limit smtp integer 998 +.cindex "line length" limit +This option sets the maximum line length, in bytes, that the transport +will send. Any messages with lines exceeding the given value +will fail and a failure-DSN ("bounce") message will if possible be returned +to the sender. +The default value is that defined by the SMTP standards. + +It is generally wise to also check in the data ACL so that messages +received via SMTP can be refused without producing a bounce. +.wen + + .option multi_domain smtp boolean&!! true .vindex "&$domain$&" When this option is set, the &(smtp)& transport can handle a number of diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 253eae2b7..82f1c5c18 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -21,6 +21,9 @@ Version 4.95 4. Single-key LMDB lookups, previously experimental, are now supported. The support is not built unless specified in the Local/Makefile. + 5. Option "message_linelength_limit" on the smtp transport to enforce (by + default) the RFC 998 character limit. + Version 4.94 ------------ diff --git a/doc/doc-txt/OptionLists.txt b/doc/doc-txt/OptionLists.txt index 5de6d6936..cfb3cd0f0 100644 --- a/doc/doc-txt/OptionLists.txt +++ b/doc/doc-txt/OptionLists.txt @@ -382,6 +382,7 @@ message_body_newlines boolean false main message_body_visible integer 500 main message_id_header_domain string* unset main 4.11 message_id_header_text string* unset main +message_linelength_limit integer 998 smtp 4.94 message_logs boolean true main 4.10 message_prefix string* + appendfile 4.00 replaces prefix string* unset pipe 4.00 replaces prefix diff --git a/src/src/configure.default b/src/src/configure.default index 57af99c14..7d54e11eb 100644 --- a/src/src/configure.default +++ b/src/src/configure.default @@ -808,13 +808,9 @@ begin transports # This transport is used for delivering messages over SMTP connections. -# Refuse to send any message with over-long lines, which could have -# been received other than via SMTP. The use of message_size_limit to -# enforce this is a red herring. remote_smtp: driver = smtp - message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}} .ifdef _HAVE_TLS tls_resumption_hosts = * #endif @@ -832,7 +828,6 @@ remote_smtp: smarthost_smtp: driver = smtp - message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}} multi_domain # .ifdef _HAVE_TLS diff --git a/src/src/deliver.c b/src/src/deliver.c index 3dcd7f949..67d711b7e 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -5380,7 +5380,8 @@ Returns: nothing static void print_dsn_diagnostic_code(const address_item *addr, FILE *f) { -uschar *s = testflag(addr, af_pass_message) ? addr->message : NULL; +uschar * s = testflag(addr, af_pass_message) ? addr->message : NULL; +unsigned cnt; /* af_pass_message and addr->message set ? print remote host answer */ if (s) @@ -5392,19 +5393,32 @@ if (s) if (!(s = Ustrstr(addr->message, ": "))) return; /* not found, bail out */ s += 2; /* skip ": " */ - fprintf(f, "Diagnostic-Code: smtp; "); + cnt = fprintf(f, "Diagnostic-Code: smtp; "); } /* no message available. do nothing */ else return; while (*s) + { + if (cnt > 950) /* RFC line length limit: 998 */ + { + DEBUG(D_deliver) debug_printf("print_dsn_diagnostic_code() truncated line\n"); + fputs("[truncated]", f); + break; + } + if (*s == '\\' && s[1] == 'n') { fputs("\n ", f); /* as defined in RFC 3461 */ s += 2; + cnt += 2; } else + { fputc(*s++, f); + cnt++; + } + } fputc('\n', f); } @@ -7831,11 +7845,11 @@ wording. */ fprintf(fp, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p); } if ((s = addr->smtp_greeting) && *s) - fprintf(fp, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s); + fprintf(fp, "X-Remote-MTA-smtp-greeting: X-str; %.900s\n", s); if ((s = addr->helo_response) && *s) - fprintf(fp, "X-Remote-MTA-helo-response: X-str; %s\n", s); + fprintf(fp, "X-Remote-MTA-helo-response: X-str; %.900s\n", s); if ((s = addr->message) && *s) - fprintf(fp, "X-Exim-Diagnostic: X-str; %s\n", s); + fprintf(fp, "X-Exim-Diagnostic: X-str; %.900s\n", s); } #endif print_dsn_diagnostic_code(addr, fp); diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 412ef4df0..2b4323bec 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -5173,9 +5173,9 @@ while (done <= 0) recipients_list[recipients_count-1].orcpt = orcpt; recipients_list[recipients_count-1].dsn_flags = dsn_flags; - DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n", + /* DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n", recipients_list[recipients_count-1].orcpt, - recipients_list[recipients_count-1].dsn_flags); + recipients_list[recipients_count-1].dsn_flags); */ } /* The recipient was discarded */ @@ -5190,8 +5190,8 @@ while (done <= 0) discarded = TRUE; log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: " "discarded by %s ACL%s%s", host_and_ident(TRUE), - sender_address_unrewritten? sender_address_unrewritten : sender_address, - smtp_cmd_argument, f.recipients_discarded? "MAIL" : "RCPT", + sender_address_unrewritten ? sender_address_unrewritten : sender_address, + smtp_cmd_argument, f.recipients_discarded ? "MAIL" : "RCPT", log_msg ? US": " : US"", log_msg ? log_msg : US""); } diff --git a/src/src/spool_out.c b/src/src/spool_out.c index 5d658fd74..9a514b331 100644 --- a/src/src/spool_out.c +++ b/src/src/spool_out.c @@ -277,9 +277,9 @@ if (message_smtputf8) #endif /* Write the dsn flags to the spool header file */ -DEBUG(D_deliver) debug_printf("DSN: Write SPOOL: -dsn_envid %s\n", dsn_envid); +/* DEBUG(D_deliver) debug_printf("DSN: Write SPOOL: -dsn_envid %s\n", dsn_envid); */ if (dsn_envid) fprintf(fp, "-dsn_envid %s\n", dsn_envid); -DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_ret %d\n", dsn_ret); +/* DEBUG(D_deliver) debug_printf("DSN: Write SPOOL: -dsn_ret %d\n", dsn_ret); */ if (dsn_ret) fprintf(fp, "-dsn_ret %d\n", dsn_ret); /* To complete the envelope, write out the tree of non-recipients, followed by @@ -293,7 +293,7 @@ for (int i = 0; i < recipients_count; i++) { recipient_item *r = recipients_list + i; - DEBUG(D_deliver) debug_printf("DSN: Flags: 0x%x\n", r->dsn_flags); + /* DEBUG(D_deliver) debug_printf("DSN: Flags: 0x%x\n", r->dsn_flags); */ if (r->pno < 0 && !r->errors_to && r->dsn_flags == 0) fprintf(fp, "%s\n", r->address); diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index f47c6d92f..12a199464 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -111,6 +111,7 @@ optionlist smtp_transport_options[] = { { "lmtp_ignore_quota", opt_bool, LOFF(lmtp_ignore_quota) }, { "max_rcpt", opt_int | opt_public, OPT_OFF(transport_instance, max_addresses) }, + { "message_linelength_limit", opt_int, LOFF(message_linelength_limit) }, { "multi_domain", opt_expand_bool | opt_public, OPT_OFF(transport_instance, multi_domain) }, { "port", opt_stringptr, LOFF(port) }, @@ -207,6 +208,7 @@ smtp_transport_options_block smtp_transport_option_defaults = { .size_addition = 1024, .hosts_max_try = 5, .hosts_max_try_hardlimit = 50, + .message_linelength_limit = 998, .address_retry_include_sender = TRUE, .allow_localhost = FALSE, .authenticated_sender_force = FALSE, @@ -4524,6 +4526,23 @@ DEBUG(D_transport) cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0); } +/* Check the restrictions on line length */ + +debug_printf("%s %d: max_received_linelength %u message_linelength_limit %u\n", __FUNCTION__, __LINE__, max_received_linelength, ob->message_linelength_limit); +if (max_received_linelength > ob->message_linelength_limit) + { + struct timeval now; + gettimeofday(&now, NULL); + + for (address_item * addr = addrlist; addr; addr = addr->next) + if (addr->transport_return == DEFER) + addr->transport_return = PENDING_DEFER; + + set_errno_nohost(addrlist, ERRNO_SMTPFORMAT, + US"message has lines too long for transport", FAIL, TRUE, &now); + goto END_TRANSPORT; + } + /* Set the flag requesting that these hosts be added to the waiting database if the delivery fails temporarily or if we are running with queue_smtp or a 2-stage queue run. This gets unset for certain diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index 037105a00..607a3772d 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -62,6 +62,7 @@ typedef struct { int size_addition; int hosts_max_try; int hosts_max_try_hardlimit; + int message_linelength_limit; BOOL address_retry_include_sender; BOOL allow_localhost; BOOL authenticated_sender_force; diff --git a/test/confs/0588 b/test/confs/0588 new file mode 100644 index 000000000..9a88c9c52 --- /dev/null +++ b/test/confs/0588 @@ -0,0 +1,42 @@ +# Exim test configuration 0588 + +.include DIR/aux-var/std_conf_prefix + +log_selector = +received_recipients +sender_on_delivery +millisec + +# ----- Main settings ----- + +acl_smtp_rcpt = accept + +# ----- Routers ----- + +begin routers + +rx_dump: + driver = redirect + condition = ${if !eq {$received_ip_address}{127.0.0.1}} + data = :blackhole: + +smtp_try: + driver = accept + transport = send_to_server + +# ----- Transports ----- + +begin transports + +send_to_server: + driver = smtp + hosts = HOSTIPV4 + allow_localhost + port = PORT_D + hosts_try_fastopen = : + +# ----- Retry ----- + +begin retry + +* * F,5d,10s + +# End + diff --git a/test/log/0588 b/test/log/0588 new file mode 100644 index 000000000..944645114 --- /dev/null +++ b/test/log/0588 @@ -0,0 +1,15 @@ + +******** SERVER ******** +2017-07-30 18:51:05.712 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D +2017-07-30 18:51:05.712 10HmaX-0005vi-00 <= CALLER@test.ex H=(test) [127.0.0.1] P=smtp S=sss for good@test.ex +2017-07-30 18:51:05.712 10HmaY-0005vi-00 <= CALLER@test.ex H=(test) [127.0.0.1] P=smtp S=sss for bad@test.ex +2017-07-30 18:51:05.712 10HmaZ-0005vi-00 <= CALLER@test.ex H=the.local.host.name [ip4.ip4.ip4.ip4] P=esmtp S=sss for good@test.ex +2017-07-30 18:51:05.712 10HmaZ-0005vi-00 => :blackhole: R=rx_dump +2017-07-30 18:51:05.712 10HmaZ-0005vi-00 Completed +2017-07-30 18:51:05.712 10HmaX-0005vi-00 => good@test.ex F= R=smtp_try T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmaZ-0005vi-00" +2017-07-30 18:51:05.712 10HmaX-0005vi-00 Completed +2017-07-30 18:51:05.712 10HmaY-0005vi-00 ** bad@test.ex F= R=smtp_try T=send_to_server: message has lines too long for transport +2017-07-30 18:51:05.712 10HmbA-0005vi-00 <= <> R=10HmaY-0005vi-00 U=EXIMUSER P=local S=sss for CALLER@test.ex +2017-07-30 18:51:05.712 10HmbA-0005vi-00 => :blackhole: R=rx_dump +2017-07-30 18:51:05.712 10HmbA-0005vi-00 Completed +2017-07-30 18:51:05.712 10HmaY-0005vi-00 Completed diff --git a/test/scripts/0000-Basic/0588 b/test/scripts/0000-Basic/0588 new file mode 100644 index 000000000..44328a70c --- /dev/null +++ b/test/scripts/0000-Basic/0588 @@ -0,0 +1,45 @@ +# message_linelength_limit +# +# The "write" script cmd subtracts 1 for the newline, +# and the linecount in exim doesn't count the line-ending. +write test-data-good 1x999 +++++ +**** +write test-data-bad 1x1000 +++++ +**** +# +exim -bd -DSERVER=server -oX PORT_D +**** +client 127.0.0.1 PORT_D +??? 220 +HELO test +??? 250 +MAIL FROM: +??? 250 +RCPT TO: +??? 250 +DATA +??? 354 +Subject: should be good + +<<< test-data-good +. +??? 250 +MAIL FROM: +??? 250 +RCPT TO: +??? 250 +DATA +??? 354 +Subject: should be bad + +<<< test-data-bad +. +??? 250 +QUIT +??? 221 +**** +# +sleep 1 +killdaemon diff --git a/test/stdout/0572 b/test/stdout/0572 index d66f928d4..fd77c72b7 100644 --- a/test/stdout/0572 +++ b/test/stdout/0572 @@ -64,6 +64,7 @@ interface = ip4.ip4.ip4.ip4 keepalive no_lmtp_ignore_quota max_rcpt = 100 +message_linelength_limit = 998 multi_domain port = 1224 protocol = smtp diff --git a/test/stdout/0588 b/test/stdout/0588 new file mode 100644 index 000000000..994f04e2e --- /dev/null +++ b/test/stdout/0588 @@ -0,0 +1,40 @@ +Connecting to 127.0.0.1 port 1225 ... connected +??? 220 +<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000 +>>> HELO test +??? 250 +<<< 250 the.local.host.name Hello test [127.0.0.1] +>>> MAIL FROM: +??? 250 +<<< 250 OK +>>> RCPT TO: +??? 250 +<<< 250 Accepted +>>> DATA +??? 354 +<<< 354 Enter message, ending with "." on a line by itself +>>> Subject: should be good +>>> +>>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +>>> . +??? 250 +<<< 250 OK id=10HmaX-0005vi-00 +>>> MAIL FROM: +??? 250 +<<< 250 OK +>>> RCPT TO: +??? 250 +<<< 250 Accepted +>>> DATA +??? 354 +<<< 354 Enter message, ending with "." on a line by itself +>>> Subject: should be bad +>>> +>>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +>>> . +??? 250 +<<< 250 OK id=10HmaY-0005vi-00 +>>> QUIT +??? 221 +<<< 221 the.local.host.name closing connection +End of script -- 2.30.2