]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Implemented SMTP submission proxy service.
authorStephan Bosch <stephan.bosch@dovecot.fi>
Sat, 11 Nov 2017 13:20:59 +0000 (14:20 +0100)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Mon, 11 Dec 2017 13:44:18 +0000 (15:44 +0200)
35 files changed:
.gitignore
TODO
configure.ac
doc/example-config/conf.d/10-director.conf
doc/example-config/conf.d/10-master.conf
doc/example-config/conf.d/10-ssl.conf
doc/example-config/conf.d/20-submission.conf [new file with mode: 0644]
doc/example-config/dovecot.conf
src/Makefile.am
src/submission-login/Makefile.am [new file with mode: 0644]
src/submission-login/client-authenticate.c [new file with mode: 0644]
src/submission-login/client-authenticate.h [new file with mode: 0644]
src/submission-login/client.c [new file with mode: 0644]
src/submission-login/client.h [new file with mode: 0644]
src/submission-login/submission-login-settings.c [new file with mode: 0644]
src/submission-login/submission-login-settings.h [new file with mode: 0644]
src/submission-login/submission-proxy.c [new file with mode: 0644]
src/submission-login/submission-proxy.h [new file with mode: 0644]
src/submission/Makefile.am [new file with mode: 0644]
src/submission/cmd-data.c [new file with mode: 0644]
src/submission/cmd-helo.c [new file with mode: 0644]
src/submission/cmd-mail.c [new file with mode: 0644]
src/submission/cmd-noop.c [new file with mode: 0644]
src/submission/cmd-quit.c [new file with mode: 0644]
src/submission/cmd-rcpt.c [new file with mode: 0644]
src/submission/cmd-rset.c [new file with mode: 0644]
src/submission/cmd-vrfy.c [new file with mode: 0644]
src/submission/main.c [new file with mode: 0644]
src/submission/submission-client.c [new file with mode: 0644]
src/submission/submission-client.h [new file with mode: 0644]
src/submission/submission-commands.c [new file with mode: 0644]
src/submission/submission-commands.h [new file with mode: 0644]
src/submission/submission-common.h [new file with mode: 0644]
src/submission/submission-settings.c [new file with mode: 0644]
src/submission/submission-settings.h [new file with mode: 0644]

index 20e5310941f20ec79c1dc40a5c5615f1cd35cff8..86ef16c3143470da57fa0eaa39183dff23d259f2 100644 (file)
@@ -137,6 +137,8 @@ src/pop3-login/pop3-login
 src/pop3/pop3
 src/replication/replicator/replicator
 src/replication/aggregator/aggregator
+src/submission-login/submission-login
+src/submission/submission
 src/util/gdbhelper
 src/util/listview
 src/util/maildirlock
diff --git a/TODO b/TODO
index d3cf6745b8a4795005d0fead1e301be216d8ad90..d5f01ad5ffaa74fc0d00e15f538886f8d7833f3a 100644 (file)
--- a/TODO
+++ b/TODO
     - Fully support DSN extension (especially ORCPT)
     - Calculate incoming mail's hash, forward it via proxying, have the
       final delivery code verify that it's correct
+
+ - submission
+    - Implement support for Postfix XFORWARD (analogous to XCLIENT)
+    - Implement a re-connect attempt to the relay server if the connection is
+      lost at some point. We now terminate the whole client with a 421, which
+      is a waste of resources.
+    - Implement running submission service without access to mail storage.
+    - Implement auto-save-to-sent feature.
+    - Implement proxy support for various (sometimes bizarre) SMTP extensions.
+      We only announce the support if available on the relay and forward
+      commands and command options. We likely don't need to implement much
+      ourselves:
+       -> RFC 2852: Deliver By
+       -> RFC 3885: Message Tracking
+       -> RFC 4865: Future Message Release
+       -> RFC 6710: Message Transfer Priorities
+    - Implement Internationalized Email (RFC 6531) support
+    - Low priority:
+       - Investigate relevance of RFC 4405 (Responsible Submitter)
+       - Add RFC5451 Authentication-Results header
+       - Implement downgrading to always support BINARYMIME
+       - Implement downgrading to always support 8BITMIME
index 138e147a344aa3739439ebb708f079268cc8be60..a5a91e43c69aa7f8cc95787a164e89f88b600e5e 100644 (file)
@@ -890,6 +890,8 @@ src/login-common/Makefile
 src/master/Makefile
 src/pop3/Makefile
 src/pop3-login/Makefile
+src/submission/Makefile
+src/submission-login/Makefile
 src/replication/Makefile
 src/replication/aggregator/Makefile
 src/replication/replicator/Makefile
index eb3bab6673f2dc41730bfdb2dc2492b8908a2eb4..073d8a8d69ef407fa223c0238230df35bb280016 100644 (file)
@@ -50,6 +50,9 @@ service imap-login {
 service pop3-login {
   #executable = pop3-login director
 }
+service submission-login {
+  #executable = submission-login director
+}
 
 # Enable director for LMTP proxying:
 protocol lmtp {
index e3d626089b6810b92614d7f98675d489f1a4d775..d52ce808249a90d3f4f2aae90ce0e6f061c6b998 100644 (file)
@@ -45,6 +45,12 @@ service pop3-login {
   }
 }
 
+service submission-login {
+  inet_listener submission {
+    #port = 587
+  }
+}
+
 service lmtp {
   unix_listener lmtp {
     #mode = 0666
@@ -72,6 +78,11 @@ service pop3 {
   #process_limit = 1024
 }
 
+service submission {
+  # Max. number of SMTP Submission processes (connections)
+  #process_limit = 1024
+}
+
 service auth {
   # auth_socket_path points to this userdb socket by default. It's typically
   # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have
index a7cd3ad7329c6caf3d54c5c19ecce1807569ccc1..f59e183f172589ddd58ec7a039025aa650ae45eb 100644 (file)
@@ -27,9 +27,10 @@ ssl_key = </etc/ssl/private/dovecot.pem
 #ssl_require_crl = yes
 
 # Directory and/or file for trusted SSL CA certificates. These are used only
-# when Dovecot needs to act as an SSL client (e.g. imapc backend). The
-# directory is usually /etc/ssl/certs in Debian-based systems and the file is
-# /etc/pki/tls/cert.pem in RedHat-based systems.
+# when Dovecot needs to act as an SSL client (e.g. imapc backend or
+# submission service). The directory is usually /etc/ssl/certs in
+# Debian-based systems and the file is /etc/pki/tls/cert.pem in
+# RedHat-based systems.
 #ssl_client_ca_dir =
 #ssl_client_ca_file =
 
diff --git a/doc/example-config/conf.d/20-submission.conf b/doc/example-config/conf.d/20-submission.conf
new file mode 100644 (file)
index 0000000..0b602e5
--- /dev/null
@@ -0,0 +1,66 @@
+##
+## Settings specific to SMTP Submission
+##
+
+# SMTP Submission logout format string:
+#  %i - total number of bytes read from client
+#  %o - total number of bytes sent to client
+#  %{command_count} - Number of commands received from client
+#  %{reply_count} - Number of replies sent to client
+#  %{session} - Session ID of the login session
+#  %{transaction_id} - ID of the current transaction, if any
+#submission_logout_format = in=%i out=%o
+
+# Host name reported by the SMTP service, for example to the client in the
+# initial greeting and to the relay server in the HELO/EHLO command.
+# Default is the system's real hostname@domain.
+#hostname =
+
+# Maximum size of messages accepted for relay. This announced in the SIZE
+# capability. If not configured, this is either determined from the relay
+# server or left unlimited if no limit is known (relay will reply with error
+# if some unknown limit exists there, which is duly passed to our client).
+#submission_max_mail_size =
+
+# Maximum number of recipients accepted per connection (default: unlimited)
+#submission_max_recipients =
+
+# Relay server configuration:
+#
+# The Dovecot SMTP submission service directly proxies the mail transaction
+# to the SMTP relay configured here.
+
+# Host name for the relay server (required)
+#submission_relay_host =
+
+# Port for the relay server
+#submission_relay_port = 25
+
+# Is the relay server trusted? This determines whether we try to send
+# (Postfix-specific) XCLIENT data to the relay server
+#submission_relay_trusted = no
+
+# Authentication data for the relay server if authentication is required
+#submission_relay_user =
+#submission_relay_master_user =
+#submission_relay_password =
+
+# SSL configuration for connection to relay server
+#submission_relay_ssl = no
+#submission_relay_ssl_verify = yes
+
+# Write protocol logs for relay connection to this directory for debugging
+#submission_relay_rawlog_dir =
+
+# BURL is configured implicitly by IMAP URLAUTH
+
+protocol submission {
+  # Space-separated list of plugins to load (default is global mail_plugins).
+  #mail_plugins = $mail_plugins
+
+  # Maximum number of SMTP submission connections allowed for a user from
+  # each IP address.
+  # NOTE: The username is compared case-sensitively.
+  #mail_max_userip_connections = 10
+}
+
index 56976616956d70354326cc94a4769e2240c7c5c5..a99d8aa775c20428b52fdb289d98e97a9830e4da 100644 (file)
@@ -21,7 +21,7 @@
 # --sysconfdir=/etc --localstatedir=/var
 
 # Protocols we want to be serving.
-#protocols = imap pop3 lmtp
+#protocols = imap pop3 lmtp submission
 
 # A comma separated list of IPs or hosts where to listen in for connections. 
 # "*" listens in all IPv4 interfaces, "::" listens in all IPv6 interfaces.
index fb8f7b348199acf7deec6bdc2530b3c0ce1fe4b7..7bcf4d3bac5454ccc6a441ae0176acb7a5dda210 100644 (file)
@@ -58,6 +58,8 @@ SUBDIRS = \
        imap-urlauth \
        pop3-login \
        pop3 \
+       submission-login \
+       submission \
        lda \
        lmtp \
        log \
diff --git a/src/submission-login/Makefile.am b/src/submission-login/Makefile.am
new file mode 100644 (file)
index 0000000..2385faf
--- /dev/null
@@ -0,0 +1,32 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = submission-login
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/src/lib \
+       -I$(top_srcdir)/src/lib-settings \
+       -I$(top_srcdir)/src/lib-sasl \
+       -I$(top_srcdir)/src/lib-auth \
+       -I$(top_srcdir)/src/lib-master \
+       -I$(top_srcdir)/src/lib-smtp \
+       -I$(top_srcdir)/src/login-common
+
+submission_login_LDADD = \
+       $(LIBDOVECOT_LOGIN) \
+       $(LIBDOVECOT) \
+       $(SSL_LIBS)
+submission_login_DEPENDENCIES = \
+       $(LIBDOVECOT_LOGIN) \
+       $(LIBDOVECOT_DEPS)
+
+submission_login_SOURCES = \
+       client.c \
+       client-authenticate.c \
+       submission-login-settings.c \
+       submission-proxy.c
+
+noinst_HEADERS = \
+       client.h \
+       client-authenticate.h \
+       submission-login-settings.h \
+       submission-proxy.h
diff --git a/src/submission-login/client-authenticate.c b/src/submission-login/client-authenticate.c
new file mode 100644 (file)
index 0000000..67e879a
--- /dev/null
@@ -0,0 +1,266 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "login-common.h"
+#include "base64.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "safe-memset.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "auth-client.h"
+#include "master-service-ssl-settings.h"
+#include "client.h"
+#include "client-authenticate.h"
+#include "submission-proxy.h"
+#include "submission-login-settings.h"
+
+static void cmd_helo_reply(struct submission_client *subm_client,
+                          struct smtp_server_cmd_ctx *cmd,
+                          struct smtp_server_cmd_helo *data)
+{
+       struct client *client = &subm_client->common;
+       struct smtp_server_reply *reply;
+
+       reply = smtp_server_reply_create_ehlo(cmd->cmd);
+       if (!data->helo.old_smtp) {
+               smtp_server_reply_ehlo_add(reply, "8BITMIME");
+
+               if (client->secured ||
+                       strcmp(client->ssl_set->ssl, "required") != 0) {
+                       const struct auth_mech_desc *mechs;
+                       unsigned int count, i;
+                       string_t *param = t_str_new(128);
+
+                       mechs = sasl_server_get_advertised_mechs(client,
+                                                                &count);
+                       for (i = 0; i < count; i++) {
+                               if (i > 0)
+                                       str_append_c(param, ' ');
+                               str_append(param, mechs[i].name);
+                       }
+                       smtp_server_reply_ehlo_add_param(reply,
+                               "AUTH", "%s", str_c(param));
+               }
+
+               smtp_server_reply_ehlo_add_param(reply,
+                       "BURL", "imap");
+               smtp_server_reply_ehlo_add(reply,
+                       "CHUNKING");
+               smtp_server_reply_ehlo_add(reply,
+                       "ENHANCEDSTATUSCODES");
+
+               if (subm_client->set->submission_max_mail_size > 0) {
+                       smtp_server_reply_ehlo_add_param(reply,
+                               "SIZE", "%"PRIuSIZE_T,
+                               subm_client->set->submission_max_mail_size);
+               } else {
+                       smtp_server_reply_ehlo_add(reply, "SIZE");
+               }
+
+               if (client_is_tls_enabled(client) && !client->tls)
+                       smtp_server_reply_ehlo_add(reply, "STARTTLS");
+               smtp_server_reply_ehlo_add(reply, "PIPELINING");
+               smtp_server_reply_ehlo_add_xclient(reply);
+       }
+       smtp_server_reply_submit(reply);
+}
+
+int cmd_helo(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            struct smtp_server_cmd_helo *data)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)conn_ctx;
+
+       T_BEGIN {
+               cmd_helo_reply(subm_client, cmd, data);
+       } T_END;
+
+       return 1;
+}
+
+void submission_client_auth_result(struct client *client,
+       enum client_auth_result result,
+       const struct client_auth_reply *reply ATTR_UNUSED,
+       const char *text)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)client;
+       struct smtp_server_cmd_ctx *cmd = subm_client->pending_auth;
+
+       subm_client->pending_auth = NULL;
+       i_assert(cmd != NULL);
+
+       switch (result) {
+       case CLIENT_AUTH_RESULT_SUCCESS:
+               /* nothing to be done for SMTP */
+               if (client->login_proxy != NULL)
+                       subm_client->pending_auth = cmd;
+               break;
+       case CLIENT_AUTH_RESULT_TEMPFAIL:
+               /* RFC4954, Section 6:
+
+                  454 4.7.0 Temporary authentication failure
+
+                  This response to the AUTH command indicates that the
+                  authentication failed due to a temporary server failure.
+                */
+               smtp_server_reply(cmd, 454, "4.7.0", "%s", text);
+               break;
+       case CLIENT_AUTH_RESULT_ABORTED:
+               /* RFC4954, Section 4:
+
+                  If the client wishes to cancel the authentication exchange,
+                  it issues a line with a single "*". If the server receives
+                  such a response, it MUST reject the AUTH command by sending
+                  a 501 reply. */
+               smtp_server_reply(cmd, 501, "5.5.2", "%s", text);
+               break;
+       case CLIENT_AUTH_RESULT_INVALID_BASE64:
+               /* RFC4954, Section 4:
+
+                  If the server cannot [BASE64] decode any client response, it
+                  MUST reject the AUTH command with a 501 reply (and an
+                  enhanced status code of 5.5.2). */
+               smtp_server_reply(cmd, 501, "5.5.2", "%s", text);
+               break;
+       case CLIENT_AUTH_RESULT_SSL_REQUIRED:
+               /* RFC3207, Section 4:
+
+                  A SMTP server that is not publicly referenced may choose to
+                  require that the client perform a TLS negotiation before
+                  accepting any commands.  In this case, the server SHOULD
+                  return the reply code:
+
+                  530 Must issue a STARTTLS command first
+
+                  to every command other than NOOP, EHLO, STARTTLS, or QUIT.
+                  If the client and server are using the ENHANCEDSTATUSCODES
+                  ESMTP extension [RFC2034], the status code to be returned
+                  SHOULD be 5.7.0. */
+               smtp_server_reply(cmd, 530, "5.7.0", "%s", text);
+               break;
+       case CLIENT_AUTH_RESULT_MECH_INVALID:
+       case CLIENT_AUTH_RESULT_MECH_SSL_REQUIRED:
+               /* RFC4954, Section 4:
+
+                  If the requested authentication mechanism is invalid (e.g.,
+                  is not supported or requires an encryption layer), the server
+                  rejects the AUTH command with a 504 reply.  If the server
+                  supports the [ESMTP-CODES] extension, it SHOULD return a
+                  5.5.4 enhanced response code. */
+               smtp_server_reply(cmd, 504, "5.5.4", "%s", text);
+               break;
+       case CLIENT_AUTH_RESULT_LOGIN_DISABLED:
+               /* RFC5248, Section 2.4:
+
+                  525 X.7.13 User Account Disabled
+
+                  Sometimes a system administrator will have to disable a
+                  user's account (e.g., due to lack of payment, abuse, evidence
+                  of a break-in attempt, etc.). This error code occurs after a
+                  successful authentication to a disabled account. This informs
+                  the client that the failure is permanent until the user
+                  contacts their system administrator to get the account
+                  re-enabled. */
+               smtp_server_reply(cmd, 525, "5.7.13", "%s", text);
+               break;
+       case CLIENT_AUTH_RESULT_PASS_EXPIRED:
+       default:
+               /* FIXME: RFC4954, Section 4:
+
+                  If the client uses an initial-response argument to the AUTH
+                  command with a SASL mechanism in which the client does not
+                  begin the authentication exchange, the server MUST reject the
+                  AUTH command with a 501 reply.  Servers using the enhanced
+                  status codes extension [ESMTP-CODES] SHOULD return an
+                  enhanced status code of 5.7.0 in this case.
+
+                  >> Currently, this is checked at the server side, but only a
+                     generic error is ever produced.
+               */
+               /* NOTE: RFC4954, Section 4:
+
+                  If, during an authentication exchange, the server receives a
+                  line that is longer than the server's authentication buffer,
+                  the server fails the AUTH command with the 500 reply. Servers
+                  using the enhanced status codes extension [ESMTP-CODES]
+                  SHOULD return an enhanced status code of 5.5.6 in this case.
+
+                  >> Currently, client is disconnected from login-common.
+               */
+               /* RFC4954, Section 4:
+
+                  If the server is unable to authenticate the client, it SHOULD
+                  reject the AUTH command with a 535 reply unless a more
+                  specific error code is appropriate.
+
+                  RFC4954, Section 6:
+
+                  535 5.7.8  Authentication credentials invalid
+
+                  This response to the AUTH command indicates that the
+                  authentication failed due to invalid or insufficient
+                  authentication credentials.
+                */
+               smtp_server_reply(cmd, 535, "5.7.8", "%s", text);
+               break;
+       }
+}
+
+int cmd_auth_continue(void *conn_ctx,
+                     struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+                     const char *response)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)conn_ctx;
+       struct client *client = &subm_client->common;
+
+       if (strcmp(response, "*") == 0) {
+               client_auth_abort(client);
+               return 0;
+       }
+
+       client_auth_respond(client, response);
+       return 0;
+}
+
+void submission_client_auth_send_challenge(struct client *client,
+                                          const char *data)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)client;
+       struct smtp_server_cmd_ctx *cmd = subm_client->pending_auth;
+
+       i_assert(cmd != NULL);
+
+       smtp_server_cmd_auth_send_challenge(cmd, data);
+}
+
+int cmd_auth(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            struct smtp_server_cmd_auth *data)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)conn_ctx;
+       struct client *client = &subm_client->common;
+       struct smtp_server_helo_data *helo;
+       char *prefix;
+
+       i_assert(subm_client->pending_auth == NULL);
+
+       helo = smtp_server_connection_get_helo_data(subm_client->conn);
+
+       prefix = i_strdup(helo->domain == NULL ? "" : helo->domain);
+
+       /* pass ehlo parameter to post-login service upon successful login */
+       i_free(client->master_data_prefix);
+       client->master_data_prefix = (void *)prefix;
+       client->master_data_prefix_len = strlen(prefix);
+
+       subm_client->pending_auth = cmd;
+
+       (void)client_auth_begin(client, data->sasl_mech, data->initial_response);
+       return 0;
+}
diff --git a/src/submission-login/client-authenticate.h b/src/submission-login/client-authenticate.h
new file mode 100644 (file)
index 0000000..80384e4
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef CLIENT_AUTHENTICATE_H
+#define CLIENT_AUTHENTICATE_H
+
+void submission_client_auth_result(struct client *client,
+                                  enum client_auth_result result,
+                                  const struct client_auth_reply *reply,
+                                  const char *text);
+
+void submission_client_auth_send_challenge(struct client *client,
+                                          const char *data);
+
+int cmd_helo(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            struct smtp_server_cmd_helo *data);
+int cmd_auth_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+                     const char *response);
+int cmd_auth(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            struct smtp_server_cmd_auth *data);
+
+#endif
diff --git a/src/submission-login/client.c b/src/submission-login/client.c
new file mode 100644 (file)
index 0000000..5e942ab
--- /dev/null
@@ -0,0 +1,287 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "login-common.h"
+#include "base64.h"
+#include "buffer.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "randgen.h"
+#include "hostpid.h"
+#include "safe-memset.h"
+#include "str.h"
+#include "strescape.h"
+#include "master-service.h"
+#include "master-service-ssl-settings.h"
+#include "client.h"
+#include "client-authenticate.h"
+#include "auth-client.h"
+#include "submission-proxy.h"
+#include "submission-login-settings.h"
+
+/* Disconnect client when it sends too many bad commands */
+#define CLIENT_MAX_BAD_COMMANDS 10
+
+static const struct smtp_server_callbacks smtp_callbacks;
+
+static struct smtp_server *smtp_server = NULL;
+
+static void submission_login_start_tls(void *conn_ctx,
+       struct istream **input, struct ostream **output)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)conn_ctx;
+       struct client *client = &subm_client->common;
+
+       client->starttls = TRUE;
+       if (client_init_ssl(client) < 0) {
+               client_notify_disconnect(client,
+                       CLIENT_DISCONNECT_INTERNAL_ERROR,
+                       "TLS initialization failed.");
+               client_destroy(client,
+                       "Disconnected: TLS initialization failed.");
+               return;
+       }
+       login_refresh_proctitle();
+
+       *input = client->input;
+       *output = client->output;
+}
+
+static struct client *submission_client_alloc(pool_t pool)
+{
+       struct submission_client *subm_client;
+
+       subm_client = p_new(pool, struct submission_client, 1);
+       return &subm_client->common;
+}
+
+static void submission_client_create(struct client *client,
+                                    void **other_sets)
+{
+       static const char *const xclient_extensions[] =
+               { "SESSION", "FORWARD", NULL };
+       struct submission_client *subm_client =
+               (struct submission_client *)client;
+       struct smtp_server_settings smtp_set;
+
+       subm_client->set = other_sets[0];
+
+       i_zero(&smtp_set);
+       smtp_set.capabilities = SMTP_CAPABILITY_SIZE |
+               SMTP_CAPABILITY_ENHANCEDSTATUSCODES | SMTP_CAPABILITY_AUTH;
+       if (client_is_tls_enabled(client))
+               smtp_set.capabilities |= SMTP_CAPABILITY_STARTTLS;
+       smtp_set.hostname = subm_client->set->hostname;
+       smtp_set.login_greeting = client->set->login_greeting;
+       smtp_set.tls_required = (strcmp(client->ssl_set->ssl, "required") == 0);
+       smtp_set.xclient_extensions = xclient_extensions;
+       smtp_set.debug = client->set->auth_debug;
+
+       subm_client->conn = smtp_server_connection_create_from_streams(
+               smtp_server, client->input, client->output,
+               &client->real_remote_ip, client->real_remote_port,
+               &smtp_set, &smtp_callbacks, subm_client);
+}
+
+static void submission_client_destroy(struct client *client)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)client;
+
+       if (subm_client->conn != NULL)
+               smtp_server_connection_close(&subm_client->conn, NULL);
+       i_free_and_null(subm_client->proxy_xclient);
+}
+
+static void submission_client_notify_auth_ready(struct client *client)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)client;
+
+       smtp_server_connection_start(subm_client->conn, FALSE);
+}
+
+static void
+submission_client_notify_disconnect(struct client *_client,
+                                   enum client_disconnect_reason reason,
+                                   const char *text)
+{
+       struct submission_client *client = (struct submission_client *)_client;
+       struct smtp_server_connection *conn;
+
+       conn = client->conn;
+       client->conn = NULL;
+       if (conn != NULL) {
+               switch (reason) {
+               case CLIENT_DISCONNECT_TIMEOUT:
+                       smtp_server_connection_terminate(&conn, "4.4.2", text);
+                       break;
+               case CLIENT_DISCONNECT_SYSTEM_SHUTDOWN:
+                       smtp_server_connection_terminate(&conn, "4.3.2", text);
+                       break;
+               case CLIENT_DISCONNECT_INTERNAL_ERROR:
+               default:
+                       smtp_server_connection_terminate(&conn, "4.0.0", text);
+                       break;
+               }
+       }
+}
+
+static void
+client_connection_cmd_xclient(void *context,
+                             struct smtp_server_cmd_ctx *cmd,
+                             struct smtp_proxy_data *data)
+{
+       unsigned int i;
+
+       struct submission_client *client =
+               (struct submission_client *)context;
+
+       client->common.ip = data->source_ip;
+       client->common.remote_port = data->source_port;
+
+       client->common.proxy_ttl = data->ttl_plus_1;
+
+       for (i = 0; i < data->extra_fields_count; i++) {
+               const char *name = data->extra_fields[i].name;
+               const char *value = data->extra_fields[i].value;
+
+               if (strcasecmp(name, "FORWARD") == 0) {
+                       size_t value_len = strlen(value);
+                       if (client->common.forward_fields != NULL) {
+                               str_truncate(client->common.forward_fields, 0);
+                       } else {
+                               client->common.forward_fields = str_new(
+                                       client->common.preproxy_pool,
+                                       MAX_BASE64_DECODED_SIZE(value_len));
+                               if (base64_decode(value, value_len, NULL,
+                                         client->common.forward_fields) < 0) {
+                                       smtp_server_reply(cmd, 501, "5.5.4",
+                                               "Invalid FORWARD parameter");
+                               }
+                       }
+               } else if (strcasecmp(name, "SESSION") == 0) {
+                       if (client->common.session_id != NULL)
+                               continue;
+                       client->common.session_id =
+                               p_strdup(client->common.pool, value);
+               }
+       }
+}
+
+static void client_connection_disconnect(void *context, const char *reason)
+{
+       struct submission_client *client =
+               (struct submission_client *)context;
+
+       client_disconnect(&client->common, reason);
+}
+
+static void client_connection_destroy(void *context)
+{
+       struct submission_client *client =
+               (struct submission_client *)context;
+
+       if (client->conn == NULL)
+               return;
+       client->conn = NULL;
+       client_destroy(&client->common, NULL);
+}
+
+static bool client_connection_is_trusted(void *context)
+{
+       struct submission_client *client =
+               (struct submission_client *)context;
+
+       return client->common.trusted;
+}
+
+static void submission_login_die(void)
+{
+       /* do nothing. submission connections typically die pretty quick anyway.
+        */
+}
+
+static void submission_login_preinit(void)
+{
+       login_set_roots = submission_login_setting_roots;
+}
+
+static void submission_login_init(void)
+{
+       struct smtp_server_settings smtp_server_set;
+
+       /* override the default login_die() */
+       master_service_set_die_callback(master_service, submission_login_die);
+
+       /* initialize SMTP server */
+       i_zero(&smtp_server_set);
+       smtp_server_set.protocol = SMTP_PROTOCOL_SMTP;
+       smtp_server_set.max_pipelined_commands = 5;
+       smtp_server_set.max_bad_commands = CLIENT_MAX_BAD_COMMANDS;
+       smtp_server = smtp_server_init(&smtp_server_set);
+}
+
+static void submission_login_deinit(void)
+{
+       clients_destroy_all();
+
+       smtp_server_deinit(&smtp_server);
+}
+
+static const struct smtp_server_callbacks smtp_callbacks = {
+       .conn_cmd_helo = cmd_helo,
+
+       .conn_start_tls = submission_login_start_tls,
+
+       .conn_cmd_auth = cmd_auth,
+       .conn_cmd_auth_continue = cmd_auth_continue,
+
+       .conn_cmd_xclient = client_connection_cmd_xclient,
+
+       .conn_disconnect = client_connection_disconnect,
+       .conn_destroy = client_connection_destroy,
+
+       .conn_is_trusted = client_connection_is_trusted
+};
+
+static struct client_vfuncs submission_client_vfuncs = {
+       submission_client_alloc,
+       submission_client_create,
+       submission_client_destroy,
+       submission_client_notify_auth_ready,
+       submission_client_notify_disconnect,
+       NULL,
+       NULL,
+       NULL,
+       NULL,
+       submission_client_auth_send_challenge,
+       NULL,
+       submission_client_auth_result,
+       submission_proxy_reset,
+       submission_proxy_parse_line,
+       submission_proxy_error,
+       submission_proxy_get_state,
+       NULL,
+       NULL,
+       NULL
+};
+
+static const struct login_binary submission_login_binary = {
+       .protocol = "submission",
+       .process_name = "submission-login",
+       .default_port = 587,
+
+       .client_vfuncs = &submission_client_vfuncs,
+       .preinit = submission_login_preinit,
+       .init = submission_login_init,
+       .deinit = submission_login_deinit,
+
+       .sasl_support_final_reply = FALSE
+};
+
+int main(int argc, char *argv[])
+{
+       return login_binary_run(&submission_login_binary, argc, argv);
+}
diff --git a/src/submission-login/client.h b/src/submission-login/client.h
new file mode 100644 (file)
index 0000000..6230b60
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include "net.h"
+#include "client-common.h"
+#include "auth-client.h"
+#include "smtp-server.h"
+
+enum submission_proxy_state {
+       SUBMISSION_PROXY_BANNER = 0,
+       SUBMISSION_PROXY_EHLO,
+       SUBMISSION_PROXY_STARTTLS,
+       SUBMISSION_PROXY_TLS_EHLO,
+       SUBMISSION_PROXY_XCLIENT,
+       SUBMISSION_PROXY_AUTHENTICATE,
+
+       SUBMISSION_PROXY_STATE_COUNT
+};
+
+struct submission_client {
+       struct client common;
+       const struct submission_login_settings *set;
+
+       struct smtp_server_connection *conn;
+       struct smtp_server_cmd_ctx *pending_auth, *pending_starttls;
+
+       enum submission_proxy_state proxy_state;
+       enum smtp_capability proxy_capability;
+       unsigned int proxy_reply_status;
+       struct smtp_server_reply *proxy_reply;
+       const char **proxy_xclient;
+};
+
+#endif
diff --git a/src/submission-login/submission-login-settings.c b/src/submission-login/submission-login-settings.c
new file mode 100644 (file)
index 0000000..d529cd7
--- /dev/null
@@ -0,0 +1,107 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "login-settings.h"
+#include "submission-login-settings.h"
+
+#include <stddef.h>
+
+static bool
+submission_login_settings_check(void *_set, pool_t pool, const char **error_r);
+
+/* <settings checks> */
+static struct inet_listener_settings submission_login_inet_listeners_array[] = {
+       { .name = "submission", .address = "", .port = 587  }
+};
+static struct inet_listener_settings *submission_login_inet_listeners[] = {
+       &submission_login_inet_listeners_array[0]
+};
+static buffer_t submission_login_inet_listeners_buf = {
+       submission_login_inet_listeners, sizeof(submission_login_inet_listeners), { 0, }
+};
+
+/* </settings checks> */
+struct service_settings submission_login_service_settings = {
+       .name = "submission-login",
+       .protocol = "submission",
+       .type = "login",
+       .executable = "submission-login",
+       .user = "$default_login_user",
+       .group = "",
+       .privileged_group = "",
+       .extra_groups = "",
+       .chroot = "login",
+
+       .drop_priv_before_exec = FALSE,
+
+       .process_min_avail = 0,
+       .process_limit = 0,
+       .client_limit = 0,
+       .service_count = 1,
+       .idle_kill = 0,
+       .vsz_limit = (uoff_t)-1,
+
+       .unix_listeners = ARRAY_INIT,
+       .fifo_listeners = ARRAY_INIT,
+       .inet_listeners = { { &submission_login_inet_listeners_buf,
+                             sizeof(submission_login_inet_listeners[0]) } }
+};
+
+#undef DEF
+#define DEF(type, name) \
+       { type, #name, offsetof(struct submission_login_settings, name), NULL }
+
+static const struct setting_define submission_login_setting_defines[] = {
+       DEF(SET_STR, hostname),
+
+       DEF(SET_SIZE, submission_max_mail_size),
+
+       SETTING_DEFINE_LIST_END
+};
+
+static const struct submission_login_settings submission_login_default_settings = {
+       .hostname = "",
+
+       .submission_max_mail_size = 0,
+};
+
+static const struct setting_parser_info *submission_login_setting_dependencies[] = {
+       &login_setting_parser_info,
+       NULL
+};
+
+const struct setting_parser_info submission_login_setting_parser_info = {
+       .module_name = "submission-login",
+       .defines = submission_login_setting_defines,
+       .defaults = &submission_login_default_settings,
+
+       .type_offset = (size_t)-1,
+       .struct_size = sizeof(struct submission_login_settings),
+       .parent_offset = (size_t)-1,
+
+#ifndef CONFIG_BINARY
+       .check_func = submission_login_settings_check,
+#endif
+       .dependencies = submission_login_setting_dependencies
+};
+
+const struct setting_parser_info *submission_login_setting_roots[] = {
+       &login_setting_parser_info,
+       &submission_login_setting_parser_info,
+       NULL
+};
+
+static bool
+submission_login_settings_check(void *_set, pool_t pool,
+                       const char **error_r ATTR_UNUSED)
+{
+       struct submission_login_settings *set = _set;
+
+       if (*set->hostname == '\0')
+               set->hostname = p_strdup(pool, my_hostdomain());
+       return TRUE;
+}
diff --git a/src/submission-login/submission-login-settings.h b/src/submission-login/submission-login-settings.h
new file mode 100644 (file)
index 0000000..8cab25f
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef SUBMISSION_LOGIN_SETTINGS_H
+#define SUBMISSION_LOGIN_SETTINGS_H
+
+struct submission_login_settings {
+       const char *hostname;
+
+       /* submission: */
+       size_t submission_max_mail_size;
+};
+
+extern const struct setting_parser_info *submission_login_setting_roots[];
+
+#endif
diff --git a/src/submission-login/submission-proxy.c b/src/submission-login/submission-proxy.c
new file mode 100644 (file)
index 0000000..71bfd4b
--- /dev/null
@@ -0,0 +1,465 @@
+/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */
+
+#include "login-common.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "base64.h"
+#include "safe-memset.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "strescape.h"
+#include "dsasl-client.h"
+#include "client.h"
+#include "submission-login-settings.h"
+#include "submission-proxy.h"
+
+#include <ctype.h>
+
+static const char *submission_proxy_state_names[SUBMISSION_PROXY_STATE_COUNT] = {
+       "banner", "ehlo", "starttls", "tls-ehlo", "xclient", "authenticate"
+};
+
+static void proxy_free_password(struct client *client)
+{
+       if (client->proxy_password == NULL)
+               return;
+
+       safe_memset(client->proxy_password, 0, strlen(client->proxy_password));
+       i_free_and_null(client->proxy_password);
+}
+
+static void
+proxy_send_xclient(struct submission_client *client, struct ostream *output)
+{
+       const char *const *arg;
+       string_t *str, *fwd;
+
+       if ((client->proxy_capability & SMTP_CAPABILITY_XCLIENT) == 0 ||
+           client->common.proxy_not_trusted)
+               return;
+
+       /* remote supports XCLIENT, send it */
+
+       fwd = t_str_new(128);
+       for(arg = client->common.auth_passdb_args; *arg != NULL; arg++) {
+               if (strncasecmp(*arg, "forward_", 8) == 0) {
+                       if (str_len(fwd) > 0)
+                               str_append_c(fwd, '\t');
+                       str_append_tabescaped(fwd, (*arg)+8);
+               }
+       }
+
+       str = t_str_new(128);
+       str_append(str, "XCLIENT ");
+       if (str_array_icase_find(client->proxy_xclient, "ADDR")) {
+               str_append(str, "ADDR=");
+               str_append(str, net_ip2addr(&client->common.ip));
+       }
+       if (str_array_icase_find(client->proxy_xclient, "PORT"))
+               str_printfa(str, "PORT=%u", client->common.remote_port);
+       if (str_array_icase_find(client->proxy_xclient, "SESSION")) {
+               str_append(str, "SESSION=");
+               str_append(str, client_get_session_id(&client->common));
+       }
+       if (str_array_icase_find(client->proxy_xclient, "TTL"))
+               str_printfa(str, "TTL=%u", client->common.proxy_ttl - 1);
+       if (str_array_icase_find(client->proxy_xclient, "FORWARD") &&
+               str_len(fwd) > 0) {
+               str_append(str, " FORWARD=");
+               base64_encode(str_data(fwd), str_len(fwd), str);
+       }
+       str_append(str, "\r\n");
+       o_stream_nsend(output, str_data(str), str_len(str));
+       client->proxy_state = SUBMISSION_PROXY_XCLIENT;
+}
+
+static int
+proxy_send_login(struct submission_client *client, struct ostream *output)
+{
+       struct dsasl_client_settings sasl_set;
+       const unsigned char *sasl_output;
+       size_t len;
+       const char *mech_name, *error;
+       string_t *str;
+
+       if ((client->proxy_capability & SMTP_CAPABILITY_AUTH) == 0) {
+               /* Prevent sending credentials to a server that has login disabled;
+                  i.e., due to the lack of TLS */
+               client_log_err(&client->common, "proxy: "
+                       "Server has disabled authentication (TLS required?)");
+               return -1;
+       }
+
+       i_assert(client->common.proxy_ttl > 1);
+       proxy_send_xclient(client, output);
+
+       str = t_str_new(128);
+
+       if (client->common.proxy_mech == NULL)
+               client->common.proxy_mech = &dsasl_client_mech_plain;
+
+       i_assert(client->common.proxy_sasl_client == NULL);
+       i_zero(&sasl_set);
+       sasl_set.authid = client->common.proxy_master_user != NULL ?
+               client->common.proxy_master_user : client->common.proxy_user;
+       sasl_set.authzid = client->common.proxy_user;
+       sasl_set.password = client->common.proxy_password;
+       client->common.proxy_sasl_client =
+               dsasl_client_new(client->common.proxy_mech, &sasl_set);
+       mech_name = dsasl_client_mech_get_name(client->common.proxy_mech);
+
+       str_printfa(str, "AUTH %s ", mech_name);
+       if (dsasl_client_output(client->common.proxy_sasl_client,
+                               &sasl_output, &len, &error) < 0) {
+               client_log_err(&client->common, t_strdup_printf(
+                       "proxy: SASL mechanism %s init failed: %s",
+                       mech_name, error));
+               return -1;
+       }
+       if (len == 0)
+               str_append_c(str, '=');
+       else
+               base64_encode(sasl_output, len, str);
+       str_append(str, "\r\n");
+       o_stream_nsend(output, str_data(str), str_len(str));
+
+       proxy_free_password(&client->common);
+
+       if (client->proxy_state != SUBMISSION_PROXY_XCLIENT)
+               client->proxy_state = SUBMISSION_PROXY_AUTHENTICATE;
+       return 0;
+}
+
+static int
+submission_proxy_continue_sasl_auth(struct client *client, struct ostream *output,
+                                   const char *line)
+{
+       string_t *str;
+       const unsigned char *data;
+       size_t data_len;
+       const char *error;
+       int ret;
+
+       str = t_str_new(128);
+       if (base64_decode(line, strlen(line), NULL, str) < 0) {
+               client_log_err(client,
+                       "proxy: Server sent invalid base64 data in AUTH response");
+               return -1;
+       }
+       ret = dsasl_client_input(client->proxy_sasl_client,
+                                str_data(str), str_len(str), &error);
+       if (ret == 0) {
+               ret = dsasl_client_output(client->proxy_sasl_client,
+                                         &data, &data_len, &error);
+       }
+       if (ret < 0) {
+               client_log_err(client, t_strdup_printf(
+                       "proxy: Server sent invalid authentication data: %s",
+                       error));
+               return -1;
+       }
+       i_assert(ret == 0);
+
+       str_truncate(str, 0);
+       base64_encode(data, data_len, str);
+       str_append(str, "\r\n");
+
+       o_stream_nsend(output, str_data(str), str_len(str));
+       return 0;
+}
+
+static const char *
+strip_enhanced_code(const char *text, const char **enh_code_r)
+{
+       const char *p = text;
+       unsigned int digits;
+
+       *enh_code_r = NULL;
+
+       if (*p != '2' && *p != '4' && *p != '5')
+               return text;
+       p++;
+       if (*p != '.')
+               return text;
+       p++;
+
+       digits = 0;
+       while (i_isdigit(*p) && digits < 3) {
+               p++;
+               digits++;
+       }
+       if (*p != '.')
+               return text;
+       p++;
+
+       digits = 0;
+       while (i_isdigit(*p) && digits < 3) {
+               p++;
+               digits++;
+       }
+       if (*p != ' ')
+               return text;
+       *enh_code_r = t_strdup_until(text, p);
+       p++;
+       return p;
+}
+
+static void
+submission_proxy_success_reply_sent(struct smtp_server_cmd_ctx *cmd)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)cmd->context;
+
+       client_proxy_finish_destroy_client(&subm_client->common);
+}
+
+int submission_proxy_parse_line(struct client *client, const char *line)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)client;
+       struct smtp_server_cmd_ctx *cmd = subm_client->pending_auth;
+       struct smtp_server_command *command = cmd->cmd;
+       struct ostream *output;
+       enum login_proxy_ssl_flags ssl_flags;
+       bool last_line = FALSE, invalid_line = FALSE;
+       const char *text = NULL, *enh_code = NULL;
+       unsigned int status = 0;
+
+       i_assert(!client->destroyed);
+       i_assert(cmd != NULL);
+
+       if ((line[3] != ' ' && line[3] != '-') ||
+               str_parse_uint(line, &status, &text) < 0 ||
+               status < 200 || status >= 560) {
+               invalid_line = TRUE;
+       }
+       if (subm_client->proxy_reply_status != 0 &&
+               subm_client->proxy_reply_status != status) {
+               client_log_err(client, t_strdup_printf(
+                       "proxy: Remote returned inconsistent SMTP reply: %s "
+                       "(status != %u)", str_sanitize(line, 160),
+                       subm_client->proxy_reply_status));
+               client_proxy_failed(client, TRUE);
+               return -1;
+       }
+       if (line[3] == ' ') {
+               last_line = TRUE;
+               subm_client->proxy_reply_status = 0;
+       } else {
+               subm_client->proxy_reply_status = status;
+       }
+       text++;
+
+       if ((subm_client->proxy_capability &
+               SMTP_CAPABILITY_ENHANCEDSTATUSCODES) != 0)
+               text = strip_enhanced_code(text, &enh_code);
+
+       output = login_proxy_get_ostream(client->login_proxy);
+       switch (subm_client->proxy_state) {
+       case SUBMISSION_PROXY_BANNER:
+               /* this is a banner */
+               if (invalid_line || status != 220) {
+                       client_log_err(client, t_strdup_printf(
+                               "proxy: Remote returned invalid banner: %s",
+                               str_sanitize(line, 160)));
+                       client_proxy_failed(client, TRUE);
+                       return -1;
+               }
+               if (!last_line)
+                       return 0;
+
+               subm_client->proxy_state = SUBMISSION_PROXY_EHLO;
+               o_stream_nsend_str(output, t_strdup_printf("EHLO %s\r\n",
+                       subm_client->set->hostname));
+               return 0;
+       case SUBMISSION_PROXY_EHLO:
+       case SUBMISSION_PROXY_TLS_EHLO:
+               if (invalid_line || (status / 100) != 2) {
+                       client_log_err(client, t_strdup_printf(
+                               "proxy: Remote returned invalid EHLO line: %s",
+                               str_sanitize(line, 160)));
+                       client_proxy_failed(client, TRUE);
+                       return -1;
+               }
+
+               if (strncasecmp(text, "XCLIENT ", 8) == 0) {
+                       subm_client->proxy_capability |=
+                               SMTP_CAPABILITY_XCLIENT;
+                       i_free_and_null(subm_client->proxy_xclient);
+                       subm_client->proxy_xclient = p_strarray_dup(
+                               default_pool, t_strsplit_spaces(text + 8, " "));
+               } else if (strncasecmp(text, "STARTTLS", 9) == 0) {
+                       subm_client->proxy_capability |=
+                               SMTP_CAPABILITY_STARTTLS;
+               } else if (strncasecmp(text, "AUTH", 4) == 0 &&
+                       text[4] == ' ' && text[5] != '\0') {
+                       subm_client->proxy_capability |=
+                               SMTP_CAPABILITY_AUTH;
+               } else if (strcasecmp(text, "ENHANCEDSTATUSCODES") == 0) {
+                       subm_client->proxy_capability |=
+                               SMTP_CAPABILITY_ENHANCEDSTATUSCODES;
+               }
+               if (!last_line)
+                       return 0;
+
+               if (subm_client->proxy_state == SUBMISSION_PROXY_TLS_EHLO) {
+                       if (proxy_send_login(subm_client, output) < 0) {
+                               client_proxy_failed(client, TRUE);
+                               return -1;
+                       }
+                       return 0;
+               }
+
+               ssl_flags = login_proxy_get_ssl_flags(client->login_proxy);
+               if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) {
+                       if (proxy_send_login(subm_client, output) < 0) {
+                               client_proxy_failed(client, TRUE);
+                               return -1;
+                       }
+               } else {
+                       if ((subm_client->proxy_capability &
+                               SMTP_CAPABILITY_STARTTLS) == 0) {
+                               client_log_err(client,
+                                       "proxy: Remote doesn't support STARTTLS");
+                               return -1;
+                       }
+                       o_stream_nsend_str(output, "STARTTLS\r\n");
+                       subm_client->proxy_state = SUBMISSION_PROXY_STARTTLS;
+               }
+               return 0;
+       case SUBMISSION_PROXY_STARTTLS:
+               if (invalid_line || status != 220) {
+                       client_log_err(client, t_strdup_printf(
+                               "proxy: Remote STARTTLS failed: %s",
+                               str_sanitize(line, 160)));
+                       client_proxy_failed(client, TRUE);
+                       return -1;
+               }
+               if (!last_line)
+                       return 0;
+               if (login_proxy_starttls(client->login_proxy) < 0) {
+                       client_proxy_failed(client, TRUE);
+                       return -1;
+               }
+               /* i/ostreams changed. */
+               output = login_proxy_get_ostream(client->login_proxy);
+
+               subm_client->proxy_capability = 0;
+               i_free_and_null(subm_client->proxy_xclient);
+               subm_client->proxy_state = SUBMISSION_PROXY_TLS_EHLO;
+               o_stream_nsend_str(output, t_strdup_printf("EHLO %s\r\n",
+                       subm_client->set->hostname));
+               return 0;
+       case SUBMISSION_PROXY_XCLIENT:
+               if (invalid_line || (status / 100) != 2) {
+                       client_log_err(client, t_strdup_printf(
+                               "proxy: Remote XCLIENT failed: %s",
+                               str_sanitize(line, 160)));
+                       client_proxy_failed(client, TRUE);
+                       return -1;
+               }
+               if (!last_line)
+                       return 0;
+               subm_client->proxy_state = SUBMISSION_PROXY_AUTHENTICATE;
+               return 0;
+       case SUBMISSION_PROXY_AUTHENTICATE:
+               if (invalid_line)
+                       break;
+               if (status == 334 && client->proxy_sasl_client != NULL) {
+                       /* continue SASL authentication */
+                       if (submission_proxy_continue_sasl_auth(client, output,
+                                                               text) < 0) {
+                               client_proxy_failed(client, TRUE);
+                               return -1;
+                       }
+                       return 0;
+               }
+
+               if (subm_client->proxy_reply == NULL) {
+                       subm_client->proxy_reply =
+                               smtp_server_reply_create(command, status, enh_code);
+               }
+               smtp_server_reply_add_text(subm_client->proxy_reply, text);
+
+               if (!last_line)
+                       return 0;
+               if ((status / 100) != 2)
+                       break;
+
+               smtp_server_connection_input_lock(cmd->conn);
+               cmd->context = subm_client;
+               cmd->hook_destroy = submission_proxy_success_reply_sent;
+               subm_client->pending_auth = NULL;
+
+               /* Login successful. Send this reply to client. */
+               smtp_server_reply_submit(subm_client->proxy_reply);
+
+               return 1;
+       case SUBMISSION_PROXY_STATE_COUNT:
+               i_unreached();
+       }
+
+       /* Login failed. Pass through the error message to client.
+
+          If the backend server isn't Dovecot, the error message may
+          be different from Dovecot's "user doesn't exist" error. This
+          would allow an attacker to find out what users exist in the
+          system.
+
+          The optimal way to handle this would be to replace the
+          backend's "password failed" error message with Dovecot's
+          AUTH_FAILED_MSG, but this would require a new setting and
+          the sysadmin to actually bother setting it properly.
+
+          So for now we'll just forward the error message. This
+          shouldn't be a real problem since of course everyone will
+          be using only Dovecot as their backend :) */
+       if ((status / 100) == 2) {
+               submission_proxy_error(client, AUTH_FAILED_MSG);
+       } else {
+               i_assert(subm_client->proxy_reply != NULL);
+               smtp_server_reply_submit(subm_client->proxy_reply);
+               subm_client->pending_auth = NULL;
+       }
+
+       if (client->set->auth_verbose) {
+               client_proxy_log_failure(client, text);
+       }
+       client->proxy_auth_failed = TRUE;
+       client_proxy_failed(client, FALSE);
+       return -1;
+}
+
+void submission_proxy_reset(struct client *client)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)client;
+
+       subm_client->proxy_state = SUBMISSION_PROXY_BANNER;
+       subm_client->proxy_capability = 0;
+       i_free_and_null(subm_client->proxy_xclient);
+       subm_client->proxy_reply_status = 0;
+       subm_client->proxy_reply = NULL;
+}
+
+void submission_proxy_error(struct client *client, const char *text)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)client;
+
+       struct smtp_server_cmd_ctx *cmd = subm_client->pending_auth;
+       if (cmd != NULL) {
+               subm_client->pending_auth = NULL;
+               smtp_server_reply(cmd, 535, "5.7.8", "%s", text);
+       }
+}
+
+const char *submission_proxy_get_state(struct client *client)
+{
+       struct submission_client *subm_client =
+               (struct submission_client *)client;
+
+       i_assert(subm_client->proxy_state < SUBMISSION_PROXY_STATE_COUNT);
+       return submission_proxy_state_names[subm_client->proxy_state];
+}
diff --git a/src/submission-login/submission-proxy.h b/src/submission-login/submission-proxy.h
new file mode 100644 (file)
index 0000000..b9370e6
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef SUBMISSION_PROXY_H
+#define SUBMISSION_PROXY_H
+
+void submission_proxy_reset(struct client *client);
+int submission_proxy_parse_line(struct client *client, const char *line);
+
+void submission_proxy_error(struct client *client, const char *text);
+const char *submission_proxy_get_state(struct client *client);
+
+#endif
diff --git a/src/submission/Makefile.am b/src/submission/Makefile.am
new file mode 100644 (file)
index 0000000..7fd81ac
--- /dev/null
@@ -0,0 +1,57 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = submission
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/src/lib \
+       -I$(top_srcdir)/src/lib-settings \
+       -I$(top_srcdir)/src/lib-auth \
+       -I$(top_srcdir)/src/lib-mail \
+       -I$(top_srcdir)/src/lib-ssl-iostream \
+       -I$(top_srcdir)/src/lib-imap \
+       -I$(top_srcdir)/src/lib-imap-storage \
+       -I$(top_srcdir)/src/lib-imap-urlauth \
+       -I$(top_srcdir)/src/lib-index \
+       -I$(top_srcdir)/src/lib-master \
+       -I$(top_srcdir)/src/lib-storage \
+       -I$(top_srcdir)/src/lib-storage/index \
+       -I$(top_srcdir)/src/lib-storage/index/raw \
+       -I$(top_srcdir)/src/lib-smtp
+
+urlauth_libs = \
+       $(top_builddir)/src/lib-imap-urlauth/libimap-urlauth.la
+
+submission_LDFLAGS = -export-dynamic
+
+submission_LDADD = \
+       $(urlauth_libs) \
+       $(LIBDOVECOT_STORAGE) \
+       $(LIBDOVECOT) \
+       $(MODULE_LIBS)
+submission_DEPENDENCIES = \
+       $(urlauth_libs) \
+       $(LIBDOVECOT_STORAGE_DEPS) \
+       $(LIBDOVECOT_DEPS)
+
+cmds = \
+       cmd-helo.c \
+       cmd-mail.c \
+       cmd-rcpt.c \
+       cmd-data.c \
+       cmd-rset.c \
+       cmd-noop.c \
+       cmd-quit.c \
+       cmd-vrfy.c
+
+submission_SOURCES = \
+       $(cmds) \
+       main.c \
+       submission-client.c \
+       submission-commands.c \
+       submission-settings.c
+
+noinst_HEADERS = \
+       submission-common.h \
+       submission-commands.h \
+       submission-client.h \
+       submission-settings.h
diff --git a/src/submission/cmd-data.c b/src/submission/cmd-data.c
new file mode 100644 (file)
index 0000000..878208f
--- /dev/null
@@ -0,0 +1,354 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "istream-concat.h"
+#include "istream-seekable.h"
+#include "mail-storage.h"
+#include "imap-url.h"
+#include "imap-msgpart.h"
+#include "imap-msgpart-url.h"
+#include "imap-urlauth.h"
+#include "imap-urlauth-fetch.h"
+#include "smtp-address.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+
+#include "submission-commands.h"
+
+/*
+ * DATA/BDAT commands
+ */
+
+struct cmd_data_context {
+       struct client *client;
+       struct smtp_server_cmd_ctx *cmd;
+       struct smtp_server_transaction *trans;
+
+       struct smtp_client_command *cmd_proxied;
+};
+
+static void cmd_data_proxy_cb(const struct smtp_reply *proxy_reply,
+                             struct cmd_data_context *data_ctx)
+{
+       struct smtp_server_cmd_ctx *cmd = data_ctx->cmd;
+       struct smtp_server_transaction *trans = data_ctx->trans;
+       struct client *client = data_ctx->client;
+       struct smtp_reply reply;
+
+       /* finished proxying message to relay server */
+
+       /* check for fatal problems */
+       if (!client_command_handle_proxy_reply(client, proxy_reply, &reply))
+               return;
+
+       if (proxy_reply->status / 100 == 2) {
+               i_info("Successfully relayed message: "
+                      "from=<%s>, size=%"PRIuUOFF_T", "
+                      "id=%s, nrcpt=%u, reply=`%s'",
+                      smtp_address_encode(trans->mail_from),
+                      client->state.data_size, trans->id,
+                      array_count(&trans->rcpt_to),
+                      str_sanitize(smtp_reply_log(proxy_reply), 128));
+
+       } else {
+               i_info("Failed to relay message: "
+                      "from=<%s>, size=%"PRIuUOFF_T", nrcpt=%u, reply=`%s'",
+                      smtp_address_encode(trans->mail_from),
+                      client->state.data_size, array_count(&trans->rcpt_to),
+                      str_sanitize(smtp_reply_log(proxy_reply), 128));
+       }
+
+       smtp_server_reply_forward(cmd, &reply);
+}
+
+int cmd_data_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+                     struct smtp_server_transaction *trans)
+{
+       struct client *client = (struct client *)conn_ctx;
+       struct cmd_data_context *data_ctx =
+               (struct cmd_data_context *)trans->context;
+       struct istream *data_input = client->state.data_input;
+       struct istream *inputs[3];
+       string_t *added_headers;
+       const unsigned char *data;
+       size_t size;
+       int ret;
+
+       while ((ret = i_stream_read_more(data_input, &data, &size)) > 0)
+               i_stream_skip(data_input, size);
+
+       if (ret == 0)
+               return 0;
+       if (ret < 0 && data_input->stream_errno != 0)
+               return -1;
+
+       /* Done reading DATA stream; remove it from state and continue with
+          local variable. */
+       client->state.data_input = NULL;
+
+       ret = i_stream_get_size(data_input, TRUE,
+                               &client->state.data_size);
+       i_assert(ret > 0); // FIXME
+
+       /* prepend our own headers */
+       added_headers = t_str_new(200);
+       smtp_server_transaction_write_trace_record(added_headers, trans);
+
+       i_stream_seek(data_input, 0);
+       inputs[0] = i_stream_create_copy_from_data(
+               str_data(added_headers), str_len(added_headers));
+       inputs[1] = data_input;
+       inputs[2] = NULL;
+
+       data_ctx->cmd = cmd;
+
+       data_input = i_stream_create_concat(inputs);
+       i_stream_unref(&inputs[0]);
+       i_stream_unref(&inputs[1]);
+
+       /* start proxying to relay server */
+
+       data_ctx->cmd_proxied = smtp_client_command_data_submit(
+               client->proxy_conn, 0, data_input,
+               cmd_data_proxy_cb, data_ctx);
+       i_stream_unref(&data_input);
+       return 0;
+}
+
+int cmd_data_begin(void *conn_ctx,
+                  struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+                  struct smtp_server_transaction *trans,
+                  struct istream *data_input)
+{
+       struct client *client = (struct client *)conn_ctx;
+       struct cmd_data_context *data_ctx;
+       struct istream *inputs[2];
+       string_t *path;
+
+       data_ctx = p_new(trans->pool, struct cmd_data_context, 1);
+       data_ctx->client = client;
+       data_ctx->trans = trans;
+       trans->context = (void*)data_ctx;
+
+       inputs[0] = data_input;
+       inputs[1] = NULL;
+
+       path = t_str_new(256);
+       mail_user_set_get_temp_prefix(path, client->user->set);
+       client->state.data_input = i_stream_create_seekable_path(inputs,
+               SUBMISSION_MAIL_DATA_MAX_INMEMORY_SIZE, str_c(path));
+       return 0;
+}
+
+/*
+ * BURL command
+ */
+
+/* FIXME: RFC 4468
+   If the  URL argument to BURL refers to binary data, then the submit server
+   MAY refuse the command or down convert as described in Binary SMTP.
+ */
+
+struct cmd_burl_context {
+       struct client *client;
+       struct smtp_server_cmd_ctx *cmd;
+
+       struct imap_urlauth_fetch *urlauth_fetch;
+       struct imap_msgpart_url *url_fetch;
+
+       bool chunk_last:1;
+};
+
+static void
+cmd_burl_destroy(struct smtp_server_cmd_ctx *cmd)
+{
+       struct cmd_burl_context *burl_cmd =
+               (struct cmd_burl_context *)cmd->context;
+
+       if (burl_cmd->urlauth_fetch != NULL)
+               imap_urlauth_fetch_deinit(&burl_cmd->urlauth_fetch);
+       if (burl_cmd->url_fetch != NULL)
+               imap_msgpart_url_free(&burl_cmd->url_fetch);
+}
+
+static int
+cmd_burl_fetch_cb(struct imap_urlauth_fetch_reply *reply,
+                 bool last, void *context)
+{
+       struct cmd_burl_context *burl_cmd = (struct cmd_burl_context *)context;
+       struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd;
+       int ret;
+
+       i_assert(last);
+
+       if (reply == NULL) {
+               /* fatal failure */
+               // FIXME: make this an internal error
+               smtp_server_reply(cmd, 554, "5.6.6",
+                       "IMAP URLAUTH resolution failed");
+               return -1;
+       }
+       if (!reply->succeeded) {
+               /* URL fetch failed */
+               if (reply->error != NULL) {
+                       smtp_server_reply(cmd, 554, "5.6.6",
+                               "IMAP URLAUTH resolution failed: %s",
+                               reply->error);
+               } else {
+                       smtp_server_reply(cmd, 554, "5.6.6",
+                               "IMAP URLAUTH resolution failed");
+               }
+               return 1;
+       }
+
+       /* URL fetch succeeded */
+       ret = smtp_server_connection_data_chunk_add(cmd,
+               reply->input, reply->size, burl_cmd->chunk_last, FALSE);
+       if (ret < 0)
+               return -1;
+
+       /* Command is likely not yet complete at this point, so return 0 */
+       return 0;
+}
+
+static int
+cmd_burl_fetch_trusted(struct cmd_burl_context *burl_cmd,
+                      struct imap_url *imap_url)
+{
+       struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd;
+       struct client *client = burl_cmd->client;
+       const char *host_name = client->set->imap_urlauth_host;
+       in_port_t host_port = client->set->imap_urlauth_port;
+       struct imap_msgpart_open_result result;
+       const char *error;
+
+       /* validate host */
+       if (imap_url->host.name == NULL ||
+               (strcmp(host_name, URL_HOST_ALLOW_ANY) != 0 &&
+                 strcmp(imap_url->host.name, host_name) != 0)) {
+               smtp_server_reply(cmd, 554, "5.6.6",
+                       "IMAP URL resolution failed: "
+                       "Inappropriate or missing host name");
+               return -1;
+       }
+
+       /* validate port */
+       if ((imap_url->port == 0 && host_port != 143) ||
+               (imap_url->port != 0 && host_port != imap_url->port)) {
+               smtp_server_reply(cmd, 554, "5.6.6",
+                       "IMAP URL resolution failed: "
+                       "Inappropriate server port");
+               return -1;
+       }
+
+       /* retrieve URL */
+       if (imap_msgpart_url_create
+               (client->user, imap_url, &burl_cmd->url_fetch, &error) < 0) {
+               smtp_server_reply(cmd, 554, "5.6.6",
+                       "IMAP URL resolution failed: %s", error);
+               return -1;
+       }
+       if (imap_msgpart_url_read_part(burl_cmd->url_fetch,
+               &result, &error) <= 0) {
+               smtp_server_reply(cmd, 554, "5.6.6",
+                       "IMAP URL resolution failed: %s", error);
+               return -1;
+       }
+
+       return smtp_server_connection_data_chunk_add(cmd,
+               result.input, result.size, burl_cmd->chunk_last, FALSE);
+}
+
+static int
+cmd_burl_fetch(struct cmd_burl_context *burl_cmd, const char *url,
+              struct imap_url *imap_url)
+{
+       struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd;
+       struct client *client = burl_cmd->client;
+
+       if (client->urlauth_ctx == NULL) {
+               smtp_server_reply(cmd, 503, "5.3.3",
+                       "No IMAP URLAUTH access available");
+               return -1;
+       }
+
+       /* urlauth */
+       burl_cmd->urlauth_fetch =
+               imap_urlauth_fetch_init(client->urlauth_ctx,
+                                       cmd_burl_fetch_cb, burl_cmd);
+       if (imap_urlauth_fetch_url_parsed(burl_cmd->urlauth_fetch,
+               url, imap_url, IMAP_URLAUTH_FETCH_FLAG_BODY) == 0) {
+               /* wait for URL fetch */
+               return 0;
+       }
+       return 1;
+}
+
+void cmd_burl(struct smtp_server_cmd_ctx *cmd, const char *params)
+{
+       struct smtp_server_connection *conn = cmd->conn;
+       struct client *client =
+               (struct client *)smtp_server_connection_get_context(conn);
+       struct cmd_burl_context *burl_cmd;
+       const char *const *argv;
+       enum imap_url_parse_flags url_parse_flags =
+               IMAP_URL_PARSE_ALLOW_URLAUTH;
+       struct imap_url *imap_url;
+       const char *url, *error;
+       bool chunk_last = FALSE;
+       int ret = 1;
+
+       smtp_server_connection_data_chunk_init(cmd);
+
+       /* burl-cmd   = "BURL" SP absolute-URI [SP end-marker] CRLF
+          end-marker = "LAST"
+        */
+       argv = t_strsplit(params, " ");
+       url = argv[0];
+       if (url == NULL) {
+               smtp_server_reply(cmd, 501, "5.5.4",
+                       "Missing chunk URL parameter");
+               ret = -1;
+       } else if (imap_url_parse(url, NULL, url_parse_flags,
+                                 &imap_url, &error) < 0) {
+               smtp_server_reply(cmd, 501, "5.5.4",
+                       "Invalid chunk URL: %s", error);
+               ret = -1;
+       } else if (argv[1] != NULL) {
+               if (strcasecmp(argv[1], "LAST") != 0) {
+                       smtp_server_reply(cmd, 501, "5.5.4",
+                               "Invalid end marker parameter");
+                       ret = -1;
+               } else if (argv[2] != NULL) {
+                       smtp_server_reply(cmd, 501, "5.5.4",
+                               "Invalid parameters");
+                       ret = -1;
+               } else {
+                       chunk_last = TRUE;
+               }
+       }
+
+       if (ret < 0 || !smtp_server_connection_data_check_state(cmd))
+               return;
+
+       burl_cmd = p_new(cmd->pool, struct cmd_burl_context, 1);
+       burl_cmd->client = client;
+       burl_cmd->cmd = cmd;
+       burl_cmd->chunk_last = chunk_last;
+
+       cmd->context = (void*)burl_cmd;
+       cmd->hook_destroy = cmd_burl_destroy;
+
+       if (imap_url->uauth_rumpurl == NULL) {
+               /* direct local url */
+               ret = cmd_burl_fetch_trusted(burl_cmd, imap_url);
+       } else {
+               ret = cmd_burl_fetch(burl_cmd, url, imap_url);
+       }
+
+       if (ret == 0 && chunk_last)
+               smtp_server_command_input_lock(cmd);
+}
diff --git a/src/submission/cmd-helo.c b/src/submission/cmd-helo.c
new file mode 100644 (file)
index 0000000..4be1de9
--- /dev/null
@@ -0,0 +1,203 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "str.h"
+#include "ostream.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+
+#include "submission-commands.h"
+
+/*
+ * EHLO, HELO commands
+ */
+
+struct cmd_helo_context {
+       struct client *client;
+       struct smtp_server_cmd_ctx *cmd;
+       struct smtp_server_cmd_helo *data;
+
+       struct smtp_client_command *cmd_proxied;
+};
+
+static void cmd_helo_update_xclient(struct client *client,
+                                   struct smtp_server_cmd_helo *data)
+{
+       struct smtp_proxy_data proxy_data;
+
+       if (!client->set->submission_relay_trusted)
+               return;
+
+       i_zero(&proxy_data);
+       proxy_data.helo = data->helo.domain;
+       proxy_data.proto = (data->helo.old_smtp ?
+               SMTP_PROXY_PROTOCOL_SMTP : SMTP_PROXY_PROTOCOL_ESMTP);
+
+       (void)smtp_client_connection_send_xclient
+               (client->proxy_conn, &proxy_data);
+       client->xclient_sent = TRUE;
+}
+
+static void cmd_helo_do_reply(struct client *client,
+                             struct smtp_server_cmd_ctx *cmd)
+{
+       struct cmd_helo_context *helo =
+               (struct cmd_helo_context *)cmd->context;
+       enum smtp_capability proxy_caps =
+               smtp_client_connection_get_capabilities(client->proxy_conn);
+       struct smtp_server_reply *reply;
+       uoff_t cap_size;
+
+       reply = smtp_server_reply_create_ehlo(cmd->cmd);
+       if (!helo->data->helo.old_smtp) {
+               string_t *burl_params = t_str_new(256);
+
+               str_append(burl_params, "imap");
+               if (*client->set->imap_urlauth_host == '\0' ||
+                       strcmp(client->set->imap_urlauth_host,
+                              URL_HOST_ALLOW_ANY) == 0) {
+                       str_printfa(burl_params, " imap://%s",
+                                   client->set->hostname);
+               } else {
+                       str_printfa(burl_params, " imap://%s",
+                                   client->set->imap_urlauth_host);
+               }
+               if (client->set->imap_urlauth_port != 143) {
+                       str_printfa(burl_params, ":%u",
+                                   client->set->imap_urlauth_port);
+               }
+
+               if ((proxy_caps & SMTP_CAPABILITY_8BITMIME) != 0)
+                       smtp_server_reply_ehlo_add(reply, "8BITMIME");
+               smtp_server_reply_ehlo_add(reply, "AUTH");
+               if ((proxy_caps & SMTP_CAPABILITY_BINARYMIME) != 0 &&
+                       (proxy_caps & SMTP_CAPABILITY_CHUNKING) != 0)
+                       smtp_server_reply_ehlo_add(reply, "BINARYMIME");
+               smtp_server_reply_ehlo_add_param(reply,
+                       "BURL", "%s", str_c(burl_params));
+               smtp_server_reply_ehlo_add(reply, "CHUNKING");
+               if ((proxy_caps & SMTP_CAPABILITY_DSN) != 0)
+                       smtp_server_reply_ehlo_add(reply, "DSN");
+               smtp_server_reply_ehlo_add(reply,
+                       "ENHANCEDSTATUSCODES");
+               smtp_server_reply_ehlo_add(reply,
+                       "PIPELINING");
+
+               cap_size = client_get_max_mail_size(client);
+               if (cap_size > 0) {
+                       smtp_server_reply_ehlo_add_param(reply,
+                               "SIZE", "%"PRIuSIZE_T, cap_size);
+               } else {
+                       smtp_server_reply_ehlo_add(reply, "SIZE");
+               }
+               smtp_server_reply_ehlo_add(reply, "VRFY");
+       }
+       smtp_server_reply_submit(reply);
+}
+
+static void cmd_helo_reply(struct smtp_server_cmd_ctx *cmd)
+{
+       struct cmd_helo_context *helo =
+               (struct cmd_helo_context *)cmd->context;
+       struct client *client = helo->client;
+
+       /* proxy an XCLIENT command */
+       if (helo->data->changed)
+               cmd_helo_update_xclient(client, helo->data);
+
+       T_BEGIN {
+               cmd_helo_do_reply(client, cmd);
+       } T_END;
+}
+
+static void cmd_helo_proxy_cb(const struct smtp_reply *proxy_reply,
+                             struct cmd_helo_context *cmd_helo)
+{
+       struct smtp_server_cmd_ctx *cmd = cmd_helo->cmd;
+       struct client *client = cmd_helo->client;
+       struct smtp_reply reply;
+
+       client->pending_helo = NULL;
+
+       if (!client_command_handle_proxy_reply(client, proxy_reply, &reply))
+               return;
+
+       if ((proxy_reply->status / 100) == 2) {
+               cmd_helo_reply(cmd);
+       } else {
+               /* RFC 2034, Section 4:
+
+                  These codes must appear in all 2xx, 4xx, and 5xx response
+                  lines other than initial greeting and any response to HELO
+                  or EHLO.
+                */
+               reply.enhanced_code = SMTP_REPLY_ENH_CODE_NONE;
+               smtp_server_reply_forward(cmd, &reply);
+       }
+}
+
+static void cmd_helo_start(struct smtp_server_cmd_ctx *cmd)
+{
+       struct cmd_helo_context *helo =
+               (struct cmd_helo_context *)cmd->context;
+       struct client *client = helo->client;
+
+       /* proxy an XCLIENT command */
+       if (helo->data->changed)
+               cmd_helo_update_xclient(client, helo->data);
+}
+
+int cmd_helo(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            struct smtp_server_cmd_helo *data)
+{
+       struct client *client = (struct client *)conn_ctx;
+       struct cmd_helo_context *helo;
+
+       helo = p_new(cmd->pool, struct cmd_helo_context, 1);
+       helo->client = client;
+       helo->cmd = cmd;
+       helo->data = data;
+       cmd->context = (void*)helo;
+
+       if (!client_proxy_is_ready(client)) {
+               if (client_proxy_is_disconnected(client)) {
+                       /* proxy connection died already */
+                       client_destroy(client,
+                               t_strdup_printf("421 %s", client->set->hostname),
+                               "Lost connection to relay server");
+                       return -1;
+               }
+
+               if (client->pending_helo == NULL)
+                       client->pending_helo = cmd;
+
+               /* wait for proxy to become ready */
+               return 0;
+       }
+
+       if (!data->first || smtp_server_connection_get_state(client->conn)
+               >= SMTP_SERVER_STATE_READY) {
+               /* this is not the first HELO/EHLO; just proxy a RSET command */
+               cmd->hook_next = cmd_helo_start;
+               helo->cmd_proxied = smtp_client_command_rset_submit
+                       (client->proxy_conn, 0, cmd_helo_proxy_cb, helo);
+               return 0;
+       }
+
+       /* respond right away */
+       cmd_helo_reply(cmd);
+       return 1;
+}
+
+void client_handshake(struct client *client)
+{
+       struct smtp_server_cmd_ctx *cmd;
+
+       if (client->pending_helo == NULL)
+               return;
+       cmd = client->pending_helo;
+       client->pending_helo = NULL;
+
+       /* continue EHLO/HELO response */
+       cmd_helo_reply(cmd);
+}
diff --git a/src/submission/cmd-mail.c b/src/submission/cmd-mail.c
new file mode 100644 (file)
index 0000000..9b62d18
--- /dev/null
@@ -0,0 +1,161 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "str.h"
+#include "mail-user.h"
+#include "smtp-syntax.h"
+#include "smtp-address.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+
+#include "submission-commands.h"
+
+/*
+ * MAIL command
+ */
+
+struct cmd_mail_context {
+       struct client *client;
+       struct smtp_server_cmd_ctx *cmd;
+       struct smtp_server_cmd_mail *data;
+
+       struct smtp_client_command *cmd_proxied;
+};
+
+static void cmd_mail_update_xclient(struct client *client)
+{
+       struct smtp_proxy_data proxy_data;
+       struct smtp_server_helo_data *helo_data =
+               smtp_server_connection_get_helo_data(client->conn);
+
+       if (client->xclient_sent)
+               return;
+       if (!client->set->submission_relay_trusted)
+               return;
+       if (helo_data->domain == NULL)
+               return;
+
+       i_zero(&proxy_data);
+       proxy_data.helo = helo_data->domain;
+       proxy_data.proto = SMTP_PROXY_PROTOCOL_ESMTP;
+
+       (void)smtp_client_connection_send_xclient(
+               client->proxy_conn, &proxy_data);
+       client->xclient_sent = TRUE;
+}
+
+static void cmd_mail_replied(struct smtp_server_cmd_ctx *cmd)
+{
+       struct cmd_mail_context *mail_cmd =
+               (struct cmd_mail_context *)cmd->context;
+
+       if (mail_cmd->cmd_proxied != NULL)
+               smtp_client_command_abort(&mail_cmd->cmd_proxied);
+}
+
+static void cmd_mail_proxy_cb(const struct smtp_reply *proxy_reply,
+                             struct cmd_mail_context *mail_cmd)
+{
+       struct smtp_server_cmd_ctx *cmd = mail_cmd->cmd;
+       struct client *client = mail_cmd->client;
+       struct smtp_reply reply;
+
+       /* finished proxying MAIL command to relay server */
+       i_assert(mail_cmd != NULL);
+       mail_cmd->cmd_proxied = NULL;
+
+       if (!client_command_handle_proxy_reply(client, proxy_reply, &reply))
+               return;
+
+       if ((proxy_reply->status / 100) == 2) {
+               /* if relay accepts it, we accept it too */
+
+               /* the default 2.0.0 code won't do */
+               if (!smtp_reply_has_enhanced_code(proxy_reply))
+                       reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 1, 0);
+       }
+
+       /* forward reply */
+       smtp_server_reply_forward(cmd, &reply);
+}
+
+static int
+cmd_mail_parameter_auth(struct client *client,
+                       struct smtp_server_cmd_ctx *cmd,
+                       enum smtp_capability proxy_caps,
+                       struct smtp_server_cmd_mail *data)
+{
+       struct smtp_params_mail *params = &data->params;
+       struct smtp_address *auth_addr;
+       const char *error;
+
+       if ((proxy_caps & SMTP_CAPABILITY_AUTH) == 0)
+               return 0;
+
+       auth_addr = NULL;
+       if (smtp_address_parse_username(cmd->pool,
+               client->user->username,
+               &auth_addr, &error) < 0) {
+               i_warning("Username `%s' is not a valid SMTP address: %s",
+                       client->user->username, error);
+       }
+
+       params->auth = auth_addr;
+       return 0;
+}
+
+static int
+cmd_mail_parameter_size(struct client *client,
+                       struct smtp_server_cmd_ctx *cmd,
+                       enum smtp_capability proxy_caps,
+                       struct smtp_server_cmd_mail *data)
+{
+       uoff_t max_size;
+
+       /* SIZE=<size-value>: RFC 1870 */
+
+       if (data->params.size == 0 || (proxy_caps & SMTP_CAPABILITY_SIZE) == 0)
+               return 0;
+
+       /* determine actual size limit (account for our additions) */
+       max_size = client_get_max_mail_size(client);
+       if (max_size > 0 && data->params.size > max_size) {
+               smtp_server_reply(cmd, 552, "5.3.4",
+                       "Message size exceeds fixed maximum message size");
+               return -1;
+       }
+
+       /* proxy the SIZE parameter (account for additional size) */
+       data->params.size += SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE;
+       return 0;
+}
+
+int cmd_mail(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            struct smtp_server_cmd_mail *data)
+{
+       struct client *client = (struct client *)conn_ctx;
+       struct cmd_mail_context *mail_cmd;
+       enum smtp_capability proxy_caps =
+               smtp_client_connection_get_capabilities(client->proxy_conn);
+
+       /* check and adjust parameters where necessary */
+       if (cmd_mail_parameter_auth(client, cmd, proxy_caps, data) < 0)
+               return -1;
+       if (cmd_mail_parameter_size(client, cmd, proxy_caps, data) < 0)
+               return -1;
+
+       cmd_mail_update_xclient(client);
+
+       /* queue command (pipeline) */
+       mail_cmd = p_new(cmd->pool, struct cmd_mail_context, 1);
+       mail_cmd->cmd = cmd;
+       mail_cmd->data = data;
+       mail_cmd->client = client;
+
+       cmd->context = (void*)mail_cmd;
+       cmd->hook_replied = cmd_mail_replied;
+       mail_cmd->cmd_proxied = smtp_client_command_mail_submit(
+               client->proxy_conn, 0, data->path, &data->params,
+               cmd_mail_proxy_cb, mail_cmd);
+       return 0;
+}
diff --git a/src/submission/cmd-noop.c b/src/submission/cmd-noop.c
new file mode 100644 (file)
index 0000000..0ede6db
--- /dev/null
@@ -0,0 +1,49 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+
+#include "submission-commands.h"
+
+/*
+ * NOOP command
+ */
+
+struct cmd_noop_context {
+       struct client *client;
+       struct smtp_server_cmd_ctx *cmd;
+       struct smtp_client_command *cmd_proxied;
+};
+
+static void cmd_noop_proxy_cb(const struct smtp_reply *proxy_reply,
+                             struct cmd_noop_context *noop_cmd)
+{
+       struct smtp_server_cmd_ctx *cmd = noop_cmd->cmd;
+       struct client *client = noop_cmd->client;
+       struct smtp_reply reply;
+
+       if (!client_command_handle_proxy_reply(client, proxy_reply, &reply))
+               return;
+
+       if ((proxy_reply->status / 100) == 2) {
+               smtp_server_reply(cmd, 250, "2.0.0", "OK");
+       } else {
+               smtp_server_reply_forward(cmd, &reply);
+       }
+}
+
+int cmd_noop(void *conn_ctx, struct smtp_server_cmd_ctx *cmd)
+{
+       struct client *client = (struct client *)conn_ctx;
+       struct cmd_noop_context *noop_cmd;
+
+       noop_cmd = p_new(cmd->pool, struct cmd_noop_context, 1);
+       noop_cmd->client = client;
+       noop_cmd->cmd = cmd;
+       cmd->context = (void*)noop_cmd;
+
+       noop_cmd->cmd_proxied = smtp_client_command_noop_submit
+               (client->proxy_conn, 0, cmd_noop_proxy_cb, noop_cmd);
+       return 0;
+}
diff --git a/src/submission/cmd-quit.c b/src/submission/cmd-quit.c
new file mode 100644 (file)
index 0000000..78904df
--- /dev/null
@@ -0,0 +1,43 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+
+#include "submission-commands.h"
+
+/*
+ * QUIT command
+ */
+
+struct cmd_quit_context {
+       struct client *client;
+       struct smtp_server_cmd_ctx *cmd;
+       struct smtp_client_command *cmd_proxied;
+};
+
+static void cmd_quit_proxy_cb(
+       const struct smtp_reply *proxy_reply ATTR_UNUSED,
+       struct cmd_quit_context *quit_cmd)
+{
+       struct smtp_server_cmd_ctx *cmd = quit_cmd->cmd;
+
+       smtp_server_reply_quit(cmd);
+}
+
+int cmd_quit(void *conn_ctx, struct smtp_server_cmd_ctx *cmd)
+{
+       struct client *client = (struct client *)conn_ctx;
+       struct cmd_quit_context *quit_cmd;
+
+       quit_cmd = p_new(cmd->pool, struct cmd_quit_context, 1);
+       quit_cmd->client = client;
+       quit_cmd->cmd = cmd;
+       cmd->context = (void*)quit_cmd;
+
+       quit_cmd->cmd_proxied = smtp_client_command_new
+               (client->proxy_conn, 0, cmd_quit_proxy_cb, quit_cmd);
+       smtp_client_command_write(quit_cmd->cmd_proxied, "QUIT");
+       smtp_client_command_submit(quit_cmd->cmd_proxied); // FIXME: timeout
+       return 0;
+}
diff --git a/src/submission/cmd-rcpt.c b/src/submission/cmd-rcpt.c
new file mode 100644 (file)
index 0000000..a965582
--- /dev/null
@@ -0,0 +1,77 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "str.h"
+#include "array.h"
+#include "smtp-parser.h"
+#include "smtp-address.h"
+#include "smtp-syntax.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+
+#include "submission-commands.h"
+
+/*
+ * RCPT command
+ */
+
+struct cmd_rcpt_context {
+       struct client *client;
+       struct smtp_server_cmd_ctx *cmd;
+       struct smtp_server_cmd_rcpt *data;
+
+       struct smtp_client_command *cmd_proxied;
+};
+
+static void cmd_rcpt_replied(struct smtp_server_cmd_ctx *cmd)
+{
+       struct cmd_rcpt_context *rcpt_cmd =
+               (struct cmd_rcpt_context *)cmd->context;
+
+       if (rcpt_cmd->cmd_proxied != NULL)
+               smtp_client_command_abort(&rcpt_cmd->cmd_proxied);
+}
+
+static void cmd_rcpt_proxy_cb(const struct smtp_reply *proxy_reply,
+                             struct cmd_rcpt_context *rcpt_cmd)
+{
+       struct smtp_server_cmd_ctx *cmd = rcpt_cmd->cmd;
+       struct client *client = rcpt_cmd->client;
+       struct smtp_reply reply;
+
+       /* finished proxying MAIL command to relay server */
+       i_assert(rcpt_cmd != NULL);
+       rcpt_cmd->cmd_proxied = NULL;
+
+       if (!client_command_handle_proxy_reply(client, proxy_reply, &reply))
+               return;
+
+       if ((proxy_reply->status / 100) == 2) {
+               /* the default 2.0.0 code won't do */
+               if (!smtp_reply_has_enhanced_code(proxy_reply))
+                       reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 1, 5);
+       }
+
+       /* forward reply */
+       smtp_server_reply_forward(cmd, &reply);
+}
+
+int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            struct smtp_server_cmd_rcpt *data)
+{
+       struct client *client = (struct client *)conn_ctx;
+       struct cmd_rcpt_context *rcpt_cmd;
+
+       /* queue command (pipeline) */
+       rcpt_cmd = p_new(cmd->pool, struct cmd_rcpt_context, 1);
+       rcpt_cmd->cmd = cmd;
+       rcpt_cmd->data = data;
+       rcpt_cmd->client = client;
+
+       cmd->context = (void*)rcpt_cmd;
+       cmd->hook_replied = cmd_rcpt_replied;
+       rcpt_cmd->cmd_proxied = smtp_client_command_rcpt_submit(
+               client->proxy_conn, 0, data->path, &data->params,
+               cmd_rcpt_proxy_cb, rcpt_cmd);
+       return 0;
+}
diff --git a/src/submission/cmd-rset.c b/src/submission/cmd-rset.c
new file mode 100644 (file)
index 0000000..578851c
--- /dev/null
@@ -0,0 +1,51 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+
+#include "submission-commands.h"
+
+/*
+ * RSET command
+ */
+
+struct cmd_rset_context {
+       struct client *client;
+       struct smtp_server_cmd_ctx *cmd;
+
+       struct smtp_client_command *cmd_proxied;
+};
+
+static void cmd_rset_proxy_cb(const struct smtp_reply *proxy_reply,
+                             struct cmd_rset_context *rset_cmd)
+{
+       struct smtp_server_cmd_ctx *cmd = rset_cmd->cmd;
+       struct client *client = rset_cmd->client;
+       struct smtp_reply reply;
+
+       /* finished proxying MAIL command to relay server */
+       i_assert(rset_cmd != NULL);
+       rset_cmd->cmd_proxied = NULL;
+
+       if (!client_command_handle_proxy_reply(client, proxy_reply, &reply))
+               return;
+
+       /* forward reply */
+       smtp_server_reply_forward(cmd, &reply);
+}
+
+int cmd_rset(void *conn_ctx, struct smtp_server_cmd_ctx *cmd)
+{
+       struct client *client = (struct client *)conn_ctx;
+       struct cmd_rset_context *rset_cmd;
+
+       rset_cmd = p_new(cmd->pool, struct cmd_rset_context, 1);
+       rset_cmd->cmd = cmd;
+       rset_cmd->client = client;
+
+       cmd->context = (void*)rset_cmd;
+       rset_cmd->cmd_proxied = smtp_client_command_rset_submit
+               (client->proxy_conn, 0, cmd_rset_proxy_cb, rset_cmd);
+       return 0;
+}
diff --git a/src/submission/cmd-vrfy.c b/src/submission/cmd-vrfy.c
new file mode 100644 (file)
index 0000000..ca3e554
--- /dev/null
@@ -0,0 +1,59 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "smtp-syntax.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+
+#include "submission-commands.h"
+
+/*
+ * VRFY command
+ */
+
+struct cmd_vrfy_context {
+       struct client *client;
+       struct smtp_server_cmd_ctx *cmd;
+       struct smtp_client_command *cmd_proxied;
+};
+
+static void cmd_vrfy_proxy_cb(const struct smtp_reply *proxy_reply,
+                             struct cmd_vrfy_context *vrfy_cmd)
+{
+       struct smtp_server_cmd_ctx *cmd = vrfy_cmd->cmd;
+       struct client *client = vrfy_cmd->client;
+       struct smtp_reply reply;
+
+       if (!client_command_handle_proxy_reply(client, proxy_reply, &reply))
+               return;
+
+       if (!smtp_reply_has_enhanced_code(proxy_reply)) {
+               switch (proxy_reply->status) {
+               case 250:
+               case 251:
+               case 252:
+                       reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 5, 0);
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       smtp_server_reply_forward(cmd, &reply);
+}
+
+int cmd_vrfy(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            const char *param)
+{
+       struct client *client = (struct client *)conn_ctx;
+       struct cmd_vrfy_context *vrfy_cmd;
+
+       vrfy_cmd = p_new(cmd->pool, struct cmd_vrfy_context, 1);
+       vrfy_cmd->client = client;
+       vrfy_cmd->cmd = cmd;
+       cmd->context = (void*)vrfy_cmd;
+
+       vrfy_cmd->cmd_proxied = smtp_client_command_vrfy_submit(
+               client->proxy_conn, 0, param, cmd_vrfy_proxy_cb, vrfy_cmd);
+       return 0;
+}
diff --git a/src/submission/main.c b/src/submission/main.c
new file mode 100644 (file)
index 0000000..1029c3c
--- /dev/null
@@ -0,0 +1,378 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "buffer.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "array.h"
+#include "base64.h"
+#include "hostpid.h"
+#include "path-util.h"
+#include "process-title.h"
+#include "restrict-access.h"
+#include "fd-util.h"
+#include "master-service.h"
+#include "master-login.h"
+#include "master-service-settings.h"
+#include "master-interface.h"
+#include "var-expand.h"
+#include "mail-error.h"
+#include "mail-user.h"
+#include "mail-storage-service.h"
+#include "smtp-server.h"
+#include "smtp-client.h"
+
+#include "submission-commands.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#define LMTP_MASTER_FIRST_LISTEN_FD 3
+
+#define IS_STANDALONE() \
+        (getenv(MASTER_IS_PARENT_ENV) == NULL)
+
+struct smtp_server *smtp_server = NULL;
+struct smtp_client *smtp_client = NULL;
+
+static bool verbose_proctitle = FALSE;
+static struct mail_storage_service_ctx *storage_service;
+static struct master_login *master_login = NULL;
+
+submission_client_created_func_t *hook_client_created = NULL;
+bool submission_debug = FALSE;
+
+submission_client_created_func_t *
+submission_client_created_hook_set(submission_client_created_func_t *new_hook)
+{
+       submission_client_created_func_t *old_hook = hook_client_created;
+
+       hook_client_created = new_hook;
+       return old_hook;
+}
+
+void submission_refresh_proctitle(void)
+{
+       struct client *client;
+       string_t *title = t_str_new(128);
+
+       if (!verbose_proctitle)
+               return;
+
+       str_append_c(title, '[');
+       switch (submission_client_count) {
+       case 0:
+               str_append(title, "idling");
+               break;
+       case 1:
+               client = submission_clients;
+               str_append(title, client->user->username);
+               if (client->user->conn.remote_ip != NULL) {
+                       str_append_c(title, ' ');
+                       str_append(title,
+                                  net_ip2addr(client->user->conn.remote_ip));
+               }
+               str_append_c(title, ' ');
+               str_append(title, client_state_get_name(client));
+               break;
+       default:
+               str_printfa(title, "%u connections", submission_client_count);
+               break;
+       }
+       str_append_c(title, ']');
+       process_title_set(str_c(title));
+}
+
+static void submission_die(void)
+{
+       /* do nothing. submission connections typically die pretty quick anyway.
+        */
+}
+
+static void
+send_error(int fd_out, const char *hostname, const char *error_code,
+          const char *error_msg)
+{
+       const char *msg;
+
+       msg = t_strdup_printf("451 %s %s\r\n"
+               "421 4.3.2 %s Shutting down due to fatal error\r\n",
+               error_code, error_msg, hostname);
+       if (write(fd_out, msg, strlen(msg)) < 0) {
+               if (errno != EAGAIN && errno != EPIPE)
+                       i_error("write(client) failed: %m");
+       }
+}
+
+static int
+client_create_from_input(const struct mail_storage_service_input *input,
+                        int fd_in, int fd_out, const buffer_t *input_buf,
+                        const char **error_r)
+{
+       struct mail_storage_service_user *user;
+       struct mail_user *mail_user;
+       const struct submission_settings *set;
+       const char *helo = NULL;
+       const unsigned char *data;
+       size_t data_len;
+
+       if (mail_storage_service_lookup_next(storage_service, input,
+                                            &user, &mail_user, error_r) <= 0) {
+               send_error(fd_out, my_hostname,
+                       "4.7.0", MAIL_ERRSTR_CRITICAL_MSG);
+               return -1;
+       }
+       restrict_access_allow_coredumps(TRUE);
+
+       set = mail_storage_service_user_get_set(user)[1];
+
+       if (set->submission_relay_host == NULL ||
+               *set->submission_relay_host == '\0') {
+               *error_r = "No relay host configured for submission proxy "
+                       "(submission_relay_host is unset)";
+               send_error(fd_out, set->hostname,
+                       "4.3.5", MAIL_ERRSTR_CRITICAL_MSG);
+               return -1;
+       }
+       if (set->verbose_proctitle)
+               verbose_proctitle = TRUE;
+
+       /* parse input data */
+       data = NULL;
+       data_len = 0;
+       if (input_buf != NULL && input_buf->used > 0) {
+               size_t len = input_buf->used, helo_len = 0;
+
+               data = input_buf->data;
+
+               if (len > 0) {
+                       if (*data == '\0') {
+                               helo_len = 1;
+                       } else {
+                               helo = t_strndup(data, len);
+                               helo_len = strlen(helo) + 1;
+                       }
+               }
+
+               /* NOTE: actually, pipelining the AUTH command is stricly
+                        speaking not allowed, but we support it anyway.
+                */
+               if (len > helo_len) {
+                       data = data + helo_len;
+                       data_len = len - helo_len;
+               }
+       }
+
+       (void)client_create(fd_in, fd_out, input->session_id, mail_user,
+                           user, set, helo, data, data_len);
+       return 0;
+}
+
+static void main_stdio_run(const char *username)
+{
+       struct mail_storage_service_input input;
+       buffer_t *input_buf;
+       const char *value, *error, *input_base64;
+
+       i_zero(&input);
+       input.module = input.service = "submission";
+       input.username = username != NULL ? username : getenv("USER");
+       if (input.username == NULL && IS_STANDALONE())
+               input.username = getlogin();
+       if (input.username == NULL)
+               i_fatal("USER environment missing");
+       if ((value = getenv("IP")) != NULL)
+               (void)net_addr2ip(value, &input.remote_ip);
+       if ((value = getenv("LOCAL_IP")) != NULL)
+               (void)net_addr2ip(value, &input.local_ip);
+
+       input_base64 = getenv("CLIENT_INPUT");
+       input_buf = input_base64 == NULL ? NULL :
+               t_base64_decode_str(input_base64);
+
+       if (client_create_from_input(&input, STDIN_FILENO, STDOUT_FILENO,
+                                    input_buf, &error) < 0)
+               i_fatal("%s", error);
+}
+
+static void
+login_client_connected(const struct master_login_client *login_client,
+                      const char *username, const char *const *extra_fields)
+{
+       struct mail_storage_service_input input;
+       enum mail_auth_request_flags flags = login_client->auth_req.flags;
+       const char *error;
+       buffer_t input_buf;
+
+       i_zero(&input);
+       input.module = input.service = "submission";
+       input.local_ip = login_client->auth_req.local_ip;
+       input.remote_ip = login_client->auth_req.remote_ip;
+       input.local_port = login_client->auth_req.local_port;
+       input.remote_port = login_client->auth_req.remote_port;
+       input.username = username;
+       input.userdb_fields = extra_fields;
+       input.session_id = login_client->session_id;
+       if ((flags & MAIL_AUTH_REQUEST_FLAG_CONN_SECURED) != 0)
+               input.conn_secured = TRUE;
+       if ((flags & MAIL_AUTH_REQUEST_FLAG_CONN_SSL_SECURED) != 0)
+               input.conn_ssl_secured = TRUE;
+
+       buffer_create_from_const_data(&input_buf, login_client->data,
+                                     login_client->auth_req.data_size);
+       if (client_create_from_input(&input, login_client->fd, login_client->fd,
+                                    &input_buf, &error) < 0) {
+               int fd = login_client->fd;
+               i_error("%s", error);
+               i_close_fd(&fd);
+               master_service_client_connection_destroyed(master_service);
+       }
+}
+
+static void login_client_failed(const struct master_login_client *client,
+                               const char *errormsg)
+{
+       const char *msg;
+
+       msg = t_strdup_printf("451 4.7.0 %s\r\n"
+               "421 4.3.2 %s Shutting down due to fatal error\r\n",
+               errormsg, my_hostname);
+       if (write(client->fd, msg, strlen(msg)) < 0) {
+               /* ignored */
+       }
+}
+
+static void client_connected(struct master_service_connection *conn)
+{
+       /* when running standalone, we shouldn't even get here */
+       i_assert(master_login != NULL);
+
+       master_service_client_connection_accept(conn);
+       master_login_add(master_login, conn->fd);
+}
+
+int main(int argc, char *argv[])
+{
+       static const struct setting_parser_info *set_roots[] = {
+               &submission_setting_parser_info,
+               NULL
+       };
+       struct master_login_settings login_set;
+       enum master_service_flags service_flags = 0;
+       enum mail_storage_service_flags storage_service_flags = 0;
+       struct smtp_server_settings smtp_server_set;
+       struct smtp_client_settings smtp_client_set;
+       const char *username = NULL, *auth_socket_path = "auth-master";
+       const char *error;
+       int c;
+
+       i_zero(&login_set);
+       login_set.postlogin_timeout_secs = MASTER_POSTLOGIN_TIMEOUT_DEFAULT;
+       login_set.request_auth_token = TRUE;
+
+       if (IS_STANDALONE() && getuid() == 0 &&
+           net_getpeername(1, NULL, NULL) == 0) {
+               printf("421 5.3.5 The submission binary must not be started "
+                      "from inetd, use submission-login instead.\r\n");
+               return 1;
+       }
+
+       if (IS_STANDALONE()) {
+               service_flags |= MASTER_SERVICE_FLAG_STANDALONE |
+                       MASTER_SERVICE_FLAG_STD_CLIENT;
+       } else {
+               service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
+               storage_service_flags |=
+                       MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT;
+       }
+
+       master_service = master_service_init("submission", service_flags,
+                                            &argc, &argv, "a:Dt:u:");
+       while ((c = master_getopt(master_service)) > 0) {
+               switch (c) {
+               case 'a':
+                       auth_socket_path = optarg;
+                       break;
+               case 't':
+                       if (str_to_uint(optarg,
+                                       &login_set.postlogin_timeout_secs) < 0 ||
+                           login_set.postlogin_timeout_secs == 0)
+                               i_fatal("Invalid -t parameter: %s", optarg);
+                       break;
+               case 'u':
+                       storage_service_flags |=
+                               MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+                       username = optarg;
+                       break;
+               case 'D':
+                       submission_debug = TRUE;
+                       break;
+               default:
+                       return FATAL_DEFAULT;
+               }
+       }
+
+       if (t_abspath(auth_socket_path, &login_set.auth_socket_path,
+                     &error) < 0) {
+               i_fatal("t_abspath(%s) failed: %s", auth_socket_path,
+                       error);
+       }
+       if (argv[optind] != NULL) {
+               if (t_abspath(argv[optind],
+                             &login_set.postlogin_socket_path, &error) < 0) {
+                       i_fatal("t_abspath(%s) failed: %s",
+                               argv[optind], error);
+               }
+       }
+       login_set.callback = login_client_connected;
+       login_set.failure_callback = login_client_failed;
+
+       master_service_init_finish(master_service);
+       master_service_set_die_callback(master_service, submission_die);
+
+       storage_service =
+               mail_storage_service_init(master_service,
+                                         set_roots, storage_service_flags);
+
+       /* initialize SMTP server */
+       i_zero(&smtp_server_set);
+       smtp_server_set.capabilities = SMTP_CAPABILITY_DSN;
+       smtp_server_set.protocol = SMTP_PROTOCOL_SMTP;
+       smtp_server_set.max_pipelined_commands = 5;
+       smtp_server_set.debug = submission_debug;
+       smtp_server = smtp_server_init(&smtp_server_set);
+       smtp_server_command_register(smtp_server, "BURL", cmd_burl, 0);
+
+       /* initialize SMTP client */
+       i_zero(&smtp_client_set);
+       smtp_client_set.my_hostname = my_hostdomain();
+       smtp_client_set.debug = submission_debug;
+       smtp_client = smtp_client_init(&smtp_client_set);
+
+       /* fake that we're running, so we know if client was destroyed
+          while handling its initial input */
+       io_loop_set_running(current_ioloop);
+
+       if (IS_STANDALONE()) {
+               T_BEGIN {
+                       main_stdio_run(username);
+               } T_END;
+       } else {
+               master_login = master_login_init(master_service, &login_set);
+               io_loop_set_running(current_ioloop);
+       }
+
+       if (io_loop_is_running(current_ioloop))
+               master_service_run(master_service, client_connected);
+       clients_destroy_all();
+
+       smtp_client_deinit(&smtp_client);
+       smtp_server_deinit(&smtp_server);
+
+       if (master_login != NULL)
+               master_login_deinit(&master_login);
+       mail_storage_service_deinit(&storage_service);
+       master_service_deinit(&master_service);
+       return 0;
+}
diff --git a/src/submission/submission-client.c b/src/submission/submission-client.c
new file mode 100644 (file)
index 0000000..79de3cc
--- /dev/null
@@ -0,0 +1,457 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "array.h"
+#include "ioloop.h"
+#include "base64.h"
+#include "str.h"
+#include "llist.h"
+#include "net.h"
+#include "istream.h"
+#include "ostream.h"
+#include "iostream-ssl.h"
+#include "hostpid.h"
+#include "var-expand.h"
+#include "settings-parser.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mail-storage-service.h"
+#include "raw-storage.h"
+#include "imap-urlauth.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+
+#include "submission-commands.h"
+#include "submission-settings.h"
+
+#include <unistd.h>
+
+/* max. length of input command line */
+#define MAX_INBUF_SIZE 4096
+
+/* Stop reading input when output buffer has this many bytes. Once the buffer
+   size has dropped to half of it, start reading input again. */
+#define OUTBUF_THROTTLE_SIZE 4096
+
+/* Disconnect client when it sends too many bad commands in a row */
+#define CLIENT_MAX_BAD_COMMANDS 20
+
+/* Disconnect client after idling this many milliseconds */
+#define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000)
+
+struct client *submission_clients;
+unsigned int submission_client_count;
+
+static const struct smtp_server_callbacks smtp_callbacks;
+
+/* try to proxy pipelined commands in a similarly pipelined fashion */
+static void client_input_pre(void *context)
+{
+       struct client *client = (struct client *)context;
+
+       if (client->proxy_conn != NULL)
+               smtp_client_connection_cork(client->proxy_conn);
+}
+static void client_input_post(void *context)
+{
+       struct client *client = (struct client *)context;
+
+       if (client->proxy_conn != NULL)
+               smtp_client_connection_uncork(client->proxy_conn);
+}
+
+static const char *client_remote_id(struct client *client)
+{
+       const char *addr = NULL;
+
+       if (client->user->conn.remote_ip != NULL)
+               addr = net_ip2addr(client->user->conn.remote_ip);
+       if (addr == NULL)
+               addr = "local";
+       return addr;
+}
+
+static void client_proxy_ready_cb(const struct smtp_reply *reply,
+                                 void *context)
+{
+       struct client *client = (struct client *)context;
+       enum smtp_capability caps;
+
+       /* check proxy status */
+       if ((reply->status / 100) != 2) {
+               i_error("Failed to establish relay connection: %s",
+                       smtp_reply_log(reply));
+               client_destroy(client,
+                       "4.4.0", "Failed to establish relay connection");
+               return;
+       }
+
+       /* propagate capabilities */
+       caps = smtp_client_connection_get_capabilities(client->proxy_conn);
+       caps |= SMTP_CAPABILITY_AUTH | SMTP_CAPABILITY_PIPELINING |
+               SMTP_CAPABILITY_SIZE | SMTP_CAPABILITY_ENHANCEDSTATUSCODES |
+               SMTP_CAPABILITY_CHUNKING | SMTP_CAPABILITY_BURL |
+               SMTP_CAPABILITY_VRFY;
+       smtp_server_connection_set_capabilities(client->conn, caps);
+
+       /* send EHLO reply when EHLO command is pending */
+       client_handshake(client);
+}
+
+static void client_proxy_create(struct client *client,
+                               const struct submission_settings *set)
+{
+       struct mail_user *user = client->user;
+       struct ssl_iostream_settings ssl_set;
+       struct smtp_client_settings smtp_set;
+       enum smtp_client_connection_ssl_mode ssl_mode;
+
+       i_zero(&ssl_set);
+       mail_user_init_ssl_client_settings(user, &ssl_set);
+       ssl_set.allow_invalid_cert = !set->submission_relay_ssl_verify;
+
+       /* make proxy connection */
+       i_zero(&smtp_set);
+       smtp_set.my_hostname = set->hostname;
+       smtp_set.ssl = &ssl_set;
+       smtp_set.debug = user->mail_debug;
+       smtp_set.rawlog_dir =
+               mail_user_home_expand(user,
+                                     set->submission_relay_rawlog_dir);
+
+       if (set->submission_relay_trusted) {
+               smtp_set.peer_trusted = TRUE;
+
+               if (user->conn.remote_ip != NULL) {
+                       smtp_set.proxy_data.source_ip =
+                               *user->conn.remote_ip;
+                       smtp_set.proxy_data.source_port =
+                               user->conn.remote_port;
+               }
+               smtp_set.proxy_data.login = user->username;
+       }
+
+       smtp_set.username = set->submission_relay_user;
+       smtp_set.master_user = set->submission_relay_master_user;
+       smtp_set.password = set->submission_relay_password;
+
+       if (strcmp(set->submission_relay_ssl, "smtps") == 0)
+               ssl_mode = SMTP_CLIENT_SSL_MODE_IMMEDIATE;
+       else if (strcmp(set->submission_relay_ssl, "starttls") == 0)
+               ssl_mode = SMTP_CLIENT_SSL_MODE_STARTTLS;
+       else
+               ssl_mode = SMTP_CLIENT_SSL_MODE_NONE;
+
+       client->proxy_conn = smtp_client_connection_create(smtp_client,
+               SMTP_PROTOCOL_SMTP, set->submission_relay_host,
+               set->submission_relay_port, ssl_mode, &smtp_set);
+       smtp_client_connection_connect(client->proxy_conn,
+               client_proxy_ready_cb, client);
+}
+
+static void client_init_urlauth(struct client *client)
+{
+       static const char *access_apps[] = { "submit+", NULL };
+       struct imap_urlauth_config config;
+
+       i_zero(&config);
+       config.url_host = client->set->imap_urlauth_host;
+       config.url_port = client->set->imap_urlauth_port;
+       config.socket_path = t_strconcat(client->user->set->base_dir,
+                                        "/"IMAP_URLAUTH_SOCKET_NAME, NULL);
+       config.session_id = client->session_id;
+       config.access_anonymous = client->user->anonymous;
+       config.access_user = client->user->username;
+       config.access_service = "submission";
+       config.access_applications = access_apps;
+
+       client->urlauth_ctx = imap_urlauth_init(client->user, &config);
+}
+
+struct client *client_create(int fd_in, int fd_out,
+                            const char *session_id, struct mail_user *user,
+                            struct mail_storage_service_user *service_user,
+                            const struct submission_settings *set,
+                            const char *helo,
+                            const unsigned char *pdata, unsigned int pdata_len)
+{
+       const struct mail_storage_settings *mail_set;
+       struct smtp_server_settings smtp_set;
+       const char *ident;
+       struct client *client;
+
+       /* always use nonblocking I/O */
+       net_set_nonblock(fd_in, TRUE);
+       net_set_nonblock(fd_out, TRUE);
+
+       client = i_new(struct client, 1);
+       client->user = user;
+       client->service_user = service_user;
+       client->set = set;
+       client->session_id = i_strdup(session_id);
+
+       i_zero(&smtp_set);
+       smtp_set.hostname = set->hostname;
+       smtp_set.login_greeting = set->login_greeting;
+       smtp_set.max_recipients = set->submission_max_recipients;
+       smtp_set.max_client_idle_time_msecs = CLIENT_IDLE_TIMEOUT_MSECS;
+       smtp_set.debug = user->mail_debug;
+
+       client->conn = smtp_server_connection_create(smtp_server,
+               fd_in, fd_out, user->conn.remote_ip, user->conn.remote_port,
+               &smtp_set, &smtp_callbacks, client);
+       smtp_server_connection_login(client->conn,
+               client->user->username, helo,
+               pdata, pdata_len, user->conn.ssl_secured);
+
+       client_proxy_create(client, set);
+
+       mail_set = mail_user_set_get_storage_set(user);
+       if (*set->imap_urlauth_host != '\0' &&
+           *mail_set->mail_attribute_dict != '\0') {
+               /* Enable BURL capability only when urlauth dict is
+                  configured correctly */
+               client_init_urlauth(client);
+       }
+
+       submission_client_count++;
+       DLLIST_PREPEND(&submission_clients, client);
+
+       ident = mail_user_get_anvil_userip_ident(client->user);
+       if (ident != NULL) {
+               master_service_anvil_send(master_service, t_strconcat(
+                       "CONNECT\t", my_pid, "\tsubmission/",
+                       ident, "\n", NULL));
+               client->anvil_sent = TRUE;
+       }
+
+       if (hook_client_created != NULL)
+               hook_client_created(&client);
+
+       submission_refresh_proctitle();
+       return client;
+}
+
+static void client_state_reset(struct client *client)
+{
+       i_stream_unref(&client->state.data_input);
+
+       i_zero(&client->state);
+}
+
+void client_destroy(struct client *client, const char *prefix,
+                   const char *reason)
+{
+       if (client->destroyed)
+               return;
+       client->destroyed = TRUE;
+
+       client_disconnect(client, prefix, reason);
+
+       submission_client_count--;
+       DLLIST_REMOVE(&submission_clients, client);
+
+       if (client->proxy_conn != NULL)
+               smtp_client_connection_close(&client->proxy_conn);
+
+       if (client->anvil_sent) {
+               master_service_anvil_send(master_service, t_strconcat(
+                       "DISCONNECT\t", my_pid, "\tsubmission/",
+                       mail_user_get_anvil_userip_ident(client->user),
+                       "\n", NULL));
+       }
+
+       if (client->urlauth_ctx != NULL)
+               imap_urlauth_deinit(&client->urlauth_ctx);
+
+       mail_user_unref(&client->user);
+       mail_storage_service_user_unref(&client->service_user);
+
+       client_state_reset(client);
+
+       i_free(client->session_id);
+       i_free(client);
+
+       master_service_client_connection_destroyed(master_service);
+       submission_refresh_proctitle();
+}
+
+static void
+client_connection_trans_free(void *context,
+                            struct smtp_server_transaction *trans ATTR_UNUSED)
+{
+       struct client *client = (struct client *)context;
+
+       client_state_reset(client);
+}
+
+static void
+client_connection_state_changed(void *context ATTR_UNUSED,
+                               enum smtp_server_state newstate ATTR_UNUSED)
+{
+       if (submission_client_count == 1)
+               submission_refresh_proctitle();
+}
+
+static void client_connection_disconnect(void *context, const char *reason)
+{
+       struct client *client = (struct client *)context;
+       struct smtp_server_connection *conn = client->conn;
+       const struct smtp_server_stats *stats;
+
+       if (conn != NULL) {
+               stats = smtp_server_connection_get_stats(conn);
+               client->stats = *stats;
+               client->last_state = smtp_server_connection_get_state(conn);
+       }
+       client_disconnect(client, NULL, reason);
+}
+
+static void client_connection_destroy(void *context)
+{
+       struct client *client = (struct client *)context;
+
+       client_destroy(client, NULL, NULL);
+}
+
+const char *client_state_get_name(struct client *client)
+{
+       enum smtp_server_state state;
+
+       if (client->conn == NULL)
+               state = client->last_state;
+       else
+               state = smtp_server_connection_get_state(client->conn);
+       return smtp_server_state_names[state];
+}
+
+static const char *client_stats(struct client *client)
+{
+       const char *trans_id = (client->conn == NULL ? "" :
+               smtp_server_connection_get_transaction_id(client->conn));
+       const struct var_expand_table logout_tab[] = {
+               { 'i', dec2str(client->stats.input), "input" },
+               { 'o', dec2str(client->stats.output), "output" },
+               { '\0', dec2str(client->stats.command_count), "command_count" },
+               { '\0', dec2str(client->stats.reply_count), "reply_count" },
+               { '\0', client->session_id, "session" },
+               { '\0', trans_id, "transaction_id" },
+               { '\0', NULL, NULL }
+       };
+       const struct var_expand_table *user_tab =
+               mail_user_var_expand_table(client->user);
+       const struct var_expand_table *tab =
+               t_var_expand_merge_tables(logout_tab, user_tab);
+       string_t *str;
+       const char *error;
+
+       str = t_str_new(128);
+       if (var_expand_with_funcs(str, client->set->submission_logout_format,
+                                 tab, mail_user_var_expand_func_table,
+                                 client->user, &error) < 0) {
+               i_error("Failed to expand submission_logout_format=%s: %s",
+                       client->set->submission_logout_format, error);
+       }
+       return str_c(str);
+}
+
+void client_disconnect(struct client *client, const char *enh_code,
+                      const char *reason)
+{
+       struct smtp_server_connection *conn;
+
+       if (client->disconnected)
+               return;
+       client->disconnected = TRUE;
+
+       if (client->proxy_conn != NULL)
+               smtp_client_connection_close(&client->proxy_conn);
+
+       if (client->conn != NULL) {
+               const struct smtp_server_stats *stats =
+                       smtp_server_connection_get_stats(client->conn);
+               client->stats = *stats;
+       }
+
+       if (reason == NULL)
+               reason = "Connection closed";
+       i_info("Disconnect from %s: %s %s (state = %s)",
+              client_remote_id(client),
+              reason, client_stats(client),
+              client_state_get_name(client));
+
+       conn = client->conn;
+       client->conn = NULL;
+       if (conn != NULL) {
+               client->last_state = smtp_server_connection_get_state(conn);
+               smtp_server_connection_terminate(&conn,
+                       (enh_code == NULL ? "4.0.0" : enh_code), reason);
+       }
+}
+
+bool client_proxy_is_ready(struct client *client)
+{
+       return (smtp_client_connection_get_state(client->proxy_conn) ==
+               SMTP_CLIENT_CONNECTION_STATE_READY);
+}
+
+bool client_proxy_is_disconnected(struct client *client)
+{
+       return (smtp_client_connection_get_state(client->proxy_conn) ==
+               SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED);
+}
+
+uoff_t client_get_max_mail_size(struct client *client)
+{
+       uoff_t max_size;
+
+       /* Account for the backend server's SIZE limit and calculate our own
+          relative to it. */
+       max_size = smtp_client_connection_get_size_capability(client->proxy_conn);
+       if (max_size == 0 || max_size <= SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE) {
+               max_size = client->set->submission_max_mail_size;
+       } else {
+               max_size = max_size - SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE;
+               if (client->set->submission_max_mail_size > 0 &&
+                       max_size > client->set->submission_max_mail_size)
+                       max_size = client->set->submission_max_mail_size;
+       }
+
+       return max_size;
+}
+
+void clients_destroy_all(void)
+{
+       while (submission_clients != NULL) {
+               client_destroy(submission_clients,
+                       "4.3.2", "Shutting down");
+       }
+}
+
+static const struct smtp_server_callbacks smtp_callbacks = {
+       .conn_cmd_helo = cmd_helo,
+
+       .conn_cmd_mail = cmd_mail,
+       .conn_cmd_rcpt = cmd_rcpt,
+       .conn_cmd_rset = cmd_rset,
+
+       .conn_cmd_data_begin = cmd_data_begin,
+       .conn_cmd_data_continue = cmd_data_continue,
+
+       .conn_cmd_vrfy = cmd_vrfy,
+
+       .conn_cmd_noop = cmd_noop,
+       .conn_cmd_quit = cmd_quit,
+
+       .conn_cmd_input_pre = client_input_pre,
+       .conn_cmd_input_post = client_input_post,
+
+       .conn_trans_free = client_connection_trans_free,
+
+       .conn_state_changed = client_connection_state_changed,
+
+       .conn_disconnect = client_connection_disconnect,
+       .conn_destroy = client_connection_destroy,
+};
diff --git a/src/submission/submission-client.h b/src/submission/submission-client.h
new file mode 100644 (file)
index 0000000..a69abff
--- /dev/null
@@ -0,0 +1,75 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include "net.h"
+
+struct smtp_reply;
+
+struct client;
+struct client_command_context;
+
+struct client_state {
+       struct istream *data_input;
+       uoff_t data_size;
+};
+
+struct client {
+       struct client *prev, *next;
+       char *session_id;
+
+       const struct setting_parser_info *user_set_info;
+       const struct submission_settings *set;
+
+       struct smtp_server_connection *conn;
+       enum smtp_server_state last_state;
+       struct client_state state;
+       struct smtp_server_cmd_ctx *pending_helo;
+
+       struct mail_storage_service_user *service_user;
+       struct mail_user *user;
+
+       /* IMAP URLAUTH context (RFC4467) for BURL (RFC4468) */
+       struct imap_urlauth_context *urlauth_ctx;
+
+       struct smtp_client_connection *proxy_conn;
+       struct smtp_client_connection *proxy_trans;
+
+       struct smtp_server_stats stats;
+
+       bool standalone:1;
+       bool xclient_sent:1;
+       bool disconnected:1;
+       bool destroyed:1;
+       bool anvil_sent:1;
+};
+
+extern struct client *submission_clients;
+extern unsigned int submission_client_count;
+
+struct client *client_create(int fd_in, int fd_out,
+                            const char *session_id, struct mail_user *user,
+                            struct mail_storage_service_user *service_user,
+                            const struct submission_settings *set,
+                            const char *helo,
+                            const unsigned char *pdata,
+                            unsigned int pdata_len);
+void client_destroy(struct client *client, const char *prefix,
+                   const char *reason) ATTR_NULL(2, 3);
+void client_disconnect(struct client *client, const char *prefix,
+                      const char *reason);
+
+typedef void (*client_input_callback_t)(struct client *context);
+
+const char *client_state_get_name(struct client *client);
+
+bool client_proxy_is_ready(struct client *client);
+bool client_proxy_is_disconnected(struct client *client);
+
+uoff_t client_get_max_mail_size(struct client *client);
+
+int client_input_read(struct client *client);
+int client_handle_input(struct client *client);
+
+void clients_destroy_all(void);
+
+#endif
diff --git a/src/submission/submission-commands.c b/src/submission/submission-commands.c
new file mode 100644 (file)
index 0000000..07cc1cf
--- /dev/null
@@ -0,0 +1,57 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "submission-common.h"
+#include "ioloop.h"
+#include "array.h"
+#include "str.h"
+#include "llist.h"
+#include "istream.h"
+#include "ostream.h"
+#include "mail-storage.h"
+#include "smtp-reply.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+
+#include "submission-commands.h"
+
+/* The command handling of the submission proxy service aims to follow the
+   following rules:
+
+   - Attempt to keep pipelined commands pipelined when proxying them to the
+     actual relay service.
+   - Don't forward commands if they're known to fail at the relay server. Errors
+     can still occur if pipelined commands fail. Abort subsequent pending
+     commands if such failures affect those commands.
+   - Keep predictable errors consistent as much as possible; send our own reply
+     if the error condition is clear (e.g. missing MAIL, RCPT).
+*/
+
+bool client_command_handle_proxy_reply(struct client *client,
+       const struct smtp_reply *reply, struct smtp_reply *reply_r)
+{
+       *reply_r = *reply;
+
+       switch (reply->status) {
+       case SMTP_CLIENT_COMMAND_ERROR_ABORTED:
+       case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED:
+       case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED:
+       case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED:
+               i_unreached();
+               return FALSE;
+       case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED:
+       case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST:
+       case SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY:
+       case SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT:
+               client_destroy(client,
+                       "4.4.0", "Lost connection to relay server");
+               return FALSE;
+       default:
+               break;
+       }
+
+       if (!smtp_reply_has_enhanced_code(reply)) {
+               reply_r->enhanced_code =
+                       SMTP_REPLY_ENH_CODE(reply->status / 100, 0, 0);
+       }
+       return TRUE;
+}
diff --git a/src/submission/submission-commands.h b/src/submission/submission-commands.h
new file mode 100644 (file)
index 0000000..d0d5b1d
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef SUBMISSION_COMMANDS_H
+#define SUBMISSION_COMMANDS_H
+
+bool client_command_handle_proxy_reply(struct client *client,
+       const struct smtp_reply *reply, struct smtp_reply *reply_r);
+
+int cmd_helo(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            struct smtp_server_cmd_helo *data);
+
+int cmd_mail(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            struct smtp_server_cmd_mail *data);
+int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            struct smtp_server_cmd_rcpt *data);
+int cmd_rset(void *conn_ctx, struct smtp_server_cmd_ctx *cmd);
+
+int cmd_data_begin(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+                  struct smtp_server_transaction *trans,
+                  struct istream *data_input);
+int cmd_data_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+                     struct smtp_server_transaction *trans);
+void cmd_burl(struct smtp_server_cmd_ctx *cmd, const char *params);
+
+int cmd_vrfy(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+            const char *param);
+
+int cmd_noop(void *conn_ctx, struct smtp_server_cmd_ctx *cmd);
+int cmd_quit(void *conn_ctx, struct smtp_server_cmd_ctx *cmd);
+
+#endif
diff --git a/src/submission/submission-common.h b/src/submission/submission-common.h
new file mode 100644 (file)
index 0000000..bb28301
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef SUBMISSION_COMMON_H
+#define SUBMISSION_COMMON_H
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "smtp-reply.h"
+#include "smtp-server.h"
+#include "submission-client.h"
+#include "submission-settings.h"
+
+#define URL_HOST_ALLOW_ANY "*"
+
+/* Maximum number of bytes added to a relayed message. This is used to
+   calculate the SIZE capability based on what the backend server states. */
+#define SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE 1024
+#define SUBMISSION_MAIL_DATA_MAX_INMEMORY_SIZE (1024*128)
+
+typedef void submission_client_created_func_t(struct client **client);
+
+extern submission_client_created_func_t *hook_client_created;
+extern bool submission_debug;
+
+extern struct smtp_server *smtp_server;
+extern struct smtp_client *smtp_client;
+
+/* Sets the hook_client_created and returns the previous hook,
+   which the new_hook should call if it's non-NULL. */
+submission_client_created_func_t *
+submission_client_created_hook_set(submission_client_created_func_t *new_hook);
+
+void submission_refresh_proctitle(void);
+
+void client_handshake(struct client *client);
+
+#endif
diff --git a/src/submission/submission-settings.c b/src/submission/submission-settings.c
new file mode 100644 (file)
index 0000000..92fba31
--- /dev/null
@@ -0,0 +1,153 @@
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "mail-storage-settings.h"
+#include "submission-settings.h"
+
+#include <stddef.h>
+#include <unistd.h>
+
+static bool submission_settings_check(void *_set, pool_t pool, const char **error_r);
+
+/* <settings checks> */
+static struct file_listener_settings submission_unix_listeners_array[] = {
+       { "login/submission", 0666, "", "" }
+};
+static struct file_listener_settings *submission_unix_listeners[] = {
+       &submission_unix_listeners_array[0]
+};
+static buffer_t submission_unix_listeners_buf = {
+       submission_unix_listeners, sizeof(submission_unix_listeners), { 0, }
+};
+/* </settings checks> */
+
+struct service_settings submission_service_settings = {
+       .name = "submission",
+       .protocol = "submission",
+       .type = "",
+       .executable = "submission",
+       .user = "",
+       .group = "",
+       .privileged_group = "",
+       .extra_groups = "",
+       .chroot = "",
+
+       .drop_priv_before_exec = FALSE,
+
+       .process_min_avail = 0,
+       .process_limit = 1024,
+       .client_limit = 1,
+       .service_count = 1,
+       .idle_kill = 0,
+       .vsz_limit = (uoff_t)-1,
+
+       .unix_listeners = { { &submission_unix_listeners_buf,
+                             sizeof(submission_unix_listeners[0]) } },
+       .fifo_listeners = ARRAY_INIT,
+       .inet_listeners = ARRAY_INIT
+};
+
+#undef DEF
+#define DEF(type, name) \
+       { type, #name, offsetof(struct submission_settings, name), NULL }
+
+static const struct setting_define submission_setting_defines[] = {
+       DEF(SET_BOOL, verbose_proctitle),
+
+       DEF(SET_STR, hostname),
+
+       DEF(SET_STR, login_greeting),
+       DEF(SET_STR, login_trusted_networks),
+
+       DEF(SET_SIZE, submission_max_mail_size),
+       DEF(SET_UINT, submission_max_recipients),
+       DEF(SET_STR, submission_logout_format),
+
+       DEF(SET_STR, submission_relay_host),
+       DEF(SET_IN_PORT, submission_relay_port),
+       DEF(SET_BOOL, submission_relay_trusted),
+
+       DEF(SET_STR, submission_relay_user),
+       DEF(SET_STR, submission_relay_master_user),
+       DEF(SET_STR, submission_relay_password),
+
+       DEF(SET_ENUM, submission_relay_ssl),
+       DEF(SET_BOOL, submission_relay_ssl_verify),
+
+       DEF(SET_STR, submission_relay_rawlog_dir),
+       DEF(SET_TIME, submission_relay_max_idle_time),
+
+       DEF(SET_STR, imap_urlauth_host),
+       DEF(SET_IN_PORT, imap_urlauth_port),
+
+       SETTING_DEFINE_LIST_END
+};
+
+static const struct submission_settings submission_default_settings = {
+       .verbose_proctitle = FALSE,
+
+       .hostname = "",
+
+       .login_greeting = PACKAGE_NAME" ready.",
+       .login_trusted_networks = "",
+
+       .submission_max_mail_size = 0,
+       .submission_max_recipients = 0,
+       .submission_logout_format = "in=%i out=%o",
+
+       .submission_relay_host = "",
+       .submission_relay_port = 25,
+       .submission_relay_trusted = FALSE,
+
+       .submission_relay_user = "",
+       .submission_relay_master_user = "",
+       .submission_relay_password = "",
+
+       .submission_relay_ssl = "no:smtps:starttls",
+       .submission_relay_ssl_verify = TRUE,
+
+       .submission_relay_rawlog_dir = "",
+       .submission_relay_max_idle_time = 60*29,
+
+       .imap_urlauth_host = "",
+       .imap_urlauth_port = 143,
+};
+
+static const struct setting_parser_info *submission_setting_dependencies[] = {
+       &mail_user_setting_parser_info,
+       NULL
+};
+
+const struct setting_parser_info submission_setting_parser_info = {
+       .module_name = "submission",
+       .defines = submission_setting_defines,
+       .defaults = &submission_default_settings,
+
+       .type_offset = (size_t)-1,
+       .struct_size = sizeof(struct submission_settings),
+
+       .parent_offset = (size_t)-1,
+
+#ifndef CONFIG_BINARY
+       .check_func = submission_settings_check,
+#endif
+       .dependencies = submission_setting_dependencies
+};
+
+static bool submission_settings_check(void *_set, pool_t pool,
+                                const char **error_r ATTR_UNUSED)
+{
+       struct submission_settings *set = _set;
+
+       if (set->submission_relay_max_idle_time == 0) {
+               *error_r = "submission_relay_max_idle_time must not be 0";
+               return FALSE;
+       }
+       if (*set->hostname == '\0')
+               set->hostname = p_strdup(pool, my_hostdomain());
+       return TRUE;
+}
diff --git a/src/submission/submission-settings.h b/src/submission/submission-settings.h
new file mode 100644 (file)
index 0000000..8b4b4dd
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef SUBMISSION_SETTINGS_H
+#define SUBMISSION_SETTINGS_H
+
+struct submission_settings {
+       bool verbose_proctitle;
+
+       const char *hostname;
+
+       const char *login_greeting;
+       const char *login_trusted_networks;
+
+       /* submission: */
+       size_t submission_max_mail_size;
+       unsigned int submission_max_recipients;
+       const char *submission_logout_format;
+
+       /* submission relay: */
+       const char *submission_relay_host;
+       in_port_t submission_relay_port;
+       bool submission_relay_trusted;
+
+       const char *submission_relay_user;
+       const char *submission_relay_master_user;
+       const char *submission_relay_password;
+
+       const char *submission_relay_ssl;
+       bool submission_relay_ssl_verify;
+
+       const char *submission_relay_rawlog_dir;
+       unsigned int submission_relay_max_idle_time;
+
+       /* imap urlauth: */
+       const char *imap_urlauth_host;
+       in_port_t imap_urlauth_port;
+};
+
+extern const struct setting_parser_info submission_setting_parser_info;
+
+#endif