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