/*
 * Copyright (c) 2004, 2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: conf.c,v 1.29 2007/09/29 02:10:06 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/sysexits.h"
#include "sm/string.h"
#include "sm/io.h"
#include "sm/syslog.h"
#include "sm/signal.h"

#include "sm/pwd.h"
#include "sm/grp.h"

#include "sm/sm-conf.h"

#include "sm/net.h"
#include "mcp.h"
#include "inetdconf.h"

#if SM_MCP_USE_CONF

#define SM_SOCKCNFDEF 1
#include "sm/sockcnf.h"
#include "sm/sockcnfdef.h"

#define SM_MCPCNFDEF 1
#include "sm/mcpcnfdef.h"

#if 0
static void
print_structure(servtab_T *st, char const *name, size_t name_n)
{
	size_t i;

	printf("%.*s:\n", (int)name_n, name);
	printf("prg           = \"%s\"\n", st->se_prg);
#if 0
	if (*st->se_proto != '\0')
		printf("proto         = \"%s\"\n", st->se_proto);
#endif
	if (st->se_port != 0)
		printf("port          = %2d\n", st->se_port);
	if (st->se_addr != 0)
		printf("addr          = %X\n", st->se_addr);
	if (MCP_OPT_IS_SET(st->se_socket_name))
		printf("socket_name   = \"%s\"\n", st->se_socket_name);
	if (st->se_socket_umask != 066)
		printf("socket_umask  = %03o\n", st->se_socket_umask);
	if (MCP_OPT_IS_SET(st->se_socket_user))
		printf("socket_user   = \"%s\"\n", st->se_socket_user);
	if (MCP_OPT_IS_SET(st->se_socket_group))
		printf("socket_group  = \"%s\"\n", st->se_socket_group);
	if (st->se_minchild != 1)
		printf("minproc       = %2d\n", st->se_minchild);
	if (st->se_maxchild != 1)
		printf("maxproc       = %2d\n", st->se_maxchild);
	if (*st->se_exsock != '\0')
		printf("exchangesocket= \"%s\"\n", st->se_exsock);
	if (*st->se_user != '\0')
		printf("user          = \"%s\"\n", st->se_user);
	if (*st->se_group != '\0')
		printf("group         = \"%s\"\n", st->se_group);
#if 0
	if (*st->se_restartdeps != '\0')
		printf("restartdeps   = \"%s\"\n", st->se_restartdeps);
#else /* 0 */

	if (st->se_restartdep[0] != NULL)
	{
		printf("restartdeps   =");
		for (i = 0;
		     i < SM_ARRAY_SIZE(st->se_restartdep)
			&& st->se_restartdep[i] != NULL;
		     i++)
			printf(" %s,", st->se_restartdep[i]);
		putchar('\n');
	}

#endif /* 0 */
	if (st->se_flags != 0)
		printf("type          = %02x\n", st->se_flags);
#if 0
	printf("base          = \"%s\"\n", st->se_server_name);
#endif
	printf("path          = \"%s\"\n", st->se_server);
	printf("args          = \"%s\"\n", st->se_args);
	putchar('\n');
}
#endif /* 0 */

/*
**  Initially there is no configuration context.
**  1. call: fill in smc
**  2. call: save smc in smc_prev, fill in smc
**  se_config() needs the old configuration data and the new one
**  (to see what's changed), hence the old data can be free()d
**  after se_config() is done.
*/

static sm_conf_T *smc = NULL;
static sm_conf_T *smc_prev = NULL;

/*
**  SE_SETCONFIG -- Initialize configuration data for reading
**
**	Parameters:
**		none
**
**	Returns:
**		0 on success, everything else is an error
**
**	Side Effects:
**		Uses global variable CONFIG to access config file.
**		Sets global variables smc and smc_prev.
*/

static int
se_setconfig(void)
{
	int err;
	FILE *fp;

	fp = NULL;

	/* save previous configuration */
	if (smc != NULL)
		smc_prev = smc;
	smc = sm_conf_new(CONFIG);
	if (smc == NULL)
	{
		err = errno;

		m_syslog(LOG_ERR, "%s: sm_conf_new=NULL, errno=%d",
			CONFIG, err);
		return ENOMEM;
	}
	err = sm_conf_read_FILE(smc, CONFIG, fp);
	if (err != 0)
	{
		char buf[256];
		char const *e;

		e = NULL;
		m_syslog(LOG_ERR, "%s: %s", CONFIG,
			sm_conf_strerror(err, buf, sizeof buf));
		while ((e = sm_conf_syntax_error(smc, e)) != NULL)
			m_syslog(LOG_ERR, "%s: %s", CONFIG, e);
		sm_conf_destroy(smc);
		smc = NULL;
		return 2;
	}

	return 0;
}

/*
**  SM_STRINGEQ -- are two strings identical?
**
**	Parameters:
**		s1 -- first string
**		s2 -- second string
**
**	Returns:
**		true iff strings are identical or both NULL
*/

static bool
sm_stringeq(const char *s1, const char *s2)
{
	if (s1 == NULL)
		return s2 == NULL;
	if (s2 == NULL)
		return false;
	return sm_streq(s1, s2);
}

/*
**  SE_ENDCONFIG -- Clean up configuration data
**
**	Parameters:
**		none
**
**	Returns:
**		0 on success, everything else is an error
**
**	Side Effects:
**		Frees smc_prev.
*/

static int
se_endconfig(void)
{
	/* delete previous configuration */
	if (smc_prev != NULL)
	{
		sm_conf_destroy(smc_prev);
		smc_prev = NULL;
	}
	return 0;
}

/*
**  SE_PRINT -- Dump relevant information to stderr
**
**	Parameters:
**		action -- prefix to print
**		sep -- service entry to print
**
**	Returns:
**		none.
*/

#if 0
extern void
print_structure(servtab_T *_s, char const *_name, size_t _name_n);
#endif

static void
se_print(char *action, servtab_P sep)
{
	fprintf(stderr,
#ifdef LOGIN_CAP
		"%s: %s proto=%s X-socket=%s flags=%x max=%d user=%s group=%s class=%s"
		"server=%s sock=%s umask=%o\n"
#else
		"%s: %s X-socket=%s flags=%x max=%d user=%s group=%s "
		"server=%s sock=%s umask=%o\n"
#endif /* LOGIN_CAP */
		, action, sep->se_prg, sep->se_exsock
		, sep->se_flags, sep->se_maxchild, sep->se_user
		, sep->se_group == NULL ? "-" : sep->se_group
#ifdef LOGIN_CAP
		, sep->se_class
#endif
		, sep->se_server
		, sep->se_socket_name == NULL ? "-" : sep->se_socket_name
		, sep->se_socket_umask
		);
}

/*
**  SE_FREE_ENTRY -- free a service entry
**
**	Parameters:
**		cp -- service entry
**
**	Returns:
**		none.
*/

static void
se_free_entry(servtab_P cp)
{
	/*
	**  Most of the data are just references to smc which will
	**  be free()d in se_endconfig(), the only data to be free()d
	**  is (currently) se_pids.
	*/

	if (cp == NULL)
		return;
	SM_FREE(cp->se_ids);
	SM_FREE(cp->se_pids);
}

/*
**  SE_ADD_ENTRY -- Prepend a new service entry
**	initialize some values that are not read from the configuration file
**	(values that are read from the conf.file are initialized elsewhere!)
**
**	Parameters:
**		cp -- service entry
**
**	Returns:
**		pointer to Servtab.
*/

static servtab_P
se_add_entry(servtab_P cp)
{
	servtab_P sep;
	sigset_t omask;

	sep = (servtab_P) sm_zalloc(sizeof(*sep));
	if (sep == (servtab_P) 0)
	{
		m_syslog(LOG_ERR, "Out of memory.");
		sm_exit(EX_OSERR);
	}
	*sep = *cp;
	sep->se_fd = INVALID_SOCKET;
	sigprocmask(SIG_BLOCK, &Blockmask, &omask);
	sep->se_next = Servtab;
	Servtab = sep;
	sigprocmask(SIG_SETMASK, &omask, NULL);
	return sep;
}

/*
**  SE_CONFIG -- (re)read configuration
**
**	Parameters:
**		mcp_ctx -- MCP context
**		first -- first time call?
**
**	Returns:
**		none.
**
**	Side Effects:
**		reads configuration file, writes Servtab.
*/

extern char *sm_getnextstring(char **_cpp);

int
se_config(mcp_ctx_P mcp_ctx, bool first)
{
	servtab_P sep, new, *sepp;
	servtab_T st;
	sigset_t omask;
	int ret, err, argc;
	ushort port;
	uint prev_total_proc;
	char *cp, *arg;
	char const *kw, *title;
	size_t kw_n, title_n;
	sm_conf_node_T *node, *root;

	ret = SM_SUCCESS;

	if ((err = se_setconfig()) != 0)
	{
		m_syslog(LOG_ERR, "%s: %d", CONFIG, err);
		return SM_FAILURE;
	}

	for (sep = Servtab; sep != NULL; sep = sep->se_next)
		SE_CLR_FLAG(sep, SE_FL_CHECKED);
	sm_memzero(&st, sizeof(st));
	prev_total_proc = mcp_ctx->mcp_total_proc;
	root = sm_conf_root(smc);
	node = NULL;
	while ((node = sm_conf_section_next_subsection(smc, root,
				NULL, 0, NULL, 0, node)) != NULL)
	{
		err = sm_conf_section_keyword(smc, node, &kw, &kw_n);
		if (err != 0)
		{
			/* silently ignore? */
			continue;
		}

		st.se_prg = kw;
		st.se_kw = kw;
		err = sm_conf_section_name(smc, node, &title, &title_n);
		if (err != 0)
		{
			/* silently ignore? */
			continue;
		}
		if (sm_conf_node_type(smc, node) != SM_CONF_NODE_SECTION)
			continue;
		if (title != NULL)
		{
			st.se_prg = title;
			st.se_title = title;
		}
		else
			st.se_title = NULL;

		/* set default values */
		st.se_addr = INADDR_NONE;

		/* ToDo: need to figure out whether this is an MCP section! */
		err = sm_conf_get_relative(smc, node, NULL,
				sm_conf_type_section, mcp_defs,
				SM_CONF_FLAG_ALLOW_ANY, &st, sizeof(st));
		if (err != 0)
		{
			char buf[200];
			char const *e = NULL;

			/* silently ignore? */
#if 1
			fprintf(stderr, "%s: %s\n", CONFIG,
				sm_conf_strerror(err, buf, sizeof buf));
			while ((e = sm_conf_syntax_error(smc, e)) != NULL)
				fprintf(stderr, "%s\n", e);
#endif

			continue;
		}
		new = &st;

#if MCP_OLD_SOCKET
		/* consistency check (CC) */
		if (new->se_port != 0 &&
		    MCP_OPT_IS_SET(new->se_socket_name))
		{
			m_syslog(LOG_ERR,
				"%s: cannot set port and socket for %s",
				CONFIG, new->se_prg);
			ret = SM_FAILURE;
			continue;
		}
#endif /* MCP_OLD_SOCKET */

		/* consistency checks (CC) */
		if (new->se_minchild > new->se_maxchild)
		{
			m_syslog(LOG_ERR,
				"%s: min=%d must not be greater than max=%d for %s",
				CONFIG, new->se_minchild, new->se_maxchild,
				new->se_prg);
			ret = SM_FAILURE;
			continue;
		}
		if (new->se_maxchild < 0)
		{			/* apply default max-children */
			new->se_maxchild = SE_IS_ACCEPT(new) ? 0 : 1;
		}
		if (new->se_maxchild > 0)
		{
			size_t bytes;

			bytes = new->se_maxchild * sizeof(*new->se_pids);
			new->se_pids = sm_malloc(bytes);
			if (new->se_pids == NULL)
			{
				m_syslog(LOG_ERR, "Cannot allocate %lu bytes",
					(ulong) bytes);
				sm_exit(EX_OSERR);
			}
			bytes = new->se_maxchild * sizeof(*new->se_ids);
			new->se_ids = sm_malloc(new->se_maxchild *
						sizeof(*new->se_ids));
			if (new->se_ids == NULL)
			{
				m_syslog(LOG_ERR, "Cannot allocate %lu bytes",
					(ulong) bytes);
				sm_exit(EX_OSERR);
			}
		}

		/* server name (basename of full path) [not read from config] */
		if ((new->se_server_name = rindex(new->se_server, '/')))
			new->se_server_name++;

		/* argument vector (rest of entry, must begin with name of program) */
		argc = 0;
		cp = new->se_args;
		for (arg = sm_getnextstring(&cp);
		     cp != NULL && arg != NULL && ret != SM_FAILURE;
		     arg = sm_getnextstring(&cp))
		{
			/* HACK: omit first argument to pass id later on */
			if (argc == 1 && new->se_pass_id != NULL)
				++argc;
			if (argc < MAXARGV)
			{
				new->se_argv[argc++] = arg;
			}
			else
			{
				m_syslog(LOG_ERR,
				       "%s: too many arguments for service %s",
				       CONFIG, new->se_prg);
				ret = SM_FAILURE;
				break;
			}
		}
		if (ret == SM_FAILURE)
			break;
		while (argc <= MAXARGV)
			new->se_argv[argc++] = NULL;

		/* HACK: count number of restart dependencies */
		/* is there some sm_conf_ function to get the value? */
		for (new->se_nrestartdep = 0;
		     new->se_nrestartdep < SM_ARRAY_SIZE(new->se_restartdep)
			&& new->se_restartdep[new->se_nrestartdep] != NULL;
		     new->se_nrestartdep++)
			;

		/* Could be checked when conf is read (CC) */
		if (getpwnam(new->se_user) == NULL)
		{
			m_syslog(LOG_ERR,
			       "%s: No such user '%s', service ignored",
			       new->se_prg, new->se_user);
			ret = SM_FAILURE;
			continue;
		}

		/* Hack: config reader sets it to '\0', not NULL? */
#define SET_EMPTY2NULL(str)	do {	\
		if ((str) != NULL && *(str) == '\0')	\
			(str) = NULL;	\
		} while (0)

		SET_EMPTY2NULL(new->se_group);
		SET_EMPTY2NULL(new->se_socket_group);

		/* Could be checked when conf is read (CC) */
		if (new->se_group != NULL && getgrnam(new->se_group) == NULL)
		{
			m_syslog(LOG_ERR,
			       "%s: No such group '%s', service ignored",
			       new->se_prg, new->se_group);
			ret = SM_FAILURE;
			continue;
		}
#ifdef LOGIN_CAP
		if (login_getclass(new->se_class) == NULL)
		{
			/* error syslogged by getclass */
			m_syslog(LOG_ERR,
			       "%s: %s: login class error, service ignored",
			       new->se_prg, new->se_class);
			ret = SM_FAILURE;
			continue;
		}
#endif /* LOGIN_CAP */

		/* count total number of possible processes */
		mcp_ctx->mcp_total_proc += new->se_maxchild;

		/* Is this service already defined? */
		sep = se_findsepbyname(new->se_prg);
		if (sep != NULL)
		{
			int i;

			/*
			**  Service already defined:
			**  If it's the first invocation: then it's an error
			**  (can't have two services with the same name).
			**  If it's a later call: copy over the old data.
			*/

			if (first)
			{
				m_syslog(LOG_ERR,
					"%s: already defined, stop",
					new->se_prg);
				ret = SM_FAILURE;
				continue;
			}

			if (!sm_stringeq(new->se_kw, sep->se_kw) ||
			    !sm_stringeq(new->se_title, sep->se_title))
			{
				m_syslog(LOG_ERR,
					"%s: keyword[%s/%s] or title[%s/%s] mismatch, stop",
					new->se_prg,
					new->se_kw, sep->se_kw,
					new->se_title, sep->se_title);
				ret = SM_FAILURE;
				continue;
			}

			/*
			**  Copy all data from new to sep.
			**  This is mostly done by pointer swapping.
			*/

			SM_ASSERT(mcp_ctx->mcp_total_proc >= sep->se_maxchild);
			mcp_ctx->mcp_total_proc -= sep->se_maxchild;

#define SWAP_PTRC(a, b) do {char const *c = a; a = b; b = c; } while(0)
#define SWAP_PTR(a, b) do {void *c = a; a = b; b = c; } while(0)
			sigprocmask(SIG_BLOCK, &Blockmask, &omask);

			/* copy over outstanding child pids */
			if (sep->se_maxchild > 0 && new->se_maxchild > 0)
			{
				new->se_numchild = sep->se_numchild;
				if (new->se_numchild > new->se_maxchild)
					new->se_numchild = new->se_maxchild;
				sm_memcpy(new->se_pids, sep->se_pids,
				    new->se_numchild * sizeof(*new->se_pids));
				sm_memcpy(new->se_ids, sep->se_ids,
				    new->se_numchild * sizeof(*new->se_ids));
			}
			SWAP_PTR(sep->se_pids, new->se_pids);
			SWAP_PTR(sep->se_ids, new->se_ids);
			sep->se_minchild = new->se_minchild;
			sep->se_maxchild = new->se_maxchild;
			sep->se_numchild = new->se_numchild;
#if MTA_USE_CPML
			sep->se_maxcpm = new->se_maxcpm;
#endif

			/* might need to turn on or off service now */
			if (is_valid_socket(sep->se_fd))
			{
				if (sep->se_maxchild
				    && sep->se_numchild == sep->se_maxchild)
				{
					if (FD_ISSET(sep->se_fd, &Allsock))
						se_disable(sep, false);
				}
				else
				{
					if (!FD_ISSET(sep->se_fd, &Allsock))
						se_enable(sep, false);
				}
			}
			sep->se_flags = new->se_flags;

			SWAP_PTRC(sep->se_prg, new->se_prg);
			SWAP_PTRC(sep->se_kw, new->se_kw);
			SWAP_PTRC(sep->se_title, new->se_title);
#if 0
			SWAP_PTR(sep->se_proto, new->se_proto);
#endif
			SWAP_PTR(sep->se_socket_name, new->se_socket_name);
			SWAP_PTR(sep->se_socket_user, new->se_socket_user);
			SWAP_PTR(sep->se_socket_group, new->se_socket_group);
			SWAP_PTR(sep->se_exsock, new->se_exsock);
			SWAP_PTR(sep->se_user, new->se_user);
			SWAP_PTR(sep->se_group, new->se_group);
			sep->se_nrestartdep = new->se_nrestartdep;
			for (i = 0; i < SM_ARRAY_SIZE(sep->se_restartdep); i++)
				SWAP_PTR(sep->se_restartdep[i],
						new->se_restartdep[i]);
			SWAP_PTR(sep->se_restartdeps, new->se_restartdeps);
#ifdef LOGIN_CAP
			SWAP_PTR(sep->se_class, new->se_class);
#endif
			SWAP_PTR(sep->se_server, new->se_server);
			SWAP_PTR(sep->se_args, new->se_args);
			for (i = 0; i < MAXARGV; i++)
				SWAP_PTR(sep->se_argv[i], new->se_argv[i]);
			sigprocmask(SIG_SETMASK, &omask, NULL);
			se_free_entry(new);
			if (Debug)
				se_print("REDO", sep);
		}
		else
		{
			sep = se_add_entry(new);
			if (Debug)
				se_print("ADD ", sep);
		}
		SE_SET_FLAG(sep, SE_FL_CHECKED);
		if (sep->se_port > 0)
			port = htons((uint16_t) sep->se_port);
		else
			port = 0;
#if MCP_OLD_SOCKET
		/* Could be checked when conf is read (CC) */
		if (!DONOTBIND(sep) &&
		    sep->se_port <= 0 &&
		    !MCP_OPT_IS_SET(sep->se_socket_name))
		{
			m_syslog(LOG_ERR,
				"%s: unknown service and no valid port %hd",
				sep->se_prg, ntohs(port));
			SE_CLR_FLAG(sep, SE_FL_CHECKED);
			continue;
		}
#else
		/* Could be checked when conf is read (CC) */
		if (!DONOTBIND(sep) &&
		    !MCP_SOCKET_IS_UNIX(sep) &&
		    !MCP_SOCKET_IS_INET(sep))
		{
			m_syslog(LOG_ERR,
				"%s: unknown service and no socket defined %d",
				sep->se_prg, sep->se_socket.sckspc_type);
			SE_CLR_FLAG(sep, SE_FL_CHECKED);
			continue;
		}
#endif

		if (port > 0 && port != sep->se_ctrladdr.sin.sin_port)
		{
			sep->se_ctrladdr.sin.sin_family = AF_INET;
			if (sep->se_addr != INADDR_NONE)
				sep->se_ctrladdr.sin.sin_addr.s_addr =
								sep->se_addr;
			else
				sep->se_ctrladdr.sin.sin_addr = Bind_address;
			sep->se_ctrladdr.sin.sin_port = port;
			if (sep->se_fd >= 0)
				close_sep(sep);
			sep->se_ctrladdr_size = sizeof(sep->se_ctrladdr.sin);
		}
		else if (MCP_OPT_IS_SET(sep->se_socket_name) &&
			 strcmp(sep->se_socket_name,
				sep->se_ctrladdr.sunix.sun_path) != 0)
		{
			int n;

			sep->se_ctrladdr.sunix.sun_family = AF_UNIX;
			n = strlcpy(sep->se_ctrladdr.sunix.sun_path,
				sep->se_socket_name,
				sizeof(sep->se_ctrladdr.sunix.sun_path));
			if (n <= 0
			    || n >= sizeof(sep->se_ctrladdr.sunix.sun_path))
			{
				m_syslog(LOG_ERR,
					"%s: path=%s, status=too_long",
					sep->se_prg,
					sep->se_socket_name);
				continue;
			}
			if (sep->se_fd >= 0)
				close_sep(sep);
			sep->se_ctrladdr_size = sizeof(sep->se_ctrladdr.sunix);

			/* unlink socket (might not exist) */
			(void) unlink(sep->se_socket_name);
		}
		if (sep->se_fd == INVALID_SOCKET
		    && (first || !SE_IS_FLAG(sep, SE_FL_PASS)))
			se_setup(sep);
		sm_memzero(&st, sizeof(st));
	}
	if (err != 0 && err != SM_CONF_ERR_NOT_FOUND)
	{
		char buf[200];
		char const *e = NULL;

		fprintf(stderr, "%s: %s\n", CONFIG,
			sm_conf_strerror(err, buf, sizeof buf));

		while ((e = sm_conf_syntax_error(smc, e)) != NULL)
			fprintf(stderr, "%s\n", e);
	}

	/*
	**  Purge anything not looked at above.
	*/

	sigprocmask(SIG_BLOCK, &Blockmask, &omask);
	sepp = &Servtab;
	while ((sep = *sepp) != NULL)
	{
		if (SE_IS_FLAG(sep, SE_FL_CHECKED))
		{
			sepp = &sep->se_next;
			continue;
		}
		*sepp = sep->se_next;
		if (sep->se_fd >= 0)
			close_sep(sep);
		if (Debug)
			se_print("FREE", sep);
		SM_ASSERT(mcp_ctx->mcp_total_proc >= sep->se_maxchild);
		mcp_ctx->mcp_total_proc -= sep->se_maxchild;
		se_free_entry(sep);
		sm_free((char *) sep);
	}
	sigprocmask(SIG_SETMASK, &omask, NULL);

	if (first && mcp_ctx->mcp_total_proc > 0)
	{
		ret = sm_new_id_ctx(mcp_ctx->mcp_total_proc, 0,
				&mcp_ctx->mcp_id_ctx);
		if (sm_is_err(ret))
		{
			m_syslog(LOG_ERR, "cannot alloc %d bits",
				mcp_ctx->mcp_total_proc );
			sm_exit(EX_OSERR);
		}
		if (Debug)
			fprintf(stderr, "total_proc=%u\n",
				mcp_ctx->mcp_total_proc);
	}
	else if (prev_total_proc < mcp_ctx->mcp_total_proc)
	{
		ret = sm_chg_id_ctx(mcp_ctx->mcp_id_ctx,
				mcp_ctx->mcp_total_proc);
		if (sm_is_err(ret))
		{
			m_syslog(LOG_ERR, "cannot alloc %d bits",
				mcp_ctx->mcp_total_proc );
			sm_exit(EX_OSERR);
		}
	}
	se_endconfig();
	return ret;
}
#endif /* SM_MCP_USE_CONF */


syntax highlighted by Code2HTML, v. 0.9.1