diff --git a/src/agents/smtp/protocol.c b/src/agents/smtp/protocol.c index f25532b..5a27ca6 100644 --- a/src/agents/smtp/protocol.c +++ b/src/agents/smtp/protocol.c @@ -26,6 +26,8 @@ #include #include +#include + typedef struct { const char *name; SMTPCommand command; @@ -793,6 +795,44 @@ SMTPFormatDsnRcptOptions(int notify_explicit, return 1; } +int +SMTPDeliveryResultFromReply(unsigned int reply_code, + const char *reply_text) +{ + const char *enhanced = reply_text; + + if (reply_code / 100U == 2U) return DELIVER_SUCCESS; + if (reply_code / 100U == 4U) return DELIVER_TRY_LATER; + if (reply_code / 100U != 5U) return DELIVER_TRY_LATER; + if (enhanced == NULL) return DELIVER_FAILURE; + if (isdigit((unsigned char)enhanced[0]) && + isdigit((unsigned char)enhanced[1]) && + isdigit((unsigned char)enhanced[2]) && + (enhanced[3] == ' ' || enhanced[3] == '-')) + enhanced += 4; + while (*enhanced == ' ' || *enhanced == '\t') enhanced++; + if (enhanced[0] != '5' || enhanced[1] != '.' || + !isdigit((unsigned char)enhanced[2]) || enhanced[3] != '.' || + !isdigit((unsigned char)enhanced[4])) + return DELIVER_FAILURE; + if (enhanced[2] == '1' && enhanced[4] == '1') + return DELIVER_USER_UNKNOWN; + if (enhanced[2] == '1' && enhanced[4] == '3') + return DELIVER_BOGUS_NAME; + if (enhanced[2] == '2' && enhanced[4] == '2') + return DELIVER_QUOTA_EXCEEDED; + if (enhanced[2] == '4' && enhanced[4] == '1') + return DELIVER_REFUSED; + if (enhanced[2] == '4' && enhanced[4] == '4') + return DELIVER_HOST_UNKNOWN; + if (enhanced[2] == '4' && enhanced[4] == '6') + return DELIVER_HOPCOUNT_EXCEEDED; + if (enhanced[2] == '4' && enhanced[4] == '7') + return DELIVER_TIMEOUT; + if (enhanced[2] == '7') return DELIVER_BLOCKED; + return DELIVER_FAILURE; +} + static int OrcptValid(const char *value, size_t length, int smtp_utf8) { diff --git a/src/agents/smtp/protocol.h b/src/agents/smtp/protocol.h index 5f0f773..cb1030a 100644 --- a/src/agents/smtp/protocol.h +++ b/src/agents/smtp/protocol.h @@ -115,6 +115,8 @@ int SMTPFormatDsnRcptOptions(int notify_explicit, const char *original_recipient, int smtp_utf8, char *options, size_t options_size); +int SMTPDeliveryResultFromReply(unsigned int reply_code, + const char *reply_text); int SMTPRequireTlsHasAuthenticatedPolicy(int mx_dnssec_authenticated, int dane_authenticated, int mta_sts_enforced, diff --git a/src/agents/smtp/smtpc.c b/src/agents/smtp/smtpc.c index bbec3fd..d4e8cc5 100755 --- a/src/agents/smtp/smtpc.c +++ b/src/agents/smtp/smtpc.c @@ -23,6 +23,7 @@ #include +#include #include #include #include @@ -908,6 +909,34 @@ FormatDsnOptions(const SMTPClient *queue, const RecipStruct *recipient, rcpt_options, rcpt_options_size); } +typedef struct { + char reply[CONN_BUFSIZE + 1U]; +} SMTPRelayTrace; + +static int +CaptureRelayReply(CURL *curl, curl_infotype type, char *data, + size_t size, void *user_data) +{ + SMTPRelayTrace *trace = user_data; + size_t length; + + (void)curl; + if (trace == NULL || type != CURLINFO_HEADER_IN || size < 4U || + !isdigit((unsigned char)data[0]) || + !isdigit((unsigned char)data[1]) || + !isdigit((unsigned char)data[2]) || + (data[3] != ' ' && data[3] != '-')) + return 0; + length = size < sizeof(trace->reply) - 1U + ? size : sizeof(trace->reply) - 1U; + while (length > 0U && + (data[length - 1U] == '\r' || data[length - 1U] == '\n')) + length--; + memcpy(trace->reply, data, length); + trace->reply[length] = '\0'; + return 0; +} + static BOOL DeliverRelay(SMTPClient *queue, RecipStruct *recipient, const SMTPRelayRoute *route) @@ -932,7 +961,9 @@ DeliverRelay(SMTPClient *queue, RecipStruct *recipient, long response = 0; BOOL success = FALSE; BongoMailAuthStatus senderStatus; + SMTPRelayTrace relayTrace; + memset(&relayTrace, 0, sizeof(relayTrace)); recipient->Result = DELIVER_TRY_LATER; if (route == NULL || route->host == NULL || route->host[0] == '\0' || route->port < 1 || route->port > 65535 || @@ -1078,13 +1109,17 @@ DeliverRelay(SMTPClient *queue, RecipStruct *recipient, curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 300L); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlError); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, CaptureRelayReply); + curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &relayTrace); code = curl_easy_perform(curl); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); if (code == CURLE_OK && response >= 200 && response < 300) { recipient->Result = DELIVER_SUCCESS; success = TRUE; } else if (response >= 500 && code != CURLE_LOGIN_DENIED) { - recipient->Result = DELIVER_FAILURE; + recipient->Result = SMTPDeliveryResultFromReply( + (unsigned int)response, relayTrace.reply); } Log(success ? LOG_INFO : LOG_WARN, "%s %s returned %ld (%s) for message %s to %s", @@ -1771,8 +1806,8 @@ beginConversation: if (!ReadRemoteSMTPReply(Remote, &ReplyCode, NULL, NULL) || ReplyCode != 250U) { - Recip->Result = ReplyCode / 100U == 5U - ? DELIVER_FAILURE : DELIVER_TRY_LATER; + Recip->Result = SMTPDeliveryResultFromReply( + ReplyCode, Remote->line); goto finalization; } @@ -1798,9 +1833,8 @@ beginConversation: * 4xx are temporary failures and should be re-attempted * 5xx are fatal errors */ if (ReplyCode / 100U == 5U) { - Recip->Result = DELIVER_FAILURE; - - /* TODO: am i supposed to bounce the mail here? */ + Recip->Result = SMTPDeliveryResultFromReply( + ReplyCode, Remote->line); goto finalization; } else if (ReplyCode / 100U != 2U) { /* all other errors will be re-tried later */ @@ -1815,8 +1849,8 @@ beginConversation: if (!ReadRemoteSMTPReply(Remote, &ReplyCode, NULL, NULL) || ReplyCode != 354U) { - Recip->Result = ReplyCode / 100U == 5U - ? DELIVER_FAILURE : DELIVER_TRY_LATER; + Recip->Result = SMTPDeliveryResultFromReply( + ReplyCode, Remote->line); goto finalization; } diff --git a/src/agents/smtp/tests/protocol-test.c b/src/agents/smtp/tests/protocol-test.c index 2d82ed2..b9f9d1b 100644 --- a/src/agents/smtp/tests/protocol-test.c +++ b/src/agents/smtp/tests/protocol-test.c @@ -24,6 +24,8 @@ #include #include +#include + static void TestCommands(void) { @@ -399,6 +401,19 @@ TestDsnOptions(void) assert(!strcmp(options, "")); assert(!SMTPFormatDsnRcptOptions( 1, SMTP_NOTIFY_NEVER, NULL, 0, options, sizeof(options))); + + assert(SMTPDeliveryResultFromReply( + 250U, "250 2.1.5 Recipient accepted") == DELIVER_SUCCESS); + assert(SMTPDeliveryResultFromReply( + 451U, "451 4.3.0 Try later") == DELIVER_TRY_LATER); + assert(SMTPDeliveryResultFromReply( + 550U, "550 5.1.1 No such user") == DELIVER_USER_UNKNOWN); + assert(SMTPDeliveryResultFromReply( + 552U, "5.2.2 Mailbox full") == DELIVER_QUOTA_EXCEEDED); + assert(SMTPDeliveryResultFromReply( + 550U, "550 5.7.1 Policy rejection") == DELIVER_BLOCKED); + assert(SMTPDeliveryResultFromReply( + 550U, "550 Rejected") == DELIVER_FAILURE); } int