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

#include "lib.h"
#include "str.h"
#include "array.h"
#include "smtp-syntax.h"

#include "smtp-server-private.h"

/* EHLO, HELO commands */

static void
cmd_helo_completed(struct smtp_server_cmd_ctx *cmd,
		   struct smtp_server_cmd_helo *data)
{
	struct smtp_server_connection *conn = cmd->conn;
	struct smtp_server_command *command = cmd->cmd;

	i_assert(smtp_server_command_is_replied(command));
	if (!smtp_server_command_replied_success(command)) {
		/* Failure */
		return;
	}

	if (conn->pending_helo == &data->helo)
		conn->pending_helo = NULL;

	/* Success */
	smtp_server_connection_reset_state(conn);

	i_free(conn->helo_domain);
	conn->helo_domain = i_strdup(data->helo.domain);
	conn->helo.domain = conn->helo_domain;
	conn->helo.domain_valid = data->helo.domain_valid;
	conn->helo.old_smtp = data->helo.old_smtp;
}

static void
cmd_helo_next(struct smtp_server_cmd_ctx *cmd,
	      struct smtp_server_cmd_helo *data)
{
	struct smtp_server_connection *conn = cmd->conn;

	if (null_strcmp(conn->helo.domain, data->helo.domain) != 0 ||
	    conn->helo.old_smtp != data->helo.old_smtp)
		data->changed = TRUE; /* Definitive assessment */
}

static void
smtp_server_cmd_helo_run(struct smtp_server_cmd_ctx *cmd, const char *params,
			 bool old_smtp)
{
	struct smtp_server_connection *conn = cmd->conn;
	const struct smtp_server_callbacks *callbacks = conn->callbacks;
	struct smtp_server_cmd_helo *helo_data;
	struct smtp_server_command *command = cmd->cmd;
	bool first = (conn->pending_helo == NULL && conn->helo.domain == NULL);
	const char *domain = NULL;
	int ret;

	/* Parse domain argument */

	if (*params == '\0') {
		smtp_server_reply(cmd, 501, "", "Missing hostname");
		return;
	}
	ret = smtp_helo_domain_parse(params, !old_smtp, &domain);

	smtp_server_command_pipeline_block(cmd);
	if (conn->state.state == SMTP_SERVER_STATE_GREETING) {
		smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_HELO,
						 NULL);
	}

	helo_data = p_new(cmd->pool, struct smtp_server_cmd_helo, 1);
	helo_data->helo.domain = p_strdup(cmd->pool, domain);
	helo_data->helo.domain_valid = ( ret >= 0 );
	helo_data->helo.old_smtp = old_smtp;
	helo_data->first = first;
	command->data = helo_data;

	if (null_strcmp(conn->helo.domain, domain) != 0 ||
	    conn->helo.old_smtp != old_smtp)
		helo_data->changed = TRUE; /* Preliminary assessment */

	if (conn->pending_helo == NULL)
		conn->pending_helo = &helo_data->helo;

	smtp_server_command_add_hook(
		command, SMTP_SERVER_COMMAND_HOOK_NEXT,
		cmd_helo_next, helo_data);
	smtp_server_command_add_hook(
		command, SMTP_SERVER_COMMAND_HOOK_COMPLETED,
		cmd_helo_completed, helo_data);

	smtp_server_command_ref(command);
	if (callbacks != NULL && callbacks->conn_cmd_helo != NULL) {
		/* Specific implementation of EHLO command */
		ret = callbacks->conn_cmd_helo(conn->context, cmd, helo_data);
		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)) {
		/* Submit default EHLO reply if none is provided */
		smtp_server_cmd_ehlo_reply_default(cmd);
	}
	smtp_server_command_unref(&command);
}

void smtp_server_cmd_ehlo(struct smtp_server_cmd_ctx *cmd, const char *params)
{
	/* ehlo = "EHLO" SP ( Domain / address-literal ) CRLF */

	smtp_server_cmd_helo_run(cmd, params, FALSE);
}

void smtp_server_cmd_helo(struct smtp_server_cmd_ctx *cmd, const char *params)
{
	/* helo = "HELO" SP Domain CRLF */

	smtp_server_cmd_helo_run(cmd, params, TRUE);
}

struct smtp_server_reply *
smtp_server_cmd_ehlo_reply_create(struct smtp_server_cmd_ctx *cmd)
{
	static struct {
		const char *name;
		void (*add)(struct smtp_server_reply *reply);
	} standard_caps[] = {
		/* Sorted alphabetically */
		{ "8BITMIME", smtp_server_reply_ehlo_add_8bitmime },
		{ "BINARYMIME", smtp_server_reply_ehlo_add_binarymime },
		{ "CHUNKING", smtp_server_reply_ehlo_add_chunking },
		{ "DSN", smtp_server_reply_ehlo_add_dsn },
		{ "ENHANCEDSTATUSCODES",
		  smtp_server_reply_ehlo_add_enhancedstatuscodes },
		{ "PIPELINING", smtp_server_reply_ehlo_add_pipelining },
		{ "SIZE", smtp_server_reply_ehlo_add_size },
#ifdef EXPERIMENTAL_MAIL_UTF8
		{ "SMTPUTF8", smtp_server_reply_ehlo_add_smtputf8 },
#endif
		{ "STARTTLS", smtp_server_reply_ehlo_add_starttls },
		{ "VRFY", smtp_server_reply_ehlo_add_vrfy },
		{ "XCLIENT", smtp_server_reply_ehlo_add_xclient }
	};
	const unsigned int standard_caps_count = N_ELEMENTS(standard_caps);
	struct smtp_server_connection *conn = cmd->conn;
	struct smtp_server_command *command = cmd->cmd;
	struct smtp_server_cmd_helo *helo_data = command->data;
	const struct smtp_capability_extra *extra_caps = NULL;
	unsigned int extra_caps_count, i, j;
	struct smtp_server_reply *reply;

	reply = smtp_server_reply_create_ehlo(cmd->cmd);

	if (helo_data->helo.old_smtp) {
		i_assert(cmd->cmd->reg->func == smtp_server_cmd_helo);
		return reply;
	}
	i_assert(cmd->cmd->reg->func == smtp_server_cmd_ehlo);

	extra_caps_count = 0;
	if (array_is_created(&conn->extra_capabilities)) {
		extra_caps = array_get(&conn->extra_capabilities,
				       &extra_caps_count);
	}

	i = j = 0;
	while (i < standard_caps_count || j < extra_caps_count) {
		if (i < standard_caps_count &&
		    (j >= extra_caps_count ||
		     strcasecmp(standard_caps[i].name,
				extra_caps[j].name) < 0)) {
			standard_caps[i].add(reply);
			i++;
		} else {
			smtp_server_reply_ehlo_add_params(
				reply, extra_caps[j].name,
				extra_caps[j].params);
			j++;
		}
	}
	return reply;
}

void smtp_server_cmd_ehlo_reply_default(struct smtp_server_cmd_ctx *cmd)
{
	struct smtp_server_reply *reply;

	reply = smtp_server_cmd_ehlo_reply_create(cmd);
	smtp_server_reply_submit(reply);
}
