]> git.ipfire.org Git - thirdparty/mlmmj.git/commitdiff
Isolate smtp code to allow reuse and add test for it
authorBaptiste Daroussin <bapt@FreeBSD.org>
Mon, 26 Dec 2022 16:26:35 +0000 (17:26 +0100)
committerBaptiste Daroussin <bapt@FreeBSD.org>
Mon, 26 Dec 2022 16:26:35 +0000 (17:26 +0100)
include/send_mail.h [new file with mode: 0644]
src/Makefile.am
src/mlmmj-send.c
src/send_mail.c [new file with mode: 0644]
tests/mlmmj.c

diff --git a/include/send_mail.h b/include/send_mail.h
new file mode 100644 (file)
index 0000000..060981e
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2004, 2003, 2004 Mads Martin Joergensen <mmj at mmj.dk>
+ * Copyright (C) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#pragma once
+
+int initsmtp(int *sockfd, const char *relayhost, unsigned short port, const char *heloname);
+int endsmtp(int *sockfd);
index b9281814c948ee8a74ef122acb929f6c6f5db1db..f087759125b982a8a9f6bdb7c644ff016de7fabd 100644 (file)
@@ -28,7 +28,7 @@ libmlmmj_a_SOURCES=   mail-functions.c chomp.c incindexfile.c \
                        unistr.c gethdrline.c send_digest.c \
                        writen.c getlistaddr.c strgen.c statctrl.c \
                        ctrlvalue.c readn.c getlistdelim.c ctrlvalues.c \
-                       utils.c mlmmj.c
+                       utils.c mlmmj.c send_mail.c
 
 mlmmj_send_SOURCES = mlmmj-send.c
 mlmmj_send_LDADD=      libmlmmj.a
index 6816358cafca8554bcce44346e94c9f6c1a6cf68..d91f0999e44f064d007586f49bf674823965519f 100644 (file)
@@ -63,6 +63,7 @@
 #include "stdbool.h"
 #include "getaddrsfromfd.h"
 #include "utils.h"
+#include "send_mail.h"
 
 static int addtohdr = false;
 static int prepmailinmem = 0;
@@ -354,151 +355,6 @@ int send_mail(int sockfd, const char *from, const char *to,
        return 0;
 }
 
-int initsmtp(int *sockfd, const char *relayhost, unsigned short port, const char *heloname)
-{
-       int retval = 0;
-       int try_ehlo = 1;
-       char *reply = NULL;
-
-       do {
-               init_sockfd(sockfd, relayhost, port);
-
-               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);
-                       free(reply);
-                       retval = MLMMJ_CONNECT;
-                       /* FIXME: Queue etc. */
-                       break;
-               }
-
-               if (try_ehlo) {
-                       write_ehlo(*sockfd, heloname);
-                       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) {
-                               free(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') {
-                               free(reply);
-                               /* Server doesn't understand EHLO, but gives a
-                                * broken response. Try with new connection.
-                                */
-                               endsmtp(sockfd);
-                               try_ehlo = 0;
-                               continue;
-                       }
-
-                       free(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.
-                        */
-                       free(reply);
-
-                       /* Try HELO on the same connection
-                        */
-               }
-
-               write_helo(*sockfd, heloname);
-               if((reply = checkwait_smtpreply(*sockfd, MLMMJ_HELO))
-                               == NULL) {
-                       /* EHLO successful don't try more */
-                       break;
-               }
-               if (try_ehlo) {
-                       free(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);
-               free(reply);
-               /* FIXME: quit and tell admin to configure
-                * correctly */
-               retval = MLMMJ_HELO;
-               break;
-
-       } while (1);
-
-       return retval;
-}
-
-int endsmtp(int *sockfd)
-{
-       int retval = 0;
-       char *reply = NULL;
-
-       if(*sockfd == -1)
-               return retval;
-       
-       write_quit(*sockfd);
-       reply = checkwait_smtpreply(*sockfd, MLMMJ_QUIT);
-       if(reply) {
-               printf("reply from quit: %s\n", reply);
-               log_error(LOG_ARGS, "Mailserver would not let us QUIT. "
-                         "We close the socket anyway though. "
-                         "Mailserver reply = [%s]", reply);
-               free(reply);
-               retval = MLMMJ_QUIT;
-       }
-
-       close(*sockfd);
-       *sockfd = -1;
-
-       return retval;
-}
-
 int send_mail_verp(int sockfd, struct strlist *addrs, char *mailmap,
                   size_t mailsize, const char *from,
                   const char *hdrs, const char *body, const char *verpextra)
diff --git a/src/send_mail.c b/src/send_mail.c
new file mode 100644 (file)
index 0000000..47d2c97
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2004, 2003, 2004 Mads Martin Joergensen <mmj at mmj.dk>
+ * Copyright (C) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "checkwait_smtpreply.h"
+#include "mail-functions.h"
+#include "send_mail.h"
+#include "log_error.h"
+#include "init_sockfd.h"
+#include "mlmmj.h"
+#include "xmalloc.h"
+
+int
+initsmtp(int *sockfd, const char *relayhost, unsigned short port, const char *heloname)
+{
+       int retval = 0;
+       int try_ehlo = 1;
+       char *reply = NULL;
+
+       do {
+               init_sockfd(sockfd, relayhost, port);
+
+               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);
+                       free(reply);
+                       retval = MLMMJ_CONNECT;
+                       /* FIXME: Queue etc. */
+                       break;
+               }
+
+               if (try_ehlo) {
+                       write_ehlo(*sockfd, heloname);
+                       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) {
+                               free(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') {
+                               free(reply);
+                               /* Server doesn't understand EHLO, but gives a
+                                * broken response. Try with new connection.
+                                */
+                               endsmtp(sockfd);
+                               try_ehlo = 0;
+                               continue;
+                       }
+
+                       free(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.
+                        */
+                       free(reply);
+
+                       /* Try HELO on the same connection
+                        */
+               }
+
+               write_helo(*sockfd, heloname);
+               if((reply = checkwait_smtpreply(*sockfd, MLMMJ_HELO))
+                               == NULL) {
+                       /* EHLO successful don't try more */
+                       break;
+               }
+               if (try_ehlo) {
+                       free(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);
+               free(reply);
+               /* FIXME: quit and tell admin to configure
+                * correctly */
+               retval = MLMMJ_HELO;
+               break;
+
+       } while (1);
+
+       return retval;
+}
+
+int
+endsmtp(int *sockfd)
+{
+       int retval = 0;
+       char *reply = NULL;
+
+       if(*sockfd == -1)
+               return retval;
+       
+       write_quit(*sockfd);
+       reply = checkwait_smtpreply(*sockfd, MLMMJ_QUIT);
+       if(reply) {
+               printf("reply from quit: %s\n", reply);
+               log_error(LOG_ARGS, "Mailserver would not let us QUIT. "
+                         "We close the socket anyway though. "
+                         "Mailserver reply = [%s]", reply);
+               free(reply);
+               retval = MLMMJ_QUIT;
+       }
+
+       close(*sockfd);
+       *sockfd = -1;
+
+       return retval;
+}
+
index d6239c65ed8704982d8266b37e800bc1fb0a36b2..7183588a0fe833299edda5ccd0cf70f324f32648 100644 (file)
@@ -47,6 +47,7 @@
 #include "init_sockfd.h"
 #include "checkwait_smtpreply.h"
 #include "find_email_adr.h"
+#include "send_mail.h"
 
 ATF_TC_WITHOUT_HEAD(random_int);
 ATF_TC_WITHOUT_HEAD(chomp);
@@ -78,6 +79,7 @@ ATF_TC_WITHOUT_HEAD(open_subscriber_directory);
 ATF_TC_WITHOUT_HEAD(unsubscribe);
 ATF_TC_WITHOUT_HEAD(genlistname);
 ATF_TC_WITHOUT_HEAD(genlistfqdn);
+ATF_TC_WITHOUT_HEAD(smtp);
 
 #ifndef NELEM
 #define NELEM(array)    (sizeof(array) / sizeof((array)[0]))
@@ -945,6 +947,86 @@ ATF_TC_BODY(genlistfqdn, tc)
        ATF_REQUIRE_STREQ(ret, "bla@meh");
 }
 
+ATF_TC_BODY(smtp, tc)
+{
+       int smtppipe[2];
+       char *reply;
+       ATF_REQUIRE(pipe(smtppipe) >= 0);
+       pid_t p = atf_utils_fork();
+       if (p == 0) {
+               dprintf(smtppipe[0], "220 me fake smtp\n");
+               /* EHLO */
+               reply = mygetline(smtppipe[0]);
+               dprintf(smtppipe[0],
+                   "250-hostname.net\n"
+                   "250-PIPELINEING\n"
+                   "250-SIZE 20480000\n"
+                   "250-ETRN\n"
+                   "250-STARTTLS\n"
+                   "250-ENHANCEDSTATUSCODES\n"
+                   "250-8BITMIME\n"
+                   "250-DSN\n"
+                   "250-SMTPUTF8\n"
+                   "250 CHUNKING\n");
+               /* HELO */
+               reply = mygetline(smtppipe[0]);
+               dprintf(smtppipe[0],
+                   "250-hostname.net\n");
+               /* MAIL FROM */
+               reply = mygetline(smtppipe[0]);
+               dprintf(smtppipe[0],
+                   "250 2.1.0 Ok\n");
+               /* RCPT TO */
+               reply = mygetline(smtppipe[0]);
+               dprintf(smtppipe[0],
+                   "250 2.1.0 Ok\n");
+               /* DATA */
+               reply = mygetline(smtppipe[0]);
+               dprintf(smtppipe[0],
+                   "354 Send message content; end with <CRLF>.<CRLF>\n");
+               /* DATA */
+               reply = mygetline(smtppipe[0]);
+               dprintf(smtppipe[0],
+                   "250 2.1.0 Ok\n");
+               /* QUIT */
+               reply = mygetline(smtppipe[0]);
+               dprintf(smtppipe[0],
+                   "221 2.0.0 Bye\n");
+               /* RSET */
+               reply = mygetline(smtppipe[0]);
+               dprintf(smtppipe[0],
+                   "250 2.0.0 Ok\n");
+               exit (0);
+       }
+       close(smtppipe[0]);
+       reply = checkwait_smtpreply(smtppipe[1], MLMMJ_CONNECT);
+       ATF_REQUIRE_EQ(reply, NULL);
+       write_ehlo(smtppipe[1], "plop");
+       reply = checkwait_smtpreply(smtppipe[1], MLMMJ_EHLO);
+       ATF_REQUIRE_EQ(reply, NULL);
+       write_helo(smtppipe[1], "plop");
+       reply = checkwait_smtpreply(smtppipe[1], MLMMJ_HELO);
+       ATF_REQUIRE_EQ(reply, NULL);
+       write_mail_from(smtppipe[1], "plop", NULL);
+       reply = checkwait_smtpreply(smtppipe[1], MLMMJ_FROM);
+       ATF_REQUIRE_EQ(reply, NULL);
+       write_rcpt_to(smtppipe[1], "plop");
+       reply = checkwait_smtpreply(smtppipe[1], MLMMJ_RCPTTO);
+       ATF_REQUIRE_EQ(reply, NULL);
+       write_data(smtppipe[1]);
+       reply = checkwait_smtpreply(smtppipe[1], MLMMJ_DATA);
+       ATF_REQUIRE_EQ(reply, NULL);
+       write_dot(smtppipe[1]);
+       reply = checkwait_smtpreply(smtppipe[1], MLMMJ_DOT);
+       ATF_REQUIRE_EQ(reply, NULL);
+       write_quit(smtppipe[1]);
+       reply = checkwait_smtpreply(smtppipe[1], MLMMJ_QUIT);
+       ATF_REQUIRE_EQ(reply, NULL);
+       write_rset(smtppipe[1]);
+       reply = checkwait_smtpreply(smtppipe[1], MLMMJ_RSET);
+       ATF_REQUIRE_EQ(reply, NULL);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
        ATF_TP_ADD_TC(tp, random_int);
@@ -977,6 +1059,7 @@ ATF_TP_ADD_TCS(tp)
        ATF_TP_ADD_TC(tp, unsubscribe);
        ATF_TP_ADD_TC(tp, genlistname);
        ATF_TP_ADD_TC(tp, genlistfqdn);
+       ATF_TP_ADD_TC(tp, smtp);
 
        return (atf_no_error());
 }