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