#include <ctype.h>
#include "lib.h"
#include "settings.h"
#include "settings-parser.h"

#include "antispam-settings.h"
#include "antispam-plugin.h"

#define __DEFN(type, name, memb) \
	SETTING_DEFINE_STRUCT_##type("antispam_"#name, memb, struct antispam_settings)
#define DEF(type, name) __DEFN(type, name, name)
#define DEFN(type, name) __DEFN(type, name, _##name)

static const struct setting_define antispam_setting_defines[] = {
	DEFN(STR, debug_target),
	DEF(UINT, verbose_debug),
	DEF(STR, debug_prefix),

	DEFN(STR, backend),

	DEF(STR, signature),
	DEF(STR, signature_missing),

	DEFN(STR, spam_keywords),

	DEFN(STR, trash),
	DEFN(STR, trash_pattern),
	DEFN(STR, trash_pattern_ignorecase),

	DEFN(STR, spam),
	DEFN(STR, spam_pattern),
	DEFN(STR, spam_pattern_ignorecase),

	DEFN(STR, unsure),
	DEFN(STR, unsure_pattern),
	DEFN(STR, unsure_pattern_ignorecase),

	DEF(BOOL, allow_append_to_spam),

	DEF(STR, dspam_binary),
	DEFN(STR, dspam_args),
	DEF(STR, dspam_result_header),
	DEFN(STR, dspam_result_blacklist),
	DEFN(STR, dspam_env),

	DEF(STR, pipe_program),
	DEFN(STR, pipe_program_args),
	DEFN(STR, pipe_program_spam_args),
	DEFN(STR, pipe_program_notspam_args),
	DEF(STR, pipe_tmpdir),

	DEF(STR, crm_binary),
	DEFN(STR, crm_args),
	DEFN(STR, crm_env),

	DEF(STR, spool2dir_spam),
	DEF(STR, spool2dir_notspam),

	SETTING_DEFINE_LIST_END,
};

static const struct antispam_settings antispam_setting_defaults = {
	._debug_target = "",
	.debug_prefix = "antispam: ",
	._backend = "",
	._spam_keywords = "",

	._trash = "",
	._trash_pattern = "",
	._trash_pattern_ignorecase = "",

	._spam = "SPAM",
	._spam_pattern = "",
	._spam_pattern_ignorecase = "",

	._unsure = "",
	._unsure_pattern = "",
	._unsure_pattern_ignorecase = "",

	.signature = "X-DSPAM-Signature",
	.signature_missing = "error",

	.dspam_binary = "/usr/bin/dspam",
	._dspam_args = "",
	.dspam_result_header = "X-DSPAM-Result",
	._dspam_result_blacklist = "",
	._dspam_env = "",

	.crm_binary = "/bin/false",
	._crm_args = "",
	._crm_env = "",

	.pipe_program = "/usr/sbin/sendmail",
	._pipe_program_args = "",
	._pipe_program_spam_args = "",
	._pipe_program_notspam_args = "",
	.pipe_tmpdir = "/tmp",

	.spool2dir_spam = "",
	.spool2dir_notspam = "",
};

/* match information */
static const char *match_desc[NUM_MT] ATTR_UNUSED = {
	[MT_REG] = "exact match",
	[MT_PATTERN] = "wildcard match",
	[MT_PATTERN_IGNCASE] = "case-insensitive wildcard match",
};

/* lower-case string, but keep modified UTF7 unchanged */
void lowercase_string(const char *in, char *out)
{
	char ch;

	while ((ch = *out++ = i_tolower(*in++))) {
		/* lower case */
		if (ch == '&') {
			/* modified UTF7 -- find end of sequence ('-') */
			while ((ch = *out++ = *in++)) {
				if (ch == '-')
					break;
			}
		}
	}
}

static void setup_folders(const char *name ATTR_UNUSED, struct antispam_folders *out,
			  pool_t pool, struct antispam_settings *cfg,
			  const char *r, const char *p, const char *pi)
{
	if (!r[0]) {
		out->folders[MT_REG] = NULL;
	} else {
		cfg->need_folder_hook = true;
		out->folders[MT_REG] = p_strsplit(pool, r, ";");
	}

	if (!p[0]) {
		out->folders[MT_PATTERN] = NULL;
	} else {
		cfg->need_folder_hook = true;
		out->folders[MT_PATTERN] = p_strsplit(pool, r, ";");
	}

	if (!pi[0]) {
		out->folders[MT_PATTERN_IGNCASE] = NULL;
	} else {
		cfg->need_folder_hook = true;
		out->folders[MT_PATTERN_IGNCASE] = p_strsplit(pool, r, ";");
		for (int i = 0; out->folders[MT_PATTERN_IGNCASE][i]; i++)
			lowercase_string(out->folders[MT_PATTERN_IGNCASE][i],
					 out->folders[MT_PATTERN_IGNCASE][i]);
	}

#ifndef ANTISPAM_SETTINGS_ONLY
	for (enum match_type i = 0; i < NUM_MT; i++) {
		if (!out->folders[i])
			continue;
		for (int j = 0; out->folders[i][j]; j++)
			debug(cfg, "\"%s\" is %s %s folder\n",
			      out->folders[i][j], match_desc[i], name);
	}
#endif
}

static bool
antispam_settings_check(void *_set, pool_t pool, const char **error_r)
{
	struct antispam_settings *set = _set;

	if (!set->_backend[0]) {
		*error_r = t_strdup("No backend configured");
		return false;
	}

	if (!strcmp(set->_backend, "crm114")) {
#ifndef ANTISPAM_SETTINGS_ONLY
		set->backend = &crm114_backend;
#endif
	} else if (!strcmp(set->_backend, "dspam")) {
#ifndef ANTISPAM_SETTINGS_ONLY
		set->backend = &dspam_backend;
#endif
	} else if (!strcmp(set->_backend, "pipe")) {
#ifndef ANTISPAM_SETTINGS_ONLY
		set->backend = &pipe_backend;
#endif
	} else if (!strcmp(set->_backend, "spool2dir")) {
#ifndef ANTISPAM_SETTINGS_ONLY
		set->backend = &spool2dir_backend;
#endif
	} else {
		*error_r = t_strdup_printf("Unknown backend '%s'", set->_backend);
		return false;
	}

	if (!set->_debug_target[0]) {
		set->debug_target = ADT_NONE;
	} else if (!strcmp(set->_debug_target, "syslog")) {
		set->debug_target = ADT_SYSLOG;
	} else if (!strcmp(set->_debug_target, "stderr")) {
		set->debug_target = ADT_STDERR;
	} else {
		*error_r = t_strdup_printf("Unknown debug target '%s'",
					   set->_debug_target);
		return false;
	}

#ifndef ANTISPAM_SETTINGS_ONLY
	debug(set, "signature header line is \"%s\"\n", set->signature);
#endif

	if (!strcmp(set->signature_missing, "error")) {
		/* nothing */
	} else if (!strcmp(set->signature_missing, "move")) {
#ifndef ANTISPAM_SETTINGS_ONLY
		debug(set, "will silently move mails with missing signature\n");
#endif
		set->signature_missing_ok = true;
	} else {
		*error_r = t_strdup_printf("invalid antispam_signature_missing setting '%s'",
					   set->signature_missing);
		return false;
	}

	setup_folders("spam", &set->spam, pool, set,
		      set->_spam, set->_spam_pattern,
		      set->_spam_pattern_ignorecase);
	setup_folders("unsure", &set->unsure, pool, set,
		      set->_unsure, set->_unsure_pattern,
		      set->_unsure_pattern_ignorecase);
	setup_folders("trash", &set->trash, pool, set,
		      set->_trash, set->_trash_pattern,
		      set->_trash_pattern_ignorecase);

#ifndef ANTISPAM_SETTINGS_ONLY
	return set->backend->settings(set, pool, error_r);
#else
	return true;
#endif
}

const struct setting_parser_info antispam_setting_parser_info = {
	.name = "antispam",
	.plugin_dependency = "lib90_antispam_plugin",

	.defines = antispam_setting_defines,
	.defaults = &antispam_setting_defaults,

	.struct_size = sizeof(struct antispam_settings),
	.check_func = antispam_settings_check,

	.pool_offset1 = 1 + offsetof(struct antispam_settings, pool),
};

const char *antispam_settings_version = DOVECOT_ABI_VERSION;
const struct setting_parser_info *antispam_settings_set_infos[] = {
	&antispam_setting_parser_info, NULL
};
