static bool
backend_relay_handle_relay_reply(struct submission_backend_relay *backend,
+ struct smtp_server_cmd_ctx *cmd,
const struct smtp_reply *reply,
struct smtp_reply *reply_r)
{
- struct client *client = backend->backend.client;
+ const char *enh_code, *msg, *detail = "";
+ bool result = TRUE;
*reply_r = *reply;
case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED:
case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED:
case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED:
- client_destroy(client,
- "4.4.0", "Failed to connect to relay server");
- return FALSE;
+ enh_code = "4.4.0";
+ msg = "Failed to connect to relay server";
+ result = FALSE;
+ break;
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;
+ enh_code = "4.4.0";
+ msg = "Lost connection to relay server";
+ result = FALSE;
+ break;
/* RFC 4954, Section 6: 530 5.7.0 Authentication required
This response SHOULD be returned by any command other than AUTH,
case 530:
i_error("Relay server requires authentication: %s",
smtp_reply_log(reply));
- client_destroy(client, "4.3.5",
- "Internal error occurred. "
- "Refer to server log for more information.");
- return FALSE;
+ enh_code = "4.3.5",
+ msg = "Internal error occurred. "
+ "Refer to server log for more information.";
+ result = FALSE;
+ break;
+ default:
+ break;
+ }
+
+ switch (reply->status) {
+ case SMTP_CLIENT_COMMAND_ERROR_ABORTED:
+ i_unreached();
+ case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED:
+ detail = " (DNS lookup)";
+ break;
+ case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED:
+ case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED:
+ detail = " (connect)";
+ break;
+ case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST:
+ case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED:
+ detail = " (connection lost)";
+ break;
+ case SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY:
+ detail = " (bad reply)";
+ break;
+ case SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT:
+ detail = " (timed out)";
+ break;
default:
break;
}
+ if (!result) {
+ const char *reason = t_strdup_printf("%s%s", msg, detail);
+
+ submission_backend_fail(&backend->backend, cmd,
+ enh_code, reason);
+ return FALSE;
+ }
+
if (!smtp_reply_has_enhanced_code(reply)) {
reply_r->enhanced_code =
SMTP_REPLY_ENH_CODE(reply->status / 100, 0, 0);
struct submission_backend_relay *backend = helo->backend;
struct smtp_reply reply;
- if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply))
+ if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply,
+ &reply))
return;
if (smtp_reply_is_success(&reply)) {
i_assert(mail_cmd != NULL);
mail_cmd->relay_mail = NULL;
- if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply))
+ if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply,
+ &reply))
return;
if (smtp_reply_is_success(relay_reply)) {
i_assert(rcpt_cmd != NULL);
rcpt_cmd->relay_rcpt = NULL;
- if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply))
+ if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply,
+ &reply))
return;
if (smtp_reply_is_success(&reply)) {
i_assert(rset_cmd != NULL);
rset_cmd->cmd_relayed = NULL;
- if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply))
+ if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply,
+ &reply))
return;
/* forward reply */
/* finished relaying message to relay server */
/* check for fatal problems */
- if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply))
+ if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply,
+ &reply))
return;
if (smtp_reply_is_success(&reply)) {
struct submission_backend_relay *backend = vrfy_cmd->backend;
struct smtp_reply reply;
- if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply))
+ if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply,
+ &reply))
return;
if (!smtp_reply_has_enhanced_code(&reply)) {
struct submission_backend_relay *backend = noop_cmd->backend;
struct smtp_reply reply;
- if (!backend_relay_handle_relay_reply(backend, relay_reply, &reply))
+ if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply,
+ &reply))
return;
if (smtp_reply_is_success(&reply)) {
void *context)
{
struct submission_backend_relay *backend = context;
- struct client *client = backend->backend.client;
/* check relay status */
if (!smtp_reply_is_success(reply)) {
i_error("Failed to establish relay connection: %s",
smtp_reply_log(reply));
- client_destroy(client,
+ submission_backend_fail(&backend->backend, NULL,
"4.4.0", "Failed to establish relay connection");
return;
}
i_stream_unref(&backend->data_input);
+ i_free(backend->fail_enh_code);
+ i_free(backend->fail_reason);
+
DLLIST_REMOVE(&client->backends, backend);
backend->v.destroy(backend);
}
{
if (backend->started)
return;
+ if (backend->fail_reason != NULL) {
+ /* Don't restart until failure is reset at transaction end */
+ return;
+ }
backend->v.start(backend);
backend->started = TRUE;
}
client_default_backend_started(client, caps);
}
+static void
+submission_backend_fail_rcpts(struct submission_backend *backend)
+{
+ struct client *client = backend->client;
+ struct submission_recipient *const *rcptp;
+ const char *enh_code = backend->fail_enh_code;
+ const char *reason = backend->fail_reason;
+
+ i_assert(array_count(&client->rcpt_to) > 0);
+
+ i_assert(reason != NULL);
+ if (enh_code == NULL)
+ enh_code = "4.0.0";
+
+ array_foreach_modifiable(&client->rcpt_to, rcptp) {
+ struct submission_recipient *rcpt = *rcptp;
+ struct smtp_server_cmd_ctx *cmd = rcpt->rcpt->cmd;
+ unsigned int index = 0;
+
+ if (rcpt->backend != backend)
+ continue;
+ if (cmd == NULL)
+ continue;
+
+ if (smtp_server_command_get_reply_count(cmd->cmd) > 1)
+ index = rcpt->rcpt->index;
+
+ smtp_server_reply_index(cmd, index, 451,
+ enh_code, "%s", reason);
+ }
+}
+
+static inline void
+submission_backend_reply_failure(struct submission_backend *backend,
+ struct smtp_server_cmd_ctx *cmd)
+{
+ const char *enh_code = backend->fail_enh_code;
+ const char *reason = backend->fail_reason;
+
+ if (enh_code == NULL)
+ enh_code = "4.0.0";
+
+ i_assert(smtp_server_command_get_reply_count(cmd->cmd) == 1);
+ smtp_server_reply(cmd, 451, enh_code, "%s", reason);
+}
+
+static inline bool
+submission_backend_handle_failure(struct submission_backend *backend,
+ struct smtp_server_cmd_ctx *cmd)
+{
+ if (backend->fail_reason == NULL)
+ return TRUE;
+
+ /* already failed */
+ submission_backend_reply_failure(backend, cmd);
+ return TRUE;
+}
+
+void submission_backend_fail(struct submission_backend *backend,
+ struct smtp_server_cmd_ctx *cmd,
+ const char *enh_code, const char *reason)
+{
+ struct client *client = backend->client;
+ bool failed_before = (backend->fail_reason != NULL);
+
+ /* Can be called several times */
+
+ if (backend == client->backend_default) {
+ /* default backend: fail the whole client */
+ client_destroy(client, enh_code, reason);
+ return;
+ }
+
+ /* Non-default backend for this transaction (maybe even for only
+ some of the approved recipients): fail only the affected
+ transaction and/or specific recipients. */
+
+ /* Remember the failure only once */
+ if (!failed_before) {
+ backend->fail_enh_code = i_strdup(enh_code);
+ backend->fail_reason = i_strdup(reason);
+ }
+ if (cmd == NULL) {
+ /* Called outside command context: just remember the failure */
+ } else if (smtp_server_command_get_reply_count(cmd->cmd) > 1) {
+ /* Fail DATA/BDAT/BURL command expecting several replies */
+ submission_backend_fail_rcpts(backend);
+ } else {
+ /* Single command */
+ submission_backend_reply_failure(backend, cmd);
+ }
+
+ /* Call the fail vfunc only once */
+ if (!failed_before && backend->v.fail != NULL)
+ backend->v.fail(backend, enh_code, reason);
+ backend->started = FALSE;
+}
+
void submission_backends_client_input_pre(struct client *client)
{
struct submission_backend *backend;
i_stream_unref(&backend->data_input);
if (backend->v.trans_free != NULL)
backend->v.trans_free(backend, trans);
+
+ i_free(backend->fail_enh_code);
+ i_free(backend->fail_reason);
}
void submission_backends_trans_start(struct client *client,
struct smtp_server_cmd_ctx *cmd,
struct smtp_server_cmd_helo *data)
{
+ /* failure on default backend closes the client connection */
+ i_assert(backend->fail_reason == NULL);
+
if (!backend->started || backend->v.cmd_helo == NULL) {
/* default backend is not interested, respond right away */
submission_helo_reply_submit(cmd, data);
struct smtp_server_cmd_ctx *cmd,
struct smtp_server_cmd_mail *data)
{
+ if (!submission_backend_handle_failure(backend, cmd))
+ return -1;
+
submission_backend_start(backend);
if (backend->v.cmd_mail == NULL) {
{
struct smtp_server_transaction *trans;
+ if (!submission_backend_handle_failure(backend, cmd))
+ return -1;
+
if (backend->v.cmd_rcpt == NULL) {
/* backend is not interested, respond right away */
return 1;
int submission_backend_cmd_rset(struct submission_backend *backend,
struct smtp_server_cmd_ctx *cmd)
{
+ if (!submission_backend_handle_failure(backend, cmd))
+ return -1;
+
if (backend->v.cmd_rset == NULL) {
/* backend is not interested, respond right away */
return 1;
struct smtp_server_cmd_ctx *cmd,
struct smtp_server_transaction *trans)
{
+ if (backend->fail_reason != NULL) {
+ submission_backend_fail_rcpts(backend);
+ return 0;
+ }
+
return backend->v.cmd_data(backend, cmd, trans,
backend->data_input, backend->data_size);
}
struct smtp_server_cmd_ctx *cmd,
const char *param)
{
+ /* failure on default backend closes the client connection */
+ i_assert(backend->fail_reason == NULL);
+
if (backend->v.cmd_vrfy == NULL) {
/* backend is not interested, respond right away */
return 1;
int submission_backend_cmd_noop(struct submission_backend *backend,
struct smtp_server_cmd_ctx *cmd)
{
+ /* failure on default backend closes the client connection */
+ i_assert(backend->fail_reason == NULL);
+
if (backend->v.cmd_noop == NULL) {
/* backend is not interested, respond right away */
return 1;
int submission_backend_cmd_quit(struct submission_backend *backend,
struct smtp_server_cmd_ctx *cmd)
{
+ /* failure on default backend closes the client connection */
+ i_assert(backend->fail_reason == NULL);
+
if (backend->v.cmd_quit == NULL) {
/* backend is not interested, respond right away */
return 1;