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

#include "lib.h"
#include "array.h"
#include "event-filter.h"
#include "path-util.h"
#include "istream.h"
#include "write-full.h"
#include "str.h"
#include "strescape.h"
#include "syslog-util.h"
#include "eacces-error.h"
#include "env-util.h"
#include "execv-const.h"
#include "settings-parser.h"
#include "stats-client.h"
#include "master-service-private.h"
#include "master-service-ssl-settings.h"
#include "master-service-settings.h"

#include <stddef.h>
#include <unistd.h>
#include <time.h>
#include <sys/stat.h>

#define DOVECOT_CONFIG_BIN_PATH BINDIR"/doveconf"
#define DOVECOT_CONFIG_SOCKET_PATH PKG_RUNDIR"/config"

#define CONFIG_READ_TIMEOUT_SECS 10
#define CONFIG_HANDSHAKE "VERSION\tconfig\t2\t0\n"

#undef DEF
#define DEF(type, name) \
	SETTING_DEFINE_STRUCT_##type(#name, name, struct master_service_settings)

static bool
master_service_settings_check(void *_set, pool_t pool, const char **error_r);

static const struct setting_define master_service_setting_defines[] = {
	DEF(STR, base_dir),
	DEF(STR, state_dir),
	DEF(STR, instance_name),
	DEF(STR, log_path),
	DEF(STR, info_log_path),
	DEF(STR, debug_log_path),
	DEF(STR, log_timestamp),
	DEF(STR, log_debug),
	DEF(STR, log_core_filter),
	DEF(STR, process_shutdown_filter),
	DEF(STR, syslog_facility),
	DEF(STR, import_environment),
	DEF(STR, stats_writer_socket_path),
	DEF(SIZE, config_cache_size),
	DEF(BOOL, version_ignore),
	DEF(BOOL, shutdown_clients),
	DEF(BOOL, verbose_proctitle),

	DEF(STR, haproxy_trusted_networks),
	DEF(TIME, haproxy_timeout),

	SETTING_DEFINE_LIST_END
};

/* <settings checks> */
#ifdef HAVE_LIBSYSTEMD
#  define ENV_SYSTEMD " LISTEN_PID LISTEN_FDS NOTIFY_SOCKET"
#else
#  define ENV_SYSTEMD ""
#endif
#ifdef DEBUG
#  define ENV_GDB " GDB DEBUG_SILENT"
#else
#  define ENV_GDB ""
#endif
/* </settings checks> */

static const struct master_service_settings master_service_default_settings = {
	.base_dir = PKG_RUNDIR,
	.state_dir = PKG_STATEDIR,
	.instance_name = PACKAGE,
	.log_path = "syslog",
	.info_log_path = "",
	.debug_log_path = "",
	.log_timestamp = DEFAULT_FAILURE_STAMP_FORMAT,
	.log_debug = "",
	.log_core_filter = "",
	.process_shutdown_filter = "",
	.syslog_facility = "mail",
	.import_environment = "TZ CORE_OUTOFMEM CORE_ERROR" ENV_SYSTEMD ENV_GDB,
	.stats_writer_socket_path = "stats-writer",
	.config_cache_size = 1024*1024,
	.version_ignore = FALSE,
	.shutdown_clients = TRUE,
	.verbose_proctitle = FALSE,

	.haproxy_trusted_networks = "",
	.haproxy_timeout = 3
};

const struct setting_parser_info master_service_setting_parser_info = {
	.module_name = "master",
	.defines = master_service_setting_defines,
	.defaults = &master_service_default_settings,

	.type_offset = SIZE_MAX,
	.struct_size = sizeof(struct master_service_settings),

	.parent_offset = SIZE_MAX,
	.check_func = master_service_settings_check
};

/* <settings checks> */
static bool
setting_filter_parse(const char *set_name, const char *set_value,
		     void (*handle_filter)(struct event_filter *) ATTR_UNUSED,
		     const char **error_r)
{
	struct event_filter *filter;
	const char *error;

	if (set_value[0] == '\0')
		return TRUE;

	filter = event_filter_create();
	if (event_filter_parse(set_value, filter, &error) < 0) {
		*error_r = t_strdup_printf("Invalid %s: %s", set_name, error);
		event_filter_unref(&filter);
		return FALSE;
	}
#ifndef CONFIG_BINARY
	handle_filter(filter);
#endif
	event_filter_unref(&filter);
	return TRUE;
}

static void
master_service_set_process_shutdown_filter_wrapper(struct event_filter *filter)
{
	master_service_set_process_shutdown_filter(master_service, filter);
}

static bool
master_service_settings_check(void *_set, pool_t pool ATTR_UNUSED,
			      const char **error_r)
{
	struct master_service_settings *set = _set;
	int facility;

	if (*set->log_path == '\0') {
		/* default to syslog logging */
		set->log_path = "syslog";
	}
	if (!syslog_facility_find(set->syslog_facility, &facility)) {
		*error_r = t_strdup_printf("Unknown syslog_facility: %s",
					   set->syslog_facility);
		return FALSE;
	}

	if (!setting_filter_parse("log_debug", set->log_debug,
				  event_set_global_debug_log_filter, error_r))
		return FALSE;
	if (!setting_filter_parse("log_core_filter", set->log_core_filter,
				  event_set_global_core_log_filter, error_r))
		return FALSE;
	if (!setting_filter_parse("process_shutdown_filter",
				  set->process_shutdown_filter,
				  master_service_set_process_shutdown_filter_wrapper,
				  error_r))
		return FALSE;
	return TRUE;
}
/* </settings checks> */

static void strarr_push(ARRAY_TYPE(const_string) *argv, const char *str)
{
	array_push_back(argv, &str);
}

static void ATTR_NORETURN
master_service_exec_config(struct master_service *service,
			   const struct master_service_settings_input *input)
{
	ARRAY_TYPE(const_string) conf_argv;
	const char *binary_path = service->argv[0];
	const char *error = NULL;

	if (!t_binary_abspath(&binary_path, &error)) {
		i_fatal("t_binary_abspath(%s) failed: %s", binary_path, error);
	}

	if (!service->keep_environment && !input->preserve_environment) {
		if (input->preserve_home)
			master_service_import_environment("HOME");
		if (input->preserve_user)
			master_service_import_environment("USER");
		if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0)
			master_service_import_environment("LOG_STDERR_TIMESTAMP");

		/* doveconf empties the environment before exec()ing us back
		   if DOVECOT_PRESERVE_ENVS is set, so make sure it is. */
		if (getenv(DOVECOT_PRESERVE_ENVS_ENV) == NULL)
			env_put(DOVECOT_PRESERVE_ENVS_ENV, "");
	} else {
		/* make sure doveconf doesn't remove any environment */
		env_remove(DOVECOT_PRESERVE_ENVS_ENV);
	}
	if (input->use_sysexits)
		env_put("USE_SYSEXITS", "1");

	t_array_init(&conf_argv, 11 + (service->argc + 1) + 1);
	strarr_push(&conf_argv, DOVECOT_CONFIG_BIN_PATH);
	if (input->service != NULL) {
		strarr_push(&conf_argv, "-f");
		strarr_push(&conf_argv,
			    t_strconcat("service=", input->service, NULL));
	}
	strarr_push(&conf_argv, "-c");
	strarr_push(&conf_argv, service->config_path);
	if (input->module != NULL) {
		strarr_push(&conf_argv, "-m");
		strarr_push(&conf_argv, input->module);
	}
	if (input->extra_modules != NULL) {
		for (unsigned int i = 0; input->extra_modules[i] != NULL; i++) {
			strarr_push(&conf_argv, "-m");
			strarr_push(&conf_argv, input->extra_modules[i]);
		}
	}
	if ((service->flags & MASTER_SERVICE_FLAG_DISABLE_SSL_SET) == 0 &&
	    (input->module != NULL || input->extra_modules != NULL)) {
		strarr_push(&conf_argv, "-m");
		if (service->want_ssl_server)
			strarr_push(&conf_argv, "ssl-server");
		else
			strarr_push(&conf_argv, "ssl");
	}
	if (input->parse_full_config)
		strarr_push(&conf_argv, "-p");

	strarr_push(&conf_argv, "-e");
	strarr_push(&conf_argv, binary_path);
	array_append(&conf_argv, (const char *const *)service->argv + 1,
		     service->argc);
	array_append_zero(&conf_argv);

	const char *const *argv = array_front(&conf_argv);
	execv_const(argv[0], argv);
}

static void
config_error_update_path_source(struct master_service *service,
				const struct master_service_settings_input *input,
				const char **error)
{
	if (input->config_path == NULL && service->config_path_from_master) {
		*error = t_strdup_printf("%s (path is from %s environment)",
					 *error, MASTER_CONFIG_FILE_ENV);
	}
}

static void
config_exec_fallback(struct master_service *service,
		     const struct master_service_settings_input *input,
		     const char **error)
{
	const char *path, *stat_error;
	struct stat st;
	int saved_errno = errno;

	if (input->never_exec) {
		*error = t_strdup_printf(
			"%s - doveconf execution fallback is disabled", *error);
		return;
	}

	path = input->config_path != NULL ? input->config_path :
		master_service_get_config_path(service);
	if (stat(path, &st) < 0)
		stat_error = t_strdup_printf("stat(%s) failed: %m", path);
	else if (S_ISSOCK(st.st_mode))
		stat_error = t_strdup_printf("%s is a UNIX socket", path);
	else if (S_ISFIFO(st.st_mode))
		stat_error = t_strdup_printf("%s is a FIFO", path);
	else {
		/* it's a file, not a socket/pipe */
		master_service_exec_config(service, input);
	}
	*error = t_strdup_printf(
		"%s - Also failed to read config by executing doveconf: %s",
		*error, stat_error);
	config_error_update_path_source(service, input, error);
	errno = saved_errno;
}

static int
master_service_open_config(struct master_service *service,
			   const struct master_service_settings_input *input,
			   const char **path_r, const char **error_r)
{
	struct stat st;
	const char *path;
	int fd;

	*path_r = path = input->config_path != NULL ? input->config_path :
		master_service_get_config_path(service);

	if (service->config_fd != -1 && input->config_path == NULL &&
	    !service->config_path_changed_with_param) {
		/* use the already opened config socket */
		fd = service->config_fd;
		service->config_fd = -1;
		return fd;
	}

	if (!service->config_path_from_master &&
	    !service->config_path_changed_with_param &&
	    input->config_path == NULL) {
		/* first try to connect to the default config socket.
		   configuration may contain secrets, so in default config
		   this fails because the socket is 0600. it's useful for
		   developers though. :) */
		fd = net_connect_unix(DOVECOT_CONFIG_SOCKET_PATH);
		if (fd >= 0) {
			*path_r = DOVECOT_CONFIG_SOCKET_PATH;
			net_set_nonblock(fd, FALSE);
			return fd;
		}
		/* fallback to executing doveconf */
	}

	if (stat(path, &st) < 0) {
		*error_r = errno == EACCES ? eacces_error_get("stat", path) :
			t_strdup_printf("stat(%s) failed: %m", path);
		config_error_update_path_source(service, input, error_r);
		return -1;
	}

	if (!S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode)) {
		/* it's not an UNIX socket, don't even try to connect */
		fd = -1;
		errno = ENOTSOCK;
	} else {
		fd = net_connect_unix_with_retries(path, 1000);
	}
	if (fd < 0) {
		*error_r = t_strdup_printf("net_connect_unix(%s) failed: %m",
					   path);
		config_exec_fallback(service, input, error_r);
		return -1;
	}
	net_set_nonblock(fd, FALSE);
	return fd;
}

static void
config_build_request(struct master_service *service, string_t *str,
		     const struct master_service_settings_input *input)
{
	str_append(str, "REQ");
	if (input->module != NULL)
		str_printfa(str, "\tmodule=%s", input->module);
	if (input->extra_modules != NULL) {
		for (unsigned int i = 0; input->extra_modules[i] != NULL; i++)
			str_printfa(str, "\tmodule=%s", input->extra_modules[i]);
	}
	if ((service->flags & MASTER_SERVICE_FLAG_DISABLE_SSL_SET) == 0 &&
	    (input->module != NULL || input->extra_modules != NULL)) {
		str_printfa(str, "\tmodule=%s",
			    service->want_ssl_server ? "ssl-server" : "ssl");
	}
	if (input->no_ssl_ca)
		str_append(str, "\texclude=ssl_ca\texclude=ssl_verify_client_cert");
	if (input->service != NULL)
		str_printfa(str, "\tservice=%s", input->service);
	if (input->username != NULL)
		str_printfa(str, "\tuser=%s", input->username);
	if (input->local_ip.family != 0)
		str_printfa(str, "\tlip=%s", net_ip2addr(&input->local_ip));
	if (input->remote_ip.family != 0)
		str_printfa(str, "\trip=%s", net_ip2addr(&input->remote_ip));
	if (input->local_name != NULL)
		str_printfa(str, "\tlname=%s", input->local_name);
	str_append_c(str, '\n');
}

static int
config_send_request(struct master_service *service,
		    const struct master_service_settings_input *input,
		    int fd, const char *path, const char **error_r)
{
	int ret;

	T_BEGIN {
		string_t *str;

		str = t_str_new(128);
		str_append(str, CONFIG_HANDSHAKE);
		config_build_request(service, str, input);
		ret = write_full(fd, str_data(str), str_len(str));
	} T_END;
	if (ret < 0) {
		*error_r = t_strdup_printf("write_full(%s) failed: %m", path);
		return -1;
	}
	return 0;
}

static int
config_send_filters_request(int fd, const char *path, const char **error_r)
{
	int ret;
	ret = write_full(fd, CONFIG_HANDSHAKE"FILTERS\n", strlen(CONFIG_HANDSHAKE"FILTERS\n"));
	if (ret < 0) {
		*error_r = t_strdup_printf("write_full(%s) failed: %m", path);
		return -1;
	}
	return 0;
}

static int
master_service_apply_config_overrides(struct master_service *service,
				      struct setting_parser_context *parser,
				      const char **error_r)
{
	const char *const *overrides;
	unsigned int i, count;

	overrides = array_get(&service->config_overrides, &count);
	for (i = 0; i < count; i++) {
		if (settings_parse_line(parser, overrides[i]) < 0) {
			*error_r = t_strdup_printf(
				"Invalid -o parameter %s: %s", overrides[i],
				settings_parser_get_error(parser));
			return -1;
		}
		settings_parse_set_key_expanded(parser, service->set_pool,
						t_strcut(overrides[i], '='));
	}
	return 0;
}

static int
config_read_reply_header(struct istream *istream, const char *path, pool_t pool,
			 const struct master_service_settings_input *input,
			 struct master_service_settings_output *output_r,
			 const char **error_r)
{
	const char *line;
	ssize_t ret;

	while ((ret = i_stream_read(istream)) > 0) {
		line = i_stream_next_line(istream);
		if (line != NULL)
			break;
	}
	if (ret <= 0) {
		if (ret == 0)
			return 1;
		*error_r = istream->stream_errno != 0 ?
			t_strdup_printf("read(%s) failed: %s", path,
					i_stream_get_error(istream)) :
			t_strdup_printf("read(%s) failed: EOF", path);
		return -1;
	}

	T_BEGIN {
		const char *const *arg = t_strsplit_tabescaped(line);
		ARRAY_TYPE(const_string) services;

		p_array_init(&services, pool, 8);
		for (; *arg != NULL; arg++) {
			if (strcmp(*arg, "service-uses-local") == 0)
				output_r->service_uses_local = TRUE;
			else if (strcmp(*arg, "service-uses-remote") == 0)
				output_r->service_uses_remote = TRUE;
			if (strcmp(*arg, "used-local") == 0)
				output_r->used_local = TRUE;
			else if (strcmp(*arg, "used-remote") == 0)
				output_r->used_remote = TRUE;
			else if (str_begins(*arg, "service=")) {
				const char *name = p_strdup(pool, *arg + 8);
				array_push_back(&services, &name);
			 }
		}
		if (input->service == NULL) {
			array_append_zero(&services);
			output_r->specific_services = array_front(&services);
		}
	} T_END;
	return 0;
}

void master_service_config_socket_try_open(struct master_service *service)
{
	struct master_service_settings_input input;
	const char *path, *error;
	int fd;

	/* we'll get here before command line parameters have been parsed,
	   so -O, -c and -i parameters haven't been handled yet at this point.
	   this means we could end up opening config socket connection
	   unnecessarily, but this isn't a problem. we'll just have to
	   ignore it later on. (unfortunately there isn't a master_service_*()
	   call where this function would be better called.) */
	if (getenv("DOVECONF_ENV") != NULL ||
	    (service->flags & MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS) != 0)
		return;

	i_zero(&input);
	input.never_exec = TRUE;
	fd = master_service_open_config(service, &input, &path, &error);
	if (fd != -1)
		service->config_fd = fd;
}

int master_service_settings_get_filters(struct master_service *service,
					const char *const **filters,
					const char **error_r)
{
	struct master_service_settings_input input;
	int fd;
	bool retry = TRUE;
	const char *path = NULL;
	ARRAY_TYPE(const_string) filters_tmp;
	t_array_init(&filters_tmp, 8);
	i_zero(&input);

	if (getenv("DOVECONF_ENV") == NULL &&
	    (service->flags & MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS) == 0) {
		retry = service->config_fd != -1;
		for (;;) {
			fd = master_service_open_config(service, &input, &path, error_r);
			if (fd == -1) {
				return -1;
			}
			if (config_send_filters_request(fd, path, error_r) == 0)
				break;

			i_close_fd(&fd);
			if (!retry)
				return -1;
			retry = FALSE;
		}
		service->config_fd = fd;
		struct istream *is = i_stream_create_fd(fd, SIZE_MAX);
		const char *line;
		/* try read response */
		while((line = i_stream_read_next_line(is)) != NULL) {
			if (*line == '\0')
				break;
			if (str_begins(line, "FILTER\t")) {
				line = t_strdup(line+7);
				array_push_back(&filters_tmp, &line);
			}
		}
		i_stream_unref(&is);
	}

	array_append_zero(&filters_tmp);
	*filters = array_front(&filters_tmp);
	return 0;
}

int master_service_settings_read(struct master_service *service,
				 const struct master_service_settings_input *input,
				 struct master_service_settings_output *output_r,
				 const char **error_r)
{
	ARRAY(const struct setting_parser_info *) all_roots;
	const struct setting_parser_info *tmp_root;
	struct setting_parser_context *parser;
	struct istream *istream;
	const char *path = NULL, *error;
	void **sets;
	unsigned int i;
	int ret, fd = -1;
	time_t now, timeout;
	bool use_environment, retry;

	i_zero(output_r);

	if (getenv("DOVECONF_ENV") == NULL &&
	    (service->flags & MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS) == 0) {
		retry = service->config_fd != -1;
		for (;;) {
			fd = master_service_open_config(service, input,
							&path, error_r);
			if (fd == -1) {
				if (errno == EACCES)
					output_r->permission_denied = TRUE;
				return -1;
			}

			if (config_send_request(service, input, fd,
						path, error_r) == 0)
				break;
			i_close_fd(&fd);
			if (!retry) {
				config_exec_fallback(service, input, error_r);
				return -1;
			}
			/* config process died, retry connecting */
			retry = FALSE;
		}
	}

	if (service->set_pool != NULL) {
		if (service->set_parser != NULL)
			settings_parser_deinit(&service->set_parser);
		p_clear(service->set_pool);
	} else {
		service->set_pool =
			pool_alloconly_create("master service settings", 16384);
	}

	p_array_init(&all_roots, service->set_pool, 8);
	tmp_root = &master_service_setting_parser_info;
	array_push_back(&all_roots, &tmp_root);
	tmp_root = &master_service_ssl_setting_parser_info;
	array_push_back(&all_roots, &tmp_root);
	if (service->want_ssl_server) {
		tmp_root = &master_service_ssl_server_setting_parser_info;
		array_push_back(&all_roots, &tmp_root);
	}
	if (input->roots != NULL) {
		for (i = 0; input->roots[i] != NULL; i++)
			array_push_back(&all_roots, &input->roots[i]);
	}

	parser = settings_parser_init_list(service->set_pool,
			array_front(&all_roots), array_count(&all_roots),
			SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS);

	if (fd != -1) {
		istream = i_stream_create_fd(fd, SIZE_MAX);
		now = time(NULL);
		timeout = now + CONFIG_READ_TIMEOUT_SECS;
		do {
			alarm(timeout - now);
			ret = config_read_reply_header(istream, path,
						       service->set_pool, input,
						       output_r, error_r);
			if (ret == 0) {
				ret = settings_parse_stream_read(parser,
								 istream);
				if (ret < 0)
					*error_r = t_strdup(
						settings_parser_get_error(parser));
			}
			alarm(0);
			if (ret <= 0)
				break;

			/* most likely timed out, but just in case some other
			   signal was delivered early check if we need to
			   continue */
			now = time(NULL);
		} while (now < timeout);
		i_stream_unref(&istream);

		if (ret != 0) {
			if (ret > 0) {
				*error_r = t_strdup_printf(
					"Timeout reading config from %s", path);
			}
			i_close_fd(&fd);
			config_exec_fallback(service, input, error_r);
			settings_parser_deinit(&parser);
			return -1;
		}

		if ((service->flags & MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN) != 0 &&
		    service->config_fd == -1 && input->config_path == NULL)
			service->config_fd = fd;
		else
			i_close_fd(&fd);
		use_environment = FALSE;
	} else {
		use_environment = TRUE;
	}

	if (use_environment || service->keep_environment) {
		if (settings_parse_environ(parser) < 0) {
			*error_r = t_strdup(settings_parser_get_error(parser));
			settings_parser_deinit(&parser);
			return -1;
		}
	}

	if (array_is_created(&service->config_overrides)) {
		if (master_service_apply_config_overrides(service, parser,
							  error_r) < 0) {
			settings_parser_deinit(&parser);
			return -1;
		}
	}

	if (!settings_parser_check(parser, service->set_pool, &error)) {
		*error_r = t_strdup_printf("Invalid settings: %s", error);
		settings_parser_deinit(&parser);
		return -1;
	}

	sets = settings_parser_get_list(parser);
	service->set = sets[0];
	service->set_parser = parser;

	if (service->set->version_ignore &&
	    (service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0) {
		/* running standalone. we want to ignore plugin versions. */
		service->version_string = NULL;
	}
	if ((service->flags & MASTER_SERVICE_FLAG_DONT_SEND_STATS) == 0 &&
	    (service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0) {
		/* When running standalone (e.g. doveadm) try to connect to the
		   stats socket, but don't log an error if it's not running.
		   It may be intentional. Non-standalone stats-client
		   initialization was already done earlier. */
		master_service_init_stats_client(service, TRUE);
	}

	if (service->set->shutdown_clients)
		master_service_set_die_with_master(master_service, TRUE);

	/* if we change any settings afterwards, they're in expanded form.
	   especially all settings from userdb are already expanded. */
	settings_parse_set_expanded(service->set_parser, TRUE);
	return 0;
}

int master_service_settings_read_simple(struct master_service *service,
					const struct setting_parser_info **roots,
					const char **error_r)
{
	struct master_service_settings_input input;
	struct master_service_settings_output output;

	i_zero(&input);
	input.roots = roots;
	input.module = service->name;
	return master_service_settings_read(service, &input, &output, error_r);
}

pool_t master_service_settings_detach(struct master_service *service)
{
	pool_t pool = service->set_pool;

	settings_parser_deinit(&service->set_parser);
	service->set_pool = NULL;
	return pool;
}

const struct master_service_settings *
master_service_settings_get(struct master_service *service)
{
	void **sets;

	sets = settings_parser_get_list(service->set_parser);
	return sets[0];
}

void **master_service_settings_get_others(struct master_service *service)
{
	return master_service_settings_parser_get_others(service,
							 service->set_parser);
}

void **master_service_settings_parser_get_others(struct master_service *service,
						 const struct setting_parser_context *set_parser)
{
	return settings_parser_get_list(set_parser) + 2 +
		(service->want_ssl_server ? 1 : 0);
}

struct setting_parser_context *
master_service_get_settings_parser(struct master_service *service)
{
	return service->set_parser;
}

int master_service_set(struct master_service *service, const char *line)
{
	return settings_parse_line(service->set_parser, line);
}

bool master_service_set_has_config_override(struct master_service *service,
					    const char *key)
{
	const char *override, *key_root;
	bool ret;

	if (!array_is_created(&service->config_overrides))
		return FALSE;

	key_root = settings_parse_unalias(service->set_parser, key);
	if (key_root == NULL)
		key_root = key;

	array_foreach_elem(&service->config_overrides, override) {
		T_BEGIN {
			const char *okey, *okey_root;

			okey = t_strcut(override, '=');
			okey_root = settings_parse_unalias(service->set_parser,
							   okey);
			if (okey_root == NULL)
				okey_root = okey;
			ret = strcmp(okey_root, key_root) == 0;
		} T_END;

		if (ret)
			return TRUE;
	}
	return FALSE;
}
