Skip to content

Commit 71aff94

Browse files
committed
mod_smtp_delivery_external: Fully read multiline responses on failure.
If the transaction fails, and we got the beginning of a multiline response, read the rest of the response so that we can include it in its entirety in the bounce message, if needed.
1 parent 7e29335 commit 71aff94

1 file changed

Lines changed: 65 additions & 1 deletion

File tree

modules/mod_smtp_delivery_external.c

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,70 @@ static int __attribute__ ((nonnull (2, 3, 9, 16))) try_send(struct smtp_session
764764

765765
cleanup:
766766
free_if(saslstr);
767+
if (strlen(buf) >= 4 && buf[3] == '-' && (buf[0] == '4' || buf[0] == '5')) {
768+
/* If we got the beginning of a multiline response when the error was received,
769+
* keep reading so we can get the entire message, in case we send a bounce,
770+
* as we'll want to include the whole response. */
771+
772+
/* Temporary buf for reading full response
773+
* There's no inherent guarantee that a multiline response in its entirety would
774+
* fit in the max space allocated for one line, but it's very unlikely that it wouldn't.
775+
* We use the same size so that at the end, we can just copy it into the readline buffer that was used,
776+
* so if truncation occurs, so be it. */
777+
char respbuf[SMTP_MAX_BUFSIZE] = "";
778+
char *respptr = respbuf;
779+
size_t respbuflen = sizeof(respbuf);
780+
size_t bytesleft = respbuflen;
781+
/* If we aborted due to a nonfinal response, before we quit, continue reading the rest
782+
* of the response, since we probably want the whole thing to include in our bounce message. */
783+
SAFE_FAST_APPEND(respbuf, respbuflen, respptr, bytesleft, "%s", buf);
784+
for (;;) {
785+
char *continuation;
786+
int e1, e2, e3;
787+
char emsg[3];
788+
ssize_t rres = bbs_readline(smtpclient.client.rfd, &smtpclient.client.rldata, "\r\n", SEC_MS(1));
789+
if (rres < 0) {
790+
bbs_warning("Unexpected end of response (so far: '%s')\n", respbuf);
791+
break;
792+
}
793+
/* Skip the first four characters since this is a continuation of the response */
794+
if (rres < 4) {
795+
bbs_warning("Unexpected end of response: '%s' (so far: '%s')\n", buf, respbuf);
796+
break;
797+
}
798+
continuation = buf + 3; /* That will skip e.g. "550-" or "550 " */
799+
/* ... but if we have something like "550-5.1.1 text..." or "550 5.1.1 text...", then we need to skip until after the next space
800+
* We don't assume that we necessarily have an enhanced status code, this accomodates either case. */
801+
while (*continuation != ' ') {
802+
continuation++;
803+
}
804+
if (!*continuation) {
805+
break;
806+
}
807+
continuation++; /* Skip the space, what lies afterwards is the message continuation */
808+
/* This handles the case for something like 550 5.1.1 text... we need to skip the enhanced status code.
809+
* However, note that the enhanced status codes could be variable length (not always X.Y.Z, could be X.Y.ZZ, etc. )
810+
* Hence, if we see something here that looks like an enhanced status code (which can only happen for the last line),
811+
* skip it. */
812+
if (buf[3] == ' ' && sscanf(continuation, "%d.%d.%d %1s", &e1, &e2, &e3, emsg) == 4) {
813+
/* Skip until after the next space, again */
814+
while (*continuation != ' ') {
815+
continuation++;
816+
}
817+
continuation++; /* We know this is valid since sscanf already parsed the string */
818+
}
819+
SAFE_FAST_APPEND(respbuf, respbuflen, respptr, bytesleft, "%s", continuation); /* automatically adds space inbetween */
820+
if (buf[3] != '-') {
821+
/* Response is done */
822+
bbs_debug(6, "Full multi-line response: '%s'\n", respbuf);
823+
break;
824+
}
825+
}
826+
/* Afterwards, copy the ENTIRE response back into the readline buffer.
827+
* Yes, this will clobber it since it'll screw up its bookkeeping, but we are done with it at this point.
828+
* This allows us to conveniently shove the full response into the buffer in which the response is already expected. */
829+
strcpy(buf, respbuf); /* Safe, because the buffers are the same size */
830+
}
767831
if (res > 0) {
768832
bbs_smtp_client_send(&smtpclient, "QUIT\r\n");
769833
}
@@ -1399,7 +1463,7 @@ static int skip_qfile(struct mailq_run *qrun, struct mailq_file *mqf, const char
13991463
static int process_queue_file(struct mailq_run *qrun, struct mailq_file *mqf)
14001464
{
14011465
int res = -1;
1402-
char buf[256] = "";
1466+
char buf[SMTP_MAX_BUFSIZE] = ""; /* Long enough to store a full response line */
14031467
struct stringlist *static_routes;
14041468
struct smtp_tx_data tx;
14051469
time_t message_age;

0 commit comments

Comments
 (0)