From 300083232cd87b89f6b1dedda64f124d1cb4d578 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Thu, 16 Jul 2026 20:03:43 +0200 Subject: [PATCH] Advertise only available mail capabilities --- src/agents/pop/CMakeLists.txt | 12 +++++ src/agents/pop/capabilities.c | 48 +++++++++++++++++++ src/agents/pop/capabilities.h | 11 +++++ src/agents/pop/pop3.c | 26 ++++++----- src/agents/pop/pop3.h | 2 - src/agents/pop/tests/capabilities-test.c | 31 ++++++++++++ src/agents/smtp/CMakeLists.txt | 12 +++++ src/agents/smtp/capabilities.c | 57 +++++++++++++++++++++++ src/agents/smtp/capabilities.h | 20 ++++++++ src/agents/smtp/smtpd.c | 50 +++++++++----------- src/agents/smtp/smtpd.h | 6 --- src/agents/smtp/tests/capabilities-test.c | 43 +++++++++++++++++ 12 files changed, 271 insertions(+), 47 deletions(-) create mode 100644 src/agents/pop/capabilities.c create mode 100644 src/agents/pop/capabilities.h create mode 100644 src/agents/pop/tests/capabilities-test.c create mode 100644 src/agents/smtp/capabilities.c create mode 100644 src/agents/smtp/capabilities.h create mode 100644 src/agents/smtp/tests/capabilities-test.c diff --git a/src/agents/pop/CMakeLists.txt b/src/agents/pop/CMakeLists.txt index b24f100..b69361e 100644 --- a/src/agents/pop/CMakeLists.txt +++ b/src/agents/pop/CMakeLists.txt @@ -1,5 +1,6 @@ add_executable(bongopop3 pop3.c + capabilities.c ) target_link_libraries(bongopop3 @@ -12,3 +13,14 @@ target_link_libraries(bongopop3 ) install(TARGETS bongopop3 DESTINATION ${SBIN_INSTALL_DIR}) + +if(BUILD_TESTING) + add_executable(pop3-capabilities-test + tests/capabilities-test.c + capabilities.c + ) + target_include_directories(pop3-capabilities-test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ) + add_test(NAME pop3-capabilities COMMAND pop3-capabilities-test) +endif() diff --git a/src/agents/pop/capabilities.c b/src/agents/pop/capabilities.c new file mode 100644 index 0000000..d7b4dd8 --- /dev/null +++ b/src/agents/pop/capabilities.c @@ -0,0 +1,48 @@ +#include "capabilities.h" + +#include +#include + +static int +Append(char *buffer, size_t buffer_size, size_t *length, + const char *format, ...) +{ + int written; + va_list args; + + if (*length >= buffer_size) return 0; + + va_start(args, format); + written = vsnprintf(buffer + *length, buffer_size - *length, format, args); + va_end(args); + if (written < 0 || (size_t)written >= buffer_size - *length) { + buffer[0] = '\0'; + return 0; + } + *length += (size_t)written; + return 1; +} + +size_t +POP3FormatCapabilities(char *buffer, size_t buffer_size, + int tls_active, int stls_available, + const char *sasl_mechanisms) +{ + size_t length = 0; + + if (!buffer || buffer_size == 0) return 0; + buffer[0] = '\0'; + + if (!Append(buffer, buffer_size, &length, + "+OK\r\nTOP\r\nRESP-CODES\r\nAUTH-RESP-CODE\r\n" + "PIPELINING\r\nEXPIRE NEVER\r\nUIDL\r\n") || + (tls_active && sasl_mechanisms && *sasl_mechanisms && + !Append(buffer, buffer_size, &length, "SASL %s\r\nUSER\r\n", + sasl_mechanisms)) || + (!tls_active && stls_available && + !Append(buffer, buffer_size, &length, "STLS\r\n")) || + !Append(buffer, buffer_size, &length, ".\r\n")) + return 0; + + return length; +} diff --git a/src/agents/pop/capabilities.h b/src/agents/pop/capabilities.h new file mode 100644 index 0000000..0255ad5 --- /dev/null +++ b/src/agents/pop/capabilities.h @@ -0,0 +1,11 @@ +#ifndef BONGO_POP3_CAPABILITIES_H +#define BONGO_POP3_CAPABILITIES_H + +#include + +/* Returns the number of bytes written, or 0 if the buffer was too small. */ +size_t POP3FormatCapabilities(char *buffer, size_t buffer_size, + int tls_active, int stls_available, + const char *sasl_mechanisms); + +#endif diff --git a/src/agents/pop/pop3.c b/src/agents/pop/pop3.c index 645bfcc..0fea94e 100644 --- a/src/agents/pop/pop3.c +++ b/src/agents/pop/pop3.c @@ -30,6 +30,7 @@ #include #include +#include "capabilities.h" #include #include #include @@ -905,22 +906,23 @@ static int POP3CommandCapa(void *param) { int ccode; + size_t capabilityLength; POP3Client *client = (POP3Client *)param; BongoSaslSession *session = NULL; const char *mechanisms = NULL; - ccode = ConnWrite(client->conn, "+OK\r\nTOP\r\nRESP-CODES\r\nAUTH-RESP-CODE\r\n" - "PIPELINING\r\nEXPIRE NEVER\r\nUIDL\r\n", - sizeof("+OK\r\nTOP\r\nRESP-CODES\r\nAUTH-RESP-CODE\r\n" - "PIPELINING\r\nEXPIRE NEVER\r\nUIDL\r\n") - 1); - if (ccode != -1 && client->conn->ssl.enable && - BongoSaslSessionCreate(&session, "pop", BongoGlobals.hostname, - NULL, NULL, 1, POP3SaslVerify, client) && - BongoSaslMechanisms(session, &mechanisms)) - ccode = ConnWriteF(client->conn, "SASL %s\r\nUSER\r\n", mechanisms); - if (ccode != -1 && !client->conn->ssl.enable) - ccode = ConnWrite(client->conn, "STLS\r\n", 6); - if (ccode != -1) ccode = ConnWrite(client->conn, ".\r\n", 3); + if (client->conn->ssl.enable) { + if (!BongoSaslSessionCreate(&session, "pop", BongoGlobals.hostname, + NULL, NULL, 1, POP3SaslVerify, client) || + !BongoSaslMechanisms(session, &mechanisms)) + mechanisms = NULL; + } + capabilityLength = POP3FormatCapabilities( + client->buffer, sizeof(client->buffer), client->conn->ssl.enable, + POP3.server.ssl.enable && POP3.server.ssl.context != NULL, mechanisms); + ccode = capabilityLength + ? ConnWrite(client->conn, client->buffer, capabilityLength) + : ConnWrite(client->conn, MSGERRINTERNAL, sizeof(MSGERRINTERNAL) - 1); BongoSaslSessionDestroy(&session); return(ccode); diff --git a/src/agents/pop/pop3.h b/src/agents/pop/pop3.h index b436017..08e2d1e 100644 --- a/src/agents/pop/pop3.h +++ b/src/agents/pop/pop3.h @@ -37,8 +37,6 @@ #define MSGOK "+OK\r\n" #define MSGOKHELLO "Bongo POP3 server ready\r\n" #define MSGOKBYE "signing off" -#define MSGCAPALIST_TLS "+OK\r\nTOP\r\nUSER\r\nRESP-CODES\r\nAUTH-RESP-CODE\r\nPIPELINING\r\nEXPIRE NEVER\r\nUIDL\r\n.\r\n" -#define MSGCAPALIST_STARTTLS "+OK\r\nTOP\r\nRESP-CODES\r\nAUTH-RESP-CODE\r\nPIPELINING\r\nEXPIRE NEVER\r\nUIDL\r\nSTLS\r\n.\r\n" #define MSGWRONGMETHOD "-ERR [AUTH] Unsupported method\r\n" #define MSGOKPASSWD "+OK Password required\r\n" #define MSGOKSTARTTLS "+OK Begin TLS negotiations\r\n" diff --git a/src/agents/pop/tests/capabilities-test.c b/src/agents/pop/tests/capabilities-test.c new file mode 100644 index 0000000..c7525e8 --- /dev/null +++ b/src/agents/pop/tests/capabilities-test.c @@ -0,0 +1,31 @@ +#include "capabilities.h" + +#include +#include + +int +main(void) +{ + char buffer[1024]; + char small[8]; + + assert(POP3FormatCapabilities(buffer, sizeof(buffer), 0, 0, NULL) > 0); + assert(!strstr(buffer, "STLS")); + assert(!strstr(buffer, "SASL")); + assert(!strstr(buffer, "USER")); + + assert(POP3FormatCapabilities(buffer, sizeof(buffer), 0, 1, NULL) > 0); + assert(strstr(buffer, "STLS\r\n")); + assert(!strstr(buffer, "SASL")); + + assert(POP3FormatCapabilities(buffer, sizeof(buffer), 1, 1, + "PLAIN LOGIN") > 0); + assert(!strstr(buffer, "STLS")); + assert(strstr(buffer, "SASL PLAIN LOGIN\r\n")); + assert(strstr(buffer, "USER\r\n")); + + assert(POP3FormatCapabilities(small, sizeof(small), 1, 1, + "PLAIN LOGIN") == 0); + assert(small[0] == '\0'); + return 0; +} diff --git a/src/agents/smtp/CMakeLists.txt b/src/agents/smtp/CMakeLists.txt index 6d6e3b5..654ae46 100644 --- a/src/agents/smtp/CMakeLists.txt +++ b/src/agents/smtp/CMakeLists.txt @@ -2,6 +2,7 @@ add_executable(bongosmtp smtpd.c + capabilities.c ) target_link_libraries(bongosmtp @@ -31,3 +32,14 @@ target_link_libraries(bongosmtpc install(TARGETS bongosmtp DESTINATION ${SBIN_INSTALL_DIR}) install(TARGETS bongosmtpc DESTINATION ${SBIN_INSTALL_DIR}) + +if(BUILD_TESTING) + add_executable(smtp-capabilities-test + tests/capabilities-test.c + capabilities.c + ) + target_include_directories(smtp-capabilities-test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ) + add_test(NAME smtp-capabilities COMMAND smtp-capabilities-test) +endif() diff --git a/src/agents/smtp/capabilities.c b/src/agents/smtp/capabilities.c new file mode 100644 index 0000000..97f1c34 --- /dev/null +++ b/src/agents/smtp/capabilities.c @@ -0,0 +1,57 @@ +#include "capabilities.h" + +#include +#include + +static int +Append(char *buffer, size_t buffer_size, size_t *length, + const char *format, ...) +{ + int written; + va_list args; + + if (*length >= buffer_size) return 0; + + va_start(args, format); + written = vsnprintf(buffer + *length, buffer_size - *length, format, args); + va_end(args); + if (written < 0 || (size_t)written >= buffer_size - *length) { + buffer[0] = '\0'; + return 0; + } + *length += (size_t)written; + return 1; +} + +size_t +SMTPFormatEhlo(char *buffer, size_t buffer_size, + const SMTPCapabilityOptions *options) +{ + size_t length = 0; + + if (!buffer || buffer_size == 0 || !options || !options->hostname) + return 0; + buffer[0] = '\0'; + + if (!Append(buffer, buffer_size, &length, "250-%s Pleased to meet you\r\n", + options->hostname) || + (options->etrn && + !Append(buffer, buffer_size, &length, "250-ETRN\r\n")) || + (options->starttls && + !Append(buffer, buffer_size, &length, "250-STARTTLS\r\n")) || + (options->auth && + !Append(buffer, buffer_size, &length, "250-AUTH PLAIN LOGIN\r\n")) || + (options->smtputf8 && + !Append(buffer, buffer_size, &length, "250-SMTPUTF8\r\n")) || + !Append(buffer, buffer_size, &length, "250-HELP\r\n") || + (options->expn && + !Append(buffer, buffer_size, &length, "250-EXPN\r\n")) || + !Append(buffer, buffer_size, &length, + "250-PIPELINING\r\n250-8BITMIME\r\n250-DSN\r\n250 SIZE") || + (options->message_limit > 0 && + !Append(buffer, buffer_size, &length, " %d", options->message_limit)) || + !Append(buffer, buffer_size, &length, "\r\n")) + return 0; + + return length; +} diff --git a/src/agents/smtp/capabilities.h b/src/agents/smtp/capabilities.h new file mode 100644 index 0000000..ae874ce --- /dev/null +++ b/src/agents/smtp/capabilities.h @@ -0,0 +1,20 @@ +#ifndef BONGO_SMTP_CAPABILITIES_H +#define BONGO_SMTP_CAPABILITIES_H + +#include + +typedef struct { + const char *hostname; + int message_limit; + int etrn; + int starttls; + int auth; + int smtputf8; + int expn; +} SMTPCapabilityOptions; + +/* Returns the number of bytes written, or 0 if the buffer was too small. */ +size_t SMTPFormatEhlo(char *buffer, size_t buffer_size, + const SMTPCapabilityOptions *options); + +#endif diff --git a/src/agents/smtp/smtpd.c b/src/agents/smtp/smtpd.c index da8a228..594dbd2 100644 --- a/src/agents/smtp/smtpd.c +++ b/src/agents/smtp/smtpd.c @@ -46,6 +46,7 @@ #include "mailauth.h" #include "smtpd.h" #include "external_accounts.h" +#include "capabilities.h" struct { int port; @@ -1911,35 +1912,30 @@ HandleConnection (void *param) switch (toupper (Client->Command[1])) { case 'H':{ /* EHLO */ if (Client->State == STATE_FRESH) { + SMTPCapabilityOptions capabilities; + size_t answerLength; + XplRWReadLockAcquire (&ConfigLock); - if (SMTP.message_limit > 0) { - snprintf (Answer, sizeof (Answer), - "250-%s %s\r\n%s%s%s%s%s %d\r\n", - BongoGlobals.hostname, MSG250HELLO, - SMTP.accept_etrn ? MSG250ETRN : "", - ((SMTP.allow_client_ssl || Client->InternalRelay || Client->Submission) - && (Client->InternalRelay ? InternalSSLContext != NULL : TRUE) - && !Client->client.conn->ssl.enable) ? MSG250TLS : "", - (AllowAuth == TRUE && - Client->client.conn->ssl.enable) ? MSG250AUTH : "", - SMTP.smtp_utf8_enabled ? MSG250SMTPUTF8 : "", - MSG250EHLO, SMTP.message_limit); - } - else { - snprintf (Answer, sizeof (Answer), - "250-%s %s\r\n%s%s%s%s%s\r\n", - BongoGlobals.hostname, MSG250HELLO, - SMTP.accept_etrn ? MSG250ETRN : "", - ((SMTP.allow_client_ssl || Client->InternalRelay || Client->Submission) - && (Client->InternalRelay ? InternalSSLContext != NULL : TRUE) - && !Client->client.conn->ssl.enable) ? MSG250TLS : "", - (AllowAuth == TRUE && - Client->client.conn->ssl.enable) ? MSG250AUTH : "", - SMTP.smtp_utf8_enabled ? MSG250SMTPUTF8 : "", - MSG250EHLO); - } + capabilities.hostname = BongoGlobals.hostname; + capabilities.message_limit = SMTP.message_limit; + capabilities.etrn = SMTP.accept_etrn; + capabilities.starttls = + (SMTP.allow_client_ssl || Client->InternalRelay || Client->Submission) && + (Client->InternalRelay ? InternalSSLContext != NULL : SSLContext != NULL) && + !Client->client.conn->ssl.enable; + capabilities.auth = AllowAuth == TRUE && + Client->client.conn->ssl.enable; + capabilities.smtputf8 = SMTP.smtp_utf8_enabled; + capabilities.expn = SMTP.allow_expn; + answerLength = SMTPFormatEhlo(Answer, sizeof(Answer), + &capabilities); XplRWReadLockRelease (&ConfigLock); - ConnWrite (Client->client.conn, Answer, strlen (Answer)); + if (!answerLength) { + ConnWrite(Client->client.conn, MSG451INTERNALERR, + MSG451INTERNALERR_LEN); + break; + } + ConnWrite (Client->client.conn, Answer, answerLength); snprintf(Client->RemoteHost, sizeof(Client->RemoteHost), "%s", Client->Command + 5); Client->State = STATE_HELO; diff --git a/src/agents/smtp/smtpd.h b/src/agents/smtp/smtpd.h index 65bb3c1..457d3e7 100755 --- a/src/agents/smtp/smtpd.h +++ b/src/agents/smtp/smtpd.h @@ -74,12 +74,6 @@ #define MSG250HELLO "Pleased to meet you" #define MSG250HELLO_LEN 19 -#define MSG250ETRN "250-ETRN\r\n" -#define MSG250TLS "250-STARTTLS\r\n" -#define MSG250AUTH "250-AUTH PLAIN LOGIN\r\n" -#define MSG250SMTPUTF8 "250-SMTPUTF8\r\n" -#define MSG250EHLO "250-HELP\r\n250-EXPN\r\n250-PIPELINING\r\n250-8BITMIME\r\n250-DSN\r\n250 SIZE" - #define MSG251NOMESSAGES "251 OK No messages waiting for you\r\n" #define MSG251NOMESSAGES_LEN 36 diff --git a/src/agents/smtp/tests/capabilities-test.c b/src/agents/smtp/tests/capabilities-test.c new file mode 100644 index 0000000..b65ea3f --- /dev/null +++ b/src/agents/smtp/tests/capabilities-test.c @@ -0,0 +1,43 @@ +#include "capabilities.h" + +#include +#include + +int +main(void) +{ + char buffer[1024]; + char small[8]; + SMTPCapabilityOptions options = { + "mail.example.test", 52428800, 0, 0, 0, 0, 0 + }; + + assert(SMTPFormatEhlo(buffer, sizeof(buffer), &options) > 0); + assert(strstr(buffer, "250-mail.example.test Pleased to meet you\r\n")); + assert(strstr(buffer, "250 SIZE 52428800\r\n")); + assert(!strstr(buffer, "EXPN")); + assert(!strstr(buffer, "STARTTLS")); + assert(!strstr(buffer, "AUTH")); + assert(!strstr(buffer, "SMTPUTF8")); + + options.etrn = 1; + options.starttls = 1; + options.auth = 1; + options.smtputf8 = 1; + options.expn = 1; + assert(SMTPFormatEhlo(buffer, sizeof(buffer), &options) > 0); + assert(strstr(buffer, "250-ETRN\r\n")); + assert(strstr(buffer, "250-STARTTLS\r\n")); + assert(strstr(buffer, "250-AUTH PLAIN LOGIN\r\n")); + assert(strstr(buffer, "250-SMTPUTF8\r\n")); + assert(strstr(buffer, "250-EXPN\r\n")); + + options.message_limit = 0; + assert(SMTPFormatEhlo(buffer, sizeof(buffer), &options) > 0); + assert(strstr(buffer, "250 SIZE\r\n")); + assert(!strstr(buffer, "250 SIZE ")); + + assert(SMTPFormatEhlo(small, sizeof(small), &options) == 0); + assert(small[0] == '\0'); + return 0; +}