changeset 899:04d916168efb

Support ESMTP so OpenSMTPD uses 8 bits (Paul Fariello).
author Ben Schmidt
date Wed, 25 Feb 2015 18:22:37 +1100
parents b0b8ccdb490f
children b68213ccb9d2
files ChangeLog include/checkwait_smtpreply.h include/mail-functions.h src/checkwait_smtpreply.c src/mail-functions.c src/mlmmj-send.c
diffstat 6 files changed, 168 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Mar 24 11:57:08 2014 +1100
+++ b/ChangeLog	Wed Feb 25 18:22:37 2015 +1100
@@ -1,3 +1,4 @@
+ o Support ESMTP so OpenSMTPD uses 8 bits (Paul Fariello)
  o Use iconv to convert unknown character sets
  o Handle unfolded header lines better
  o Add a tunable for moderation request lifetime (Timo Boettcher)
--- a/include/checkwait_smtpreply.h	Mon Mar 24 11:57:08 2014 +1100
+++ b/include/checkwait_smtpreply.h	Wed Feb 25 18:22:37 2015 +1100
@@ -25,13 +25,14 @@
 #define CHECK_REPLY_H
 
 #define MLMMJ_CONNECT 1
-#define MLMMJ_HELO 2
+#define MLMMJ_EHLO 2
+#define MLMMJ_HELO 3
 #define MLMMJ_FROM 4
-#define MLMMJ_RCPTTO 8
-#define MLMMJ_DATA 16
-#define MLMMJ_DOT 32
-#define MLMMJ_QUIT 64
-#define MLMMJ_RSET 128
+#define MLMMJ_RCPTTO 5
+#define MLMMJ_DATA 6
+#define MLMMJ_DOT 7
+#define MLMMJ_QUIT 8
+#define MLMMJ_RSET 9
 
 #include "mlmmj.h"
 
--- a/include/mail-functions.h	Mon Mar 24 11:57:08 2014 +1100
+++ b/include/mail-functions.h	Wed Feb 25 18:22:37 2015 +1100
@@ -29,6 +29,7 @@
 #include <stdio.h>
 
 int write_helo(int sockfd, const char *hostname);
+int write_ehlo(int sockfd, const char *hostname);
 int write_mail_from(int sockfd, const char *from_addr, const char *extra);
 int write_rcpt_to(int sockfd, const char *rcpt_addr);
 int write_custom_line(int sockfd, const char *line);
--- a/src/checkwait_smtpreply.c	Mon Mar 24 11:57:08 2014 +1100
+++ b/src/checkwait_smtpreply.c	Wed Feb 25 18:22:37 2015 +1100
@@ -33,9 +33,18 @@
 
 char *checkwait_smtpreply(int sockfd, int replytype)
 {
-	char *smtpreply;
+	char *smtpreply = NULL;
 
+	if(replytype == MLMMJ_EHLO) {
+		/* Consume all 8BITMIME 250- reply */
+		do {
+			myfree(smtpreply);
 	smtpreply = mygetline(sockfd);
+		} while (strncmp(smtpreply, "250-", 4) == 0);
+	} else {
+		smtpreply = mygetline(sockfd);
+	}
+
 	if(smtpreply == NULL) {
 		/* This will never be a valid SMTP response so will always be returned,
 		 * but is more descriptive than an empty string. */
@@ -55,6 +64,10 @@
 			if(smtpreply[0] != '2' || smtpreply[1] != '2')
 				return smtpreply;
 			break;
+		case MLMMJ_EHLO:
+			if(smtpreply[0] != '2' || smtpreply[1] != '5')
+				return smtpreply;
+			break;
 		case MLMMJ_HELO:
 			if(smtpreply[0] != '2' || smtpreply[1] != '5')
 				return smtpreply;
--- a/src/mail-functions.c	Mon Mar 24 11:57:08 2014 +1100
+++ b/src/mail-functions.c	Wed Feb 25 18:22:37 2015 +1100
@@ -37,11 +37,35 @@
 #include "log_error.h"
 #include "memory.h"
 
+/* "EHLO \r\n" has length 7 */
+#define EXTRA_EHLO_LEN 7
+int write_ehlo(int sockfd, const char *hostname)
+{
+	size_t len = (size_t)(strlen(hostname) + EXTRA_EHLO_LEN + 1);
+	char *ehlo;
+	size_t bytes_written;
+	
+	if((ehlo = mymalloc(len)) == 0)
+		return errno;
+	snprintf(ehlo, len, "EHLO %s\r\n", hostname);
+	len = strlen(ehlo);
+#if 0
+	fprintf(stderr, "\nwrite_ehlo, ehlo = [%s]\n", ehlo);
+#endif
+	bytes_written = writen(sockfd, ehlo, len);
+	if(bytes_written < 0) {
+		log_error(LOG_ARGS, "Could not write EHLO");
+		myfree(ehlo);
+		return errno;
+	}
+	myfree(ehlo);
+	return 0;
+}
 /* "HELO \r\n " has length 7 */
-#define EXTRA_HELO_LEN 8
+#define EXTRA_HELO_LEN 7
 int write_helo(int sockfd, const char *hostname)
 {
-	size_t len = (size_t)(strlen(hostname) + EXTRA_HELO_LEN);
+	size_t len = (size_t)(strlen(hostname) + EXTRA_HELO_LEN + 1);
 	char *helo;
 	size_t bytes_written;
 	
@@ -66,7 +90,7 @@
 int write_mail_from(int sockfd, const char *from_addr, const char *extra)
 {
 	size_t len = (size_t)(strlen(from_addr) + EXTRA_FROM_LEN +
-			strlen(extra) + 2);
+			strlen(extra) + 1);
 	char *mail_from;
 	size_t bytes_written;
 
@@ -95,11 +119,10 @@
 }
 
 /* "RCPT TO: <>\r\n" has length 13 */
-#define EXTRA_RCPT_LEN 14
-
+#define EXTRA_RCPT_LEN 13
 int write_rcpt_to(int sockfd, const char *rcpt_addr)
 {
-	size_t len = (size_t)(strlen(rcpt_addr) + EXTRA_RCPT_LEN);
+	size_t len = (size_t)(strlen(rcpt_addr) + EXTRA_RCPT_LEN + 1);
 	char *rcpt_to;
 	size_t bytes_written;
 	
@@ -241,9 +264,6 @@
 	return retstr;
 }
 
-/* "\r\n" has length 2 */
-#define EXTRA_CUSTOM_LEN 3
-
 int write_dot(int sockfd)
 {
 	size_t bytes_written;
@@ -255,9 +275,11 @@
 	return 0;
 }
 
+/* "\r\n" has length 2 */
+#define EXTRA_CUSTOM_LEN 2
 int write_custom_line(int sockfd, const char *line)
 {
-	size_t len = strlen(line) + EXTRA_CUSTOM_LEN;
+	size_t len = strlen(line) + EXTRA_CUSTOM_LEN + 1;
 	size_t bytes_written;
 	char *customline;
 	
@@ -281,11 +303,10 @@
 }
 
 /* "Reply-To: \r\n" has length 12 */
-#define EXTRA_REPLYTO_LEN 13
-
+#define EXTRA_REPLYTO_LEN 12
 int write_replyto(int sockfd, const char *replyaddr)
 {
-	size_t len = (size_t)(strlen(replyaddr) + EXTRA_REPLYTO_LEN);
+	size_t len = (size_t)(strlen(replyaddr) + EXTRA_REPLYTO_LEN + 1);
 	char *replyto;
 	size_t bytes_written;
 	
--- a/src/mlmmj-send.c	Mon Mar 24 11:57:08 2014 +1100
+++ b/src/mlmmj-send.c	Wed Feb 25 18:22:37 2015 +1100
@@ -379,13 +379,17 @@
 int initsmtp(int *sockfd, const char *relayhost, unsigned short port)
 {
 	int retval = 0;
+	int try_ehlo = 1;
 	char *reply = NULL;
 	char *myhostname = hostnamestr();
 
+	do {
 	init_sockfd(sockfd, relayhost, port);
 
-	if(*sockfd == -1)
-		return EBADF;
+		if(*sockfd == -1) {
+			retval = EBADF;
+			break;
+		}
 
 	if((reply = checkwait_smtpreply(*sockfd, MLMMJ_CONNECT)) != NULL) {
 		log_error(LOG_ARGS, "No proper greeting to our connect"
@@ -393,16 +397,104 @@
 		myfree(reply);
 		retval = MLMMJ_CONNECT;
 		/* FIXME: Queue etc. */
+			break;
 	}	
-	write_helo(*sockfd, myhostname);
-	myfree(myhostname);
-	if((reply = checkwait_smtpreply(*sockfd, MLMMJ_HELO)) != NULL) {
-		log_error(LOG_ARGS, "Error with HELO. Reply: [%s]", reply);
-		/* FIXME: quit and tell admin to configure correctly */
+
+		if (try_ehlo) {
+			write_ehlo(*sockfd, myhostname);
+			if((reply = checkwait_smtpreply(*sockfd, MLMMJ_EHLO))
+					== NULL) {
+				/* EHLO successful don't try more */
+				break;
+			}
+
+			/* RFC 1869 - 4.5. - In the case of any error response,
+			 * the client SMTP should issue either the HELO or QUIT
+			 * command.
+			 * RFC 1869 - 4.5. - If the server SMTP recognizes the
+			 * EHLO command, but the command argument is
+			 * unacceptable, it will return code 501.
+			 */
+			if (strncmp(reply, "501", 3) == 0) {
 		myfree(reply);
-		retval = MLMMJ_HELO;
+				/* Commmand unacceptable; we choose to QUIT but
+				 * ignore any QUIT errors; return that EHLO was
+				 * the error.
+				 */
+				endsmtp(sockfd);
+				retval = MLMMJ_EHLO;
+				break;
+			}
+
+			/* RFC 1869 - 4.6. - A server SMTP that conforms to RFC
+			 * 821 but does not support the extensions specified
+			 * here will not recognize the EHLO command and will
+			 * consequently return code 500, as specified in RFC
+			 * 821.  The server SMTP should stay in the same state
+			 * after returning this code (see section 4.1.1 of RFC
+			 * 821).  The client SMTP may then issue either a HELO
+			 * or a QUIT command.
+			 */
+
+			if (reply[0] != '5') {
+				myfree(reply);
+				/* Server doesn't understand EHLO, but gives a
+				 * broken response. Try with new connection.
+				 */
+				endsmtp(sockfd);
+				try_ehlo = 0;
+				continue;
 	}
 
+			myfree(reply);
+
+			/* RFC 1869 - 4.7. - Other improperly-implemented
+			 * servers will not accept a HELO command after EHLO has
+			 * been sent and rejected.  In some cases, this problem
+			 * can be worked around by sending a RSET after the
+			 * failure response to EHLO, then sending the HELO.
+			 */
+			write_rset(*sockfd);
+			reply = checkwait_smtpreply(*sockfd, MLMMJ_RSET);
+
+			/* RFC 1869 - 4.7. - Clients that do this should be
+			 * aware that many implementations will return a failure
+			 * code (e.g., 503 Bad sequence of commands) in response
+			 * to the RSET. This code can be safely ignored.
+			 */
+			myfree(reply);
+
+			/* Try HELO on the same connection
+			 */
+		}
+
+		write_helo(*sockfd, myhostname);
+		if((reply = checkwait_smtpreply(*sockfd, MLMMJ_HELO))
+				== NULL) {
+			/* EHLO successful don't try more */
+			break;
+		}
+		if (try_ehlo) {
+			myfree(reply);
+			/* We reused a connection we tried EHLO on. Maybe
+			 * that's why it failed. Try with new connection.
+			 */
+			endsmtp(sockfd);
+			try_ehlo = 0;
+			continue;
+		}
+
+		log_error(LOG_ARGS, "Error with HELO. Reply: "
+				"[%s]", reply);
+		myfree(reply);
+		/* FIXME: quit and tell admin to configure
+		 * correctly */
+		retval = MLMMJ_HELO;
+		break;
+
+	} while (1);
+
+	myfree(myhostname);
 	return retval;
 }