/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "str.h"
#include "array.h"
#include "smtp-parser.h"
#include "smtp-address.h"
#include "smtp-reply.h"
#include "smtp-syntax.h"

#include "smtp-server-private.h"
#include "settings-consts.h"

/* RCPT command */

struct smtp_server_cmd_rcpt {
	struct smtp_server_recipient *rcpt;
};

static void
cmd_rcpt_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
		 struct smtp_server_cmd_rcpt *data)
{
	smtp_server_recipient_destroy(&data->rcpt);
}

static bool
cmd_rcpt_check_state(struct smtp_server_cmd_ctx *cmd, bool next_to_reply)
{
	struct smtp_server_connection *conn = cmd->conn;
	struct smtp_server_command *command = cmd->cmd;
	struct smtp_server_transaction *trans = conn->state.trans;

	if (smtp_server_command_is_replied(command) &&
	    !smtp_server_command_replied_success(command) &&
	    !smtp_server_command_reply_is_forwarded(command))
		return FALSE;

	if (trans == NULL &&
	    (conn->state.pending_mail_cmds == 0 || next_to_reply)) {
		smtp_server_reply(cmd,
			503, "5.5.0", "MAIL needed first");
		return FALSE;
	}
	i_assert(conn->set.max_recipients != 0);
	if (conn->set.max_recipients != SET_UINT_UNLIMITED &&
	    trans != NULL &&
	    smtp_server_transaction_rcpt_count(trans) >=
			conn->set.max_recipients) {
		smtp_server_reply(cmd,
			451, "4.5.3", "Too many recipients");
		return FALSE;
	}

	return TRUE;
}

static void
cmd_rcpt_replied(struct smtp_server_cmd_ctx *cmd,
		 struct smtp_server_cmd_rcpt *data)
{
	struct smtp_server_recipient *rcpt = data->rcpt;

	smtp_server_recipient_replied(
		rcpt, smtp_server_command_get_reply(cmd->cmd, 0));
}

static void
cmd_rcpt_completed(struct smtp_server_cmd_ctx *cmd,
		   struct smtp_server_cmd_rcpt *data)
{
	struct smtp_server_connection *conn = cmd->conn;
	struct smtp_server_command *command = cmd->cmd;
	struct smtp_server_recipient *rcpt = data->rcpt;

	i_assert(conn->state.pending_rcpt_cmds > 0);
	conn->state.pending_rcpt_cmds--;

	i_assert(smtp_server_command_is_replied(command));
	i_assert(conn->state.state == SMTP_SERVER_STATE_RCPT_TO ||
		 !smtp_server_command_replied_success(command));

	if (!smtp_server_command_replied_success(command)) {
		/* Failure */
		conn->state.denied_rcpt_cmds++;
		smtp_server_recipient_denied(
			rcpt, smtp_server_command_get_reply(cmd->cmd, 0));
		return;
	}

	/* Success */
	data->rcpt = NULL; /* clear to prevent destruction */
	(void)smtp_server_recipient_approved(&rcpt);
}

static void
cmd_rcpt_recheck(struct smtp_server_cmd_ctx *cmd,
		 struct smtp_server_cmd_rcpt *data ATTR_UNUSED)
{
	struct smtp_server_connection *conn = cmd->conn;

	/* All preceding commands have finished and now the transaction state
	   is clear. This provides the opportunity to re-check the transaction
	   state and abort the pending proxied mail command if it is bound to
	   fail */
	if (!cmd_rcpt_check_state(cmd, TRUE))
		return;

	/* Advance state */
	smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_RCPT_TO,
					 smtp_address_encode(data->rcpt->path));
}

void smtp_server_cmd_rcpt(struct smtp_server_cmd_ctx *cmd,
			  const char *params)
{
	struct smtp_server_connection *conn = cmd->conn;
	const struct smtp_server_settings *set = &conn->set;
	enum smtp_capability caps = set->capabilities;
	const struct smtp_server_callbacks *callbacks = conn->callbacks;
	struct smtp_server_command *command = cmd->cmd;
	struct smtp_server_cmd_rcpt *rcpt_data;
	struct smtp_server_recipient *rcpt;
	enum smtp_address_parse_flags path_parse_flags;
	enum smtp_param_rcpt_parse_flags param_parse_flags;
	const char *const *param_extensions = NULL;
	struct smtp_address *path;
	struct smtp_params_rcpt rcpt_params;
	enum smtp_param_parse_error pperror;
	const char *error;
	int ret;

	/* rcpt         = "RCPT TO:" ( "<Postmaster@" Domain ">" /
	      "<Postmaster>" / Forward-path ) [SP Rcpt-parameters] CRLF
	   Forward-path = Path
	 */

	/* Check transaction state as far as possible */
	if (!cmd_rcpt_check_state(cmd, FALSE))
		return;

	/* ( "<Postmaster@" Domain ">" / "<Postmaster>" / Forward-path ) */
	if (params == NULL || !str_begins_icase(params, "TO:", &params)) {
		smtp_server_reply(cmd,
			501, "5.5.4", "Invalid parameters");
		return;
	}
	if (params[0] != ' ' && params[0] != '\t') {
		/* no whitespace */
	} else if ((set->workarounds &
		    SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH) != 0) {
		while (*params == ' ' || *params == '\t')
			params++;
	} else {
		smtp_server_reply(cmd, 501, "5.5.4",
				  "Invalid TO: "
				  "Unexpected whitespace before path");
		return;
	}
	path_parse_flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART;
	if ((set->workarounds & SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH) != 0)
		path_parse_flags |= SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL;
	if (smtp_address_parse_path_full(pool_datastack_create(), params,
					 path_parse_flags, &path, &error,
					 &params) < 0) {
		smtp_server_reply(cmd,
			501, "5.5.4", "Invalid TO: %s", error);
		return;
	}
	if (*params == ' ')
		params++;
	else if (*params != '\0') {
		smtp_server_reply(cmd, 501, "5.5.4",
			"Invalid TO: Invalid character in path");
		return;
	}
	if (path->domain == NULL && !conn->set.rcpt_domain_optional &&
		strcasecmp(path->localpart, "postmaster") != 0) {
		smtp_server_reply(cmd,
			501, "5.5.4", "Invalid TO: Missing domain");
		return;
	}

	/* [SP Rcpt-parameters] */
	param_parse_flags = 0;
	if (conn->set.rcpt_domain_optional)
		param_parse_flags |= SMTP_PARAM_RCPT_FLAG_ORCPT_ALLOW_LOCALPART;
	if (array_is_created(&conn->rcpt_param_extensions))
		param_extensions = array_front(&conn->rcpt_param_extensions);
	if (smtp_params_rcpt_parse(pool_datastack_create(), params,
				   param_parse_flags, caps, param_extensions,
				   &rcpt_params, &pperror, &error) < 0) {
		switch (pperror) {
		case SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX:
			smtp_server_reply(cmd,
				501, "5.5.4", "%s", error);
			break;
		case SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED:
			smtp_server_reply(cmd,
				555, "5.5.4", "%s", error);
			break;
		default:
			i_unreached();
		}
		return;
	}

	rcpt = smtp_server_recipient_create(cmd, path, &rcpt_params);

	rcpt_data = p_new(cmd->pool, struct smtp_server_cmd_rcpt, 1);
	rcpt_data->rcpt = rcpt;
	command->data = rcpt_data;

	smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT,
				     cmd_rcpt_recheck, rcpt_data);
	smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_REPLIED,
				     cmd_rcpt_replied, rcpt_data);
	smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED,
				     cmd_rcpt_completed, rcpt_data);
	smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_DESTROY,
				     cmd_rcpt_destroy, rcpt_data);

	conn->state.pending_rcpt_cmds++;

	smtp_server_command_ref(command);
	i_assert(callbacks != NULL && callbacks->conn_cmd_rcpt != NULL);

	struct event_reason *reason =
		smtp_server_connection_reason_begin(conn, "cmd_rcpt");
	ret = callbacks->conn_cmd_rcpt(conn->context, cmd, rcpt);
	event_reason_end(&reason);
	if (ret <= 0) {
		i_assert(ret == 0 || smtp_server_command_is_replied(command));
		/* Command is waiting for external event or it failed */
		smtp_server_command_unref(&command);
		return;
	}
	if (!smtp_server_command_is_replied(command)) {
		/* Set generic RCPT success reply if none is provided */
		smtp_server_cmd_rcpt_reply_success(cmd);
	}
	smtp_server_command_unref(&command);
}

bool smtp_server_command_is_rcpt(struct smtp_server_cmd_ctx *cmd)
{
	return (cmd->cmd->reg->func == smtp_server_cmd_rcpt);
}

void smtp_server_cmd_rcpt_reply_success(struct smtp_server_cmd_ctx *cmd)
{
	struct smtp_server_cmd_rcpt *rcpt_data = cmd->cmd->data;

	i_assert(smtp_server_command_is_rcpt(cmd));

	smtp_server_recipient_reply(rcpt_data->rcpt, 250, "2.1.5", "OK");
}
