From a01fd233fdf9491ea29580b9ace0b47fd159234b Mon Sep 17 00:00:00 2001 From: Simon Arlott Date: Tue, 8 Nov 2022 21:31:27 +0000 Subject: [PATCH] Escape quotation marks when logging SMTP strings The SMTP command buffer and various other messages logged inside smtp_in.c are wrapped in quotes. If the content contains quotes it becomes very difficult to parse the log correctly because the quoted section can be ended prematurely. This happens if someone sends JSON to Exim (attempting to exploit a non-SMTP service) with a string like: {"hello": ["world"]} The distinguished name and possibly other externally supplied strings enclosed in quotes could also contain quotes. --- src/src/deliver.c | 2 +- src/src/macros.h | 1 + src/src/receive.c | 2 +- src/src/smtp_in.c | 29 +++++++++++++++-------------- src/src/string.c | 2 ++ src/src/tls-openssl.c | 2 +- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/src/deliver.c b/src/src/deliver.c index 725d0c872f..7ca4d40477 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -845,7 +845,7 @@ if (LOGGING(tls_certificate_verified) && addr->cipher) "yes" : "no"); if (LOGGING(tls_peerdn) && addr->peerdn) - g = string_append(g, 3, US" DN=\"", string_printing(addr->peerdn), US"\""); + g = string_append(g, 3, US" DN=\"", string_printing2(addr->peerdn, SP_QUOT), US"\""); return g; } #endif diff --git a/src/src/macros.h b/src/src/macros.h index adbe6a267c..de8040b307 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -46,6 +46,7 @@ allow tabs & spaces. A macro just makes life a bit easier. */ #define string_printing(s) string_printing2((s), 0) #define SP_TAB BIT(0) #define SP_SPACE BIT(1) +#define SP_QUOT BIT(2) /* We need a special return code for "no recipients and failed to send an error diff --git a/src/src/receive.c b/src/src/receive.c index 0a27c7950a..d1a556b3cc 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -4072,7 +4072,7 @@ if (LOGGING(tls_cipher) && tls_in.cipher) if (LOGGING(tls_certificate_verified) && tls_in.cipher) g = string_append(g, 2, US" CV=", tls_in.certificate_verified ? "yes":"no"); if (LOGGING(tls_peerdn) && tls_in.peerdn) - g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\""); + g = string_append(g, 3, US" DN=\"", string_printing2(tls_in.peerdn, SP_QUOT), US"\""); if (LOGGING(tls_sni) && tls_in.sni) g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE)); #endif diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 28e529bae2..e57ddce658 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -710,8 +710,8 @@ for(;;) log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error " "(next input sent too soon: pipelining was not advertised): " "rejected \"%s\" %s next input=\"%s\"%s", - smtp_cmd_buffer, host_and_ident(TRUE), - string_printing(string_copyn(smtp_inptr, n)), + string_printing2(smtp_cmd_buffer, SP_QUOT), host_and_ident(TRUE), + string_printing2(string_copyn(smtp_inptr, n), SP_QUOT), smtp_inend - smtp_inptr > n ? "..." : ""); (void) synprot_error(L_smtp_protocol_error, 554, NULL, US"SMTP synchronization error"); @@ -1846,7 +1846,7 @@ if (LOGGING(tls_cipher) && tls_in.cipher) if (LOGGING(tls_certificate_verified) && tls_in.cipher) g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no"); if (LOGGING(tls_peerdn) && tls_in.peerdn) - g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\""); + g = string_append(g, 3, US" DN=\"", string_printing2(tls_in.peerdn, SP_QUOT), US"\""); if (LOGGING(tls_sni) && tls_in.sni) g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE)); return g; @@ -2873,7 +2873,7 @@ if (!f.sender_host_unknown) errno = 0; if (!(tcp_wrappers_name = expand_string(tcp_wrappers_daemon_name))) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" " - "(tcp_wrappers_name) failed: %s", string_printing(tcp_wrappers_name), + "(tcp_wrappers_name) failed: %s", string_printing2(tcp_wrappers_name, SP_QUOT), expand_string_message); if (!hosts_ctl(tcp_wrappers_name, @@ -3095,7 +3095,7 @@ if (!check_sync()) log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol " "synchronization error (input sent without waiting for greeting): " "rejected connection from %s input=\"%s\"", host_and_ident(TRUE), - string_printing(string_copyn(smtp_inptr, n))); + string_printing2(string_copyn(smtp_inptr, n)), SP_QUOT); smtp_printf("554 SMTP synchronization error\r\n", FALSE); return FALSE; } @@ -3152,14 +3152,14 @@ int yield = -1; log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s", type == L_smtp_syntax_error ? "syntax" : "protocol", - string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess); + string_printing2(smtp_cmd_buffer, SP_QUOT), host_and_ident(TRUE), errmess); if (++synprot_error_count > smtp_max_synprot_errors) { yield = 1; log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " "syntax or protocol errors (last command was \"%s\", %s)", - host_and_ident(FALSE), string_printing(smtp_cmd_buffer), + host_and_ident(FALSE), string_printing2(smtp_cmd_buffer, SP_QUOT), string_from_gstring(s_connhad_log(NULL)) ); } @@ -4286,7 +4286,7 @@ while (done <= 0) { log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " "syntax or protocol errors (last command was \"%s\", %s)", - host_and_ident(FALSE), string_printing(smtp_cmd_buffer), + host_and_ident(FALSE), string_printing2(smtp_cmd_buffer, SP_QUOT), string_from_gstring(s_connhad_log(NULL)) ); done = 1; @@ -6010,8 +6010,8 @@ while (done <= 0) "(next input sent too soon: pipelining was%s advertised): " "rejected \"%s\" %s next input=\"%s\"", f.smtp_in_pipelining_advertised ? "" : " not", - smtp_cmd_buffer, host_and_ident(TRUE), - string_printing(smtp_inptr)); + string_printing2(smtp_cmd_buffer, SP_QUOT), host_and_ident(TRUE), + string_printing2(smtp_inptr, SP_QUOT)); smtp_notquit_exit(US"synchronization-error", US"554", US"SMTP synchronization error"); done = 1; /* Pretend eof - drops connection */ @@ -6021,10 +6021,11 @@ while (done <= 0) case TOO_MANY_NONMAIL_CMD: s = smtp_cmd_buffer; while (*s != 0 && !isspace(*s)) s++; + *s = 0; incomplete_transaction_log(US"too many non-mail commands"); log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " - "nonmail commands (last was \"%.*s\")", host_and_ident(FALSE), - (int)(s - smtp_cmd_buffer), smtp_cmd_buffer); + "nonmail commands (last was \"%s\")", host_and_ident(FALSE), + string_printing2(smtp_cmd_buffer, SP_QUOT)); smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands"); done = 1; /* Pretend eof - drops connection */ break; @@ -6040,7 +6041,7 @@ while (done <= 0) { log_write(L_smtp_syntax_error, LOG_MAIN, "SMTP syntax error in \"%s\" %s %s", - string_printing(smtp_cmd_buffer), host_and_ident(TRUE), + string_printing2(smtp_cmd_buffer, SP_QUOT), host_and_ident(TRUE), US"unrecognized command"); incomplete_transaction_log(US"unrecognized command"); smtp_notquit_exit(US"bad-commands", US"500", @@ -6048,7 +6049,7 @@ while (done <= 0) done = 2; log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " "unrecognized commands (last was \"%s\")", host_and_ident(FALSE), - string_printing(smtp_cmd_buffer)); + string_printing2(smtp_cmd_buffer, SP_QUOT)); } else done = synprot_error(L_smtp_syntax_error, 500, NULL, diff --git a/src/src/string.c b/src/src/string.c index a5161bb31e..b9501aeef7 100644 --- a/src/src/string.c +++ b/src/src/string.c @@ -304,6 +304,7 @@ while (*t) if ( !mac_isprint(c) || flags & SP_TAB && c == '\t' || flags & SP_SPACE && c == ' ' + || flags & SP_QUOT && c == '\"' ) nonprintcount++; length++; } @@ -323,6 +324,7 @@ for (t = s; *t; ) if ( mac_isprint(c) && (!(flags & SP_TAB) || c != '\t') && (!(flags & SP_SPACE) || c != ' ') + && (!(flags & SP_QUOT) || c != '\"') ) *tt++ = *t++; else diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 68ad6f15b9..d8056a854f 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1180,7 +1180,7 @@ else log_write(0, LOG_MAIN, "[%s] SSL verify error%s: certificate name mismatch: DN=\"%s\" H=\"%s\"", tlsp == &tls_out ? deliver_host_address : sender_host_address, - extra, dn, verify_cert_hostnames); + extra, string_printing2(dn, SP_QUOT), verify_cert_hostnames); *calledp = TRUE; if (!*optionalp) {