From: David Levine Date: Sun, 14 Aug 2016 14:57:51 +0000 (-0400) Subject: Attempt to decode base64-encoded strings in -snoop traffic. X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/23e9ccce9de7e5190c2ea7dda03e54ece5b5a3da?hp=28c31eb83bf6113dd72182bb5f13c4da99372d9e Attempt to decode base64-encoded strings in -snoop traffic. --- diff --git a/docs/pending-release-notes b/docs/pending-release-notes index d56ae28d..869befcc 100644 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@ -45,6 +45,7 @@ NEW FEATURES binary when using the sendmail/pipe MTS. - Added support to send(1) to specify switches to post(1) based on address or domain name in From: header line in message draft. +- post(8) -snoop now attempts to decode base64-encoded SMTP traffic. ----------------- OBSOLETE FEATURES diff --git a/h/prototypes.h b/h/prototypes.h index 45ee338d..884e0e0a 100644 --- a/h/prototypes.h +++ b/h/prototypes.h @@ -482,6 +482,8 @@ int writeBase64raw (unsigned char *, size_t, unsigned char *); */ int decodeBase64 (const char *, const char **, size_t *, int, unsigned char *); +void hexify (const unsigned char *, size_t, char **); + /* * credentials management */ diff --git a/man/inc.man b/man/inc.man index 61f6f844..46dda0e4 100644 --- a/man/inc.man +++ b/man/inc.man @@ -1,4 +1,4 @@ -.TH INC %manext1% "April 18, 2014" "%nmhversion%" +.TH INC %manext1% "August 14, 2016" "%nmhversion%" .\" .\" %nmhwarning% .\" @@ -263,7 +263,11 @@ will attempt to negotiate a security layer for session encryption. Encrypted traffic is labelled with `(encrypted)' and `(decrypted)' when viewing the POP transaction with the .B \-snoop -switch. +switch; see the +.B post +man page description of +.B \-snoop +for its other features. .SH FILES .PD 0 .TP 20 diff --git a/man/msgchk.man b/man/msgchk.man index 0d5d6c98..b0c7bd48 100644 --- a/man/msgchk.man +++ b/man/msgchk.man @@ -1,4 +1,4 @@ -.TH MSGCHK %manext1% "April 14, 2013" "%nmhversion%" +.TH MSGCHK %manext1% "August 14, 2016" "%nmhversion%" .\" .\" %nmhwarning% .\" @@ -119,7 +119,11 @@ a security layer for session encryption. Encrypted traffic is labelled with `(encrypted)' and `(decrypted)' when viewing the POP transaction with the .B \-snoop -switch. +switch; see the +.B post +man page description of +.B \-snoop +for its other features. .SH FILES .fc ^ ~ .nf @@ -138,6 +142,7 @@ None .SH "SEE ALSO" .IR inc (1), .IR mh\-mail (5) +.IR post (8) .SH DEFAULTS .nf .RB ` user "' defaults to the current user" diff --git a/man/post.man b/man/post.man index fe135381..1b1042b9 100644 --- a/man/post.man +++ b/man/post.man @@ -1,4 +1,4 @@ -.TH POST %manext8% "July 8, 2014" "%nmhversion%" +.TH POST %manext8% "August 14, 2016" "%nmhversion%" .\" .\" %nmhwarning% .\" @@ -232,7 +232,11 @@ will attempt to negotiate a security layer for session encryption. Encrypted data is labelled with `(sasl-encrypted)' and `(sasl-decrypted)' when viewing the SMTP transaction with the .B \-snoop -switch. The +switch. +Base64-encoded data is wrapped with ` b64<>'. +(Beware that the SMTP transaction may contain authentication information either +in plaintext or easily decoded base64.) +The .B \-saslmaxssf switch can be used to select the maximum value of the Security Strength Factor. This is an integer value and the exact meaning of this value depends on the @@ -256,6 +260,9 @@ is labelled with `(tls-encrypted)' and `(tls-decrypted)' when viewing the SMTP transction with the .B \-snoop switch. +Base64-encoded data is wrapped with `b64<>'. +(Beware that the SMTP transaction may contain authentication information either +in plaintext or easily decoded base64.) The .B \-notls switch will disable all attempts to negotiate TLS. diff --git a/man/send.man b/man/send.man index f568d991..87c2cfd2 100644 --- a/man/send.man +++ b/man/send.man @@ -1,7 +1,7 @@ .\" .\" %nmhwarning% .\" -.TH SEND %manext1% "July 8, 2016" "%nmhversion%" +.TH SEND %manext1% "August 14, 2016" "%nmhversion%" .SH NAME send \- send a message .SH SYNOPSIS @@ -418,7 +418,12 @@ will attempt to negotiate a security layer for session encryption. Encrypted data is labelled with `(encrypted)' and `(decrypted)' when viewing the SMTP transaction with the .B \-snoop -switch. The +switch; see the +.B post +man page description of +.B \-snoop +for its other features. +The .B \-saslmaxssf switch can be used to select the maximum value of the Security Strength Factor. This is an integer value and the exact meaning of this value depends on the @@ -441,7 +446,11 @@ taken place, before any SMTP commands are sent or received. Encrypted data is labelled with `(tls-encrypted)' and `(tls-decrypted)' when viewing the SMTP transction with the .B \-snoop -switch. +switch; see the +.B post +man page description of +.B \-snoop +for its other features. The .B \-notls switch will disable all attempts to negotiate TLS. diff --git a/man/whom.man b/man/whom.man index da7aee2e..cbad25f5 100644 --- a/man/whom.man +++ b/man/whom.man @@ -1,4 +1,4 @@ -.TH WHOM %manext1% "April 14, 2013" "%nmhversion%" +.TH WHOM %manext1% "August 14, 2016" "%nmhversion%" .\" .\" %nmhwarning% .\" @@ -102,7 +102,11 @@ will attempt to negotiate a security layer for session encryption. Encrypted data is labelled with `(encrypted)' and `(decrypted)' when viewing the SMTP transaction with the .B \-snoop -switch. The +switch; see the +.B post +man page description of +.B \-snoop +for its other features. The .B \-saslmaxssf switch can be used to select the maximum value of the Security Strength Factor. This is an integer value and the exact meaning of this value depends on the @@ -118,7 +122,11 @@ switches will require and disable the negotiation of TLS support when connecting SMTP MTA. Encrypted data is labelled with `(tls-encrypted)' and `(tls-decrypted)' when viewing the SMTP transction with the .B \-snoop -switch. +switch; see the +.B post +man page description of +.B \-snoop +for its other features. .PP The files specified by the profile entry \*(lqAliasfile:\*(rq and any additional alias files given by the diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c index 8b89dec3..92b91095 100644 --- a/mts/smtp/smtp.c +++ b/mts/smtp/smtp.c @@ -90,6 +90,8 @@ static int sm_verbose = 0; static FILE *sm_rfp = NULL; static FILE *sm_wfp = NULL; +static int next_line_encoded = 0; + #ifdef CYRUS_SASL /* * Some globals needed by SASL @@ -168,6 +170,7 @@ static int sm_rrecord (char *, int *); static int sm_rerror (int); static void alrmser (int); static char *EHLOset (char *); +static char *prepare_for_display (const char *, int *); static int sm_fwrite(char *, int); static int sm_fputs(char *); static int sm_fputc(int); @@ -1169,11 +1172,15 @@ smtalk (int time, char *fmt, ...) } if (sm_debug) { + char *decoded_buffer = + prepare_for_display (buffer, &next_line_encoded); + if (sasl_ssf) printf("(sasl-encrypted) "); if (tls_active) printf("(tls-encrypted) "); - printf ("=> %s\n", buffer); + printf ("=> %s\n", decoded_buffer); + free (decoded_buffer); fflush (stdout); } @@ -1504,11 +1511,15 @@ again: ; for (more = FALSE; sm_rrecord ((char *) (bp = (unsigned char *) buffer), &bc) != NOTOK ; ) { if (sm_debug) { + char *decoded_buffer = + prepare_for_display (buffer, &next_line_encoded); + if (sasl_ssf > 0) printf("(sasl-decrypted) "); if (tls_active) printf("(tls-decrypted) "); - printf ("<= %s\n", buffer); + printf ("<= %s\n", decoded_buffer); + free (decoded_buffer); fflush (stdout); } @@ -1850,3 +1861,75 @@ EHLOset (char *s) return 0; } + + +/* + * Detects, using heuristics, if an SMTP server or client response string + * contains a base64-encoded portion. If it does, decodes it and replaces + * any non-printable characters with a hex representation. Caller is + * responsible for free'ing return value. If the decode fails, a copy of + * the input string is returned. + */ +static +char * +prepare_for_display (const char *string, int *next_line_encoded) { + const char *start = NULL; + const char *decoded; + size_t decoded_len; + int prefix_len = -1; + + if (strncmp (string, "AUTH XOAUTH2 ", 13) == 0) { + /* Entire XOAUTH2 line. */ + prefix_len = 13; + *next_line_encoded = 0; + } else if (strncmp (string, "AUTH LOGIN ", 11) == 0) { + /* AUTH LOGIN followed by login name. + For AUTH LOGIN not followed by the name, the response to the 334 + server request will be handled by the code below. */ + prefix_len = 11; + *next_line_encoded = 0; + } else if (strncmp (string, "AUTH PLAIN ", 11) == 0) { + /* AUTH PLAIN followed by authorization/authentication string, e.g., + the display output will be: + AUTH PLAIN b64 + For AUTH PLAIN not followed by the string, the response to the 334 + will be handled by the code below. */ + prefix_len = 11; + *next_line_encoded = 0; + } else if (strncmp (string, "334 ", 4) == 0) { + /* 334 is the server's request for user or password. */ + prefix_len = 4; + /* The next (client response) line must be base64 encoded. */ + *next_line_encoded = 1; + } else if (*next_line_encoded) { + /* "next" line now refers to this line, which is a base64-encoded + client response. */ + prefix_len = 0; + *next_line_encoded = 0; + } else { + *next_line_encoded = 0; + } + + if (prefix_len > -1) { + start = string + prefix_len; + } + + if (start && decodeBase64 (start, &decoded, &decoded_len, 1, NULL) == OK) { + char *hexified; + char *prefix = mh_xmalloc(prefix_len + 1); + char *display_string; + + /* prefix is the beginning portion, which isn't base64 encoded. */ + snprintf (prefix, prefix_len + 1, "%*s", prefix_len, string); + hexify ((const unsigned char *) decoded, decoded_len, &hexified); + /* Wrap the decoded portion in "b64<>". */ + display_string = concat (prefix, "b64<", hexified, ">", NULL); + free (hexified); + free (prefix); + free ((char *) decoded); + + return display_string; + } else { + return getcpy (string); + } +} diff --git a/sbr/base64.c b/sbr/base64.c index 80ca86e0..9047eb5f 100644 --- a/sbr/base64.c +++ b/sbr/base64.c @@ -324,3 +324,35 @@ test_end: return OK; } + + +/* + * Prepare an unsigned char array for display by replacing non-printable + * ASCII bytes with their hex representation. Assumes ASCII input. output + * is allocated by the function and must be freed by the caller. + */ +void +hexify (const unsigned char *input, size_t len, char **output) { + /* Start with a charstring capacity that's arbitrarily larger than len. */ + const charstring_t tmp = charstring_create (2 * len); + const unsigned char *cp = input; + size_t i; + + for (i = 0; i < len; ++i, ++cp) { + if (isprint(*cp)) { + charstring_push_back (tmp, (const char) *cp); + } else { + char s[16]; + const int num = snprintf(s, sizeof s, "[0x%02x]", *cp); + + if (num <= 0 || (unsigned int) num >= sizeof s) { + advise (NULL, "hexify failed to write nonprintable character, needed %d bytes", num + 1); + } else { + charstring_append_cstring (tmp, s); + } + } + } + + *output = charstring_buffer_copy (tmp); + charstring_free (tmp); +}