/*
**  This file is derived from inetd.c
**	"@(#)from: inetd.c	8.4 (Berkeley) 4/13/94";
**  as available in the OpenBSD source tree.
*/

/*
 * Copyright (c) 1983, 1991, 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1983, 1991, 1993, 1994\n\
	The Regents of the University of California.  All rights reserved.\n";
#endif /* not lint */

#ifndef lint
#if 0
static char sccsid[] = "@(#)from: inetd.c	8.4 (Berkeley) 4/13/94";
#endif
#endif /* not lint */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: inetdconf.c,v 1.10 2007/09/29 02:10:07 ca Exp $")

#include "sm/error.h"
#include "sm/assert.h"
#include "sm/ctype.h"
#include "sm/unixsock.h"
#include "sm/cmsg.h"

#include "sm/param.h"
#include "sm/stat.h"
#include <sys/ioctl.h>
#include "sm/socket.h"
#include "sm/filio.h"
#include "sm/wait.h"
#include "sm/time.h"

#include <errno.h>
#include "sm/fcntl.h"
#include <grp.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include "sm/string.h"
#include "sm/syslog.h"
#include <unistd.h>
#include "sm/sysexits.h"
#include <stdarg.h>

#include "mcp.h"

#if !SM_MCP_USE_CONF

#include "inetdconf.h"


static struct servent *Sp;

/* filepointer for configuration file */
static FILE		*Fconfig = NULL;
static servtab_T	 Serv;

#ifndef MCP_LINE_MAX
# define MCP_LINE_MAX	1024
#endif /* MCP_LINE_MAX */

static char Line[MCP_LINE_MAX];

/*
**  SE_NEXTLINE -- read another line from file
**
**	Parameters:
**		fp -- file pointer from which to read
**
**	Returns:
**		Line (global variable!)
*/

static char *
se_nextline(FILE *fp)
{
	char *cp;

	if (fgets(Line, sizeof(Line), fp) == NULL)
		return ((char *) 0);
	cp = strchr(Line, '\n');
	if (cp != NULL)
		*cp = '\0';
	return Line;
}

/*
**  SE_GETSTRING -- skip over whitespace in configuration line, scan a string
**	(can be quoted; single/double quotes are allowed),
**	mark the end of the string with '\0'.
**
**	Parameters:
**		cpp -- pointer to string ('\0' terminated)
**
**	Returns:
**		new position in string (NULL on EOF)
*/

static char *
se_getstring(char **cpp)
{
	char *cp = *cpp;
	char *start;
	char quote = '\0';

  again:
	while (*cp == ' ' || *cp == '\t')
		cp++;
	if (*cp == '\0')
	{
		int c;

		c = getc(Fconfig);
		(void) ungetc(c, Fconfig);
		if (c == ' ' || c == '\t')
			if ((cp = se_nextline(Fconfig)))
				goto again;
		*cpp = (char *) 0;
		return ((char *) 0);
	}
	if (*cp == '"' || *cp == '\'')
		quote = *cp++;
	start = cp;
	if (quote)
		while (*cp && *cp != quote)
			cp++;
	else
		while (*cp && *cp != ' ' && *cp != '\t')
			cp++;
	if (*cp != '\0')
		*cp++ = '\0';
	*cpp = cp;
	return start;
}

/*
**  SE_SAFEGETSTRING -- Safe se_getstring - if se_getstring returns null,
**	log a syntax error in the configuration file and exit.
**
**	Parameters:
**		cpp -- pointer to string
**
**	Returns:
**		new position in string
*/

static char *
se_safegetstring(char **cpp)
{
	char *cp;

	cp = se_getstring(cpp);
	if (cp == NULL)
	{
		m_syslog(LOG_ERR, "%s: syntax error", CONFIG);
		sm_exit(EX_DATAERR);
	}
	return cp;
}

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

static void
print_service(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\n",
#else
		"%s: %s proto=%s X-socket=%s flags=%x max=%d user=%s group=%s server=%s\n",
#endif /* LOGIN_CAP */
		action, sep->se_prg, sep->se_proto, 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 /* LOGIN_CAP */
		sep->se_server);
}

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

static void
se_freeconfig(servtab_P cp)
{
	int i;

#define FREEPTR(ptr)	do { if ((ptr) != NULL) free(ptr); } while (0)

	FREEPTR(cp->se_prg);
	FREEPTR(cp->se_proto);
	FREEPTR(cp->se_exsock);
	FREEPTR(cp->se_user);
	FREEPTR(cp->se_group);
#ifdef LOGIN_CAP
	FREEPTR(cp->se_class);
#endif /* LOGIN_CAP */
	FREEPTR(cp->se_server);
	FREEPTR(cp->se_restartdeps);
	FREEPTR(cp->se_pids);
	for (i = 0; i < MAXARGV; i++)
		FREEPTR(cp->se_argv[i]);
}

/*
**  SE_NEWSTR -- make a copy of cp (exits on error)
**
**	Parameters:
**		cp -- string to copy
**
**	Returns:
**		new string
*/

static char *
se_newstr(char *cp)
{
	if ((cp = strdup(cp ? cp : "")))
		return cp;
	m_syslog(LOG_ERR, "strdup: %m");
	sm_exit(EX_OSERR);
	/* NOTREACHED */
}

/*
**  SE_ENTER -- Enter (prepend) a new service entry.
**
**	Parameters:
**		cp -- service entry
**
**	Returns:
**		pointer to Servtab.
*/

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

	sep = (servtab_P) malloc(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_SETCONFIG -- open configuration file for reading (or rewind it)
**
**	Parameters:
**		none.
**
**	Returns:
**		successful
**
**	Side Effects:
**		sets/changes Fconfig
*/

static bool
se_setconfig(void)
{

	if (Fconfig != NULL)
	{
		fseek(Fconfig, 0L, SEEK_SET);
		return true;
	}
	Fconfig = fopen(CONFIG, "r");
	return Fconfig != NULL;
}

/*
**  SE_ENDCONFIG -- close configuration file pointer
**
**	Parameters:
**		none.
**
**	Returns:
**		none.
**
**	Side Effects:
**		Fconfig is modified.
*/

static void
se_endconfig(void)
{
	if (Fconfig != NULL)
	{
		(void) fclose(Fconfig);
		Fconfig = NULL;
	}
}

/*
**  SE_GETINTCONF -- read an integer from a string
**
**	Parameters:
**		cp -- pointer to string (might be modified)
**		whine -- complain string if error occurs
**		defval -- default value to use
**
**	Returns:
**		value
**
**	Side Effects:
**		exit()s on error.
*/

static int
se_getintconf(char **cp, char *whine, int defval)
{
	char *arg;
	char *eptr;
	u_long val;

	arg = se_safegetstring(cp);
	if (arg == NULL || *arg == '\0')
	{
		m_syslog(LOG_ERR, "%s: %s", CONFIG, whine);
		sm_exit(EX_DATAERR);
	}
	if (*arg == DEFAULT_CHAR)	/* XXX ugly... */
		return defval;

	val = strtoul(arg, &eptr, 0);
	if (eptr == arg)
	{
		m_syslog(LOG_ERR, "%s: %s", CONFIG, whine);
		sm_exit(EX_DATAERR);
	}
	return (int) val;
}

/*
**  IDISOK -- Check identifier for valid "syntax" (alnum + '_')
**
**	Parameters:
**		id -- identifier to check
**
**	Returns:
**		ok?
*/

static bool
idisok(char *id)
{
	uchar c;

	if (id == NULL)
		return false;
	while ((c = (uchar) *id++) != '\0')
		if (!(ISALNUM(c) || c == '_'))
			return false;
	return true;
}

/*
**  SE_GETCONFIGENT -- get a configuration line entry
**
**	Parameters:
**		none.
**
**	Returns:
**		service entry
*/

static servtab_P
se_getconfigent(void)
{
	servtab_P sep;
	int argc;
	char *cp, *arg, *s;

	sep = &Serv;
  more:
	/* XXX this function leaks memory on illegal input... */

	while ((cp = se_nextline(Fconfig)) && (*cp == '#' || *cp == '\0'))
		;
	if (cp == NULL)
		return ((servtab_P) 0);
	/*
	**  clear the static buffer, since some fields (se_ctrladdr,
	**  for example) don't get initialized here.
	*/

	memset((caddr_t) sep, 0, sizeof *sep);

	/* Program name */
	arg = se_getstring(&cp);
	if (cp == NULL)
	{
		/* got an empty line containing just blanks/tabs. */
		goto more;
	}
	if (!idisok(arg))
	{
		m_syslog(LOG_ERR,
		       "%s: program name '%s' must be alpha-numerical",
		       CONFIG, arg);
		goto more;
	}

	sep->se_prg = se_newstr(arg);

	/* Port */
	sep->se_port = se_getintconf(&cp, "Port", 0);

	/* local (UNIX) socket */
	arg = se_safegetstring(&cp);
	sep->se_socket_name = se_newstr(arg);

	/* consistency check (CC) */
	if (sep->se_port != 0 && *(sep->se_socket_name) != DEFAULT_CHAR)
	{
		m_syslog(LOG_ERR,
		       "%s: cannot set port and socket for %s",
		       CONFIG, sep->se_prg);
		goto more;
	}

	/* umask for socket */
	sep->se_socket_umask = se_getintconf(&cp, "umask", 007);

	/* socket owner */
	sep->se_socket_user = se_newstr(se_safegetstring(&cp));
	if ((s = strrchr(sep->se_socket_user, ':')) != NULL)
	{
		*s = '\0';
		sep->se_socket_group = se_newstr(s + 1);
	}
	else
		sep->se_socket_group = NULL;

	/* Protocol (always TCP for now) */
	arg = se_safegetstring(&cp);
	sep->se_proto = se_newstr(arg);

	/* Type */
	arg = se_safegetstring(&cp);
	if (!strncmp(arg, "nostartaccept", 13))
		SE_SET_FLAG(sep, SE_FL_ACCEPT|SE_FL_W4REQ);
	else if (!strncmp(arg, "accept", 6))
		SE_SET_FLAG(sep, SE_FL_ACCEPT);
	else if (!strncmp(arg, "pass", 4))
		SE_SET_FLAG(sep, SE_FL_PASS);
	else if (!strncmp(arg, "wait", 4))
		SE_SET_FLAG(sep, SE_FL_WAIT);
	else
	{
		m_syslog(LOG_ERR,
		       "%s: bad type for program %s",
		       CONFIG, sep->se_prg);
		goto more;
	}

	/* Exchange socket */
	sep->se_exsock = se_newstr(se_safegetstring(&cp));

	/* Min/Max children */
	sep->se_minchild = se_getintconf(&cp, "Min child", Minchild);
	sep->se_maxchild = se_getintconf(&cp, "Max child", Maxchild);

	/* consistency checks (CC) */
	if (sep->se_minchild > sep->se_maxchild)
	{
		m_syslog(LOG_ERR,
			"%s: min=%d must not be greater than max=%d for %s",
			CONFIG, sep->se_minchild, sep->se_maxchild,
			sep->se_prg);
		goto more;
	}
	if (sep->se_maxchild < 0)
	{			/* apply default max-children */
		sep->se_maxchild = SE_IS_ACCEPT(sep) ? 0 : 1;
	}
	if (sep->se_maxchild > 0)
	{
		sep->se_pids = malloc(sep->se_maxchild * sizeof(*sep->se_pids));
		if (sep->se_pids == NULL)
		{
			m_syslog(LOG_ERR, "Out of memory.");
			sm_exit(EX_OSERR);
		}
	}

#if MTA_USE_CPML
	sep->se_maxcpm = se_getintconf(&cp, "Max cpm", maxcpm);;
#endif /* MTA_USE_CPML */
	sep->se_user = se_newstr(se_safegetstring(&cp));
#ifdef LOGIN_CAP
	if ((s = strrchr(sep->se_user, '/')) != NULL)
	{
		*s = '\0';
		sep->se_class = se_newstr(s + 1);
	}
	else
		sep->se_class = se_newstr(RESOURCE_RC);
#endif /* LOGIN_CAP */
	if ((s = strrchr(sep->se_user, ':')) != NULL)
	{
		*s = '\0';
		sep->se_group = se_newstr(s + 1);
	}
	else
		sep->se_group = NULL;

	/*
	**  Restart dependencies.
	**  Comma separated list (NO white space) of programs that need to be
	**  restarted when this one goes down.
	**  Entire list (one string) is stored in se_restartdeps,
	**  se_restartdep[] just points to this (',' replaced by '\0');
	**  Note: there could be some consistency check after the entire
	**  configuration file has been read, i.e., whether all the
	**  dependencies have been actually defined as services.
	*/

	sep->se_restartdeps = s = se_newstr(se_safegetstring(&cp));
	if (s != NULL && *s != '\0' && *s != DEFAULT_CHAR)
	{
		for (sep->se_nrestartdep = 0, arg = s;
		     sep->se_nrestartdep < MAXRESTARTDEP
		     && (arg = strsep(&s, ",")) != NULL;
		     )
		{
			if (arg == NULL || *arg == '\0')
			{
				m_syslog(LOG_ERR,
					"%s: empty restart dependency for service %s",
					CONFIG, sep->se_prg);
				goto more;
			}
			else
			{
				sep->se_restartdep[sep->se_nrestartdep] = arg;
				sep->se_nrestartdep++;
			}
		}
	}

	/* server program (path) */
	sep->se_server = se_newstr(se_safegetstring(&cp));

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

	/* argument vector (rest of entry, must begin with name of program) */
	argc = 0;
	for (arg = se_getstring(&cp); cp; arg = se_getstring(&cp))
	{
		if (argc < MAXARGV)
		{
			sep->se_argv[argc++] = se_newstr(arg);
		}
		else
		{
			m_syslog(LOG_ERR,
			       "%s: too many arguments for service %s",
			       CONFIG, sep->se_prg);
			goto more;
		}
	}
	while (argc <= MAXARGV)
		sep->se_argv[argc++] = 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.
*/

int
se_config(mcp_ctx_P mcp_ctx, bool first)
{
	servtab_P sep, new, *sepp;
	sigset_t omask;
	int port;
	int ret;

	ret = SM_SUCCESS;
	if (!se_setconfig())
	{
		m_syslog(LOG_ERR, "%s: %m", CONFIG);
		return SM_FAILURE;
	}
	for (sep = Servtab; sep != NULL; sep = sep->se_next)
		SE_CLR_FLAG(sep, SE_FL_CHECKED);
	while ((new = se_getconfigent()) != NULL)
	{
		/* Could be checked when conf is read (CC) */
		if (getpwnam(new->se_user) == NULL)
		{
			m_syslog(LOG_ERR,
			       "%s/%s: No such user '%s', service ignored",
			       new->se_prg, new->se_proto, new->se_user);
			ret = SM_FAILURE;
			continue;
		}

		/* Could be checked when conf is read (CC) */
		if (new->se_group && getgrnam(new->se_group) == NULL)
		{
			m_syslog(LOG_ERR,
			       "%s/%s: No such group '%s', service ignored",
			       new->se_prg, new->se_proto, 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: %s: login class error, service ignored",
			       new->se_prg, new->se_proto, new->se_class);
			ret = SM_FAILURE;
			continue;
		}
#endif /* LOGIN_CAP */
		sep = se_findsepbyname(new->se_prg);
		if (sep != NULL)
		{
			int i;

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

#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 && new->se_maxchild)
			{
				new->se_numchild = sep->se_numchild;
				if (new->se_numchild > new->se_maxchild)
					new->se_numchild = new->se_maxchild;
				memcpy(new->se_pids, sep->se_pids,
				    new->se_numchild * sizeof(*new->se_pids));
			}
			SWAP_PTR(sep->se_pids, new->se_pids);
			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 (sep->se_fd >= 0)
			{
				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_PTR(sep->se_exsock, new->se_exsock);
			SWAP_PTR(sep->se_user, new->se_user);
			SWAP_PTR(sep->se_group, new->se_group);
#ifdef LOGIN_CAP
			SWAP_PTR(sep->se_class, new->se_class);
#endif
			SWAP_PTR(sep->se_server, new->se_server);
			for (i = 0; i < MAXARGV; i++)
				SWAP_PTR(sep->se_argv[i], new->se_argv[i]);
			sigprocmask(SIG_SETMASK, &omask, NULL);
			se_freeconfig(new);
			if (Debug)
				print_service("REDO", sep);
		}
		else
		{
			sep = se_enter(new);
			if (Debug)
				print_service("ADD ", sep);
		}
		SE_SET_FLAG(sep, SE_FL_CHECKED);
		port = 0;
		if (sep->se_port > 0)
			port = htons(sep->se_port);
		else if (Sp != NULL)
			port = Sp->s_port;

		/* Could be checked when conf is read (CC) */
		if (!DONOTBIND(sep) && sep->se_port <= 0 &&
		    *(sep->se_socket_name) == DEFAULT_CHAR)
		{
			m_syslog(LOG_ERR,
				"%s/%s: unknown service and no valid port %d",
				sep->se_prg, sep->se_proto, port);
			SE_CLR_FLAG(sep, SE_FL_CHECKED);
			continue;
		}
		if (port > 0 && port != sep->se_ctrladdr.sin.sin_port)
		{
			sep->se_ctrladdr.sin.sin_family = AF_INET;
			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 (*(sep->se_socket_name) != DEFAULT_CHAR &&
			 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/%s: path=%s, status=too_long",
					sep->se_prg, sep->se_proto,
					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)
			se_setup(sep);
	}
	se_endconfig();

	/*
	**  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)
			print_service("FREE", sep);
		se_freeconfig(sep);
		free((char *) sep);
	}
	sigprocmask(SIG_SETMASK, &omask, NULL);
	return ret;
}
#endif /* !SM_MCP_USE_CONF */


syntax highlighted by Code2HTML, v. 0.9.1