From: Baptiste Daroussin Date: Mon, 26 Dec 2022 16:26:35 +0000 (+0100) Subject: Isolate smtp code to allow reuse and add test for it X-Git-Tag: RELEASE_1_4_0_a2~68 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d3ba6abee4ed1f8f4fb5da50d40c8b263ea317c1;p=thirdparty%2Fmlmmj.git Isolate smtp code to allow reuse and add test for it --- diff --git a/include/send_mail.h b/include/send_mail.h new file mode 100644 index 00000000..060981e7 --- /dev/null +++ b/include/send_mail.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2004, 2003, 2004 Mads Martin Joergensen + * Copyright (C) 2022 Baptiste Daroussin + * + * 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); diff --git a/src/Makefile.am b/src/Makefile.am index b9281814..f0877591 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/mlmmj-send.c b/src/mlmmj-send.c index 6816358c..d91f0999 100644 --- a/src/mlmmj-send.c +++ b/src/mlmmj-send.c @@ -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 index 00000000..47d2c979 --- /dev/null +++ b/src/send_mail.c @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2004, 2003, 2004 Mads Martin Joergensen + * Copyright (C) 2022 Baptiste Daroussin + * + * 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 +#include + +#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; +} + diff --git a/tests/mlmmj.c b/tests/mlmmj.c index d6239c65..7183588a 100644 --- a/tests/mlmmj.c +++ b/tests/mlmmj.c @@ -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 .\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()); }