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
- 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
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
service pop3-login {
#executable = pop3-login director
}
+service submission-login {
+ #executable = submission-login director
+}
# Enable director for LMTP proxying:
protocol lmtp {
}
}
+service submission-login {
+ inet_listener submission {
+ #port = 587
+ }
+}
+
service lmtp {
unix_listener lmtp {
#mode = 0666
#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
#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 =
--- /dev/null
+##
+## 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
+}
+
# --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.
imap-urlauth \
pop3-login \
pop3 \
+ submission-login \
+ submission \
lda \
lmtp \
log \
--- /dev/null
+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
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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
--- /dev/null
+/* 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);
+}
--- /dev/null
+#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
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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
--- /dev/null
+/* 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];
+}
--- /dev/null
+#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
--- /dev/null
+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
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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,
+};
--- /dev/null
+#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
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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