+ 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)
#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"
#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);
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);
+ }
- 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. */
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;
#include "log_error.h"
#include "memory.h"
-/* "HELO \r\n " has length 7 */
-#define EXTRA_HELO_LEN 8
+/* "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 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;
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;
}
/* "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;
return retstr;
}
-/* "\r\n" has length 2 */
-#define EXTRA_CUSTOM_LEN 3
-
int write_dot(int sockfd)
{
size_t bytes_written;
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;
}
/* "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;
int initsmtp(int *sockfd, const char *relayhost, unsigned short port)
{
int retval = 0;
+ int try_ehlo = 1;
char *reply = NULL;
char *myhostname = hostnamestr();
- init_sockfd(sockfd, relayhost, port);
+ 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"
- "Reply: [%s]", reply);
- myfree(reply);
- retval = MLMMJ_CONNECT;
- /* FIXME: Queue etc. */
- }
- 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((reply = checkwait_smtpreply(*sockfd, MLMMJ_CONNECT)) != NULL) {
+ log_error(LOG_ARGS, "No proper greeting to our connect"
+ "Reply: [%s]", reply);
+ myfree(reply);
+ retval = MLMMJ_CONNECT;
+ /* FIXME: Queue etc. */
+ break;
+ }
+
+ 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);
+ /* 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;
}