/*
** 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