]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-smtp: client: Avoid sending an XCLIENT command longer than 512 bytes.
authorStephan Bosch <stephan.bosch@dovecot.fi>
Sat, 26 May 2018 12:49:01 +0000 (14:49 +0200)
committerStephan Bosch <stephan.bosch@dovecot.fi>
Sun, 27 May 2018 10:30:42 +0000 (12:30 +0200)
Send several separate XCLIENT commands instead. This way, it complies with the
base SMTP line length limit and with Postfix' original specification for the
XCLIENT command.

With the XCLIENT fields that Dovecot currently uses, this is very unlikely to
happen. Still, this needs to be fixed to make things reliable.

src/lib-smtp/smtp-client-connection.c
src/lib-smtp/smtp-client-private.h

index 88cfe9ce9572fdcfc81c466a0feb4d98ba2dd20e..e82b9d67e11fcea4ffdf40da426a58ab87374d20 100644 (file)
@@ -606,6 +606,8 @@ smtp_client_connection_xclient_cb(const struct smtp_reply *reply,
                        smtp_reply_log(reply));
        }
 
+       i_assert(conn->xclient_replies_expected > 0);
+
        if (reply->status == 421) {
                smtp_client_connection_fail_reply(conn, reply);
                return;
@@ -615,16 +617,79 @@ smtp_client_connection_xclient_cb(const struct smtp_reply *reply,
 
        if (conn->to_connect != NULL)
                timeout_reset(conn->to_connect);
-       smtp_client_connection_handshake(conn);
+       if (--conn->xclient_replies_expected == 0)
+               smtp_client_connection_handshake(conn);
+}
+
+static void
+smtp_client_connection_xclient_submit(struct smtp_client_connection *conn,
+                                     const char *cmdstr)
+{
+       struct smtp_client_command *cmd;
+       enum smtp_client_command_flags flags;
+
+       smtp_client_connection_debug(conn,
+               "Sending XCLIENT handshake");
+
+       flags = SMTP_CLIENT_COMMAND_FLAG_PRELOGIN |
+               SMTP_CLIENT_COMMAND_FLAG_PRIORITY;
+
+       cmd = smtp_client_command_new(conn, flags,
+               smtp_client_connection_xclient_cb, conn);
+       smtp_client_command_write(cmd, cmdstr);
+       smtp_client_command_submit(cmd);
+
+       conn->xclient_replies_expected++;
+}
+
+static void
+smtp_client_connection_xclient_add(struct smtp_client_connection *conn,
+                                  string_t *str, size_t offset,
+                                  const char *field, const char *value)
+{
+       size_t prev_offset = str_len(str);
+       const char *new_field;
+
+       str_append_c(str, ' ');
+       str_append(str, field);
+       str_append_c(str, '=');
+       smtp_xtext_encode_cstr(str, value);
+
+       if (str_len(str) <= SMTP_CLIENT_BASE_LINE_LENGTH_LIMIT)
+               return;
+               
+       /* preserve field we just added */
+       new_field = t_strdup(str_c(str) + prev_offset);
+
+       /* revert to previous position */
+       str_truncate(str, prev_offset);
+
+       /* send XCLIENT command */
+       smtp_client_connection_xclient_submit(conn, str_c(str));
+
+       /* start next XCLIENT command with new field */
+       str_truncate(str, offset);
+       str_append(str, new_field);
+}
+
+static void ATTR_FORMAT(5, 6)
+smtp_client_connection_xclient_addf(struct smtp_client_connection *conn,
+                                   string_t *str, size_t offset,
+                                   const char *field, const char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+       smtp_client_connection_xclient_add(conn, str, offset, field,
+                                          t_strdup_vprintf(format, args));
+       va_end(args);
 }
 
 bool smtp_client_connection_send_xclient(struct smtp_client_connection *conn,
                                         struct smtp_proxy_data *xclient)
 {
        const char **xclient_args = conn->cap_xclient_args;
-       struct smtp_client_command *cmd;
-       enum smtp_client_command_flags flags;
-       unsigned int empty_len;
+       size_t offset;
        string_t *str;
 
        if (!conn->set.peer_trusted)
@@ -633,73 +698,102 @@ bool smtp_client_connection_send_xclient(struct smtp_client_connection *conn,
            conn->cap_xclient_args == NULL)
                return TRUE;
 
+       i_assert(conn->xclient_replies_expected == 0);
+
+       /* http://www.postfix.org/XCLIENT_README.html:
+
+          The client must not send XCLIENT commands that exceed the 512
+          character limit for SMTP commands. To avoid exceeding the limit the
+          client should send the information in multiple XCLIENT commands; for
+          example, send NAME and ADDR last, after HELO and PROTO. Once ADDR is
+          sent, the client is usually no longer authorized to send XCLIENT
+          commands.
+        */
+
        str = t_str_new(64);
        str_append(str, "XCLIENT");
-       empty_len = str_len(str);
+       offset = str_len(str);
 
-       if (xclient->source_ip.family != 0 &&
-           str_array_icase_find(xclient_args, "ADDR")) {
-               /* Older versions of Dovecot LMTP don't quite follow Postfix'
-                  specification of the XCLIENT command regarding IPv6
-                  addresses: the "IPV6:" prefix is omitted. For now, we
-                  maintain this deviation for LMTP. Newer versions of Dovecot
-                  LMTP can work with or without the prefix. */
-               if (conn->protocol != SMTP_PROTOCOL_LMTP &&
-                       xclient->source_ip.family == AF_INET6)
-                       str_append(str, " ADDR=IPV6:");
-               else
-                       str_append(str, " ADDR=");
-               str_append(str, net_ip2addr(&xclient->source_ip));
-       }
-       if (xclient->source_port != 0 &&
-           str_array_icase_find(xclient_args, "PORT"))
-               str_printfa(str, " PORT=%u", xclient->source_port);
+       /* HELO */
        if (xclient->helo != NULL &&
            str_array_icase_find(xclient_args, "HELO")) {
-               str_append(str, " HELO=");
-               smtp_xtext_encode_cstr(str, xclient->helo);
+               smtp_client_connection_xclient_add(conn, str, offset,
+                                                  "HELO", xclient->helo);
        }
+
+       /* PROTO */
        if (str_array_icase_find(xclient_args, "PROTO")) {
                switch (xclient->proto) {
                case SMTP_PROXY_PROTOCOL_SMTP:
-                       str_printfa(str, " PROTO=SMTP");
+                       smtp_client_connection_xclient_add(conn, str, offset,
+                                                          "PROTO", "SMTP");
                        break;
                case SMTP_PROXY_PROTOCOL_ESMTP:
-                       str_printfa(str, " PROTO=ESMTP");
+                       smtp_client_connection_xclient_add(conn, str, offset,
+                                                          "PROTO", "ESMTP");
                        break;
                case SMTP_PROXY_PROTOCOL_LMTP:
-                       str_printfa(str, " PROTO=LMTP");
+                       smtp_client_connection_xclient_add(conn, str, offset,
+                                                          "PROTO", "LMTP");
                        break;
                default:
                        break;
                }
        }
+
+       /* LOGIN */
        if (xclient->login != NULL &&
            str_array_icase_find(xclient_args, "LOGIN")) {
-               str_append(str, " LOGIN=");
-               smtp_xtext_encode_cstr(str, xclient->login);
+               smtp_client_connection_xclient_add(conn, str, offset,
+                                                  "LOGIN", xclient->login);
        }
+
+       /* TTL */
        if (xclient->ttl_plus_1 > 0 &&
-           str_array_icase_find(xclient_args, "TTL"))
-               str_printfa(str, " TTL=%u", xclient->ttl_plus_1-1);
+           str_array_icase_find(xclient_args, "TTL")) {
+               smtp_client_connection_xclient_addf(conn, str, offset,
+                                                   "TTL", "%u",
+                                                   xclient->ttl_plus_1-1);
+       }
+
+       /* TIMEOUT */
        if (xclient->timeout_secs > 0 &&
-           str_array_icase_find(xclient_args, "TIMEOUT"))
-               str_printfa(str, " TIMEOUT=%u", xclient->timeout_secs);
+           str_array_icase_find(xclient_args, "TIMEOUT")) {
+               smtp_client_connection_xclient_addf(conn, str, offset,
+                                                   "TIMEOUT", "%u",
+                                                   xclient->timeout_secs);
+       }
 
-       if (str_len(str) <= empty_len)
-               return TRUE;
+       /* PORT */
+       if (xclient->source_port != 0 &&
+           str_array_icase_find(xclient_args, "PORT")) {
+               smtp_client_connection_xclient_addf(conn, str, offset,
+                                                   "PORT", "%u",
+                                                   xclient->source_port);
+       }
 
-       smtp_client_connection_debug(conn,
-               "Sending XCLIENT handshake");
+       /* ADDR */
+       if (xclient->source_ip.family != 0 &&
+           str_array_icase_find(xclient_args, "ADDR")) {
+               const char *addr = net_ip2addr(&xclient->source_ip);
 
-       flags = SMTP_CLIENT_COMMAND_FLAG_PRELOGIN |
-               SMTP_CLIENT_COMMAND_FLAG_PRIORITY;
+               /* Older versions of Dovecot LMTP don't quite follow Postfix'
+                  specification of the XCLIENT command regarding IPv6
+                  addresses: the "IPV6:" prefix is omitted. For now, we
+                  maintain this deviation for LMTP. Newer versions of Dovecot
+                  LMTP can work with or without the prefix. */
+               if (conn->protocol != SMTP_PROTOCOL_LMTP &&
+                       xclient->source_ip.family == AF_INET6)
+                       addr = t_strconcat("IPV6:", addr, NULL);
+               smtp_client_connection_xclient_add(conn, str, offset,
+                                                  "ADDR", addr);
+       }
 
-       cmd = smtp_client_command_new(conn, flags,
-               smtp_client_connection_xclient_cb, conn);
-       smtp_client_command_write(cmd, str_c(str));
-       smtp_client_command_submit(cmd);
-       return FALSE;
+       /* final XCLIENT command */
+       if (str_len(str) > offset)
+               smtp_client_connection_xclient_submit(conn, str_c(str));
+
+       return (conn->xclient_replies_expected == 0);
 }
 
 static bool
@@ -1545,6 +1639,9 @@ void smtp_client_connection_connect(struct smtp_client_connection *conn,
                i_assert(login_callback == NULL);
                return;
        }
+
+       conn->xclient_replies_expected = 0;
+
        i_assert(conn->login_callback == NULL);
        conn->login_callback = login_callback;
        conn->login_context = login_context;
index f70b2fd6848301c1319bd4d8f1a45b76a8930902..ec7a91b6ad3e862b3755919423c9aea8bb3da271 100644 (file)
@@ -10,6 +10,7 @@
 #include "smtp-client-transaction.h"
 #include "smtp-client-connection.h"
 
+#define SMTP_CLIENT_BASE_LINE_LENGTH_LIMIT 512
 #define SMTP_CLIENT_DATA_CHUNK_SIZE IO_BLOCK_SIZE
 
 struct smtp_client_command {
@@ -130,6 +131,7 @@ struct smtp_client_connection {
 
        struct smtp_reply_parser *reply_parser;
        struct smtp_reply reply;
+       unsigned int xclient_replies_expected;
 
        struct dns_lookup *dns_lookup;
        struct dsasl_client *sasl_client;