# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. PROG = inetd MANIFEST= inetd.xml inetd-upgrade.xml SVCMETHOD= inetd-upgrade OBJS = inetd.o tlx.o config.o util.o contracts.o repval.o wait.o env.o include ../../../Makefile.cmd include ../../Makefile.cmd-inet include ../../../Makefile.ctf ROOTMANIFESTDIR= $(ROOTSVCNETWORK) CPPFLAGS += -D_FILE_OFFSET_BITS=64 -I$(CMDINETCOMMONDIR) -D_REENTRANT $(RELEASE_BUILD)CPPFLAGS += -DNDEBUG CERRWARN += -Wno-switch SMOFF += kmalloc_wrong_size LDLIBS += -lsocket -lnsl -lrestart -lscf -lcontract -linetutil \ -lwrap -linetsvc -luutil -lumem -lbsm CLOBBERFILES += $(SVCMETHOD) .PARALLEL: $(OBJS) .WAIT: $(PROG) .KEEP_STATE: all: $(PROG) $(SVCMETHOD) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) include ../Makefile.lib install: all $(ROOTLIBINETPROG) $(ROOTMANIFEST) $(ROOTSVCMETHOD) -$(RM) $(ROOTUSRSBINPROG) -$(SYMLINK) ../lib/inet/${PROG} $(ROOTUSRSBINPROG) check: $(CHKMANIFEST) clean: $(RM) $(OBJS) include ../../../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Routines used by inetd to read inetd's configuration from the repository, * to validate it and setup inetd's data structures appropriately based on * in. */ #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* method timeout used if one isn't explicitly specified */ #define DEFAULT_METHOD_TIMEOUT 10 /* supported method properties and their attributes */ static inetd_prop_t method_props[] = { {PR_EXEC_NAME, "", INET_TYPE_STRING, B_FALSE, IVE_UNSET, 0, B_FALSE}, {PR_ARG0_NAME, "", INET_TYPE_STRING, B_TRUE, IVE_UNSET, 0, B_FALSE}, {SCF_PROPERTY_TIMEOUT, "", INET_TYPE_COUNT, B_TRUE, IVE_UNSET, 0, B_FALSE}, {NULL}, }; /* enumeration of method properties; used to index into method_props[] */ typedef enum { MP_EXEC, MP_ARG0, MP_TIMEOUT } method_prop_t; /* handle used for repository access in read_prop() */ static scf_handle_t *rep_handle = NULL; /* pool used to create proto_info_t lists (generic proto info structure) */ static uu_list_pool_t *proto_info_pool = NULL; static void destroy_method_props(inetd_prop_t *); static int proto_info_compare(const void *, const void *, void *); int config_init(void) { if ((rep_handle = scf_handle_create(SCF_VERSION)) == NULL) { error_msg("%s: %s", gettext("Failed to create repository handle"), scf_strerror(scf_error())); return (-1); } else if (make_handle_bound(rep_handle) == -1) { /* let config_fini clean-up */ return (-1); } if ((proto_info_pool = uu_list_pool_create("proto_info_pool", sizeof (proto_info_t), offsetof(proto_info_t, link), proto_info_compare, UU_LIST_POOL_DEBUG)) == NULL) { error_msg(gettext("Failed to create uu list pool: %s"), uu_strerror(uu_error())); return (-1); } return (0); } void config_fini(void) { if (rep_handle == NULL) return; if (proto_info_pool != NULL) { uu_list_pool_destroy(proto_info_pool); proto_info_pool = NULL; } (void) scf_handle_unbind(rep_handle); scf_handle_destroy(rep_handle); rep_handle = NULL; } static void destroy_method_info(method_info_t *mi) { if (mi == NULL) return; if (mi->wordexp_arg0_backup != NULL) { /* * Return the wordexp structure back to its original * state so it can be consumed by wordfree. */ free(mi->exec_args_we.we_wordv[0]); mi->exec_args_we.we_wordv[0] = (char *)mi->wordexp_arg0_backup; } free(mi->exec_path); wordfree(&mi->exec_args_we); free(mi); } /* * Transforms the properties read from the repository for a method into a * method_info_t and returns a pointer to it. If expansion of the exec * property fails, due to an invalid string or memory allocation failure, * NULL is returned and exec_invalid is set appropriately to indicate whether * it was a memory allocation failure or an invalid exec string. */ static method_info_t * create_method_info(const inetd_prop_t *mprops, boolean_t *exec_invalid) { method_info_t *ret; int i; if ((ret = calloc(1, sizeof (method_info_t))) == NULL) goto alloc_fail; /* Expand the exec string. */ if ((i = wordexp(get_prop_value_string(mprops, PR_EXEC_NAME), &ret->exec_args_we, WRDE_NOCMD|WRDE_UNDEF)) != 0) { if (i == WRDE_NOSPACE) goto alloc_fail; *exec_invalid = B_TRUE; free(ret); return (NULL); } if ((ret->exec_path = strdup(ret->exec_args_we.we_wordv[0])) == NULL) goto alloc_fail; if (mprops[MP_ARG0].ip_error == IVE_VALID) { /* arg0 is set */ /* * Keep a copy of arg0 of the wordexp structure so that * wordfree() gets passed what wordexp() originally returned, * as documented as required in the man page. */ ret->wordexp_arg0_backup = ret->exec_args_we.we_wordv[0]; if ((ret->exec_args_we.we_wordv[0] = strdup(get_prop_value_string(mprops, PR_ARG0_NAME))) == NULL) goto alloc_fail; } if (mprops[MP_TIMEOUT].ip_error == IVE_VALID) { ret->timeout = get_prop_value_count(mprops, SCF_PROPERTY_TIMEOUT); } else { ret->timeout = DEFAULT_METHOD_TIMEOUT; } /* exec_invalid not set on success */ return (ret); alloc_fail: error_msg(strerror(errno)); destroy_method_info(ret); *exec_invalid = B_FALSE; return (NULL); } /* * Returns B_TRUE if the contents of the 2 method_info_t structures are * equivalent, else B_FALSE. */ boolean_t method_info_equal(const method_info_t *mi, const method_info_t *mi2) { int i; if ((mi == NULL) && (mi2 == NULL)) { return (B_TRUE); } else if (((mi == NULL) || (mi2 == NULL)) || (mi->exec_args_we.we_wordc != mi2->exec_args_we.we_wordc) || (strcmp(mi->exec_path, mi2->exec_path) != 0)) { return (B_FALSE); } for (i = 0; i < mi->exec_args_we.we_wordc; i++) { if (strcmp(mi->exec_args_we.we_wordv[i], mi2->exec_args_we.we_wordv[i]) != 0) { return (B_FALSE); } } return (B_TRUE); } /* * Checks if the contents of the 2 socket_info_t structures are equivalent. * If 'isrpc' is false, the address components of the two structures are * compared for equality as part of this. If the two structures are * equivalent B_TRUE is returned, else B_FALSE. */ boolean_t socket_info_equal(const socket_info_t *si, const socket_info_t *si2, boolean_t isrpc) { return ((isrpc || (memcmp(&si->local_addr, &si2->local_addr, sizeof (si->local_addr)) == 0)) && (si->type == si2->type)); } /* * proto_info_t comparison function. Returns 0 on match, else -1, as required * by uu_list_find(). */ static int proto_info_compare(const void *lv, const void *rv, void *istlx) { proto_info_t *pi = (proto_info_t *)lv; proto_info_t *pi2 = (proto_info_t *)rv; /* check their RPC configuration matches */ if (pi->ri != NULL) { if ((pi2->ri == NULL) || !rpc_info_equal(pi->ri, pi2->ri)) return (-1); } else if (pi2->ri != NULL) { return (-1); } if (pi->v6only != pi2->v6only) return (-1); if (*(boolean_t *)istlx) { if (tlx_info_equal((tlx_info_t *)lv, (tlx_info_t *)rv, pi->ri != NULL)) return (0); } else { if (socket_info_equal((socket_info_t *)lv, (socket_info_t *)rv, pi->ri != NULL)) return (0); } return (-1); } /* * Returns B_TRUE if the bind configuration of the two instance_cfg_t * structures are equivalent, else B_FALSE. */ boolean_t bind_config_equal(const basic_cfg_t *c1, const basic_cfg_t *c2) { proto_info_t *pi; if ((c1->iswait != c2->iswait) || (c1->istlx != c2->istlx)) return (B_FALSE); if (uu_list_numnodes(c1->proto_list) != uu_list_numnodes(c2->proto_list)) return (B_FALSE); /* * For each element in the first configuration's socket/tlx list, * check there's a matching one in the other list. */ for (pi = uu_list_first(c1->proto_list); pi != NULL; pi = uu_list_next(c1->proto_list, pi)) { uu_list_index_t idx; if (uu_list_find(c2->proto_list, pi, (void *)&c1->istlx, &idx) == NULL) return (B_FALSE); } return (B_TRUE); } /* * Write the default values contained in 'bprops', read by * read_instance_props(), into 'cfg'. * Returns -1 if memory allocation fails, else 0. */ static int populate_defaults(inetd_prop_t *bprops, basic_cfg_t *cfg) { cfg->do_tcp_wrappers = get_prop_value_boolean(bprops, PR_DO_TCP_WRAPPERS_NAME); cfg->do_tcp_trace = get_prop_value_boolean(bprops, PR_DO_TCP_TRACE_NAME); cfg->do_tcp_keepalive = get_prop_value_boolean(bprops, PR_DO_TCP_KEEPALIVE_NAME); cfg->inherit_env = get_prop_value_boolean(bprops, PR_INHERIT_ENV_NAME); cfg->wait_fail_cnt = get_prop_value_int(bprops, PR_MAX_FAIL_RATE_CNT_NAME); cfg->wait_fail_interval = get_prop_value_int(bprops, PR_MAX_FAIL_RATE_INTVL_NAME); cfg->max_copies = get_prop_value_int(bprops, PR_MAX_COPIES_NAME); cfg->conn_rate_offline = get_prop_value_int(bprops, PR_CON_RATE_OFFLINE_NAME); cfg->conn_rate_max = get_prop_value_int(bprops, PR_CON_RATE_MAX_NAME); cfg->bind_fail_interval = get_prop_value_int(bprops, PR_BIND_FAIL_INTVL_NAME); cfg->bind_fail_max = get_prop_value_int(bprops, PR_BIND_FAIL_MAX_NAME); cfg->conn_backlog = get_prop_value_int(bprops, PR_CONNECTION_BACKLOG_NAME); if ((cfg->bind_addr = strdup(get_prop_value_string(bprops, PR_BIND_ADDR_NAME))) == NULL) { error_msg(strerror(errno)); return (-1); } return (0); } void destroy_method_infos(method_info_t **mis) { int i; for (i = 0; i < NUM_METHODS; i++) { destroy_method_info(mis[i]); mis[i] = NULL; } } /* * For each method, if it was specifed convert its entry in 'mprops', * into an entry in 'mis'. Returns -1 if memory allocation fails or one of the * exec strings was invalid, else 0. */ static int create_method_infos(const char *fmri, inetd_prop_t **mprops, method_info_t **mis) { int i; for (i = 0; i < NUM_METHODS; i++) { /* * Only create a method info structure if the method properties * contain an exec string, which we take to mean the method * is specified. */ if (mprops[i][MP_EXEC].ip_error == IVE_VALID) { boolean_t exec_invalid; if ((mis[i] = create_method_info(mprops[i], &exec_invalid)) == NULL) { if (exec_invalid) { error_msg(gettext("Property %s for " "method %s of instance %s is " "invalid"), PR_EXEC_NAME, methods[i].name, fmri); } return (-1); } } } return (0); } /* * Try and read each of the method properties for the method 'method' of * instance 'inst', and return a table containing all method properties. If an * error occurs, NULL is returned, with 'err' set to indicate the cause. * Otherwise, a pointer to an inetd_prop_t table is returned containing all * the method properties, and each of the properties is flagged according to * whether it was present or not, and if it was present its value is set in * the property's entry in the table. */ static inetd_prop_t * read_method_props(const char *inst, instance_method_t method, scf_error_t *err) { inetd_prop_t *ret; int i; if ((ret = calloc(1, sizeof (method_props))) == NULL) { *err = SCF_ERROR_NO_MEMORY; return (NULL); } (void) memcpy(ret, method_props, sizeof (method_props)); for (i = 0; ret[i].ip_name != NULL; i++) { *err = read_prop(rep_handle, &ret[i], i, inst, methods[method].name); if ((*err != 0) && (*err != SCF_ERROR_NOT_FOUND)) { destroy_method_props(ret); return (NULL); } } return (ret); } static void destroy_method_props(inetd_prop_t *mprop) { int i; if (mprop == NULL) return; for (i = 0; mprop[i].ip_name != NULL; i++) { if (mprop[i].ip_type == INET_TYPE_STRING && mprop[i].ip_error == IVE_VALID) free(mprop[i].ip_value.iv_string); } free(mprop); } /* * Destroy the basic and method properties returned by read_inst_props(). */ static void destroy_inst_props(inetd_prop_t *bprops, inetd_prop_t **mprops) { int i; free_instance_props(bprops); for (i = 0; i < NUM_METHODS; i++) destroy_method_props(mprops[i]); } /* * Read all the basic and method properties for instance 'inst', as inetd_prop_t * tables, into the spaces referenced by 'bprops' and 'mprops' respectively. * Each of the properties in the tables are flagged to indicate if the * property was present or not, and if it was the value is stored within it. * If an error occurs at any time -1 is returned and 'err' is set to * indicate the reason, else 0 is returned. */ static int read_inst_props(const char *fmri, inetd_prop_t **bprops, inetd_prop_t **mprops, scf_error_t *err) { size_t nprops; int i; if ((*bprops = read_instance_props(rep_handle, (char *)fmri, &nprops, err)) == NULL) return (-1); for (i = 0; i < NUM_METHODS; i++) { if ((mprops[i] = read_method_props(fmri, (instance_method_t)i, err)) == NULL) { for (i--; i >= 0; i--) destroy_method_props(mprops[i]); free_instance_props(*bprops); return (-1); } } return (0); } /* * Returns B_TRUE if all required properties were read from the repository * (whether taken from the defaults or directly from the instance), they * all had valid values, all the required methods were present, and they * each had the required properties with valid values. Else, returns B_FALSE. * If the function returns B_TRUE, the storage referenced by 'cfg' is set * to point at an allocated instance_cfg_t initialized based on the basic * properties (not method or defaults). */ static boolean_t valid_inst_props(const char *fmri, inetd_prop_t *bprops, inetd_prop_t **mprops, basic_cfg_t **cfg) { boolean_t valid; size_t num_bprops; int i; valid = valid_props(bprops, fmri, cfg, proto_info_pool, conn_ind_pool); /* * Double check we've got all necessary properties (valid_props() * doesn't enforce the presence of defaults), and output error messages * for each invalid/ missing property. */ (void) get_prop_table(&num_bprops); for (i = 0; bprops[i].ip_name != NULL; i++) { switch (bprops[i].ip_error) { case IVE_UNSET: if (!bprops[i].ip_default) continue; if ((i == PT_ARG0_INDEX) || (i == PT_EXEC_INDEX)) continue; /* FALLTHROUGH */ case IVE_INVALID: error_msg(gettext("Property '%s' of instance " "%s is missing, inconsistent or invalid"), bprops[i].ip_name, fmri); valid = B_FALSE; } } for (i = 0; i < NUM_METHODS; i++) { int j; /* check if any properties are set */ for (j = 0; mprops[i][j].ip_name != NULL; j++) { if (mprops[i][j].ip_error != IVE_UNSET) break; } if (mprops[i][j].ip_name == NULL) { /* an unspecified method */ if ((instance_method_t)i == IM_START) { error_msg(gettext( "Unspecified %s method for instance %s"), START_METHOD_NAME, fmri); valid = B_FALSE; } } else if (mprops[i][MP_EXEC].ip_error == IVE_UNSET) { error_msg(gettext("Missing %s property from method %s " "of instance %s"), PR_EXEC_NAME, methods[(instance_method_t)i].name, fmri); valid = B_FALSE; } } if (!valid) { destroy_basic_cfg(*cfg); *cfg = NULL; } return (valid); } void destroy_instance_cfg(instance_cfg_t *cfg) { if (cfg != NULL) { destroy_basic_cfg(cfg->basic); destroy_method_infos(cfg->methods); free(cfg); } } /* * Returns an allocated instance_cfg_t representation of an instance's * configuration read from the repository. If the configuration is invalid, a * repository error occurred, or a memory allocation occurred returns NULL, * else returns a pointer to the allocated instance_cfg_t. */ instance_cfg_t * read_instance_cfg(const char *fmri) { uint_t retries; inetd_prop_t *bprops; inetd_prop_t *mprops[NUM_METHODS]; instance_cfg_t *ret = NULL; scf_error_t err; if ((ret = calloc(1, sizeof (instance_cfg_t))) == NULL) return (NULL); for (retries = 0; retries <= REP_OP_RETRIES; retries++) { if (make_handle_bound(rep_handle) == -1) { err = scf_error(); goto read_error; } if (read_inst_props(fmri, &bprops, mprops, &err) == 0) break; if (err != SCF_ERROR_CONNECTION_BROKEN) goto read_error; (void) scf_handle_unbind(rep_handle); } if (retries > REP_OP_RETRIES) goto read_error; /* * Switch off validation of the start method's exec string, since * during boot the filesystem it resides on may not have been * mounted yet, which would result in a false validation failure. * We'll catch any real errors when the start method is first run * in passes_basic_exec_checks(). */ bprops[PT_EXEC_INDEX].ip_error = IVE_UNSET; if ((!valid_inst_props(fmri, bprops, mprops, &ret->basic)) || (populate_defaults(bprops, ret->basic) != 0) || (create_method_infos(fmri, mprops, ret->methods) != 0)) { destroy_instance_cfg(ret); ret = NULL; } destroy_inst_props(bprops, mprops); return (ret); read_error: error_msg(gettext( "Failed to read the configuration of instance %s: %s"), fmri, scf_strerror(err)); free(ret); return (NULL); } /* * Returns a pointer to an allocated method context for the specified method * of the specified instance if it could retrieve it. Else, if there were * errors retrieving it, NULL is returned and the pointer referenced by * 'errstr' is set to point at an appropriate error string. */ struct method_context * read_method_context(const char *inst_fmri, const char *method, const char *path) { scf_instance_t *scf_inst = NULL; struct method_context *ret; uint_t retries; mc_error_t *tmperr; char *fail; fail = gettext("Failed to retrieve method context for the %s method of " "instance %s : %s"); for (retries = 0; retries <= REP_OP_RETRIES; retries++) { if (make_handle_bound(rep_handle) == -1) goto inst_failure; if (((scf_inst = scf_instance_create(rep_handle)) != NULL) && (scf_handle_decode_fmri(rep_handle, inst_fmri, NULL, NULL, scf_inst, NULL, NULL, SCF_DECODE_FMRI_EXACT) == 0)) break; if (scf_error() != SCF_ERROR_CONNECTION_BROKEN) { scf_instance_destroy(scf_inst); goto inst_failure; } (void) scf_instance_destroy(scf_inst); scf_inst = NULL; (void) scf_handle_unbind(rep_handle); } if (retries > REP_OP_RETRIES) goto inst_failure; if ((tmperr = restarter_get_method_context( RESTARTER_METHOD_CONTEXT_VERSION, scf_inst, NULL, method, path, &ret)) != NULL) { ret = NULL; error_msg(fail, method, inst_fmri, tmperr->msg); restarter_mc_error_destroy(tmperr); } scf_instance_destroy(scf_inst); return (ret); inst_failure: /* * We can rely on this string not becoming invalid * since we don't call bind_textdomain_codeset() or * setlocale(3C) after initialization. */ error_msg(fail, method, inst_fmri, gettext("failed to get instance from repository")); return (NULL); } /* * Reads the value of the enabled property from the named property group * of the given instance. * If an error occurs, the SCF error code is returned. The possible errors are: * - SCF_ERROR_INVALID_ARGUMENT: The enabled property is not a boolean. * - SCF_ERROR_NONE: No value exists for the enabled property. * - SCF_ERROR_CONNECTION_BROKEN: Repository connection broken. * - SCF_ERROR_NOT_FOUND: The property wasn't found. * - SCF_ERROR_NO_MEMORY: allocation failure. * Else 0 is returned and 'enabled' set appropriately. */ static scf_error_t read_enable_prop(const char *fmri, boolean_t *enabled, const char *pg) { scf_simple_prop_t *sp; uint8_t *u8p; if ((sp = scf_simple_prop_get(rep_handle, fmri, pg, SCF_PROPERTY_ENABLED)) == NULL) return (scf_error()); if ((u8p = scf_simple_prop_next_boolean(sp)) == NULL) { scf_simple_prop_free(sp); return (scf_error()); } *enabled = (*u8p != 0); scf_simple_prop_free(sp); return (0); } /* * Reads the enabled value for the given instance FMRI. The read value * is based on a merge of the 'standard' enabled property, and the temporary * override one; the merge involves using the latter properties value if * present, else resporting to the formers. If an error occurs -1 is returned, * else 0 is returned and 'enabled' set approriately. */ int read_enable_merged(const char *fmri, boolean_t *enabled) { uint_t retries; for (retries = 0; retries <= REP_OP_RETRIES; retries++) { if (make_handle_bound(rep_handle) == -1) goto gen_fail; switch (read_enable_prop(fmri, enabled, SCF_PG_GENERAL_OVR)) { case 0: debug_msg("read %d from override", *enabled); return (0); case SCF_ERROR_CONNECTION_BROKEN: break; case SCF_ERROR_NOT_FOUND: case SCF_ERROR_NONE: case SCF_ERROR_INVALID_ARGUMENT: switch (read_enable_prop(fmri, enabled, SCF_PG_GENERAL)) { case 0: debug_msg("read %d from non_override", *enabled); return (0); case SCF_ERROR_CONNECTION_BROKEN: break; case SCF_ERROR_NOT_FOUND: case SCF_ERROR_NONE: case SCF_ERROR_INVALID_ARGUMENT: error_msg(gettext("Missing %s property/value " "for instance %s"), SCF_PROPERTY_ENABLED, fmri); return (-1); default: goto gen_fail; } break; default: goto gen_fail; } (void) scf_handle_unbind(rep_handle); continue; } gen_fail: error_msg(gettext("Failed to read the %s property of instance %s: %s"), SCF_PROPERTY_ENABLED, fmri, scf_strerror(scf_error())); return (-1); } /* * Refresh the value of debug property under the property group "config" * for network/inetd service. */ void refresh_debug_flag(void) { scf_simple_prop_t *sprop; uint8_t *tmp_bool; if ((sprop = scf_simple_prop_get(rep_handle, INETD_INSTANCE_FMRI, PG_NAME_APPLICATION_CONFIG, PR_NAME_DEBUG_FLAG)) == NULL) { error_msg(gettext("Unable to read %s property from %s property " "group. scf_simple_prop_get() failed: %s"), PR_NAME_DEBUG_FLAG, PG_NAME_APPLICATION_CONFIG, scf_strerror(scf_error())); return; } else if ((tmp_bool = scf_simple_prop_next_boolean(sprop)) == NULL) { error_msg(gettext("Unable to read %s property for %s service. " "scf_simple_prop_next_boolean() failed: %s"), PR_NAME_DEBUG_FLAG, INETD_INSTANCE_FMRI, scf_strerror(scf_error())); } else { debug_enabled = ((*tmp_bool == 0) ? B_FALSE : B_TRUE); } scf_simple_prop_free(sprop); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* paths/filenames of contract related files */ #define CONTRACT_ROOT_PATH CTFS_ROOT "/process/" #define CONTRACT_TEMPLATE_PATH CONTRACT_ROOT_PATH "template" static int active_tmpl_fd = -1; /* * Creates and configures the the contract template used for all inetd's * methods. * Returns -1 on error, else the fd of the created template. */ static int create_contract_template(void) { int fd; int err; if ((fd = open(CONTRACT_TEMPLATE_PATH, O_RDWR)) == -1) { error_msg(gettext("Failed to open contract file %s: %s"), CONTRACT_TEMPLATE_PATH, strerror(errno)); return (-1); } /* * Make contract inheritable and make hardware errors fatal. * We also limit the scope of fatal events to the process * group. In order of preference we would have contract-aware * login services or a property indicating which services need * such scoping, but for the time being we'll assume that most * non login-style services run in a single process group. */ if (((err = ct_pr_tmpl_set_param(fd, CT_PR_INHERIT|CT_PR_PGRPONLY)) != 0) || ((err = ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR)) != 0) || ((err = ct_tmpl_set_critical(fd, 0)) != 0) || ((err = ct_tmpl_set_informative(fd, 0)) != 0)) { error_msg(gettext( "Failed to set parameter for contract template: %s"), strerror(err)); (void) close(fd); return (-1); } return (fd); } /* Returns -1 on error, else 0. */ int contract_init(void) { if ((active_tmpl_fd = create_contract_template()) == -1) { error_msg(gettext("Failed to create contract template")); return (-1); } return (0); } void contract_fini(void) { if (active_tmpl_fd != -1) { (void) close(active_tmpl_fd); active_tmpl_fd = -1; } } /* * To be called directly before a service method is forked, this function * results in the method process being in a new contract based on the active * contract template. */ int contract_prefork(const char *fmri, int method) { int err; if ((err = ct_pr_tmpl_set_svc_fmri(active_tmpl_fd, fmri)) != 0) { error_msg(gettext("Failed to set svc_fmri term: %s"), strerror(err)); return (-1); } if ((err = ct_pr_tmpl_set_svc_aux(active_tmpl_fd, methods[method].name)) != 0) { error_msg(gettext("Failed to set svc_aux term: %s"), strerror(err)); return (-1); } if ((err = ct_tmpl_activate(active_tmpl_fd)) != 0) { error_msg(gettext("Failed to activate contract template: %s"), strerror(err)); return (-1); } return (0); } /* * To be called in both processes directly after a service method is forked, * this function results in switching off contract creation for any * forks done by either process, unless contract_prefork() is called beforehand. */ void contract_postfork(void) { int err; if ((err = ct_tmpl_clear(active_tmpl_fd)) != 0) error_msg("Failed to clear active contract template: %s", strerror(err)); } /* * Fetch the latest created contract id into the space referenced by 'cid'. * Returns -1 on error, else 0. */ int get_latest_contract(ctid_t *cid) { if ((errno = contract_latest(cid)) != 0) { error_msg(gettext("Failed to get new contract's id: %s"), strerror(errno)); return (-1); } return (0); } /* Returns -1 on error (with errno set), else fd. */ static int open_contract_ctl_file(ctid_t cid) { return (contract_open(cid, "process", "ctl", O_WRONLY)); } /* * Adopt a contract. Emits an error message and returns -1 on failure, else * 0. */ int adopt_contract(ctid_t ctid, const char *fmri) { int fd; int err; int ret = 0; if ((fd = open_contract_ctl_file(ctid)) == -1) { if (errno == EACCES || errno == ENOENT) { /* * We must not have inherited this contract. That can * happen if we were disabled and restarted. */ debug_msg("Could not adopt contract %ld for %s " "(could not open ctl file: permission denied).\n", ctid, fmri); return (-1); } error_msg(gettext("Could not adopt contract id %ld registered " "with %s (could not open ctl file: %s). Events will be " "ignored."), ctid, fmri, strerror(errno)); return (-1); } if ((err = ct_ctl_adopt(fd)) != 0) { error_msg(gettext("Could not adopt contract id %ld registered " "with %s (%s). Events will be ignored."), ctid, fmri, strerror(err)); ret = -1; } err = close(fd); if (err != 0) error_msg(gettext("Could not close file descriptor %d."), fd); return (ret); } /* Returns -1 on error, else 0. */ int abandon_contract(ctid_t ctid) { int fd; int err; assert(ctid != -1); if ((fd = open_contract_ctl_file(ctid)) == -1) { error_msg(gettext("Failed to abandon contract %d: %s"), ctid, strerror(errno)); return (-1); } if ((err = ct_ctl_abandon(fd)) != 0) { (void) close(fd); error_msg(gettext("Failed to abandon contract %d: %s"), ctid, strerror(err)); return (-1); } (void) close(fd); return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include "inetd_impl.h" extern char **environ; static int valid_env_var(const char *var, const char *instance, const char *method) { char *cp = strchr(var, '='); if (cp == NULL || cp == var) { if (method == NULL) return (0); error_msg(gettext("Invalid environment variable \"%s\" for " "method %s of instance %s.\n"), var, method, instance); return (0); } else if (strncmp(var, "SMF_", 4) == 0) { if (method == NULL) return (0); error_msg(gettext("Invalid environment variable \"%s\" for " "method %s of instance %s; \"SMF_\" prefix is reserved.\n"), var, method, instance); return (0); } return (1); } static char ** find_dup(const char *var, char **env, const char *instance, const char *method) { char **p; char *tmp; for (p = env; *p != NULL; p++) { tmp = strchr(*p, '='); assert(tmp != NULL); tmp++; if (strncmp(*p, var, tmp - *p) == 0) break; } if (*p == NULL) return (NULL); error_msg(gettext("Ignoring duplicate environment variable \"%s\" " "for method %s of instance %s.\n"), *p, method, instance); return (p); } /* * Create an environment which is appropriate for spawning an SMF aware * process. * * In order to preserve the correctness of the new environment, various * checks are performed: * * - All SMF_ entries are ignored. All SMF_ entries should be provided * by this function. * - Duplicates in the entry are eliminated. * - Malformed entries are eliminated. * * Detected errors are logged but not fatal, since a single bad entry * should not be enough to prevent an SMF_ functional environment from * being created. */ char ** set_smf_env(struct method_context *mthd_ctxt, instance_t *instance, const char *method) { char **nenv; char **p, **np; size_t nenv_size; /* * Max. of env, three SMF_ variables, and terminating NULL. */ nenv_size = mthd_ctxt->env_sz + 3 + 1; if (instance->config->basic->inherit_env) { for (p = environ; *p != NULL; p++) nenv_size++; } nenv = malloc(sizeof (char *) * nenv_size); if (nenv == NULL) return (NULL); (void) memset(nenv, 0, sizeof (char *) * nenv_size); np = nenv; *np = uu_msprintf("SMF_RESTARTER=%s", INETD_INSTANCE_FMRI); if (*np == NULL) goto fail; else np++; *np = uu_msprintf("SMF_FMRI=%s", instance->fmri); if (*np == NULL) goto fail; else np++; *np = uu_msprintf("SMF_METHOD=%s", method); if (*np == NULL) goto fail; else np++; if (instance->config->basic->inherit_env) { for (p = environ; *p != NULL; p++) { if (!valid_env_var(*p, NULL, NULL)) continue; *np = strdup(*p); if (*np == NULL) goto fail; else np++; } } if (mthd_ctxt->env != NULL) { for (p = mthd_ctxt->env; *p != NULL; p++) { char **dup_pos; if (!valid_env_var(*p, instance->fmri, method)) continue; if ((dup_pos = find_dup(*p, nenv, instance->fmri, method)) != NULL) { free(*dup_pos); *dup_pos = strdup(*p); if (*dup_pos == NULL) goto fail; } else { *np = strdup(*p); if (*np == NULL) goto fail; else np++; } } } *np = NULL; return (nenv); fail: p = nenv; while (nenv_size--) free(*p++); free(nenv); return (NULL); } #! /usr/bin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Start by cleaning out obsolete instances. For each one that # exists in the repository, remove it. inetd_obsolete_instances=" network/nfs/rquota:ticlts network/nfs/rquota:udp network/rexec:tcp network/rexec:tcp6 network/rpc/gss:ticotsord network/rpc/mdcomm:tcp network/rpc/mdcomm:tcp6 network/rpc/meta:tcp network/rpc/meta:tcp6 network/rpc/metamed:tcp network/rpc/metamed:tcp6 network/rpc/metamh:tcp network/rpc/metamh:tcp6 network/rpc/rex:tcp network/rpc/rstat:ticlts network/rpc/rstat:udp network/rpc/rstat:udp6 network/rpc/rusers:udp network/rpc/rusers:udp6 network/rpc/rusers:ticlts network/rpc/rusers:tcp network/rpc/rusers:tcp6 network/rpc/rusers:ticotsord network/rpc/rusers:ticots network/rpc/spray:ticlts network/rpc/spray:udp network/rpc/spray:udp6 network/rpc/wall:ticlts network/rpc/wall:udp network/rpc/wall:udp6 network/security/krb5_prop:tcp network/security/ktkt_warn:ticotsord network/shell:tcp network/shell:tcp6only platform/sun4u/dcs:tcp platform/sun4u/dcs:tcp6 " for i in $inetd_obsolete_instances; do enable=`svcprop -p general/enabled $i` if [ $? = 0 ]; then # Instance found, so disable and delete svcadm disable $i svccfg delete $i if [ "$enable" = "true" ]; then # Instance was enabled, so enable the replacement. # We must do this here because the profile which # normally enables these is only applied on first # install of smf. s=`echo $i | cut -f1 -d:` svcadm enable $s:default fi fi done # The Following blocks of code cause the inetconv generated services to be # re-generated, so that the latest inetconv modifications are applied to all # services generated by it. inetdconf_entries_file=/tmp/iconf_entries.$$ # Create sed script that prints out inetd.conf src line from inetconv generated # manifest. cat < /tmp/inetd-upgrade.$$.sed /propval name='source_line'/{ n s/'//g p } /from the inetd.conf(5) format line/{ n p } EOF # get list of inetconv generated manifests inetconv_manifests=`/usr/bin/find /lib/svc/manifest -type f -name \*.xml | \ /bin/xargs /bin/grep -l "Generated by inetconv"` # For each inetconv generated manifest determine the instances that should # be disabled when the new manifests are imported, and generate a file with # the inetd.conf entries from all the manifests for consumption by inetconv. > $inetdconf_entries_file inetconv_services="" instances_to_disable="" for manifest in $inetconv_manifests; do manifest_instances=`/sbin/svccfg inventory $manifest | \ egrep "svc:/.*:.*"` manifest_service=`/sbin/svccfg inventory $manifest | \ egrep -v "svc:/.*:.*"` instance_disabled="" default_enabled="" enabled="" for instance in $manifest_instances; do # if the instance doesn't exist in the repository skip it svcprop -q $instance if [ $? -ne 0 ]; then continue fi enabled=`svcprop -p general/enabled $instance` default_instance=`echo $instance | grep ":default"` if [ "$default_instance" != "" ]; then default_enabled=$enabled else # add all non-default instances to disable list instances_to_disable="$instances_to_disable \ $instance" if [ "$enabled" != "true" ]; then instance_disabled="true" fi fi done # if none of the manifest's instances existed, skip this manifest if [ "$enabled" = "" ]; then continue fi # If the default instance existed and was disabled, or if didn't # exist and one of the other instances was disabled, add the default # to the list of instances to disable. if [ "$default_enabled" = "false" -o "$default_enabled" = "" -a \ "$instance_disabled" = "true" ]; then instances_to_disable="$instances_to_disable \ $manifest_service:default" fi # add the manifest's inetd.conf src line to file for inetconv sed -n -f /tmp/inetd-upgrade.$$.sed $manifest >> \ $inetdconf_entries_file done rm /tmp/inetd-upgrade.$$.sed # Check whether we've ever run inetconv before by looking for the # configuration file hash. If we haven't run it before, then we need # to enable services based on inetd.conf. If we have, then the # repository is authoritative. `unimported' will be 0 if the hash exists. svcprop -qp hash svc:/network/inetd:default unimported=$? # Run inetconv on generated file, overwriting previous manifests and values # in repository. /usr/sbin/inetconv -f -i $inetdconf_entries_file # disable the necessary instances for inst in $instances_to_disable; do svcadm disable $inst done # If there is a saved config file from upgrade, use it to enable services, # but only if we're coming from a release that didn't have SMF. saved_config=/etc/inet/inetd.conf.preupgrade if [ $unimported -ne 0 -a -f $saved_config ]; then /usr/sbin/inetconv -e -i $saved_config fi # Now convert the remaining entries in inetd.conf to service manifests /usr/sbin/inetconv # Now disable myself as the upgrade is done svcadm disable network/inetd-upgrade exit 0 /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2011 Gary Mills * * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * NOTES: To be expanded. * * The SMF inetd. * * Below are some high level notes of the operation of the SMF inetd. The * notes don't go into any real detail, and the viewer of this file is * encouraged to look at the code and its associated comments to better * understand inetd's operation. This saves the potential for the code * and these notes diverging over time. * * Inetd's major work is done from the context of event_loop(). Within this * loop, inetd polls for events arriving from a number of different file * descriptors, representing the following event types, and initiates * any necessary event processing: * - incoming network connections/datagrams. * - notification of terminated processes (discovered via contract events). * - instance specific events originating from the SMF master restarter. * - stop/refresh requests from the inetd method processes (coming in on a * Unix Domain socket). * There's also a timeout set for the poll, which is set to the nearest * scheduled timer in a timer queue that inetd uses to perform delayed * processing, such as bind retries. * The SIGHUP and SIGINT signals can also interrupt the poll, and will * result in inetd being refreshed or stopped respectively, as was the * behavior with the old inetd. * * Inetd implements a state machine for each instance. The states within the * machine are: offline, online, disabled, maintenance, uninitialized and * specializations of the offline state for when an instance exceeds one of * its DOS limits. The state of an instance can be changed as a * result/side-effect of one of the above events occurring, or inetd being * started up. The ongoing state of an instance is stored in the SMF * repository, as required of SMF restarters. This enables an administrator * to view the state of each instance, and, if inetd was to terminate * unexpectedly, it could use the stored state to re-commence where it left off. * * Within the state machine a number of methods are run (if provided) as part * of a state transition to aid/ effect a change in an instance's state. The * supported methods are: offline, online, disable, refresh and start. The * latter of these is the equivalent of the server program and its arguments * in the old inetd. * * Events from the SMF master restarter come in on a number of threads * created in the registration routine of librestart, the delegated restarter * library. These threads call into the restart_event_proxy() function * when an event arrives. To serialize the processing of instances, these events * are then written down a pipe to the process's main thread, which listens * for these events via a poll call, with the file descriptor of the other * end of the pipe in its read set, and processes the event appropriately. * When the event has been processed (which may be delayed if the instance * for which the event is for is in the process of executing one of its methods * as part of a state transition) it writes an acknowledgement back down the * pipe the event was received on. The thread in restart_event_proxy() that * wrote the event will read the acknowledgement it was blocked upon, and will * then be able to return to its caller, thus implicitly acknowledging the * event, and allowing another event to be written down the pipe for the main * thread to process. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* path to inetd's binary */ #define INETD_PATH "/usr/lib/inet/inetd" /* * inetd's default configuration file paths. /etc/inetd/inetd.conf is set * be be the primary file, so it is checked before /etc/inetd.conf. */ #define PRIMARY_DEFAULT_CONF_FILE "/etc/inet/inetd.conf" #define SECONDARY_DEFAULT_CONF_FILE "/etc/inetd.conf" /* Arguments passed to this binary to request which method to execute. */ #define START_METHOD_ARG "start" #define STOP_METHOD_ARG "stop" #define REFRESH_METHOD_ARG "refresh" /* connection backlog for unix domain socket */ #define UDS_BACKLOG 2 /* number of retries to recv() a request on the UDS socket before giving up */ #define UDS_RECV_RETRIES 10 /* enumeration of the different ends of a pipe */ enum pipe_end { PE_CONSUMER, PE_PRODUCER }; typedef struct { internal_inst_state_t istate; const char *name; restarter_instance_state_t smf_state; instance_method_t method_running; } state_info_t; /* * Collection of information for each state. * NOTE: This table is indexed into using the internal_inst_state_t * enumeration, so the ordering needs to be kept in synch. */ static state_info_t states[] = { {IIS_UNINITIALIZED, "uninitialized", RESTARTER_STATE_UNINIT, IM_NONE}, {IIS_ONLINE, "online", RESTARTER_STATE_ONLINE, IM_START}, {IIS_IN_ONLINE_METHOD, "online_method", RESTARTER_STATE_OFFLINE, IM_ONLINE}, {IIS_OFFLINE, "offline", RESTARTER_STATE_OFFLINE, IM_NONE}, {IIS_IN_OFFLINE_METHOD, "offline_method", RESTARTER_STATE_OFFLINE, IM_OFFLINE}, {IIS_DISABLED, "disabled", RESTARTER_STATE_DISABLED, IM_NONE}, {IIS_IN_DISABLE_METHOD, "disabled_method", RESTARTER_STATE_OFFLINE, IM_DISABLE}, {IIS_IN_REFRESH_METHOD, "refresh_method", RESTARTER_STATE_ONLINE, IM_REFRESH}, {IIS_MAINTENANCE, "maintenance", RESTARTER_STATE_MAINT, IM_NONE}, {IIS_OFFLINE_CONRATE, "cr_offline", RESTARTER_STATE_OFFLINE, IM_NONE}, {IIS_OFFLINE_BIND, "bind_offline", RESTARTER_STATE_OFFLINE, IM_NONE}, {IIS_OFFLINE_COPIES, "copies_offline", RESTARTER_STATE_OFFLINE, IM_NONE}, {IIS_DEGRADED, "degraded", RESTARTER_STATE_DEGRADED, IM_NONE}, {IIS_NONE, "none", RESTARTER_STATE_NONE, IM_NONE} }; /* * Pipe used to send events from the threads created by restarter_bind_handle() * to the main thread of control. */ static int rst_event_pipe[] = {-1, -1}; /* * Used to protect the critical section of code in restarter_event_proxy() that * involves writing an event down the event pipe and reading an acknowledgement. */ static pthread_mutex_t rst_event_pipe_mtx = PTHREAD_MUTEX_INITIALIZER; /* handle used in communication with the master restarter */ static restarter_event_handle_t *rst_event_handle = NULL; /* set to indicate a refresh of inetd is requested */ static boolean_t refresh_inetd_requested = B_FALSE; /* set by the SIGTERM handler to flag we got a SIGTERM */ static boolean_t got_sigterm = B_FALSE; /* * Timer queue used to store timers for delayed event processing, such as * bind retries. */ iu_tq_t *timer_queue = NULL; /* * fd of Unix Domain socket used to communicate stop and refresh requests * to the inetd start method process. */ static int uds_fd = -1; /* * List of inetd's currently managed instances; each containing its state, * and in certain states its configuration. */ static uu_list_pool_t *instance_pool = NULL; uu_list_t *instance_list = NULL; /* set to indicate we're being stopped */ boolean_t inetd_stopping = B_FALSE; /* TCP wrappers syslog globals. Consumed by libwrap. */ int allow_severity = LOG_INFO; int deny_severity = LOG_WARNING; /* path of the configuration file being monitored by check_conf_file() */ static char *conf_file = NULL; /* Auditing session handle */ static adt_session_data_t *audit_handle; /* Number of pending connections */ static size_t tlx_pending_counter; static void uds_fini(void); static int uds_init(void); static int run_method(instance_t *, instance_method_t, const proto_info_t *); static void create_bound_fds(instance_t *); static void destroy_bound_fds(instance_t *); static void destroy_instance(instance_t *); static void inetd_stop(void); static void exec_method(instance_t *instance, instance_method_t method, method_info_t *mi, struct method_context *mthd_ctxt, const proto_info_t *pi) __NORETURN; /* * The following two functions are callbacks that libumem uses to determine * inetd's desired debugging/logging levels. The interface they consume is * exported by FMA and is consolidation private. The comments in the two * functions give the environment variable that will effectively be set to * their returned value, and thus whose behavior for this value, described in * umem_debug(3MALLOC), will be followed. */ const char * _umem_debug_init(void) { return ("default,verbose"); /* UMEM_DEBUG setting */ } const char * _umem_logging_init(void) { return ("fail,contents"); /* UMEM_LOGGING setting */ } static void log_invalid_cfg(const char *fmri) { error_msg(gettext( "Invalid configuration for instance %s, placing in maintenance"), fmri); } /* * Returns B_TRUE if the instance is in a suitable state for inetd to stop. */ static boolean_t instance_stopped(const instance_t *inst) { return ((inst->cur_istate == IIS_OFFLINE) || (inst->cur_istate == IIS_MAINTENANCE) || (inst->cur_istate == IIS_DISABLED) || (inst->cur_istate == IIS_UNINITIALIZED)); } /* * Given the instance fmri, obtain the corresonding scf_instance. * Caller is responsible for freeing the returned scf_instance and * its scf_handle. */ static int fmri_to_instance(char *fmri, scf_instance_t **scf_instp) { int retries, ret = 1; scf_handle_t *h; scf_instance_t *scf_inst; if ((h = scf_handle_create(SCF_VERSION)) == NULL) { error_msg(gettext("Failed to get instance for %s"), fmri); return (1); } if ((scf_inst = scf_instance_create(h)) == NULL) goto out; for (retries = 0; retries <= REP_OP_RETRIES; retries++) { if (make_handle_bound(h) == -1) break; if (scf_handle_decode_fmri(h, fmri, NULL, NULL, scf_inst, NULL, NULL, SCF_DECODE_FMRI_EXACT) == 0) { ret = 0; *scf_instp = scf_inst; break; } if (scf_error() != SCF_ERROR_CONNECTION_BROKEN) break; } out: if (ret != 0) { error_msg(gettext("Failed to get instance for %s"), fmri); scf_instance_destroy(scf_inst); scf_handle_destroy(h); } return (ret); } /* * Updates the current and next repository states of instance 'inst'. If * any errors occur an error message is output. */ static void update_instance_states(instance_t *inst, internal_inst_state_t new_cur_state, internal_inst_state_t new_next_state, restarter_error_t err) { internal_inst_state_t old_cur = inst->cur_istate; internal_inst_state_t old_next = inst->next_istate; scf_instance_t *scf_inst = NULL; scf_error_t sret; int ret; restarter_str_t aux = restarter_str_none; /* update the repository/cached internal state */ inst->cur_istate = new_cur_state; inst->next_istate = new_next_state; (void) set_single_rep_val(inst->cur_istate_rep, (int64_t)new_cur_state); (void) set_single_rep_val(inst->next_istate_rep, (int64_t)new_next_state); if (((sret = store_rep_vals(inst->cur_istate_rep, inst->fmri, PR_NAME_CUR_INT_STATE)) != 0) || ((sret = store_rep_vals(inst->next_istate_rep, inst->fmri, PR_NAME_NEXT_INT_STATE)) != 0)) error_msg(gettext("Failed to update state of instance %s in " "repository: %s"), inst->fmri, scf_strerror(sret)); if (fmri_to_instance(inst->fmri, &scf_inst) == 0) { /* * If transitioning to maintenance, check auxiliary_tty set * by svcadm and assign appropriate value to auxiliary_state. * If the maintenance event comes from a service request, * validate auxiliary_fmri and copy it to * restarter/auxiliary_fmri. */ if (new_cur_state == IIS_MAINTENANCE) { if (restarter_inst_ractions_from_tty(scf_inst) == 0) aux = restarter_str_service_request; else aux = restarter_str_administrative_request; } if (aux == restarter_str_service_request) { if (restarter_inst_validate_ractions_aux_fmri( scf_inst) == 0) { if (restarter_inst_set_aux_fmri(scf_inst)) error_msg(gettext("Could not set " "auxiliary_fmri property for %s"), inst->fmri); } else { if (restarter_inst_reset_aux_fmri(scf_inst)) error_msg(gettext("Could not reset " "auxiliary_fmri property for %s"), inst->fmri); } } scf_handle_destroy(scf_instance_handle(scf_inst)); scf_instance_destroy(scf_inst); } /* update the repository SMF state */ if ((ret = restarter_set_states(rst_event_handle, inst->fmri, states[old_cur].smf_state, states[new_cur_state].smf_state, states[old_next].smf_state, states[new_next_state].smf_state, err, aux)) != 0) error_msg(gettext("Failed to update state of instance %s in " "repository: %s"), inst->fmri, strerror(ret)); } void update_state(instance_t *inst, internal_inst_state_t new_cur, restarter_error_t err) { update_instance_states(inst, new_cur, IIS_NONE, err); } /* * Sends a refresh event to the inetd start method process and returns * SMF_EXIT_OK if it managed to send it. If it fails to send the request for * some reason it returns SMF_EXIT_ERR_OTHER. */ static int refresh_method(void) { uds_request_t req = UR_REFRESH_INETD; int fd; if ((fd = connect_to_inetd()) < 0) { error_msg(gettext("Failed to connect to inetd: %s"), strerror(errno)); return (SMF_EXIT_ERR_OTHER); } /* write the request and return success */ if (safe_write(fd, &req, sizeof (req)) == -1) { error_msg( gettext("Failed to send refresh request to inetd: %s"), strerror(errno)); (void) close(fd); return (SMF_EXIT_ERR_OTHER); } (void) close(fd); return (SMF_EXIT_OK); } /* * Sends a stop event to the inetd start method process and wait till it goes * away. If inetd is determined to have stopped SMF_EXIT_OK is returned, else * SMF_EXIT_ERR_OTHER is returned. */ static int stop_method(void) { uds_request_t req = UR_STOP_INETD; int fd; char c; ssize_t ret; if ((fd = connect_to_inetd()) == -1) { debug_msg(gettext("Failed to connect to inetd: %s"), strerror(errno)); /* * Assume connect_to_inetd() failed because inetd was already * stopped, and return success. */ return (SMF_EXIT_OK); } /* * This is safe to do since we're fired off in a separate process * than inetd and in the case we get wedged, the stop method timeout * will occur and we'd be killed by our restarter. */ enable_blocking(fd); /* write the stop request to inetd and wait till it goes away */ if (safe_write(fd, &req, sizeof (req)) != 0) { error_msg(gettext("Failed to send stop request to inetd")); (void) close(fd); return (SMF_EXIT_ERR_OTHER); } /* wait until remote end of socket is closed */ while (((ret = recv(fd, &c, sizeof (c), 0)) != 0) && (errno == EINTR)) ; (void) close(fd); if (ret != 0) { error_msg(gettext("Failed to determine whether inetd stopped")); return (SMF_EXIT_ERR_OTHER); } return (SMF_EXIT_OK); } /* * This function is called to handle restarter events coming in from the * master restarter. It is registered with the master restarter via * restarter_bind_handle() and simply passes a pointer to the event down * the event pipe, which will be discovered by the poll in the event loop * and processed there. It waits for an acknowledgement to be written back down * the pipe before returning. * Writing a pointer to the function's 'event' parameter down the pipe will * be safe, as the thread in restarter_event_proxy() doesn't return until * the main thread has finished its processing of the passed event, thus * the referenced event will remain around until the function returns. * To impose the limit of only one event being in the pipe and processed * at once, a lock is taken on entry to this function and returned on exit. * Always returns 0. */ static int restarter_event_proxy(restarter_event_t *event) { boolean_t processed; (void) pthread_mutex_lock(&rst_event_pipe_mtx); /* write the event to the main worker thread down the pipe */ if (safe_write(rst_event_pipe[PE_PRODUCER], &event, sizeof (event)) != 0) goto pipe_error; /* * Wait for an acknowledgement that the event has been processed from * the same pipe. In the case that inetd is stopping, any thread in * this function will simply block on this read until inetd eventually * exits. This will result in this function not returning success to * its caller, and the event that was being processed when the * function exited will be re-sent when inetd is next started. */ if (safe_read(rst_event_pipe[PE_PRODUCER], &processed, sizeof (processed)) != 0) goto pipe_error; (void) pthread_mutex_unlock(&rst_event_pipe_mtx); return (processed ? 0 : EAGAIN); pipe_error: /* * Something's seriously wrong with the event pipe. Notify the * worker thread by closing this end of the event pipe and pause till * inetd exits. */ error_msg(gettext("Can't process restarter events: %s"), strerror(errno)); (void) close(rst_event_pipe[PE_PRODUCER]); for (;;) (void) pause(); /* NOTREACHED */ } /* * Let restarter_event_proxy() know we're finished with the event it's blocked * upon. The 'processed' argument denotes whether we successfully processed the * event. */ static void ack_restarter_event(boolean_t processed) { /* * If safe_write returns -1 something's seriously wrong with the event * pipe, so start the shutdown proceedings. */ if (safe_write(rst_event_pipe[PE_CONSUMER], &processed, sizeof (processed)) == -1) inetd_stop(); } /* * Switch the syslog identification string to 'ident'. */ static void change_syslog_ident(const char *ident) { closelog(); openlog(ident, LOG_PID|LOG_CONS, LOG_DAEMON); } /* * Perform TCP wrappers checks on this instance. Due to the fact that the * current wrappers code used in Solaris is taken untouched from the open * source version, we're stuck with using the daemon name for the checks, as * opposed to making use of instance FMRIs. Sigh. * Returns B_TRUE if the check passed, else B_FALSE. */ static boolean_t tcp_wrappers_ok(instance_t *instance) { boolean_t rval = B_TRUE; char *daemon_name; basic_cfg_t *cfg = instance->config->basic; struct request_info req; /* * Wrap the service using libwrap functions. The code below implements * the functionality of tcpd. This is done only for stream,nowait * services, following the convention of other vendors. udp/dgram and * stream/wait can NOT be wrapped with this libwrap, so be wary of * changing the test below. */ if (cfg->do_tcp_wrappers && !cfg->iswait && !cfg->istlx) { daemon_name = instance->config->methods[ IM_START]->exec_args_we.we_wordv[0]; if (*daemon_name == '/') daemon_name = strrchr(daemon_name, '/') + 1; /* * Change the syslog message identity to the name of the * daemon being wrapped, as opposed to "inetd". */ change_syslog_ident(daemon_name); (void) request_init(&req, RQ_DAEMON, daemon_name, RQ_FILE, instance->conn_fd, NULL); fromhost(&req); if (strcasecmp(eval_hostname(req.client), paranoid) == 0) { syslog(deny_severity, "refused connect from %s (name/address mismatch)", eval_client(&req)); if (req.sink != NULL) req.sink(instance->conn_fd); rval = B_FALSE; } else if (!hosts_access(&req)) { syslog(deny_severity, "refused connect from %s (access denied)", eval_client(&req)); if (req.sink != NULL) req.sink(instance->conn_fd); rval = B_FALSE; } else { syslog(allow_severity, "connect from %s", eval_client(&req)); } /* Revert syslog identity back to "inetd". */ change_syslog_ident(SYSLOG_IDENT); } return (rval); } /* * Handler registered with the timer queue code to remove an instance from * the connection rate offline state when it has been there for its allotted * time. */ /* ARGSUSED */ static void conn_rate_online(iu_tq_t *tq, void *arg) { instance_t *instance = arg; assert(instance->cur_istate == IIS_OFFLINE_CONRATE); instance->timer_id = -1; update_state(instance, IIS_OFFLINE, RERR_RESTART); process_offline_inst(instance); } /* * Check whether this instance in the offline state is in transition to * another state and do the work to continue this transition. */ void process_offline_inst(instance_t *inst) { if (inst->disable_req) { inst->disable_req = B_FALSE; (void) run_method(inst, IM_DISABLE, NULL); } else if (inst->maintenance_req) { inst->maintenance_req = B_FALSE; update_state(inst, IIS_MAINTENANCE, RERR_RESTART); /* * If inetd is in the process of stopping, we don't want to enter * any states but offline, disabled and maintenance. */ } else if (!inetd_stopping) { if (inst->conn_rate_exceeded) { basic_cfg_t *cfg = inst->config->basic; inst->conn_rate_exceeded = B_FALSE; update_state(inst, IIS_OFFLINE_CONRATE, RERR_RESTART); /* * Schedule a timer to bring the instance out of the * connection rate offline state. */ inst->timer_id = iu_schedule_timer(timer_queue, cfg->conn_rate_offline, conn_rate_online, inst); if (inst->timer_id == -1) { error_msg(gettext("%s unable to set timer, " "won't be brought on line after %d " "seconds."), inst->fmri, cfg->conn_rate_offline); } } else if (copies_limit_exceeded(inst)) { update_state(inst, IIS_OFFLINE_COPIES, RERR_RESTART); } } } /* * Create a socket bound to the instance's configured address. If the * bind fails, returns -1, else the fd of the bound socket. */ static int create_bound_socket(const instance_t *inst, socket_info_t *sock_info) { int fd; int on = 1; const char *fmri = inst->fmri; rpc_info_t *rpc = sock_info->pr_info.ri; const char *proto = sock_info->pr_info.proto; fd = socket(sock_info->local_addr.ss_family, sock_info->type, sock_info->protocol); if (fd < 0) { error_msg(gettext( "Socket creation failure for instance %s, proto %s: %s"), fmri, proto, strerror(errno)); return (-1); } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) == -1) { error_msg(gettext("setsockopt SO_REUSEADDR failed for service " "instance %s, proto %s: %s"), fmri, proto, strerror(errno)); (void) close(fd); return (-1); } if (inst->config->basic->do_tcp_keepalive && !inst->config->basic->iswait && !inst->config->basic->istlx) { /* set the keepalive option */ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) == -1) { error_msg(gettext("setsockopt SO_KEEPALIVE failed for " "service instance %s, proto %s: %s"), fmri, proto, strerror(errno)); (void) close(fd); return (-1); } } if (sock_info->pr_info.v6only) { /* restrict socket to IPv6 communications only */ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof (on)) == -1) { error_msg(gettext("setsockopt IPV6_V6ONLY failed for " "service instance %s, proto %s: %s"), fmri, proto, strerror(errno)); (void) close(fd); return (-1); } } if (rpc != NULL) SS_SETPORT(sock_info->local_addr, 0); if (bind(fd, (struct sockaddr *)&(sock_info->local_addr), SS_ADDRLEN(sock_info->local_addr)) < 0) { error_msg(gettext( "Failed to bind to the port of service instance %s, " "proto %s: %s"), fmri, proto, strerror(errno)); (void) close(fd); return (-1); } /* * Retrieve and store the address bound to for RPC services. */ if (rpc != NULL) { struct sockaddr_storage ss; int ss_size = sizeof (ss); if (getsockname(fd, (struct sockaddr *)&ss, &ss_size) < 0) { error_msg(gettext("Failed getsockname for instance %s, " "proto %s: %s"), fmri, proto, strerror(errno)); (void) close(fd); return (-1); } (void) memcpy(rpc->netbuf.buf, &ss, sizeof (struct sockaddr_storage)); rpc->netbuf.len = SS_ADDRLEN(ss); rpc->netbuf.maxlen = SS_ADDRLEN(ss); } if (sock_info->type == SOCK_STREAM) { int qlen = inst->config->basic->conn_backlog; debug_msg("Listening for service %s with backlog queue" " size %d", fmri, qlen); (void) listen(fd, qlen); } return (fd); } /* * Handler registered with the timer queue code to retry the creation * of a bound fd. */ /* ARGSUSED */ static void retry_bind(iu_tq_t *tq, void *arg) { instance_t *instance = arg; switch (instance->cur_istate) { case IIS_OFFLINE_BIND: case IIS_ONLINE: case IIS_DEGRADED: case IIS_IN_ONLINE_METHOD: case IIS_IN_REFRESH_METHOD: break; default: #ifndef NDEBUG (void) fprintf(stderr, "%s:%d: Unknown instance state %d.\n", __FILE__, __LINE__, instance->cur_istate); #endif abort(); } instance->bind_timer_id = -1; create_bound_fds(instance); } /* * For each of the fds for the given instance that are bound, if 'listen' is * set add them to the poll set, else remove them from it. If proto_name is * not NULL then apply the change only to this specific protocol endpoint. * If any additions fail, returns -1, else 0 on success. */ int poll_bound_fds(instance_t *instance, boolean_t listen, char *proto_name) { basic_cfg_t *cfg = instance->config->basic; proto_info_t *pi; int ret = 0; for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if (pi->listen_fd != -1) { /* fd bound */ if (proto_name == NULL || strcmp(pi->proto, proto_name) == 0) { if (listen == B_FALSE) { clear_pollfd(pi->listen_fd); } else if (set_pollfd(pi->listen_fd, POLLIN) == -1) { ret = -1; } } } } return (ret); } /* * Handle the case were we either fail to create a bound fd or we fail * to add a bound fd to the poll set for the given instance. */ static void handle_bind_failure(instance_t *instance) { basic_cfg_t *cfg = instance->config->basic; /* * We must be being called as a result of a failed poll_bound_fds() * as a bind retry is already scheduled. Just return and let it do * the work. */ if (instance->bind_timer_id != -1) return; /* * Check if the rebind retries limit is operative and if so, * if it has been reached. */ if (((cfg->bind_fail_interval <= 0) || /* no retries */ ((cfg->bind_fail_max >= 0) && /* limit reached */ (++instance->bind_fail_count > cfg->bind_fail_max))) || ((instance->bind_timer_id = iu_schedule_timer(timer_queue, cfg->bind_fail_interval, retry_bind, instance)) == -1)) { proto_info_t *pi; instance->bind_fail_count = 0; switch (instance->cur_istate) { case IIS_DEGRADED: case IIS_ONLINE: /* check if any of the fds are being poll'd upon */ for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if ((pi->listen_fd != -1) && (find_pollfd(pi->listen_fd) != NULL)) break; } if (pi != NULL) { /* polling on > 0 fds */ warn_msg(gettext("Failed to bind on " "all protocols for instance %s, " "transitioning to degraded"), instance->fmri); update_state(instance, IIS_DEGRADED, RERR_NONE); instance->bind_retries_exceeded = B_TRUE; break; } destroy_bound_fds(instance); /* * In the case we failed the 'bind' because set_pollfd() * failed on all bound fds, use the offline handling. */ /* FALLTHROUGH */ case IIS_OFFLINE: case IIS_OFFLINE_BIND: error_msg(gettext("Too many bind failures for instance " "%s, transitioning to maintenance"), instance->fmri); update_state(instance, IIS_MAINTENANCE, RERR_FAULT); break; case IIS_IN_ONLINE_METHOD: case IIS_IN_REFRESH_METHOD: warn_msg(gettext("Failed to bind on all " "protocols for instance %s, instance will go to " "degraded"), instance->fmri); /* * Set the retries exceeded flag so when the method * completes the instance goes to the degraded state. */ instance->bind_retries_exceeded = B_TRUE; break; default: #ifndef NDEBUG (void) fprintf(stderr, "%s:%d: Unknown instance state %d.\n", __FILE__, __LINE__, instance->cur_istate); #endif abort(); } } else if (instance->cur_istate == IIS_OFFLINE) { /* * bind re-scheduled, so if we're offline reflect this in the * state. */ update_state(instance, IIS_OFFLINE_BIND, RERR_NONE); } } /* * Check if two transport protocols for RPC conflict. */ boolean_t is_rpc_proto_conflict(const char *proto0, const char *proto1) { if (strcmp(proto0, "tcp") == 0) { if (strcmp(proto1, "tcp") == 0) return (B_TRUE); if (strcmp(proto1, "tcp6") == 0) return (B_TRUE); return (B_FALSE); } if (strcmp(proto0, "tcp6") == 0) { if (strcmp(proto1, "tcp") == 0) return (B_TRUE); if (strcmp(proto1, "tcp6only") == 0) return (B_TRUE); if (strcmp(proto1, "tcp6") == 0) return (B_TRUE); return (B_FALSE); } if (strcmp(proto0, "tcp6only") == 0) { if (strcmp(proto1, "tcp6only") == 0) return (B_TRUE); if (strcmp(proto1, "tcp6") == 0) return (B_TRUE); return (B_FALSE); } if (strcmp(proto0, "udp") == 0) { if (strcmp(proto1, "udp") == 0) return (B_TRUE); if (strcmp(proto1, "udp6") == 0) return (B_TRUE); return (B_FALSE); } if (strcmp(proto0, "udp6") == 0) { if (strcmp(proto1, "udp") == 0) return (B_TRUE); if (strcmp(proto1, "udp6only") == 0) return (B_TRUE); if (strcmp(proto1, "udp6") == 0) return (B_TRUE); return (B_FALSE); } if (strcmp(proto0, "udp6only") == 0) { if (strcmp(proto1, "udp6only") == 0) return (B_TRUE); if (strcmp(proto1, "udp6") == 0) return (B_TRUE); return (0); } /* * If the protocol isn't TCP/IP or UDP/IP assume that it has its own * port namepsace and that conflicts can be detected by literal string * comparison. */ if (strcmp(proto0, proto1)) return (FALSE); return (B_TRUE); } /* * Check if inetd thinks this RPC program number is already registered. * * An RPC protocol conflict occurs if * a) the program numbers are the same and, * b) the version numbers overlap, * c) the protocols (TCP vs UDP vs tic*) are the same. */ boolean_t is_rpc_num_in_use(int rpc_n, char *proto, int lowver, int highver) { instance_t *i; basic_cfg_t *cfg; proto_info_t *pi; for (i = uu_list_first(instance_list); i != NULL; i = uu_list_next(instance_list, i)) { if (i->cur_istate != IIS_ONLINE) continue; cfg = i->config->basic; for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if (pi->ri == NULL) continue; if (pi->ri->prognum != rpc_n) continue; if (!is_rpc_proto_conflict(pi->proto, proto)) continue; if ((lowver < pi->ri->lowver && highver < pi->ri->lowver) || (lowver > pi->ri->highver && highver > pi->ri->highver)) continue; return (B_TRUE); } } return (B_FALSE); } /* * Independent of the transport, for each of the entries in the instance's * proto list this function first attempts to create an associated network fd; * for RPC services these are then bound to a kernel chosen port and the * fd is registered with rpcbind; for non-RPC services the fds are bound * to the port associated with the instance's service name. On any successful * binds the instance is taken online. Failed binds are handled by * handle_bind_failure(). */ void create_bound_fds(instance_t *instance) { basic_cfg_t *cfg = instance->config->basic; boolean_t failure = B_FALSE; boolean_t success = B_FALSE; proto_info_t *pi; /* * Loop through and try and bind any unbound protos. */ for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if (pi->listen_fd != -1) continue; if (cfg->istlx) { pi->listen_fd = create_bound_endpoint(instance, (tlx_info_t *)pi); } else { /* * We cast pi to a void so we can then go on to cast * it to a socket_info_t without lint complaining * about alignment. This is done because the x86 * version of lint thinks a lint suppression directive * is unnecessary and flags it as such, yet the sparc * version complains if it's absent. */ void *p = pi; pi->listen_fd = create_bound_socket(instance, (socket_info_t *)p); } if (pi->listen_fd == -1) { failure = B_TRUE; continue; } if (pi->ri != NULL) { /* * Don't register the same RPC program number twice. * Doing so silently discards the old service * without causing an error. */ if (is_rpc_num_in_use(pi->ri->prognum, pi->proto, pi->ri->lowver, pi->ri->highver)) { failure = B_TRUE; close_net_fd(instance, pi->listen_fd); pi->listen_fd = -1; continue; } unregister_rpc_service(instance->fmri, pi->ri); if (register_rpc_service(instance->fmri, pi->ri) == -1) { close_net_fd(instance, pi->listen_fd); pi->listen_fd = -1; failure = B_TRUE; continue; } } success = B_TRUE; } switch (instance->cur_istate) { case IIS_OFFLINE: case IIS_OFFLINE_BIND: /* * If we've managed to bind at least one proto lets run the * online method, so we can start listening for it. */ if (success && run_method(instance, IM_ONLINE, NULL) == -1) return; /* instance gone to maintenance */ break; case IIS_ONLINE: case IIS_IN_REFRESH_METHOD: /* * We're 'online', so start polling on any bound fds we're * currently not. */ if (poll_bound_fds(instance, B_TRUE, NULL) != 0) { failure = B_TRUE; } else if (!failure) { /* * We've successfully bound and poll'd upon all protos, * so reset the failure count. */ instance->bind_fail_count = 0; } break; case IIS_IN_ONLINE_METHOD: /* * Nothing to do here as the method completion code will start * listening for any successfully bound fds. */ break; default: #ifndef NDEBUG (void) fprintf(stderr, "%s:%d: Unknown instance state %d.\n", __FILE__, __LINE__, instance->cur_istate); #endif abort(); } if (failure) handle_bind_failure(instance); } /* * Counter to create_bound_fds(), for each of the bound network fds this * function unregisters the instance from rpcbind if it's an RPC service, * stops listening for new connections for it and then closes the listening fd. */ static void destroy_bound_fds(instance_t *instance) { basic_cfg_t *cfg = instance->config->basic; proto_info_t *pi; for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if (pi->listen_fd != -1) { if (pi->ri != NULL) unregister_rpc_service(instance->fmri, pi->ri); clear_pollfd(pi->listen_fd); close_net_fd(instance, pi->listen_fd); pi->listen_fd = -1; } } /* cancel any bind retries */ if (instance->bind_timer_id != -1) cancel_bind_timer(instance); instance->bind_retries_exceeded = B_FALSE; } /* * Perform %A address expansion and return a pointer to a static string * array containing crafted arguments. This expansion is provided for * compatibility with 4.2BSD daemons, and as such we've copied the logic of * the legacy inetd to maintain this compatibility as much as possible. This * logic is a bit scatty, but it dates back at least as far as SunOS 4.x. */ static char ** expand_address(instance_t *inst, const proto_info_t *pi) { static char addrbuf[sizeof ("ffffffff.65536")]; static char *ret[3]; instance_cfg_t *cfg = inst->config; /* * We cast pi to a void so we can then go on to cast it to a * socket_info_t without lint complaining about alignment. This * is done because the x86 version of lint thinks a lint suppression * directive is unnecessary and flags it as such, yet the sparc * version complains if it's absent. */ const void *p = pi; /* set ret[0] to the basename of exec path */ if ((ret[0] = strrchr(cfg->methods[IM_START]->exec_path, '/')) != NULL) { ret[0]++; } else { ret[0] = cfg->methods[IM_START]->exec_path; } if (!cfg->basic->istlx && (((socket_info_t *)p)->type == SOCK_DGRAM)) { ret[1] = NULL; } else { addrbuf[0] = '\0'; if (!cfg->basic->iswait && (inst->remote_addr.ss_family == AF_INET)) { struct sockaddr_in *sp; sp = (struct sockaddr_in *)&(inst->remote_addr); (void) snprintf(addrbuf, sizeof (addrbuf), "%x.%hu", ntohl(sp->sin_addr.s_addr), ntohs(sp->sin_port)); } ret[1] = addrbuf; ret[2] = NULL; } return (ret); } /* * Returns the state associated with the supplied method being run for an * instance. */ static internal_inst_state_t get_method_state(instance_method_t method) { state_info_t *sip; for (sip = states; sip->istate != IIS_NONE; sip++) { if (sip->method_running == method) break; } assert(sip->istate != IIS_NONE); return (sip->istate); } /* * Store the method's PID and CID in the repository. If the store fails * we ignore it and just drive on. */ static void add_method_ids(instance_t *ins, pid_t pid, ctid_t cid, instance_method_t mthd) { if (cid != -1) (void) add_remove_contract(ins, B_TRUE, cid); if (mthd == IM_START) { if (add_rep_val(ins->start_pids, (int64_t)pid) == 0) { (void) store_rep_vals(ins->start_pids, ins->fmri, PR_NAME_START_PIDS); } } else { if (add_rep_val(ins->non_start_pid, (int64_t)pid) == 0) { (void) store_rep_vals(ins->non_start_pid, ins->fmri, PR_NAME_NON_START_PID); } } } /* * Remove the method's PID and CID from the repository. If the removal * fails we ignore it and drive on. */ void remove_method_ids(instance_t *inst, pid_t pid, ctid_t cid, instance_method_t mthd) { if (cid != -1) (void) add_remove_contract(inst, B_FALSE, cid); if (mthd == IM_START) { remove_rep_val(inst->start_pids, (int64_t)pid); (void) store_rep_vals(inst->start_pids, inst->fmri, PR_NAME_START_PIDS); } else { remove_rep_val(inst->non_start_pid, (int64_t)pid); (void) store_rep_vals(inst->non_start_pid, inst->fmri, PR_NAME_NON_START_PID); } } static instance_t * create_instance(const char *fmri) { instance_t *ret; if (((ret = calloc(1, sizeof (instance_t))) == NULL) || ((ret->fmri = strdup(fmri)) == NULL)) goto alloc_fail; ret->conn_fd = -1; ret->copies = 0; ret->conn_rate_count = 0; ret->fail_rate_count = 0; ret->bind_fail_count = 0; if (((ret->non_start_pid = create_rep_val_list()) == NULL) || ((ret->start_pids = create_rep_val_list()) == NULL) || ((ret->start_ctids = create_rep_val_list()) == NULL)) goto alloc_fail; ret->cur_istate = IIS_NONE; ret->next_istate = IIS_NONE; if (((ret->cur_istate_rep = create_rep_val_list()) == NULL) || ((ret->next_istate_rep = create_rep_val_list()) == NULL)) goto alloc_fail; ret->config = NULL; ret->new_config = NULL; ret->timer_id = -1; ret->bind_timer_id = -1; ret->disable_req = B_FALSE; ret->maintenance_req = B_FALSE; ret->conn_rate_exceeded = B_FALSE; ret->bind_retries_exceeded = B_FALSE; ret->pending_rst_event = RESTARTER_EVENT_TYPE_INVALID; return (ret); alloc_fail: error_msg(strerror(errno)); destroy_instance(ret); return (NULL); } static void destroy_instance(instance_t *inst) { if (inst == NULL) return; destroy_instance_cfg(inst->config); destroy_instance_cfg(inst->new_config); destroy_rep_val_list(inst->cur_istate_rep); destroy_rep_val_list(inst->next_istate_rep); destroy_rep_val_list(inst->start_pids); destroy_rep_val_list(inst->non_start_pid); destroy_rep_val_list(inst->start_ctids); free(inst->fmri); free(inst); } /* * Retrieves the current and next states internal states. Returns 0 on success, * else returns one of the following on error: * SCF_ERROR_NO_MEMORY if memory allocation failed. * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken. * SCF_ERROR_TYPE_MISMATCH if the property was of an unexpected type. * SCF_ERROR_NO_RESOURCES if the server doesn't have adequate resources. * SCF_ERROR_NO_SERVER if the server isn't running. */ static scf_error_t retrieve_instance_state(instance_t *inst) { scf_error_t ret; /* retrieve internal states */ if (((ret = retrieve_rep_vals(inst->cur_istate_rep, inst->fmri, PR_NAME_CUR_INT_STATE)) != 0) || ((ret = retrieve_rep_vals(inst->next_istate_rep, inst->fmri, PR_NAME_NEXT_INT_STATE)) != 0)) { if (ret != SCF_ERROR_NOT_FOUND) { error_msg(gettext( "Failed to read state of instance %s: %s"), inst->fmri, scf_strerror(scf_error())); return (ret); } debug_msg("instance with no previous int state - " "setting state to uninitialized"); if ((set_single_rep_val(inst->cur_istate_rep, (int64_t)IIS_UNINITIALIZED) == -1) || (set_single_rep_val(inst->next_istate_rep, (int64_t)IIS_NONE) == -1)) { return (SCF_ERROR_NO_MEMORY); } } /* update convenience states */ inst->cur_istate = get_single_rep_val(inst->cur_istate_rep); inst->next_istate = get_single_rep_val(inst->next_istate_rep); return (0); } /* * Retrieve stored process ids and register each of them so we process their * termination. */ static int retrieve_method_pids(instance_t *inst) { rep_val_t *rv; switch (retrieve_rep_vals(inst->start_pids, inst->fmri, PR_NAME_START_PIDS)) { case 0: break; case SCF_ERROR_NOT_FOUND: return (0); default: error_msg(gettext("Failed to retrieve the start pids of " "instance %s from repository: %s"), inst->fmri, scf_strerror(scf_error())); return (-1); } rv = uu_list_first(inst->start_pids); while (rv != NULL) { if (register_method(inst, (pid_t)rv->val, (ctid_t)-1, IM_START, NULL) == 0) { inst->copies++; rv = uu_list_next(inst->start_pids, rv); } else if (errno == ENOENT) { pid_t pid = (pid_t)rv->val; /* * The process must have already terminated. Remove * it from the list. */ rv = uu_list_next(inst->start_pids, rv); remove_rep_val(inst->start_pids, pid); } else { error_msg(gettext("Failed to listen for the completion " "of %s method of instance %s"), START_METHOD_NAME, inst->fmri); rv = uu_list_next(inst->start_pids, rv); } } /* synch the repository pid list to remove any terminated pids */ (void) store_rep_vals(inst->start_pids, inst->fmri, PR_NAME_START_PIDS); return (0); } /* * Remove the passed instance from inetd control. */ static void remove_instance(instance_t *instance) { switch (instance->cur_istate) { case IIS_ONLINE: case IIS_DEGRADED: /* stop listening for network connections */ destroy_bound_fds(instance); break; case IIS_OFFLINE_BIND: cancel_bind_timer(instance); break; case IIS_OFFLINE_CONRATE: cancel_inst_timer(instance); break; } /* stop listening for terminated methods */ unregister_instance_methods(instance); uu_list_remove(instance_list, instance); destroy_instance(instance); } /* * Refresh the configuration of instance 'inst'. This method gets called as * a result of a refresh event for the instance from the master restarter, so * we can rely upon the instance's running snapshot having been updated from * its configuration snapshot. */ void refresh_instance(instance_t *inst) { instance_cfg_t *cfg; switch (inst->cur_istate) { case IIS_MAINTENANCE: case IIS_DISABLED: case IIS_UNINITIALIZED: /* * Ignore any possible changes, we'll re-read the configuration * automatically when we exit these states. */ break; case IIS_OFFLINE_COPIES: case IIS_OFFLINE_BIND: case IIS_OFFLINE: case IIS_OFFLINE_CONRATE: destroy_instance_cfg(inst->config); if ((inst->config = read_instance_cfg(inst->fmri)) == NULL) { log_invalid_cfg(inst->fmri); if (inst->cur_istate == IIS_OFFLINE_BIND) { cancel_bind_timer(inst); } else if (inst->cur_istate == IIS_OFFLINE_CONRATE) { cancel_inst_timer(inst); } update_state(inst, IIS_MAINTENANCE, RERR_FAULT); } else { switch (inst->cur_istate) { case IIS_OFFLINE_BIND: if (copies_limit_exceeded(inst)) { /* Cancel scheduled bind retries. */ cancel_bind_timer(inst); /* * Take the instance to the copies * offline state, via the offline * state. */ update_state(inst, IIS_OFFLINE, RERR_RESTART); process_offline_inst(inst); } break; case IIS_OFFLINE: process_offline_inst(inst); break; case IIS_OFFLINE_CONRATE: /* * Since we're already in a DOS state, * don't bother evaluating the copies * limit. This will be evaluated when * we leave this state in * process_offline_inst(). */ break; case IIS_OFFLINE_COPIES: /* * Check if the copies limit has been increased * above the current count. */ if (!copies_limit_exceeded(inst)) { update_state(inst, IIS_OFFLINE, RERR_RESTART); process_offline_inst(inst); } break; default: assert(0); } } break; case IIS_DEGRADED: case IIS_ONLINE: if ((cfg = read_instance_cfg(inst->fmri)) != NULL) { instance_cfg_t *ocfg = inst->config; /* * Try to avoid the overhead of taking an instance * offline and back on again. We do this by limiting * this behavior to two eventualities: * - there needs to be a re-bind to listen on behalf * of the instance with its new configuration. This * could be because for example its service has been * associated with a different port, or because the * v6only protocol option has been newly applied to * the instance. * - one or both of the start or online methods of the * instance have changed in the new configuration. * Without taking the instance offline when the * start method changed the instance may be running * with unwanted parameters (or event an unwanted * binary); and without taking the instance offline * if its online method was to change, some part of * its running environment may have changed and would * not be picked up until the instance next goes * offline for another reason. */ if ((!bind_config_equal(ocfg->basic, cfg->basic)) || !method_info_equal(ocfg->methods[IM_ONLINE], cfg->methods[IM_ONLINE]) || !method_info_equal(ocfg->methods[IM_START], cfg->methods[IM_START])) { destroy_bound_fds(inst); assert(inst->new_config == NULL); inst->new_config = cfg; (void) run_method(inst, IM_OFFLINE, NULL); } else { /* no bind config / method changes */ /* * swap the proto list over from the old * configuration to the new, so we retain * our set of network fds. */ destroy_proto_list(cfg->basic); cfg->basic->proto_list = ocfg->basic->proto_list; ocfg->basic->proto_list = NULL; destroy_instance_cfg(ocfg); inst->config = cfg; /* re-evaluate copies limits based on new cfg */ if (copies_limit_exceeded(inst)) { destroy_bound_fds(inst); (void) run_method(inst, IM_OFFLINE, NULL); } else { /* * Since the instance isn't being * taken offline, where we assume it * would pick-up any configuration * changes automatically when it goes * back online, run its refresh method * to allow it to pick-up any changes * whilst still online. */ (void) run_method(inst, IM_REFRESH, NULL); } } } else { log_invalid_cfg(inst->fmri); destroy_bound_fds(inst); inst->maintenance_req = B_TRUE; (void) run_method(inst, IM_OFFLINE, NULL); } break; default: debug_msg("Unhandled current state %d for instance in " "refresh_instance", inst->cur_istate); assert(0); } } /* * Called by process_restarter_event() to handle a restarter event for an * instance. */ static void handle_restarter_event(instance_t *instance, restarter_event_type_t event, boolean_t send_ack) { switch (event) { case RESTARTER_EVENT_TYPE_ADD_INSTANCE: /* * When startd restarts, it sends _ADD_INSTANCE to delegated * restarters for all those services managed by them. We should * acknowledge this event, as startd's graph needs to be updated * about the current state of the service, when startd is * restarting. * update_state() is ok to be called here, as commands for * instances in transition are deferred by * process_restarter_event(). */ update_state(instance, instance->cur_istate, RERR_NONE); goto done; case RESTARTER_EVENT_TYPE_ADMIN_REFRESH: refresh_instance(instance); goto done; case RESTARTER_EVENT_TYPE_ADMIN_RESTART: /* * We've got a restart event, so if the instance is online * in any way initiate taking it offline, and rely upon * our restarter to send us an online event to bring * it back online. */ switch (instance->cur_istate) { case IIS_ONLINE: case IIS_DEGRADED: destroy_bound_fds(instance); (void) run_method(instance, IM_OFFLINE, NULL); } goto done; case RESTARTER_EVENT_TYPE_REMOVE_INSTANCE: remove_instance(instance); goto done; case RESTARTER_EVENT_TYPE_STOP_RESET: case RESTARTER_EVENT_TYPE_STOP: switch (instance->cur_istate) { case IIS_OFFLINE_CONRATE: case IIS_OFFLINE_BIND: case IIS_OFFLINE_COPIES: /* * inetd must be closing down as we wouldn't get this * event in one of these states from the master * restarter. Take the instance to the offline resting * state. */ if (instance->cur_istate == IIS_OFFLINE_BIND) { cancel_bind_timer(instance); } else if (instance->cur_istate == IIS_OFFLINE_CONRATE) { cancel_inst_timer(instance); } update_state(instance, IIS_OFFLINE, RERR_RESTART); goto done; } break; } switch (instance->cur_istate) { case IIS_OFFLINE: switch (event) { case RESTARTER_EVENT_TYPE_START: /* * Dependencies are met, let's take the service online. * Only try and bind for a wait type service if * no process is running on its behalf. Otherwise, just * mark the service online and binding will be attempted * when the process exits. */ if (!(instance->config->basic->iswait && (uu_list_first(instance->start_pids) != NULL))) { create_bound_fds(instance); } else { update_state(instance, IIS_ONLINE, RERR_NONE); } break; case RESTARTER_EVENT_TYPE_DISABLE: case RESTARTER_EVENT_TYPE_ADMIN_DISABLE: /* * The instance should be disabled, so run the * instance's disabled method that will do the work * to take it there. */ (void) run_method(instance, IM_DISABLE, NULL); break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance * go to maintenance; since we're already offline * just update the state to the maintenance state. */ update_state(instance, IIS_MAINTENANCE, RERR_RESTART); break; } break; case IIS_OFFLINE_BIND: switch (event) { case RESTARTER_EVENT_TYPE_DISABLE: case RESTARTER_EVENT_TYPE_ADMIN_DISABLE: /* * The instance should be disabled. Firstly, as for * the above dependencies unmet comment, cancel * the bind retry timer and update the state to * offline. Then, run the disable method to do the * work to take the instance from offline to * disabled. */ cancel_bind_timer(instance); update_state(instance, IIS_OFFLINE, RERR_RESTART); (void) run_method(instance, IM_DISABLE, NULL); break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance * be placed in the maintenance state. Cancel the * outstanding retry timer, and since we're already * offline, update the state to maintenance. */ cancel_bind_timer(instance); update_state(instance, IIS_MAINTENANCE, RERR_RESTART); break; } break; case IIS_DEGRADED: case IIS_ONLINE: switch (event) { case RESTARTER_EVENT_TYPE_DISABLE: case RESTARTER_EVENT_TYPE_ADMIN_DISABLE: /* * The instance needs to be disabled. Do the same work * as for the dependencies unmet event below to * take the instance offline. */ destroy_bound_fds(instance); /* * Indicate that the offline method is being run * as part of going to the disabled state, and to * carry on this transition. */ instance->disable_req = B_TRUE; (void) run_method(instance, IM_OFFLINE, NULL); break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance be * placed in the maintenance state. This involves * firstly taking the service offline, so do the * same work as for the dependencies unmet event * below. We set the maintenance_req flag to * indicate that when we get to the offline state * we should be placed directly into the maintenance * state. */ instance->maintenance_req = B_TRUE; /* FALLTHROUGH */ case RESTARTER_EVENT_TYPE_STOP_RESET: case RESTARTER_EVENT_TYPE_STOP: /* * Dependencies have become unmet. Close and * stop listening on the instance's network file * descriptor, and run the offline method to do * any work required to take us to the offline state. */ destroy_bound_fds(instance); (void) run_method(instance, IM_OFFLINE, NULL); } break; case IIS_UNINITIALIZED: if (event == RESTARTER_EVENT_TYPE_DISABLE || event == RESTARTER_EVENT_TYPE_ADMIN_DISABLE) { update_state(instance, IIS_DISABLED, RERR_NONE); break; } else if (event != RESTARTER_EVENT_TYPE_ENABLE) { /* * Ignore other events until we know whether we're * enabled or not. */ break; } /* * We've got an enabled event; make use of the handling in the * disable case. */ /* FALLTHROUGH */ case IIS_DISABLED: switch (event) { case RESTARTER_EVENT_TYPE_ENABLE: /* * The instance needs enabling. Commence reading its * configuration and if successful place the instance * in the offline state and let process_offline_inst() * take it from there. */ destroy_instance_cfg(instance->config); instance->config = read_instance_cfg(instance->fmri); if (instance->config != NULL) { update_state(instance, IIS_OFFLINE, RERR_RESTART); process_offline_inst(instance); } else { log_invalid_cfg(instance->fmri); update_state(instance, IIS_MAINTENANCE, RERR_RESTART); } break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance be * placed in the maintenance state, so just update its * state to maintenance. */ update_state(instance, IIS_MAINTENANCE, RERR_RESTART); break; } break; case IIS_MAINTENANCE: switch (event) { case RESTARTER_EVENT_TYPE_ADMIN_MAINT_OFF: case RESTARTER_EVENT_TYPE_ADMIN_DISABLE: /* * The master restarter has requested that the instance * be taken out of maintenance. Read its configuration, * and if successful place the instance in the offline * state and call process_offline_inst() to take it * from there. */ destroy_instance_cfg(instance->config); instance->config = read_instance_cfg(instance->fmri); if (instance->config != NULL) { update_state(instance, IIS_OFFLINE, RERR_RESTART); process_offline_inst(instance); } else { boolean_t enabled; /* * The configuration was invalid. If the * service has disabled requested, let's * just place the instance in disabled even * though we haven't been able to run its * disable method, as the slightly incorrect * state is likely to be less of an issue to * an administrator than refusing to move an * instance to disabled. If disable isn't * requested, re-mark the service's state * as maintenance, so the administrator can * see the request was processed. */ if ((read_enable_merged(instance->fmri, &enabled) == 0) && !enabled) { update_state(instance, IIS_DISABLED, RERR_RESTART); } else { log_invalid_cfg(instance->fmri); update_state(instance, IIS_MAINTENANCE, RERR_FAULT); } } break; } break; case IIS_OFFLINE_CONRATE: switch (event) { case RESTARTER_EVENT_TYPE_DISABLE: /* * The instance wants disabling. Take the instance * offline as for the dependencies unmet event above, * and then from there run the disable method to do * the work to take the instance to the disabled state. */ cancel_inst_timer(instance); update_state(instance, IIS_OFFLINE, RERR_RESTART); (void) run_method(instance, IM_DISABLE, NULL); break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance * be taken to maintenance. Cancel the timer setup * when we entered this state, and go directly to * maintenance. */ cancel_inst_timer(instance); update_state(instance, IIS_MAINTENANCE, RERR_RESTART); break; } break; case IIS_OFFLINE_COPIES: switch (event) { case RESTARTER_EVENT_TYPE_DISABLE: /* * The instance wants disabling. Update the state * to offline, and run the disable method to do the * work to take it to the disabled state. */ update_state(instance, IIS_OFFLINE, RERR_RESTART); (void) run_method(instance, IM_DISABLE, NULL); break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance be * placed in maintenance. Since it's already offline * simply update the state. */ update_state(instance, IIS_MAINTENANCE, RERR_RESTART); break; } break; default: debug_msg("handle_restarter_event: instance in an " "unexpected state"); assert(0); } done: if (send_ack) ack_restarter_event(B_TRUE); } /* * Tries to read and process an event from the event pipe. If there isn't one * or an error occurred processing the event it returns -1. Else, if the event * is for an instance we're not already managing we read its state, add it to * our list to manage, and if appropriate read its configuration. Whether it's * new to us or not, we then handle the specific event. * Returns 0 if an event was read and processed successfully, else -1. */ static int process_restarter_event(void) { char *fmri; size_t fmri_size; restarter_event_type_t event_type; instance_t *instance; restarter_event_t *event; ssize_t sz; /* * Try to read an event pointer from the event pipe. */ errno = 0; switch (safe_read(rst_event_pipe[PE_CONSUMER], &event, sizeof (event))) { case 0: break; case 1: if (errno == EAGAIN) /* no event to read */ return (-1); /* other end of pipe closed */ /* FALLTHROUGH */ default: /* unexpected read error */ /* * There's something wrong with the event pipe. Let's * shutdown and be restarted. */ inetd_stop(); return (-1); } /* * Check if we're currently managing the instance which the event * pertains to. If not, read its complete state and add it to our * list to manage. */ fmri_size = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH); if ((fmri = malloc(fmri_size)) == NULL) { error_msg(strerror(errno)); goto fail; } sz = restarter_event_get_instance(event, fmri, fmri_size); if (sz >= fmri_size) assert(0); for (instance = uu_list_first(instance_list); instance != NULL; instance = uu_list_next(instance_list, instance)) { if (strcmp(instance->fmri, fmri) == 0) break; } if (instance == NULL) { int err; debug_msg("New instance to manage: %s", fmri); if (((instance = create_instance(fmri)) == NULL) || (retrieve_instance_state(instance) != 0) || (retrieve_method_pids(instance) != 0)) { destroy_instance(instance); free(fmri); goto fail; } if (((err = iterate_repository_contracts(instance, 0)) != 0) && (err != ENOENT)) { error_msg(gettext( "Failed to adopt contracts of instance %s: %s"), instance->fmri, strerror(err)); destroy_instance(instance); free(fmri); goto fail; } uu_list_node_init(instance, &instance->link, instance_pool); (void) uu_list_insert_after(instance_list, NULL, instance); /* * Only read configuration for instances that aren't in any of * the disabled, maintenance or uninitialized states, since * they'll read it on state exit. */ if ((instance->cur_istate != IIS_DISABLED) && (instance->cur_istate != IIS_MAINTENANCE) && (instance->cur_istate != IIS_UNINITIALIZED)) { instance->config = read_instance_cfg(instance->fmri); if (instance->config == NULL) { log_invalid_cfg(instance->fmri); update_state(instance, IIS_MAINTENANCE, RERR_FAULT); } } } free(fmri); event_type = restarter_event_get_type(event); debug_msg("Event type: %d for instance: %s", event_type, instance->fmri); /* * If the instance is currently running a method, don't process the * event now, but attach it to the instance for processing when * the instance finishes its transition. */ if (INST_IN_TRANSITION(instance)) { debug_msg("storing event %d for instance %s", event_type, instance->fmri); instance->pending_rst_event = event_type; } else { handle_restarter_event(instance, event_type, B_TRUE); } return (0); fail: ack_restarter_event(B_FALSE); return (-1); } /* * Do the state machine processing associated with the termination of instance * 'inst''s start method for the 'proto_name' protocol if this parameter is not * NULL. */ void process_start_term(instance_t *inst, char *proto_name) { basic_cfg_t *cfg; inst->copies--; if ((inst->cur_istate == IIS_MAINTENANCE) || (inst->cur_istate == IIS_DISABLED)) { /* do any further processing/checks when we exit these states */ return; } cfg = inst->config->basic; if (cfg->iswait) { proto_info_t *pi; boolean_t listen; switch (inst->cur_istate) { case IIS_ONLINE: case IIS_DEGRADED: case IIS_IN_REFRESH_METHOD: /* * A wait type service's start method has exited. * Check if the method was fired off in this inetd's * lifetime, or a previous one; if the former, * re-commence listening on the service's behalf; if * the latter, mark the service offline and let bind * attempts commence. */ listen = B_FALSE; for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { /* * If a bound fd exists, the method was fired * off during this inetd's lifetime. */ if (pi->listen_fd != -1) { listen = B_TRUE; if (proto_name == NULL || strcmp(pi->proto, proto_name) == 0) break; } } if (pi != NULL) { if (poll_bound_fds(inst, B_TRUE, proto_name) != 0) handle_bind_failure(inst); } else if (listen == B_FALSE) { update_state(inst, IIS_OFFLINE, RERR_RESTART); create_bound_fds(inst); } } } else { /* * Check if a nowait service should be brought back online * after exceeding its copies limit. */ if ((inst->cur_istate == IIS_OFFLINE_COPIES) && !copies_limit_exceeded(inst)) { update_state(inst, IIS_OFFLINE, RERR_NONE); process_offline_inst(inst); } } } /* * If the instance has a pending event process it and initiate the * acknowledgement. */ static void process_pending_rst_event(instance_t *inst) { if (inst->pending_rst_event != RESTARTER_EVENT_TYPE_INVALID) { restarter_event_type_t re; debug_msg("Injecting pending event %d for instance %s", inst->pending_rst_event, inst->fmri); re = inst->pending_rst_event; inst->pending_rst_event = RESTARTER_EVENT_TYPE_INVALID; handle_restarter_event(inst, re, B_TRUE); } } /* * Do the state machine processing associated with the termination * of the specified instance's non-start method with the specified status. * Once the processing of the termination is done, the function also picks up * any processing that was blocked on the method running. */ void process_non_start_term(instance_t *inst, int status) { boolean_t ran_online_method = B_FALSE; if (status == IMRET_FAILURE) { error_msg(gettext("The %s method of instance %s failed, " "transitioning to maintenance"), methods[states[inst->cur_istate].method_running].name, inst->fmri); if ((inst->cur_istate == IIS_IN_ONLINE_METHOD) || (inst->cur_istate == IIS_IN_REFRESH_METHOD)) destroy_bound_fds(inst); update_state(inst, IIS_MAINTENANCE, RERR_FAULT); inst->maintenance_req = B_FALSE; inst->conn_rate_exceeded = B_FALSE; if (inst->new_config != NULL) { destroy_instance_cfg(inst->new_config); inst->new_config = NULL; } if (!inetd_stopping) process_pending_rst_event(inst); return; } /* non-failure method return */ if (status != IMRET_SUCCESS) { /* * An instance method never returned a supported return code. * We'll assume this means the method succeeded for now whilst * non-GL-cognizant methods are used - eg. pkill. */ debug_msg("The %s method of instance %s returned " "non-compliant exit code: %d, assuming success", methods[states[inst->cur_istate].method_running].name, inst->fmri, status); } /* * Update the state from the in-transition state. */ switch (inst->cur_istate) { case IIS_IN_ONLINE_METHOD: ran_online_method = B_TRUE; /* FALLTHROUGH */ case IIS_IN_REFRESH_METHOD: /* * If we've exhausted the bind retries, flag that by setting * the instance's state to degraded. */ if (inst->bind_retries_exceeded) { update_state(inst, IIS_DEGRADED, RERR_NONE); break; } /* FALLTHROUGH */ default: update_state(inst, methods[states[inst->cur_istate].method_running].dst_state, RERR_NONE); } if (inst->cur_istate == IIS_OFFLINE) { if (inst->new_config != NULL) { /* * This instance was found during refresh to need * taking offline because its newly read configuration * was sufficiently different. Now we're offline, * activate this new configuration. */ destroy_instance_cfg(inst->config); inst->config = inst->new_config; inst->new_config = NULL; } /* continue/complete any transitions that are in progress */ process_offline_inst(inst); } else if (ran_online_method) { /* * We've just successfully executed the online method. We have * a set of bound network fds that were created before running * this method, so now we're online start listening for * connections on them. */ if (poll_bound_fds(inst, B_TRUE, NULL) != 0) handle_bind_failure(inst); } /* * If we're now out of transition (process_offline_inst() could have * fired off another method), carry out any jobs that were blocked by * us being in transition. */ if (!INST_IN_TRANSITION(inst)) { if (inetd_stopping) { if (!instance_stopped(inst)) { /* * inetd is stopping, and this instance hasn't * been stopped. Inject a stop event. */ handle_restarter_event(inst, RESTARTER_EVENT_TYPE_STOP, B_FALSE); } } else { process_pending_rst_event(inst); } } } /* * Check if configuration file specified is readable. If not return B_FALSE, * else return B_TRUE. */ static boolean_t can_read_file(const char *path) { int ret; int serrno; do { ret = access(path, R_OK); } while ((ret < 0) && (errno == EINTR)); if (ret < 0) { if (errno != ENOENT) { serrno = errno; error_msg(gettext("Failed to access configuration " "file %s for performing modification checks: %s"), path, strerror(errno)); errno = serrno; } return (B_FALSE); } return (B_TRUE); } /* * Check whether the configuration file has changed contents since inetd * was last started/refreshed, and if so, log a message indicating that * inetconv needs to be run. */ static void check_conf_file(void) { char *new_hash; char *old_hash = NULL; scf_error_t ret; const char *file; if (conf_file == NULL) { /* * No explicit config file specified, so see if one of the * default two are readable, checking the primary one first * followed by the secondary. */ if (can_read_file(PRIMARY_DEFAULT_CONF_FILE)) { file = PRIMARY_DEFAULT_CONF_FILE; } else if ((errno == ENOENT) && can_read_file(SECONDARY_DEFAULT_CONF_FILE)) { file = SECONDARY_DEFAULT_CONF_FILE; } else { return; } } else { file = conf_file; if (!can_read_file(file)) return; } if (calculate_hash(file, &new_hash) == 0) { ret = retrieve_inetd_hash(&old_hash); if (((ret == SCF_ERROR_NONE) && (strcmp(old_hash, new_hash) != 0))) { /* modified config file */ warn_msg(gettext( "Configuration file %s has been modified since " "inetconv was last run. \"inetconv -i %s\" must be " "run to apply any changes to the SMF"), file, file); } else if ((ret != SCF_ERROR_NOT_FOUND) && (ret != SCF_ERROR_NONE)) { /* No message if hash not yet computed */ error_msg(gettext("Failed to check whether " "configuration file %s has been modified: %s"), file, scf_strerror(ret)); } free(old_hash); free(new_hash); } else { error_msg(gettext("Failed to check whether configuration file " "%s has been modified: %s"), file, strerror(errno)); } } /* * Refresh all inetd's managed instances and check the configuration file * for any updates since inetconv was last run, logging a message if there * are. We call the SMF refresh function to refresh each instance so that * the refresh request goes through the framework, and thus results in the * running snapshot of each instance being updated from the configuration * snapshot. */ static void inetd_refresh(void) { instance_t *inst; refresh_debug_flag(); /* call libscf to send refresh requests for all managed instances */ for (inst = uu_list_first(instance_list); inst != NULL; inst = uu_list_next(instance_list, inst)) { if (smf_refresh_instance(inst->fmri) < 0) { error_msg(gettext("Failed to refresh instance %s: %s"), inst->fmri, scf_strerror(scf_error())); } } /* * Log a message if the configuration file has changed since inetconv * was last run. */ check_conf_file(); } /* * Initiate inetd's shutdown. */ static void inetd_stop(void) { instance_t *inst; /* Block handling signals for stop and refresh */ (void) sighold(SIGHUP); (void) sighold(SIGTERM); /* Indicate inetd is coming down */ inetd_stopping = B_TRUE; /* Stop polling on restarter events. */ clear_pollfd(rst_event_pipe[PE_CONSUMER]); /* Stop polling for any more stop/refresh requests. */ clear_pollfd(uds_fd); /* * Send a stop event to all currently unstopped instances that * aren't in transition. For those that are in transition, the * event will get sent when the transition completes. */ for (inst = uu_list_first(instance_list); inst != NULL; inst = uu_list_next(instance_list, inst)) { if (!instance_stopped(inst) && !INST_IN_TRANSITION(inst)) handle_restarter_event(inst, RESTARTER_EVENT_TYPE_STOP, B_FALSE); } } /* * Sets up the intra-inetd-process Unix Domain Socket. * Returns -1 on error, else 0. */ static int uds_init(void) { struct sockaddr_un addr; if ((uds_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { error_msg("socket: %s", strerror(errno)); return (-1); } disable_blocking(uds_fd); (void) unlink(INETD_UDS_PATH); /* clean-up any stale files */ (void) memset(&addr, 0, sizeof (addr)); addr.sun_family = AF_UNIX; /* CONSTCOND */ assert(sizeof (INETD_UDS_PATH) <= sizeof (addr.sun_path)); (void) strlcpy(addr.sun_path, INETD_UDS_PATH, sizeof (addr.sun_path)); if (bind(uds_fd, (struct sockaddr *)(&addr), sizeof (addr)) < 0) { error_msg(gettext("Failed to bind socket to %s: %s"), INETD_UDS_PATH, strerror(errno)); (void) close(uds_fd); return (-1); } (void) listen(uds_fd, UDS_BACKLOG); if ((set_pollfd(uds_fd, POLLIN)) == -1) { (void) close(uds_fd); (void) unlink(INETD_UDS_PATH); return (-1); } return (0); } static void uds_fini(void) { if (uds_fd != -1) (void) close(uds_fd); (void) unlink(INETD_UDS_PATH); } /* * Handle an incoming request on the Unix Domain Socket. Returns -1 if there * was an error handling the event, else 0. */ static int process_uds_event(void) { uds_request_t req; int fd; struct sockaddr_un addr; socklen_t len = sizeof (addr); int ret; uint_t retries = 0; ucred_t *ucred = NULL; uid_t euid; do { fd = accept(uds_fd, (struct sockaddr *)&addr, &len); } while ((fd < 0) && (errno == EINTR)); if (fd < 0) { if (errno != EWOULDBLOCK) error_msg("accept failed: %s", strerror(errno)); return (-1); } if (getpeerucred(fd, &ucred) == -1) { error_msg("getpeerucred failed: %s", strerror(errno)); (void) close(fd); return (-1); } /* Check peer credentials before acting on the request */ euid = ucred_geteuid(ucred); ucred_free(ucred); if (euid != 0 && getuid() != euid) { debug_msg("peer euid %u != uid %u", (uint_t)euid, (uint_t)getuid()); (void) close(fd); return (-1); } for (retries = 0; retries < UDS_RECV_RETRIES; retries++) { if (((ret = safe_read(fd, &req, sizeof (req))) != 1) || (errno != EAGAIN)) break; (void) poll(NULL, 0, 100); /* 100ms pause */ } if (ret != 0) { error_msg(gettext("Failed read: %s"), strerror(errno)); (void) close(fd); return (-1); } switch (req) { case UR_REFRESH_INETD: /* flag the request for event_loop() to process */ refresh_inetd_requested = B_TRUE; (void) close(fd); break; case UR_STOP_INETD: inetd_stop(); break; default: error_msg("unexpected UDS request"); (void) close(fd); return (-1); } return (0); } /* * Perform checks for common exec string errors. We limit the checks to * whether the file exists, is a regular file, and has at least one execute * bit set. We leave the core security checks to exec() so as not to duplicate * and thus incur the associated drawbacks, but hope to catch the common * errors here. */ static boolean_t passes_basic_exec_checks(const char *instance, const char *method, const char *path) { struct stat sbuf; /* check the file exists */ while (stat(path, &sbuf) == -1) { if (errno != EINTR) { error_msg(gettext( "Can't stat the %s method of instance %s: %s"), method, instance, strerror(errno)); return (B_FALSE); } } /* * Check if the file is a regular file and has at least one execute * bit set. */ if ((sbuf.st_mode & S_IFMT) != S_IFREG) { error_msg(gettext( "The %s method of instance %s isn't a regular file"), method, instance); return (B_FALSE); } else if ((sbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) { error_msg(gettext("The %s method instance %s doesn't have " "any execute permissions set"), method, instance); return (B_FALSE); } return (B_TRUE); } static void exec_method(instance_t *instance, instance_method_t method, method_info_t *mi, struct method_context *mthd_ctxt, const proto_info_t *pi) { char **args; char **env; const char *errf; int serrno; sigset_t mtset; basic_cfg_t *cfg = instance->config->basic; if (method == IM_START) { /* * If wrappers checks fail, pretend the method was exec'd and * failed. */ if (!tcp_wrappers_ok(instance)) exit(IMRET_FAILURE); } /* * Revert the disposition of handled signals and ignored signals to * their defaults, unblocking any blocked ones as a side effect. */ (void) sigset(SIGHUP, SIG_DFL); (void) sigset(SIGTERM, SIG_DFL); (void) sigset(SIGINT, SIG_DFL); /* * Ensure that other signals are unblocked */ (void) sigemptyset(&mtset); (void) sigprocmask(SIG_SETMASK, &mtset, (sigset_t *)NULL); /* * Setup exec arguments. Do this before the fd setup below, so our * logging related file fd doesn't get taken over before we call * expand_address(). */ if ((method == IM_START) && (strcmp(mi->exec_args_we.we_wordv[0], "%A") == 0)) { args = expand_address(instance, pi); } else { args = mi->exec_args_we.we_wordv; } /* Generate audit trail for start operations */ if (method == IM_START) { adt_event_data_t *ae; struct sockaddr_storage ss; priv_set_t *privset; socklen_t sslen = sizeof (ss); if ((ae = adt_alloc_event(audit_handle, ADT_inetd_connect)) == NULL) { error_msg(gettext("Unable to allocate audit event for " "the %s method of instance %s"), methods[method].name, instance->fmri); exit(IMRET_FAILURE); } /* * The inetd_connect audit record consists of: * Service name * Execution path * Remote address and port * Local port * Process privileges */ ae->adt_inetd_connect.service_name = cfg->svc_name; ae->adt_inetd_connect.cmd = mi->exec_path; if (instance->remote_addr.ss_family == AF_INET) { struct in_addr *in = SS_SINADDR(instance->remote_addr); ae->adt_inetd_connect.ip_adr[0] = in->s_addr; ae->adt_inetd_connect.ip_type = ADT_IPv4; } else { uint32_t *addr6; int i; ae->adt_inetd_connect.ip_type = ADT_IPv6; addr6 = (uint32_t *)SS_SINADDR(instance->remote_addr); for (i = 0; i < 4; ++i) ae->adt_inetd_connect.ip_adr[i] = addr6[i]; } ae->adt_inetd_connect.ip_remote_port = ntohs(SS_PORT(instance->remote_addr)); if (getsockname(instance->conn_fd, (struct sockaddr *)&ss, &sslen) == 0) ae->adt_inetd_connect.ip_local_port = ntohs(SS_PORT(ss)); privset = mthd_ctxt->priv_set; if (privset == NULL) { privset = priv_allocset(); if (privset != NULL && getppriv(PRIV_EFFECTIVE, privset) != 0) { priv_freeset(privset); privset = NULL; } } ae->adt_inetd_connect.privileges = privset; (void) adt_put_event(ae, ADT_SUCCESS, ADT_SUCCESS); adt_free_event(ae); if (privset != NULL && mthd_ctxt->priv_set == NULL) priv_freeset(privset); } /* * Set method context before the fd setup below so we can output an * error message if it fails. */ if ((errno = restarter_set_method_context(mthd_ctxt, &errf)) != 0) { const char *msg; if (errno == -1) { if (strcmp(errf, "core_set_process_path") == 0) { msg = gettext("Failed to set the corefile path " "for the %s method of instance %s"); } else if (strcmp(errf, "setproject") == 0) { msg = gettext("Failed to assign a resource " "control for the %s method of instance %s"); } else if (strcmp(errf, "pool_set_binding") == 0) { msg = gettext("Failed to bind the %s method of " "instance %s to a pool due to a system " "error"); } else { assert(0); abort(); } error_msg(msg, methods[method].name, instance->fmri); exit(IMRET_FAILURE); } if (errf != NULL && strcmp(errf, "pool_set_binding") == 0) { switch (errno) { case ENOENT: msg = gettext("Failed to find resource pool " "for the %s method of instance %s"); break; case EBADF: msg = gettext("Failed to bind the %s method of " "instance %s to a pool due to invalid " "configuration"); break; case EINVAL: msg = gettext("Failed to bind the %s method of " "instance %s to a pool due to invalid " "pool name"); break; default: assert(0); abort(); } exit(IMRET_FAILURE); } if (errf != NULL) { error_msg(gettext("Failed to set credentials for the " "%s method of instance %s (%s: %s)"), methods[method].name, instance->fmri, errf, strerror(errno)); exit(IMRET_FAILURE); } switch (errno) { case ENOMEM: msg = gettext("Failed to set credentials for the %s " "method of instance %s (out of memory)"); break; case ENOENT: msg = gettext("Failed to set credentials for the %s " "method of instance %s (no passwd or shadow " "entry for user)"); break; default: assert(0); abort(); } error_msg(msg, methods[method].name, instance->fmri); exit(IMRET_FAILURE); } /* let exec() free mthd_ctxt */ /* setup standard fds */ if (method == IM_START) { (void) dup2(instance->conn_fd, STDIN_FILENO); } else { (void) close(STDIN_FILENO); (void) open("/dev/null", O_RDONLY); } (void) dup2(STDIN_FILENO, STDOUT_FILENO); (void) dup2(STDIN_FILENO, STDERR_FILENO); closefrom(STDERR_FILENO + 1); method_preexec(); env = set_smf_env(mthd_ctxt, instance, methods[method].name); if (env != NULL) { do { (void) execve(mi->exec_path, args, env); } while (errno == EINTR); } serrno = errno; /* start up logging again to report the error */ msg_init(); errno = serrno; error_msg( gettext("Failed to exec %s method of instance %s: %s"), methods[method].name, instance->fmri, strerror(errno)); if ((method == IM_START) && (instance->config->basic->iswait)) { /* * We couldn't exec the start method for a wait type service. * Eat up data from the endpoint, so that hopefully the * service's fd won't wake poll up on the next time round * event_loop(). This behavior is carried over from the old * inetd, and it seems somewhat arbitrary that it isn't * also done in the case of fork failures; but I guess * it assumes an exec failure is less likely to be the result * of a resource shortage, and is thus not worth retrying. */ consume_wait_data(instance, 0); } exit(IMRET_FAILURE); } static restarter_error_t get_method_error_success(instance_method_t method) { switch (method) { case IM_OFFLINE: return (RERR_RESTART); case IM_ONLINE: return (RERR_RESTART); case IM_DISABLE: return (RERR_RESTART); case IM_REFRESH: return (RERR_REFRESH); case IM_START: return (RERR_RESTART); } (void) fprintf(stderr, gettext("Internal fatal error in inetd.\n")); abort(); /* NOTREACHED */ } static int smf_kill_process(instance_t *instance, int sig) { rep_val_t *rv; int ret = IMRET_SUCCESS; /* Carry out process assassination */ for (rv = uu_list_first(instance->start_pids); rv != NULL; rv = uu_list_next(instance->start_pids, rv)) { if ((kill((pid_t)rv->val, sig) != 0) && (errno != ESRCH)) { ret = IMRET_FAILURE; error_msg(gettext("Unable to kill " "start process (%ld) of instance %s: %s"), rv->val, instance->fmri, strerror(errno)); } } return (ret); } /* * Runs the specified method of the specified service instance. * If the method was never specified, we handle it the same as if the * method was called and returned success, carrying on any transition the * instance may be in the midst of. * If the method isn't executable in its specified profile or an error occurs * forking a process to run the method in the function returns -1. * If a method binary is successfully executed, the function switches the * instance's cur state to the method's associated 'run' state and the next * state to the methods associated next state. * Returns -1 if there's an error before forking, else 0. */ int run_method(instance_t *instance, instance_method_t method, const proto_info_t *start_info) { pid_t child_pid; method_info_t *mi; struct method_context *mthd_ctxt = NULL; int sig = 0; int ret; instance_cfg_t *cfg = instance->config; ctid_t cid; boolean_t trans_failure = B_TRUE; int serrno; /* * Don't bother updating the instance's state for the start method * as there isn't a separate start method state. */ if (method != IM_START) update_instance_states(instance, get_method_state(method), methods[method].dst_state, get_method_error_success(method)); if ((mi = cfg->methods[method]) == NULL) { /* * If the absent method is IM_OFFLINE, default action needs * to be taken to avoid lingering processes which can prevent * the upcoming rebinding from happening. */ if ((method == IM_OFFLINE) && instance->config->basic->iswait) { warn_msg(gettext("inetd_offline method for instance %s " "is unspecified. Taking default action: kill."), instance->fmri); (void) str2sig("TERM", &sig); ret = smf_kill_process(instance, sig); process_non_start_term(instance, ret); return (0); } else { process_non_start_term(instance, IMRET_SUCCESS); return (0); } } /* Handle special method tokens, not allowed on start */ if (method != IM_START) { if (restarter_is_null_method(mi->exec_path)) { /* :true means nothing should be done */ process_non_start_term(instance, IMRET_SUCCESS); return (0); } if ((sig = restarter_is_kill_method(mi->exec_path)) >= 0) { /* Carry out contract assassination */ ret = iterate_repository_contracts(instance, sig); /* ENOENT means we didn't find any contracts */ if (ret != 0 && ret != ENOENT) { error_msg(gettext("Failed to send signal %d " "to contracts of instance %s: %s"), sig, instance->fmri, strerror(ret)); goto prefork_failure; } else { process_non_start_term(instance, IMRET_SUCCESS); return (0); } } if ((sig = restarter_is_kill_proc_method(mi->exec_path)) >= 0) { ret = smf_kill_process(instance, sig); process_non_start_term(instance, ret); return (0); } } /* * Get the associated method context before the fork so we can * modify the instances state if things go wrong. */ if ((mthd_ctxt = read_method_context(instance->fmri, methods[method].name, mi->exec_path)) == NULL) goto prefork_failure; /* * Perform some basic checks before we fork to limit the possibility * of exec failures, so we can modify the instance state if necessary. */ if (!passes_basic_exec_checks(instance->fmri, methods[method].name, mi->exec_path)) { trans_failure = B_FALSE; goto prefork_failure; } if (contract_prefork(instance->fmri, method) == -1) goto prefork_failure; child_pid = fork(); serrno = errno; contract_postfork(); switch (child_pid) { case -1: error_msg(gettext( "Unable to fork %s method of instance %s: %s"), methods[method].name, instance->fmri, strerror(serrno)); if ((serrno != EAGAIN) && (serrno != ENOMEM)) trans_failure = B_FALSE; goto prefork_failure; case 0: /* child */ exec_method(instance, method, mi, mthd_ctxt, start_info); /* NOTREACHED */ default: /* parent */ restarter_free_method_context(mthd_ctxt); mthd_ctxt = NULL; if (get_latest_contract(&cid) < 0) cid = -1; /* * Register this method so its termination is noticed and * the state transition this method participates in is * continued. */ if (register_method(instance, child_pid, cid, method, start_info->proto) != 0) { /* * Since we will never find out about the termination * of this method, if it's a non-start method treat * is as a failure so we don't block restarter event * processing on it whilst it languishes in a method * running state. */ error_msg(gettext("Failed to monitor status of " "%s method of instance %s"), methods[method].name, instance->fmri); if (method != IM_START) process_non_start_term(instance, IMRET_FAILURE); } add_method_ids(instance, child_pid, cid, method); /* do tcp tracing for those nowait instances that request it */ if ((method == IM_START) && cfg->basic->do_tcp_trace && !cfg->basic->iswait) { char buf[INET6_ADDRSTRLEN]; syslog(LOG_NOTICE, "%s[%d] from %s %d", cfg->basic->svc_name, child_pid, inet_ntop_native(instance->remote_addr.ss_family, SS_SINADDR(instance->remote_addr), buf, sizeof (buf)), ntohs(SS_PORT(instance->remote_addr))); } } return (0); prefork_failure: if (mthd_ctxt != NULL) { restarter_free_method_context(mthd_ctxt); mthd_ctxt = NULL; } if (method == IM_START) { /* * Only place a start method in maintenance if we're sure * that the failure was non-transient. */ if (!trans_failure) { destroy_bound_fds(instance); update_state(instance, IIS_MAINTENANCE, RERR_FAULT); } } else { /* treat the failure as if the method ran and failed */ process_non_start_term(instance, IMRET_FAILURE); } return (-1); } static int pending_connections(instance_t *instance, proto_info_t *pi) { if (instance->config->basic->istlx) { tlx_info_t *tl = (tlx_info_t *)pi; return (uu_list_numnodes(tl->conn_ind_queue) != 0); } else { return (0); } } static int accept_connection(instance_t *instance, proto_info_t *pi) { int fd; socklen_t size; if (instance->config->basic->istlx) { tlx_info_t *tl = (tlx_info_t *)pi; tlx_pending_counter = \ tlx_pending_counter - uu_list_numnodes(tl->conn_ind_queue); fd = tlx_accept(instance->fmri, (tlx_info_t *)pi, &(instance->remote_addr)); tlx_pending_counter = \ tlx_pending_counter + uu_list_numnodes(tl->conn_ind_queue); } else { size = sizeof (instance->remote_addr); fd = accept(pi->listen_fd, (struct sockaddr *)&(instance->remote_addr), &size); if (fd < 0) error_msg("accept: %s", strerror(errno)); } return (fd); } /* * Handle an incoming connection request for a nowait service. * This involves accepting the incoming connection on a new fd. Connection * rate checks are then performed, transitioning the service to the * conrate offline state if these fail. Otherwise, the service's start method * is run (performing TCP wrappers checks if applicable as we do), and on * success concurrent copies checking is done, transitioning the service to the * copies offline state if this fails. */ static void process_nowait_request(instance_t *instance, proto_info_t *pi) { basic_cfg_t *cfg = instance->config->basic; int ret; adt_event_data_t *ae; char buf[BUFSIZ]; /* accept nowait service connections on a new fd */ if ((instance->conn_fd = accept_connection(instance, pi)) == -1) { /* * Failed accept. Return and allow the event loop to initiate * another attempt later if the request is still present. */ return; } /* * Limit connection rate of nowait services. If either conn_rate_max * or conn_rate_offline are <= 0, no connection rate limit checking * is done. If the configured rate is exceeded, the instance is taken * to the connrate_offline state and a timer scheduled to try and * bring the instance back online after the configured offline time. */ if ((cfg->conn_rate_max > 0) && (cfg->conn_rate_offline > 0)) { if (instance->conn_rate_count++ == 0) { instance->conn_rate_start = time(NULL); } else if (instance->conn_rate_count > cfg->conn_rate_max) { time_t now = time(NULL); if ((now - instance->conn_rate_start) > 1) { instance->conn_rate_start = now; instance->conn_rate_count = 1; } else { /* Generate audit record */ if ((ae = adt_alloc_event(audit_handle, ADT_inetd_ratelimit)) == NULL) { error_msg(gettext("Unable to allocate " "rate limit audit event")); } else { adt_inetd_ratelimit_t *rl = &ae->adt_inetd_ratelimit; /* * The inetd_ratelimit audit * record consists of: * Service name * Connection rate limit */ rl->service_name = cfg->svc_name; (void) snprintf(buf, sizeof (buf), "limit=%lld", cfg->conn_rate_max); rl->limit = buf; (void) adt_put_event(ae, ADT_SUCCESS, ADT_SUCCESS); adt_free_event(ae); } error_msg(gettext( "Instance %s has exceeded its configured " "connection rate, additional connections " "will not be accepted for %d seconds"), instance->fmri, cfg->conn_rate_offline); close_net_fd(instance, instance->conn_fd); instance->conn_fd = -1; destroy_bound_fds(instance); instance->conn_rate_count = 0; instance->conn_rate_exceeded = B_TRUE; (void) run_method(instance, IM_OFFLINE, NULL); return; } } } ret = run_method(instance, IM_START, pi); close_net_fd(instance, instance->conn_fd); instance->conn_fd = -1; if (ret == -1) /* the method wasn't forked */ return; instance->copies++; /* * Limit concurrent connections of nowait services. */ if (copies_limit_exceeded(instance)) { /* Generate audit record */ if ((ae = adt_alloc_event(audit_handle, ADT_inetd_copylimit)) == NULL) { error_msg(gettext("Unable to allocate copy limit " "audit event")); } else { /* * The inetd_copylimit audit record consists of: * Service name * Copy limit */ ae->adt_inetd_copylimit.service_name = cfg->svc_name; (void) snprintf(buf, sizeof (buf), "limit=%lld", cfg->max_copies); ae->adt_inetd_copylimit.limit = buf; (void) adt_put_event(ae, ADT_SUCCESS, ADT_SUCCESS); adt_free_event(ae); } warn_msg(gettext("Instance %s has reached its maximum " "configured copies, no new connections will be accepted"), instance->fmri); destroy_bound_fds(instance); (void) run_method(instance, IM_OFFLINE, NULL); } } /* * Handle an incoming request for a wait type service. * Failure rate checking is done first, taking the service to the maintenance * state if the checks fail. Following this, the service's start method is run, * and on success, we stop listening for new requests for this service. */ static void process_wait_request(instance_t *instance, const proto_info_t *pi) { basic_cfg_t *cfg = instance->config->basic; int ret; adt_event_data_t *ae; char buf[BUFSIZ]; instance->conn_fd = pi->listen_fd; /* * Detect broken servers and transition them to maintenance. If a * wait type service exits without accepting the connection or * consuming (reading) the datagram, that service's descriptor will * select readable again, and inetd will fork another instance of * the server. If either wait_fail_cnt or wait_fail_interval are <= 0, * no failure rate detection is done. */ if ((cfg->wait_fail_cnt > 0) && (cfg->wait_fail_interval > 0)) { if (instance->fail_rate_count++ == 0) { instance->fail_rate_start = time(NULL); } else if (instance->fail_rate_count > cfg->wait_fail_cnt) { time_t now = time(NULL); if ((now - instance->fail_rate_start) > cfg->wait_fail_interval) { instance->fail_rate_start = now; instance->fail_rate_count = 1; } else { /* Generate audit record */ if ((ae = adt_alloc_event(audit_handle, ADT_inetd_failrate)) == NULL) { error_msg(gettext("Unable to allocate " "failure rate audit event")); } else { adt_inetd_failrate_t *fr = &ae->adt_inetd_failrate; /* * The inetd_failrate audit record * consists of: * Service name * Failure rate * Interval * Last two are expressed as k=v pairs * in the values field. */ fr->service_name = cfg->svc_name; (void) snprintf(buf, sizeof (buf), "limit=%lld,interval=%d", cfg->wait_fail_cnt, cfg->wait_fail_interval); fr->values = buf; (void) adt_put_event(ae, ADT_SUCCESS, ADT_SUCCESS); adt_free_event(ae); } error_msg(gettext( "Instance %s has exceeded its configured " "failure rate, transitioning to " "maintenance"), instance->fmri); instance->fail_rate_count = 0; destroy_bound_fds(instance); instance->maintenance_req = B_TRUE; (void) run_method(instance, IM_OFFLINE, NULL); return; } } } ret = run_method(instance, IM_START, pi); instance->conn_fd = -1; if (ret == 0) { /* * Stop listening for connections now we've fired off the * server for a wait type instance. */ (void) poll_bound_fds(instance, B_FALSE, pi->proto); } } /* * Process any networks requests for each proto for each instance. */ void process_network_events(void) { instance_t *instance; for (instance = uu_list_first(instance_list); instance != NULL; instance = uu_list_next(instance_list, instance)) { basic_cfg_t *cfg; proto_info_t *pi; /* * Ignore instances in states that definitely don't have any * listening fds. */ switch (instance->cur_istate) { case IIS_ONLINE: case IIS_DEGRADED: case IIS_IN_REFRESH_METHOD: break; default: continue; } cfg = instance->config->basic; for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if (((pi->listen_fd != -1) && isset_pollfd(pi->listen_fd)) || pending_connections(instance, pi)) { if (cfg->iswait) { process_wait_request(instance, pi); } else { process_nowait_request(instance, pi); } } } } } /* ARGSUSED0 */ static void sigterm_handler(int sig) { got_sigterm = B_TRUE; } /* ARGSUSED0 */ static void sighup_handler(int sig) { refresh_inetd_requested = B_TRUE; } /* * inetd's major work loop. This function sits in poll waiting for events * to occur, processing them when they do. The possible events are * master restarter requests, expired timer queue timers, stop/refresh signal * requests, contract events indicating process termination, stop/refresh * requests originating from one of the stop/refresh inetd processes and * network events. * The loop is exited when a stop request is received and processed, and * all the instances have reached a suitable 'stopping' state. */ static void event_loop(void) { instance_t *instance; int timeout; for (;;) { int pret = -1; if (tlx_pending_counter != 0) timeout = 0; else timeout = iu_earliest_timer(timer_queue); if (!got_sigterm && !refresh_inetd_requested) { pret = poll(poll_fds, num_pollfds, timeout); if ((pret == -1) && (errno != EINTR)) { error_msg(gettext("poll failure: %s"), strerror(errno)); continue; } } if (got_sigterm) { msg_fini(); inetd_stop(); got_sigterm = B_FALSE; goto check_if_stopped; } /* * Process any stop/refresh requests from the Unix Domain * Socket. */ if ((pret != -1) && isset_pollfd(uds_fd)) { while (process_uds_event() == 0) ; } /* * Process refresh request. We do this check after the UDS * event check above, as it would be wasted processing if we * started refreshing inetd based on a SIGHUP, and then were * told to shut-down via a UDS event. */ if (refresh_inetd_requested) { refresh_inetd_requested = B_FALSE; if (!inetd_stopping) inetd_refresh(); } /* * We were interrupted by a signal. Don't waste any more * time processing a potentially inaccurate poll return. */ if (pret == -1) continue; /* * Process any instance restarter events. */ if (isset_pollfd(rst_event_pipe[PE_CONSUMER])) { while (process_restarter_event() == 0) ; } /* * Process any expired timers (bind retry, con-rate offline, * method timeouts). */ (void) iu_expire_timers(timer_queue); process_terminated_methods(); /* * If inetd is stopping, check whether all our managed * instances have been stopped and we can return. */ if (inetd_stopping) { check_if_stopped: for (instance = uu_list_first(instance_list); instance != NULL; instance = uu_list_next(instance_list, instance)) { if (!instance_stopped(instance)) { debug_msg("%s not yet stopped", instance->fmri); break; } } /* if all instances are stopped, return */ if (instance == NULL) return; } process_network_events(); } } static void fini(void) { method_fini(); uds_fini(); if (timer_queue != NULL) iu_tq_destroy(timer_queue); /* * We don't bother to undo the restarter interface at all. * Because of quirks in the interface, there is no way to * disconnect from the channel and cause any new events to be * queued. However, any events which are received and not * acknowledged will be re-sent when inetd restarts as long as inetd * uses the same subscriber ID, which it does. * * By keeping the event pipe open but ignoring it, any events which * occur will cause restarter_event_proxy to hang without breaking * anything. */ if (instance_list != NULL) { void *cookie = NULL; instance_t *inst; while ((inst = uu_list_teardown(instance_list, &cookie)) != NULL) destroy_instance(inst); uu_list_destroy(instance_list); } if (instance_pool != NULL) uu_list_pool_destroy(instance_pool); tlx_fini(); config_fini(); repval_fini(); poll_fini(); /* Close audit session */ (void) adt_end_session(audit_handle); } static int init(void) { int err; if (repval_init() < 0) goto failed; if (config_init() < 0) goto failed; refresh_debug_flag(); if (tlx_init() < 0) goto failed; /* Setup instance list. */ if ((instance_pool = uu_list_pool_create("instance_pool", sizeof (instance_t), offsetof(instance_t, link), NULL, UU_LIST_POOL_DEBUG)) == NULL) { error_msg("%s: %s", gettext("Failed to create instance pool"), uu_strerror(uu_error())); goto failed; } if ((instance_list = uu_list_create(instance_pool, NULL, 0)) == NULL) { error_msg("%s: %s", gettext("Failed to create instance list"), uu_strerror(uu_error())); goto failed; } /* * Create event pipe to communicate events with the main event * loop and add it to the event loop's fdset. */ if (pipe(rst_event_pipe) < 0) { error_msg("pipe: %s", strerror(errno)); goto failed; } /* * We only leave the producer end to block on reads/writes as we * can't afford to block in the main thread, yet need to in * the restarter event thread, so it can sit and wait for an * acknowledgement to be written to the pipe. */ disable_blocking(rst_event_pipe[PE_CONSUMER]); if ((set_pollfd(rst_event_pipe[PE_CONSUMER], POLLIN)) == -1) goto failed; /* * Register with master restarter for managed service events. This * will fail, amongst other reasons, if inetd is already running. */ if ((err = restarter_bind_handle(RESTARTER_EVENT_VERSION, INETD_INSTANCE_FMRI, restarter_event_proxy, 0, &rst_event_handle)) != 0) { error_msg(gettext( "Failed to register for restarter events: %s"), strerror(err)); goto failed; } if (contract_init() < 0) goto failed; if ((timer_queue = iu_tq_create()) == NULL) { error_msg(gettext("Failed to create timer queue.")); goto failed; } if (uds_init() < 0) goto failed; if (method_init() < 0) goto failed; /* Initialize auditing session */ if (adt_start_session(&audit_handle, NULL, ADT_USE_PROC_DATA) != 0) { error_msg(gettext("Unable to start audit session")); } /* * Initialize signal dispositions/masks */ (void) sigset(SIGHUP, sighup_handler); (void) sigset(SIGTERM, sigterm_handler); (void) sigignore(SIGINT); return (0); failed: fini(); return (-1); } static int start_method(void) { int i; int pipe_fds[2]; int child; /* Create pipe for child to notify parent of initialization success. */ if (pipe(pipe_fds) < 0) { error_msg("pipe: %s", strerror(errno)); return (SMF_EXIT_ERR_OTHER); } if ((child = fork()) == -1) { error_msg("fork: %s", strerror(errno)); (void) close(pipe_fds[PE_CONSUMER]); (void) close(pipe_fds[PE_PRODUCER]); return (SMF_EXIT_ERR_OTHER); } else if (child > 0) { /* parent */ /* Wait on child to return success of initialization. */ (void) close(pipe_fds[PE_PRODUCER]); if ((safe_read(pipe_fds[PE_CONSUMER], &i, sizeof (i)) != 0) || (i < 0)) { error_msg(gettext( "Initialization failed, unable to start")); (void) close(pipe_fds[PE_CONSUMER]); /* * Batch all initialization errors as 'other' errors, * resulting in retries being attempted. */ return (SMF_EXIT_ERR_OTHER); } else { (void) close(pipe_fds[PE_CONSUMER]); return (SMF_EXIT_OK); } } else { /* child */ /* * Perform initialization and return success code down * the pipe. */ (void) close(pipe_fds[PE_CONSUMER]); i = init(); if ((safe_write(pipe_fds[PE_PRODUCER], &i, sizeof (i)) < 0) || (i < 0)) { error_msg(gettext("pipe write failure: %s"), strerror(errno)); exit(1); } (void) close(pipe_fds[PE_PRODUCER]); (void) setsid(); /* * Log a message if the configuration file has changed since * inetconv was last run. */ check_conf_file(); event_loop(); fini(); debug_msg("inetd stopped"); msg_fini(); exit(0); } /* NOTREACHED */ } /* * When inetd is run from outside the SMF, this message is output to provide * the person invoking inetd with further information that will help them * understand how to start and stop inetd, and to achieve the other * behaviors achievable with the legacy inetd command line interface, if * it is possible. */ static void legacy_usage(void) { (void) fprintf(stderr, "inetd is now an smf(7) managed service and can no longer be run " "from the\n" "command line. To enable or disable inetd refer to svcadm(8) on\n" "how to enable \"%s\", the inetd instance.\n" "\n" "The traditional inetd command line option mappings are:\n" "\t-d : there is no supported debug output\n" "\t-s : inetd is only runnable from within the SMF\n" "\t-t : See inetadm(8) on how to enable TCP tracing\n" "\t-r : See inetadm(8) on how to set a failure rate\n" "\n" "To specify an alternative configuration file see svccfg(8)\n" "for how to modify the \"%s/%s\" string type property of\n" "the inetd instance, and modify it according to the syntax:\n" "\"%s [alt_config_file] %%m\".\n" "\n" "For further information on inetd see inetd(8).\n", INETD_INSTANCE_FMRI, START_METHOD_ARG, SCF_PROPERTY_EXEC, INETD_PATH); } /* * Usage message printed out for usage errors when running under the SMF. */ static void smf_usage(const char *arg0) { error_msg("Usage: %s [alt_conf_file] %s|%s|%s", arg0, START_METHOD_ARG, STOP_METHOD_ARG, REFRESH_METHOD_ARG); } /* * Returns B_TRUE if we're being run from within the SMF, else B_FALSE. */ static boolean_t run_through_smf(void) { char *fmri; /* * check if the instance fmri environment variable has been set by * our restarter. */ return (((fmri = getenv("SMF_FMRI")) != NULL) && (strcmp(fmri, INETD_INSTANCE_FMRI) == 0)); } int main(int argc, char *argv[]) { char *method; int ret; #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); (void) setlocale(LC_ALL, ""); if (!run_through_smf()) { legacy_usage(); return (SMF_EXIT_ERR_NOSMF); } msg_init(); /* setup logging */ (void) enable_extended_FILE_stdio(-1, -1); /* inetd invocation syntax is inetd [alt_conf_file] method_name */ switch (argc) { case 2: method = argv[1]; break; case 3: conf_file = argv[1]; method = argv[2]; break; default: smf_usage(argv[0]); return (SMF_EXIT_ERR_CONFIG); } if (strcmp(method, START_METHOD_ARG) == 0) { ret = start_method(); } else if (strcmp(method, STOP_METHOD_ARG) == 0) { ret = stop_method(); } else if (strcmp(method, REFRESH_METHOD_ARG) == 0) { ret = refresh_method(); } else { smf_usage(argv[0]); return (SMF_EXIT_ERR_CONFIG); } return (ret); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _INETD_IMPL_H #define _INETD_IMPL_H /* * Header file containing inetd's shared types/data structures and * function declarations. */ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include #include /* * Number of consecutive retries of a repository operation that failed due * to a broken connection performed before giving up and failing. */ #define REP_OP_RETRIES 10 /* retryable SMF method error */ #define SMF_EXIT_ERR_OTHER 1 /* inetd's syslog ident string */ #define SYSLOG_IDENT "inetd" /* Is this instance currently executing a method ? */ #define INST_IN_TRANSITION(i) ((i)->next_istate != IIS_NONE) /* Names of properties that inetd uses to store instance state. */ #define PR_NAME_NON_START_PID "non_start_pid" #define PR_NAME_START_PIDS "start_pids" #define PR_NAME_CUR_INT_STATE "cur_state" #define PR_NAME_NEXT_INT_STATE "next_state" /* Name of the property group that holds debug flag */ #define PG_NAME_APPLICATION_CONFIG "config" /* Name of the property which holds the debug flag value */ #define PR_NAME_DEBUG_FLAG "debug" /* * Instance states used internal to svc.inetd. * NOTE: The states table in cmd/cmd-inetd/inetd/inetd.c relies on the * ordering of this enumeration, so take care if modifying it. */ typedef enum { IIS_UNINITIALIZED, IIS_ONLINE, IIS_IN_ONLINE_METHOD, IIS_OFFLINE, IIS_IN_OFFLINE_METHOD, IIS_DISABLED, IIS_IN_DISABLE_METHOD, IIS_IN_REFRESH_METHOD, IIS_MAINTENANCE, IIS_OFFLINE_CONRATE, IIS_OFFLINE_BIND, IIS_OFFLINE_COPIES, IIS_DEGRADED, IIS_NONE } internal_inst_state_t; /* * inetd's instance methods. * NOTE: The methods table in cmd/cmd-inetd/inetd/util.c relies on the * ordering of this enumeration, so take care if modifying it. */ typedef enum { IM_START, IM_ONLINE, IM_OFFLINE, IM_DISABLE, IM_REFRESH, NUM_METHODS, IM_NONE } instance_method_t; /* Collection of information pertaining to a method */ typedef struct { char *exec_path; /* path passed to exec() */ /* * Structure returned from wordexp(3c) that contains an expansion of the * exec property into a form suitable for exec(2). */ wordexp_t exec_args_we; /* * Copy of the first argument of the above wordexp_t structure in the * event that an alternate arg0 is provided, and we replace the first * argument with the alternate arg0. This is necessary so the * contents of the wordexp_t structure can be returned to their * original form as returned from wordexp(3c), which is a requirement * for calling wordfree(3c), wordexp()'s associated cleanup routine. */ const char *wordexp_arg0_backup; /* time a method can run for before being considered broken */ int timeout; } method_info_t; typedef struct { basic_cfg_t *basic; method_info_t *methods[NUM_METHODS]; } instance_cfg_t; /* * Structure used to construct a list of int64_t's and their associated * scf values. Used to store lists of process ids, internal states, and to * store the associated scf value used when writing the values back to the * repository. */ typedef struct { int64_t val; scf_value_t *scf_val; uu_list_node_t link; } rep_val_t; /* Structure containing the state and configuration of a service instance. */ typedef struct { char *fmri; /* fd we're going to take a connection on */ int conn_fd; /* number of copies of this instance active */ int64_t copies; /* connection rate counters */ int64_t conn_rate_count; time_t conn_rate_start; /* failure rate counters */ int64_t fail_rate_count; time_t fail_rate_start; /* bind failure count */ int64_t bind_fail_count; /* pids of currently running methods */ uu_list_t *non_start_pid; uu_list_t *start_pids; /* ctids of currently running start methods */ uu_list_t *start_ctids; /* remote address, used for TCP tracing */ struct sockaddr_storage remote_addr; internal_inst_state_t cur_istate; internal_inst_state_t next_istate; /* repository compatible versions of the above 2 states */ uu_list_t *cur_istate_rep; uu_list_t *next_istate_rep; /* * Current instance configuration resulting from its repository * configuration. */ instance_cfg_t *config; /* * Soon to be applied instance configuration. This configuration was * read during a refresh when this instance was online, and the * instance needed taking offline for this configuration to be applied. * The instance is currently on its way offline, and this configuration * will become the current configuration when it arrives there. */ instance_cfg_t *new_config; /* current pending conrate-offline/method timer; -1 if none pending */ iu_timer_id_t timer_id; /* current pending bind retry timer; -1 if none pending */ iu_timer_id_t bind_timer_id; /* * Flags that assist in the fanout of an instance arriving in the * offline state on-route to some other state. */ boolean_t disable_req; boolean_t maintenance_req; boolean_t conn_rate_exceeded; boolean_t bind_retries_exceeded; /* * Event waiting to be processed. RESTARTER_EVENT_TYPE_INVALID is used * to mean no event waiting. */ restarter_event_type_t pending_rst_event; /* link to next instance in list */ uu_list_node_t link; } instance_t; /* Structure used to store information pertaining to instance method types. */ typedef struct { instance_method_t method; const char *name; internal_inst_state_t dst_state; } method_type_info_t; extern uu_list_t *instance_list; extern struct pollfd *poll_fds; extern nfds_t num_pollfds; extern method_type_info_t methods[]; extern iu_tq_t *timer_queue; extern uu_list_pool_t *conn_ind_pool; extern boolean_t debug_enabled; /* * util.c */ extern void msg_init(void); extern void msg_fini(void); /* PRINTFLIKE1 */ extern void debug_msg(const char *, ...); /* PRINTFLIKE1 */ extern void error_msg(const char *, ...); /* PRINTFLIKE1 */ extern void warn_msg(const char *, ...); extern void poll_fini(void); extern boolean_t isset_pollfd(int); extern void clear_pollfd(int); extern int set_pollfd(int, uint16_t); extern struct pollfd *find_pollfd(int); extern int safe_read(int, void *, size_t); extern boolean_t copies_limit_exceeded(instance_t *); extern void cancel_inst_timer(instance_t *); extern void cancel_bind_timer(instance_t *); extern void enable_blocking(int); extern void disable_blocking(int); /* * tlx.c */ extern rpc_info_t *create_rpc_info(const char *, const char *, const char *, int, int); extern void destroy_rpc_info(rpc_info_t *); extern boolean_t rpc_info_equal(const rpc_info_t *, const rpc_info_t *); extern int register_rpc_service(const char *, const rpc_info_t *); extern void unregister_rpc_service(const char *, const rpc_info_t *); extern int create_bound_endpoint(const instance_t *, tlx_info_t *); extern void close_net_fd(instance_t *, int); extern int tlx_accept(const char *, tlx_info_t *, struct sockaddr_storage *); extern struct t_call *dequeue_conind(uu_list_t *); extern int queue_conind(uu_list_t *, struct t_call *); extern void tlx_fini(void); extern int tlx_init(void); extern boolean_t tlx_info_equal(const tlx_info_t *, const tlx_info_t *, boolean_t); extern void consume_wait_data(instance_t *, int); /* * config.c */ extern int config_init(void); extern void config_fini(void); extern boolean_t socket_info_equal(const socket_info_t *, const socket_info_t *, boolean_t); extern boolean_t method_info_equal(const method_info_t *, const method_info_t *); extern struct method_context *read_method_context(const char *, const char *, const char *); extern void destroy_instance_cfg(instance_cfg_t *); extern instance_cfg_t *read_instance_cfg(const char *); extern boolean_t bind_config_equal(const basic_cfg_t *, const basic_cfg_t *); extern int read_enable_merged(const char *, boolean_t *); extern void refresh_debug_flag(void); /* * repval.c */ extern void repval_fini(void); extern int repval_init(void); extern uu_list_t *create_rep_val_list(void); extern void destroy_rep_val_list(uu_list_t *); extern scf_error_t store_rep_vals(uu_list_t *, const char *, const char *); extern scf_error_t retrieve_rep_vals(uu_list_t *, const char *, const char *); extern rep_val_t *find_rep_val(uu_list_t *, int64_t); extern int set_single_rep_val(uu_list_t *, int64_t); extern int64_t get_single_rep_val(uu_list_t *); extern int add_rep_val(uu_list_t *, int64_t); extern void remove_rep_val(uu_list_t *, int64_t); extern void empty_rep_val_list(uu_list_t *); extern int make_handle_bound(scf_handle_t *); extern int add_remove_contract(instance_t *, boolean_t, ctid_t); extern int iterate_repository_contracts(instance_t *, int); /* * contracts.c */ extern int contract_init(void); extern void contract_fini(void); void contract_postfork(void); int contract_prefork(const char *, int); extern int get_latest_contract(ctid_t *cid); extern int adopt_contract(ctid_t, const char *); extern int abandon_contract(ctid_t); /* * inetd.c */ extern void process_offline_inst(instance_t *); extern void process_non_start_term(instance_t *, int); extern void process_start_term(instance_t *, char *); extern void remove_method_ids(instance_t *, pid_t, ctid_t, instance_method_t); /* * env.c */ char **set_smf_env(struct method_context *, instance_t *, const char *); /* * wait.c */ extern int register_method(instance_t *, pid_t, ctid_t cid, instance_method_t, char *); extern int method_init(void); extern void method_fini(void); extern void process_terminated_methods(void); extern void unregister_instance_methods(const instance_t *); extern void method_preexec(void); #ifdef __cplusplus } #endif #endif /* _INETD_IMPL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This file contains routines to manipulate lists of repository values that * are used to store process ids and the internal state. There are routines * to read/write the lists from/to the repository and routines to modify or * inspect the lists. It also contains routines that deal with the * repository side of contract ids. */ #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* * Number of consecutive repository bind retries performed by bind_to_rep() * before failing. */ #define BIND_TO_REP_RETRIES 10 /* Name of property group where inetd's state for a service is stored. */ #define PG_NAME_INSTANCE_STATE (const char *) "inetd_state" /* uu_list repval list pool */ static uu_list_pool_t *rep_val_pool = NULL; /* * Repository object pointers that get set-up in repval_init() and closed down * in repval_fini(). They're used in _retrieve_rep_vals(), _store_rep_vals(), * add_remove_contract_norebind(), and adopt_repository_contracts(). They're * global so they can be initialized once on inetd startup, and re-used * there-after in the referenced functions. */ static scf_handle_t *rep_handle = NULL; static scf_propertygroup_t *pg = NULL; static scf_instance_t *inst = NULL; static scf_transaction_t *trans = NULL; static scf_transaction_entry_t *entry = NULL; static scf_property_t *prop = NULL; /* * Pathname storage for paths generated from the fmri. * Used when updating the ctid and (start) pid files for an inetd service. */ static char genfmri_filename[MAXPATHLEN] = ""; static char genfmri_temp_filename[MAXPATHLEN] = ""; /* * Try and make the given handle bind be bound to the repository. If * it's already bound, or we succeed a new bind return 0; else return * -1 on failure, with the SCF error set to one of the following: * SCF_ERROR_NO_SERVER * SCF_ERROR_NO_RESOURCES */ int make_handle_bound(scf_handle_t *hdl) { uint_t retries; for (retries = 0; retries <= BIND_TO_REP_RETRIES; retries++) { if ((scf_handle_bind(hdl) == 0) || (scf_error() == SCF_ERROR_IN_USE)) return (0); assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); } return (-1); } int repval_init(void) { /* * Create the repval list pool. */ rep_val_pool = uu_list_pool_create("rep_val_pool", sizeof (rep_val_t), offsetof(rep_val_t, link), NULL, UU_LIST_POOL_DEBUG); if (rep_val_pool == NULL) { error_msg("%s: %s", gettext("Failed to create rep_val pool"), uu_strerror(uu_error())); return (-1); } /* * Create and bind a repository handle, and create all repository * objects that we'll use later that are associated with it. On any * errors we simply return -1 and let repval_fini() clean-up after * us. */ if ((rep_handle = scf_handle_create(SCF_VERSION)) == NULL) { error_msg("%s: %s", gettext("Failed to create repository handle"), scf_strerror(scf_error())); goto cleanup; } else if (make_handle_bound(rep_handle) == -1) { goto cleanup; } else if (((pg = scf_pg_create(rep_handle)) == NULL) || ((inst = scf_instance_create(rep_handle)) == NULL) || ((trans = scf_transaction_create(rep_handle)) == NULL) || ((entry = scf_entry_create(rep_handle)) == NULL) || ((prop = scf_property_create(rep_handle)) == NULL)) { error_msg("%s: %s", gettext("Failed to create repository object"), scf_strerror(scf_error())); goto cleanup; } return (0); cleanup: repval_fini(); return (-1); } void repval_fini(void) { if (rep_handle != NULL) { /* * We unbind from the repository before we free the repository * objects for efficiency reasons. */ (void) scf_handle_unbind(rep_handle); scf_pg_destroy(pg); pg = NULL; scf_instance_destroy(inst); inst = NULL; scf_transaction_destroy(trans); trans = NULL; scf_entry_destroy(entry); entry = NULL; scf_property_destroy(prop); prop = NULL; scf_handle_destroy(rep_handle); rep_handle = NULL; } if (rep_val_pool != NULL) { uu_list_pool_destroy(rep_val_pool); rep_val_pool = NULL; } } uu_list_t * create_rep_val_list(void) { uu_list_t *ret; if ((ret = uu_list_create(rep_val_pool, NULL, 0)) == NULL) assert(uu_error() == UU_ERROR_NO_MEMORY); return (ret); } void destroy_rep_val_list(uu_list_t *list) { if (list != NULL) { empty_rep_val_list(list); uu_list_destroy(list); } } rep_val_t * find_rep_val(uu_list_t *list, int64_t val) { rep_val_t *rv; for (rv = uu_list_first(list); rv != NULL; rv = uu_list_next(list, rv)) { if (rv->val == val) break; } return (rv); } int add_rep_val(uu_list_t *list, int64_t val) { rep_val_t *rv; if ((rv = malloc(sizeof (rep_val_t))) == NULL) return (-1); uu_list_node_init(rv, &rv->link, rep_val_pool); rv->val = val; rv->scf_val = NULL; (void) uu_list_insert_after(list, NULL, rv); return (0); } void remove_rep_val(uu_list_t *list, int64_t val) { rep_val_t *rv; if ((rv = find_rep_val(list, val)) != NULL) { uu_list_remove(list, rv); assert(rv->scf_val == NULL); free(rv); } } void empty_rep_val_list(uu_list_t *list) { void *cookie = NULL; rep_val_t *rv; while ((rv = uu_list_teardown(list, &cookie)) != NULL) { if (rv->scf_val != NULL) scf_value_destroy(rv->scf_val); free(rv); } } int64_t get_single_rep_val(uu_list_t *list) { rep_val_t *rv = uu_list_first(list); assert(rv != NULL); return (rv->val); } int set_single_rep_val(uu_list_t *list, int64_t val) { rep_val_t *rv = uu_list_first(list); if (rv == NULL) { if (add_rep_val(list, val) == -1) return (-1); } else { rv->val = val; } return (0); } /* * Partner to add_tr_entry_values. This function frees the scf_values created * in add_tr_entry_values() in the list 'vals'. */ static void remove_tr_entry_values(uu_list_t *vals) { rep_val_t *rval; for (rval = uu_list_first(vals); rval != NULL; rval = uu_list_next(vals, rval)) { if (rval->scf_val != NULL) { scf_value_destroy(rval->scf_val); rval->scf_val = NULL; } } } /* * This function creates and associates with transaction entry 'entry' an * scf value for each value in 'vals'. The pointers to the scf values * are stored in the list for later cleanup by remove_tr_entry_values. * Returns 0 on success, else -1 on error with scf_error() set to: * SCF_ERROR_NO_MEMORY if memory allocation failed. * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken. */ static int add_tr_entry_values(scf_handle_t *hdl, scf_transaction_entry_t *entry, uu_list_t *vals) { rep_val_t *rval; for (rval = uu_list_first(vals); rval != NULL; rval = uu_list_next(vals, rval)) { assert(rval->scf_val == NULL); if ((rval->scf_val = scf_value_create(hdl)) == NULL) { remove_tr_entry_values(vals); return (-1); } scf_value_set_integer(rval->scf_val, rval->val); if (scf_entry_add_value(entry, rval->scf_val) < 0) { remove_tr_entry_values(vals); return (-1); } } return (0); } /* * Stores the values contained in the list 'vals' into the property 'prop_name' * of the instance with fmri 'inst_fmri', within the instance's instance * state property group. * * Returns 0 on success, else one of the following on failure: * SCF_ERROR_NO_MEMORY if memory allocation failed. * SCF_ERROR_NO_RESOURCES if the server doesn't have required resources. * SCF_ERROR_VERSION_MISMATCH if program compiled against a newer libscf * than on system. * SCF_ERROR_PERMISSION_DENIED if insufficient privileges to modify pg. * SCF_ERROR_BACKEND_ACCESS if the repository back-end refused the pg modify. * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken. */ static scf_error_t _store_rep_vals(uu_list_t *vals, const char *inst_fmri, const char *prop_name) { int cret; int ret; if (scf_handle_decode_fmri(rep_handle, inst_fmri, NULL, NULL, inst, NULL, NULL, SCF_DECODE_FMRI_EXACT) == -1) return (scf_error()); /* * Fetch the instance state pg, and if it doesn't exist try and * create it. */ if (scf_instance_get_pg(inst, PG_NAME_INSTANCE_STATE, pg) < 0) { if (scf_error() != SCF_ERROR_NOT_FOUND) return (scf_error()); if (scf_instance_add_pg(inst, PG_NAME_INSTANCE_STATE, SCF_GROUP_FRAMEWORK, SCF_PG_FLAG_NONPERSISTENT, pg) < 0) return (scf_error()); } /* * Perform a transaction to write the values to the requested property. * If someone got there before us, loop and retry. */ do { if (scf_transaction_start(trans, pg) < 0) return (scf_error()); if ((scf_transaction_property_new(trans, entry, prop_name, SCF_TYPE_INTEGER) < 0) && (scf_transaction_property_change_type(trans, entry, prop_name, SCF_TYPE_INTEGER) < 0)) { ret = scf_error(); goto cleanup; } if (add_tr_entry_values(rep_handle, entry, vals) < 0) { ret = scf_error(); goto cleanup; } if ((cret = scf_transaction_commit(trans)) < 0) { ret = scf_error(); goto cleanup; } else if (cret == 0) { scf_transaction_reset(trans); scf_entry_reset(entry); remove_tr_entry_values(vals); if (scf_pg_update(pg) < 0) { ret = scf_error(); goto cleanup; } } } while (cret == 0); ret = 0; cleanup: scf_transaction_reset(trans); scf_entry_reset(entry); remove_tr_entry_values(vals); return (ret); } /* * Retrieves the repository values of property 'prop_name', of the instance * with fmri 'fmri', from within the instance's instance state property * group and adds them to the value list 'list'. * * Returns 0 on success, else one of the following values on error: * SCF_ERROR_NOT_FOUND if the property doesn't exist. * SCF_ERROR_NO_MEMORY if memory allocation failed. * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken. * SCF_ERROR_TYPE_MISMATCH if the property was of an unexpected type. * */ static scf_error_t _retrieve_rep_vals(uu_list_t *list, const char *fmri, const char *prop_name) { scf_simple_prop_t *sp; int64_t *ip; if ((sp = scf_simple_prop_get(rep_handle, fmri, PG_NAME_INSTANCE_STATE, prop_name)) == NULL) return (scf_error()); while ((ip = scf_simple_prop_next_integer(sp)) != NULL) { if (add_rep_val(list, *ip) == -1) { empty_rep_val_list(list); scf_simple_prop_free(sp); return (SCF_ERROR_NO_MEMORY); } } if (scf_error() != SCF_ERROR_NONE) { assert(scf_error() == SCF_ERROR_TYPE_MISMATCH); empty_rep_val_list(list); scf_simple_prop_free(sp); return (scf_error()); } scf_simple_prop_free(sp); return (0); } /* * Writes the repository values in the vals list to * a file that is generated based on the passed in fmri and name. * Returns 0 on success, * ENAMETOOLONG if unable to generate filename from fmri (including * the inability to create the directory for the generated filename) and * ENOENT on all other failures. */ static int repvals_to_file(const char *fmri, const char *name, uu_list_t *vals) { int tfd; FILE *tfp; /* temp fp */ rep_val_t *spval; /* Contains a start_pid or ctid */ int ret = 0; if (gen_filenms_from_fmri(fmri, name, genfmri_filename, genfmri_temp_filename) != 0) { /* Failure either from fmri too long or mkdir failure */ return (ENAMETOOLONG); } if ((tfd = mkstemp(genfmri_temp_filename)) == -1) { return (ENOENT); } if (fchmod(tfd, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { (void) close(tfd); ret = ENOENT; goto unlink_out; } if ((tfp = fdopen(tfd, "w")) == NULL) { (void) close(tfd); ret = ENOENT; goto unlink_out; } for (spval = uu_list_first(vals); spval != NULL; spval = uu_list_next(vals, spval)) { if (fprintf(tfp, "%lld\n", spval->val) <= 0) { (void) fclose(tfp); ret = ENOENT; goto unlink_out; } } if (fclose(tfp) != 0) { ret = ENOENT; goto unlink_out; } if (rename(genfmri_temp_filename, genfmri_filename) != 0) { ret = ENOENT; goto unlink_out; } return (0); unlink_out: if (unlink(genfmri_temp_filename) != 0) { warn_msg(gettext("Removal of temp file " "%s failed. Please remove manually."), genfmri_temp_filename); } return (ret); } /* * A routine that loops trying to read/write values until either success, * an error other than a broken repository connection or * the number of retries reaches REP_OP_RETRIES. * This action is used to read/write the values: * reads/writes to a file for the START_PIDS property due to scalability * problems with libscf * reads/writes to the repository for all other properties. * Returns 0 on success, else the error value from either _store_rep_vals or * _retrieve_rep_vals (based on whether 'store' was set or not), or one of the * following: * SCF_ERROR_NO_RESOURCES if the server doesn't have adequate resources * SCF_ERROR_NO_MEMORY if a memory allocation failure * SCF_ERROR_NO_SERVER if the server isn't running. * SCF_ERROR_CONSTRAINT_VIOLATED if an error in dealing with the speedy files */ static scf_error_t store_retrieve_rep_vals(uu_list_t *vals, const char *fmri, const char *prop, boolean_t store) { scf_error_t ret = 0; uint_t retries; FILE *tfp; /* temp fp */ int64_t tval; /* temp val holder */ int fscanf_ret; int fopen_retry_cnt = 2; /* inetd specific action for START_PIDS property */ if (strcmp(prop, PR_NAME_START_PIDS) == 0) { /* * Storage performance of START_PIDS is important, * so each instance has its own file and all start_pids * in the list are written to a temp file and then * moved (renamed). */ if (store) { /* Write all values in list to file */ if (repvals_to_file(fmri, "pid", vals)) { return (SCF_ERROR_CONSTRAINT_VIOLATED); } } else { /* no temp name needed */ if (gen_filenms_from_fmri(fmri, "pid", genfmri_filename, NULL) != 0) return (SCF_ERROR_CONSTRAINT_VIOLATED); retry_fopen: /* It's ok if no file, there are just no pids */ if ((tfp = fopen(genfmri_filename, "r")) == NULL) { if ((errno == EINTR) && (fopen_retry_cnt > 0)) { fopen_retry_cnt--; goto retry_fopen; } return (0); } /* fscanf may not set errno, so clear it first */ errno = 0; while ((fscanf_ret = fscanf(tfp, "%lld", &tval)) == 1) { /* If tval isn't a valid pid, then fail. */ if ((tval > MAXPID) || (tval <= 0)) { empty_rep_val_list(vals); return (SCF_ERROR_CONSTRAINT_VIOLATED); } if (add_rep_val(vals, tval) == -1) { empty_rep_val_list(vals); return (SCF_ERROR_NO_MEMORY); } errno = 0; } /* EOF is ok when no errno */ if ((fscanf_ret != EOF) || (errno != 0)) { empty_rep_val_list(vals); return (SCF_ERROR_CONSTRAINT_VIOLATED); } if (fclose(tfp) != 0) { /* for close failure just log a message */ warn_msg(gettext("Close of file %s failed."), genfmri_filename); } } } else { for (retries = 0; retries <= REP_OP_RETRIES; retries++) { if (make_handle_bound(rep_handle) == -1) { ret = scf_error(); break; } if ((ret = (store ? _store_rep_vals(vals, fmri, prop) : _retrieve_rep_vals(vals, fmri, prop))) != SCF_ERROR_CONNECTION_BROKEN) break; (void) scf_handle_unbind(rep_handle); } } return (ret); } scf_error_t store_rep_vals(uu_list_t *vals, const char *fmri, const char *prop) { return (store_retrieve_rep_vals(vals, fmri, prop, B_TRUE)); } scf_error_t retrieve_rep_vals(uu_list_t *vals, const char *fmri, const char *prop) { return (store_retrieve_rep_vals(vals, fmri, prop, B_FALSE)); } /* * Adds/removes a contract id to/from the cached list kept in the instance. * Then the cached list is written to a file named "ctid" in a directory * based on the fmri. Cached list is written to a file due to scalability * problems in libscf. The file "ctid" is used when inetd is restarted * so that inetd can adopt the contracts that it had previously. * Returns: * 0 on success * ENAMETOOLONG if unable to generate filename from fmri (including * the inability to create the directory for the generated filename) * ENOENT - failure accessing file * ENOMEM - memory allocation failure */ int add_remove_contract(instance_t *inst, boolean_t add, ctid_t ctid) { FILE *tfp; /* temp fp */ int ret = 0; int repval_ret = 0; int fopen_retry_cnt = 2; /* * Storage performance of contract ids is important, * so each instance has its own file. An add of a * ctid will be appended to the ctid file. * The removal of a ctid will result in the remaining * ctids in the list being written to a temp file and then * moved (renamed). */ if (add) { if (gen_filenms_from_fmri(inst->fmri, "ctid", genfmri_filename, NULL) != 0) { /* Failure either from fmri too long or mkdir failure */ return (ENAMETOOLONG); } retry_fopen: if ((tfp = fopen(genfmri_filename, "a")) == NULL) { if ((errno == EINTR) && (fopen_retry_cnt > 0)) { fopen_retry_cnt--; goto retry_fopen; } ret = ENOENT; goto out; } /* Always store ctids as long long */ if (fprintf(tfp, "%llu\n", (uint64_t)ctid) <= 0) { (void) fclose(tfp); ret = ENOENT; goto out; } if (fclose(tfp) != 0) { ret = ENOENT; goto out; } if (add_rep_val(inst->start_ctids, ctid) != 0) { ret = ENOMEM; goto out; } } else { remove_rep_val(inst->start_ctids, ctid); /* Write all values in list to file */ if ((repval_ret = repvals_to_file(inst->fmri, "ctid", inst->start_ctids)) != 0) { ret = repval_ret; goto out; } } out: return (ret); } /* * If sig !=0, iterate over all contracts in the cached list of contract * ids kept in the instance. Send each contract the specified signal. * If sig == 0, read in the contract ids that were last associated * with this instance (reload the cache) and call adopt_contract() * to take ownership. * * Returns 0 on success; * ENAMETOOLONG if unable to generate filename from fmri (including * the inability to create the directory for the generated filename) and * ENXIO if a failure accessing the file * ENOMEM if there was a memory allocation failure * ENOENT if the instance, its restarter property group, or its * contract property don't exist * EIO if invalid data read from the file */ int iterate_repository_contracts(instance_t *inst, int sig) { int ret = 0; FILE *fp; rep_val_t *spval = NULL; /* Contains a start_pid */ uint64_t tval; /* temp val holder */ uu_list_t *uup = NULL; int fscanf_ret; int fopen_retry_cnt = 2; if (sig != 0) { /* * Send a signal to all in the contract; ESRCH just * means they all exited before we could kill them */ for (spval = uu_list_first(inst->start_ctids); spval != NULL; spval = uu_list_next(inst->start_ctids, spval)) { if (sigsend(P_CTID, (ctid_t)spval->val, sig) == -1 && errno != ESRCH) { warn_msg(gettext("Unable to signal all " "contract members of instance %s: %s"), inst->fmri, strerror(errno)); } } return (0); } /* * sig == 0 case. * Attempt to adopt the contract for each ctid. */ if (gen_filenms_from_fmri(inst->fmri, "ctid", genfmri_filename, NULL) != 0) { /* Failure either from fmri too long or mkdir failure */ return (ENAMETOOLONG); } retry_fopen: /* It's ok if no file, there are no ctids to adopt */ if ((fp = fopen(genfmri_filename, "r")) == NULL) { if ((errno == EINTR) && (fopen_retry_cnt > 0)) { fopen_retry_cnt--; goto retry_fopen; } return (0); } /* * Read ctids from file into 2 lists: * - temporary list to be traversed (uup) * - cached list that can be modified if adoption of * contract fails (inst->start_ctids). * Always treat ctids as long longs. */ uup = create_rep_val_list(); /* fscanf may not set errno, so clear it first */ errno = 0; while ((fscanf_ret = fscanf(fp, "%llu", &tval)) == 1) { /* If tval isn't a valid ctid, then fail. */ if (tval == 0) { (void) fclose(fp); ret = EIO; goto out; } if ((add_rep_val(uup, tval) == -1) || (add_rep_val(inst->start_ctids, tval) == -1)) { (void) fclose(fp); ret = ENOMEM; goto out; } errno = 0; } /* EOF is not a failure when no errno */ if ((fscanf_ret != EOF) || (errno != 0)) { ret = EIO; goto out; } if (fclose(fp) != 0) { ret = ENXIO; goto out; } for (spval = uu_list_first(uup); spval != NULL; spval = uu_list_next(uup, spval)) { /* Try to adopt the contract */ if (adopt_contract((ctid_t)spval->val, inst->fmri) != 0) { /* * Adoption failed. No reason to think it'll * work later, so remove the id from our list * in the instance. */ remove_rep_val(inst->start_ctids, spval->val); } } out: if (uup) { empty_rep_val_list(uup); destroy_rep_val_list(uup); } if (ret != 0) empty_rep_val_list(inst->start_ctids); return (ret); } #!/bin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # echo_file usr/src/cmd/Makefile.ctf /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Contains routines that deal with TLI/XTI endpoints and rpc services. */ #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" uu_list_pool_t *conn_ind_pool = NULL; /* * RPC functions. */ /* * Returns B_TRUE if the non-address components of the 2 rpc_info_t structures * are equivalent, else B_FALSE. */ boolean_t rpc_info_equal(const rpc_info_t *ri, const rpc_info_t *ri2) { return ((ri->prognum == ri2->prognum) && (ri->lowver == ri2->lowver) && (ri->highver == ri2->highver) && (strcmp(ri->netid, ri2->netid) == 0)); } /* * Determine if we have a configured interface for the specified address * family. This code is a mirror of libnsl's __can_use_af(). We mirror * it because we need an exact duplicate of its behavior, yet the * function isn't exported by libnsl, and this fix is considered short- * term, so it's not worth exporting it. * * We need to duplicate __can_use_af() so we can accurately determine * when getnetconfigent() returns failure for a v6 netid due to no IPv6 * interfaces being configured: getnetconfigent() returns failure * if a netid is either 'tcp6' or 'udp6' and __can_use_af() returns 0, * but it doesn't return a return code to uniquely determine this * failure. If we don't accurately determine these failures, we could * output error messages in a case when they weren't justified. */ static int can_use_af(sa_family_t af) { struct lifnum lifn; int fd; if ((fd = open("/dev/udp", O_RDONLY)) < 0) { return (0); } lifn.lifn_family = af; /* LINTED ECONST_EXPR */ lifn.lifn_flags = IFF_UP & !(IFF_NOXMIT | IFF_DEPRECATED); if (ioctl(fd, SIOCGLIFNUM, &lifn, sizeof (lifn)) < 0) { lifn.lifn_count = 0; } (void) close(fd); return (lifn.lifn_count); } static boolean_t is_v6_netid(const char *netid) { return ((strcmp(netid, SOCKET_PROTO_TCP6) == 0) || (strcmp(netid, SOCKET_PROTO_UDP6) == 0)); } /* * Registers with rpcbind the program number with all versions, from low to * high, with the netid, all specified in 'rpc'. If registration fails, * returns -1, else 0. */ int register_rpc_service(const char *fmri, const rpc_info_t *rpc) { struct netconfig *nconf; int ver; if ((nconf = getnetconfigent(rpc->netid)) == NULL) { /* * Check whether getnetconfigent() failed as a result of * having no IPv6 interfaces configured for a v6 netid, or * as a result of a 'real' error, and output an appropriate * message with an appropriate severity. */ if (is_v6_netid(rpc->netid) && !can_use_af(AF_INET6)) { warn_msg(gettext( "Couldn't register netid %s for RPC instance %s " "because no IPv6 interfaces are plumbed"), rpc->netid, fmri); } else { error_msg(gettext( "Failed to lookup netid '%s' for instance %s: %s"), rpc->netid, fmri, nc_sperror()); } return (-1); } for (ver = rpc->lowver; ver <= rpc->highver; ver++) { if (!rpcb_set(rpc->prognum, ver, nconf, &(rpc->netbuf))) { error_msg(gettext("Failed to register version %d " "of RPC service instance %s, netid %s"), ver, fmri, rpc->netid); for (ver--; ver >= rpc->lowver; ver--) (void) rpcb_unset(rpc->prognum, ver, nconf); freenetconfigent(nconf); return (-1); } } freenetconfigent(nconf); return (0); } /* Unregister all the registrations done by register_rpc_service */ void unregister_rpc_service(const char *fmri, const rpc_info_t *rpc) { int ver; struct netconfig *nconf; if ((nconf = getnetconfigent(rpc->netid)) == NULL) { /* * Don't output an error message if getnetconfigent() fails for * a v6 netid when an IPv6 interface isn't configured. */ if (!(is_v6_netid(rpc->netid) && !can_use_af(AF_INET6))) { error_msg(gettext( "Failed to lookup netid '%s' for instance %s: %s"), rpc->netid, fmri, nc_sperror()); } return; } for (ver = rpc->lowver; ver <= rpc->highver; ver++) (void) rpcb_unset(rpc->prognum, ver, nconf); freenetconfigent(nconf); } /* * TLI/XTI functions. */ int tlx_init(void) { if ((conn_ind_pool = uu_list_pool_create("conn_ind_pool", sizeof (tlx_conn_ind_t), offsetof(tlx_conn_ind_t, link), NULL, UU_LIST_POOL_DEBUG)) == NULL) { error_msg("%s: %s", gettext("Failed to create uu pool"), uu_strerror(uu_error())); return (-1); } return (0); } void tlx_fini(void) { if (conn_ind_pool != NULL) { uu_list_pool_destroy(conn_ind_pool); conn_ind_pool = NULL; } } /* * Checks if the contents of the 2 tlx_info_t structures are equivalent. * If 'isrpc' is false, the address components of the two structures are * compared for equality as part of this. If the two structures are * equivalent B_TRUE is returned, else B_FALSE. */ boolean_t tlx_info_equal(const tlx_info_t *ti, const tlx_info_t *ti2, boolean_t isrpc) { return ((isrpc || (memcmp(ti->local_addr.buf, ti2->local_addr.buf, sizeof (struct sockaddr_storage)) == 0)) && (strcmp(ti->dev_name, ti2->dev_name) == 0)); } /* * Attempts to bind an address to the network fd 'fd'. If 'reqaddr' is non-NULL, * it attempts to bind to that requested address, else it binds to a kernel * selected address. In the former case, the function returning success * doesn't guarantee that the requested address was bound (the caller needs to * check). If 'retaddr' is non-NULL, the bound address is returned in it. The * 'qlen' parameter is used to set the connection backlog. If the bind * succeeds 0 is returned, else -1. */ static int tlx_bind(int fd, const struct netbuf *reqaddr, struct netbuf *retaddr, int qlen) { struct t_bind breq; struct t_bind bret; if (retaddr != NULL) { /* caller requests bound address be returned */ bret.addr.buf = retaddr->buf; bret.addr.maxlen = retaddr->maxlen; } if (reqaddr != NULL) { /* caller requests specific address */ breq.addr.buf = reqaddr->buf; breq.addr.len = reqaddr->len; } else { breq.addr.len = 0; } breq.qlen = qlen; if (t_bind(fd, &breq, retaddr != NULL ? &bret : NULL) < 0) return (-1); if (retaddr != NULL) retaddr->len = bret.addr.len; return (0); } static int tlx_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) { struct t_optmgmt request, reply; struct { struct opthdr sockopt; char data[256]; } optbuf; if (optlen > sizeof (optbuf.data)) { error_msg(gettext("t_optmgmt request too long")); return (-1); } optbuf.sockopt.level = level; optbuf.sockopt.name = optname; optbuf.sockopt.len = optlen; (void) memcpy(optbuf.data, optval, optlen); request.opt.len = sizeof (struct opthdr) + optlen; request.opt.buf = (char *)&optbuf; request.flags = T_NEGOTIATE; reply.opt.maxlen = sizeof (struct opthdr) + optlen; reply.opt.buf = (char *)&optbuf; reply.flags = 0; if ((t_optmgmt(fd, &request, &reply) == -1) || (reply.flags != T_SUCCESS)) { error_msg("t_optmgmt: %s", t_strerror(t_errno)); return (-1); } return (0); } /* * Compare contents of netbuf for equality. Return B_TRUE on a match and * B_FALSE for mismatch. */ static boolean_t netbufs_equal(struct netbuf *n1, struct netbuf *n2) { return ((n1->len == n2->len) && (memcmp(n1->buf, n2->buf, (size_t)n1->len) == 0)); } /* * Create a tli/xti endpoint, either bound to the address specified in * 'instance' for non-RPC services, else a kernel chosen address. * Returns -1 on failure, else 0. */ int create_bound_endpoint(const instance_t *inst, tlx_info_t *tlx_info) { int fd; int qlen; const char *fmri = inst->fmri; struct netbuf *reqaddr; struct netbuf *retaddr; struct netbuf netbuf; struct sockaddr_storage ss; rpc_info_t *rpc = tlx_info->pr_info.ri; if ((fd = t_open(tlx_info->dev_name, O_RDWR, NULL)) == -1) { error_msg(gettext("Failed to open transport %s for " "instance %s, proto %s: %s"), tlx_info->dev_name, fmri, tlx_info->pr_info.proto, t_strerror(t_errno)); return (-1); } if (tlx_info->pr_info.v6only) { int on = 1; /* restrict to IPv6 communications only */ if (tlx_setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof (on)) == -1) { (void) t_close(fd); return (-1); } } /* * Negotiate for the returning of the remote uid for loopback * transports for RPC services. This needs to be done before the * endpoint is bound using t_bind(), so that any requests to it * contain the uid. */ if ((rpc != NULL) && (rpc->is_loopback)) svc_fd_negotiate_ucred(fd); /* * Bind the service's address to the endpoint and setup connection * backlog. In the case of RPC services, we specify a NULL requested * address and accept what we're given, storing the returned address * for later RPC binding. In the case of non-RPC services we specify * the service's associated address. */ if (rpc != NULL) { reqaddr = NULL; retaddr = &(rpc->netbuf); } else { reqaddr = &(tlx_info->local_addr); netbuf.buf = (char *)&ss; netbuf.maxlen = sizeof (ss); retaddr = &netbuf; } /* ignored for conn/less services */ qlen = inst->config->basic->conn_backlog; if ((tlx_bind(fd, reqaddr, retaddr, qlen) == -1) || ((reqaddr != NULL) && !netbufs_equal(reqaddr, retaddr))) { error_msg(gettext("Failed to bind to the requested address " "for instance %s, proto %s"), fmri, tlx_info->pr_info.proto); (void) t_close(fd); return (-1); } return (fd); } /* * Takes a connection request off 'fd' in the form of a t_call structure * and returns a pointer to it. * Returns NULL on failure, else pointer to t_call structure on success. */ static struct t_call * get_new_conind(int fd) { struct t_call *call; /* LINTED E_BAD_PTR_CAST_ALIGN */ if ((call = (struct t_call *)t_alloc(fd, T_CALL, T_ALL)) == NULL) { error_msg("t_alloc: %s", t_strerror(t_errno)); return (NULL); } if (t_listen(fd, call) < 0) { error_msg("t_listen: %s", t_strerror(t_errno)); (void) t_free((char *)call, T_CALL); return (NULL); } return (call); } /* Add 'call' to the connection indication queue 'queue'. */ int queue_conind(uu_list_t *queue, struct t_call *call) { tlx_conn_ind_t *ci; if ((ci = malloc(sizeof (tlx_conn_ind_t))) == NULL) { error_msg(strerror(errno)); return (-1); } ci->call = call; uu_list_node_init(ci, &ci->link, conn_ind_pool); (void) uu_list_insert_after(queue, NULL, ci); return (0); } /* * Remove and return a pointer to the first call on queue 'queue'. However, * if the queue is empty returns NULL. */ struct t_call * dequeue_conind(uu_list_t *queue) { struct t_call *ret; tlx_conn_ind_t *ci = uu_list_first(queue); if (ci == NULL) return (NULL); ret = ci->call; uu_list_remove(queue, ci); free(ci); return (ret); } /* * Handle a TLOOK notification received during a t_accept() call. * Returns -1 on failure, else 0. */ static int process_tlook(const char *fmri, tlx_info_t *tlx_info) { int event; int fd = tlx_info->pr_info.listen_fd; switch (event = t_look(fd)) { case T_LISTEN: { struct t_call *call; debug_msg("process_tlook: T_LISTEN event"); if ((call = get_new_conind(fd)) == NULL) return (-1); if (queue_conind(tlx_info->conn_ind_queue, call) == -1) { error_msg(gettext("Failed to queue connection " "indication for instance %s"), fmri); (void) t_free((char *)call, T_CALL); return (-1); } break; } case T_DISCONNECT: { /* * Note: In Solaris 2.X (SunOS 5.X) bundled * connection-oriented transport drivers * [ e.g /dev/tcp and /dev/ticots and * /dev/ticotsord (tl)] we do not send disconnect * indications to listening endpoints. * So this will not be seen with endpoints on Solaris * bundled transport devices. However, Streams TPI * allows for this (broken?) behavior and so we account * for it here because of the possibility of unbundled * transport drivers causing this. */ tlx_conn_ind_t *cip; struct t_discon *discon; debug_msg("process_tlook: T_DISCONNECT event"); /* LINTED */ if ((discon = (struct t_discon *) t_alloc(fd, T_DIS, T_ALL)) == NULL) { error_msg("t_alloc: %s", t_strerror(t_errno)); return (-1); } if (t_rcvdis(fd, discon) < 0) { error_msg("t_rcvdis: %s", t_strerror(t_errno)); (void) t_free((char *)discon, T_DIS); return (-1); } /* * Find any queued connection pending that matches this * disconnect notice and remove from the pending queue. */ cip = uu_list_first(tlx_info->conn_ind_queue); while ((cip != NULL) && (cip->call->sequence != discon->sequence)) { cip = uu_list_next(tlx_info->conn_ind_queue, cip); } if (cip != NULL) { /* match found */ uu_list_remove(tlx_info->conn_ind_queue, cip); (void) t_free((char *)cip->call, T_CALL); free(cip); } (void) t_free((char *)discon, T_DIS); break; } case -1: error_msg("t_look: %s", t_strerror(t_errno)); return (-1); default: error_msg(gettext("do_tlook: unexpected t_look event: %d"), event); return (-1); } return (0); } /* * This call attempts to t_accept() an incoming/pending TLI connection. * If it is thwarted by a TLOOK, it is deferred and whatever is on the * file descriptor, removed after a t_look. (Incoming connect indications * get queued for later processing and disconnect indications remove a * a queued connection request if a match found). * Returns -1 on failure, else 0. */ int tlx_accept(const char *fmri, tlx_info_t *tlx_info, struct sockaddr_storage *remote_addr) { tlx_conn_ind_t *conind; struct t_call *call; int fd; int listen_fd = tlx_info->pr_info.listen_fd; if ((fd = t_open(tlx_info->dev_name, O_RDWR, NULL)) == -1) { error_msg("t_open: %s", t_strerror(t_errno)); return (-1); } if (tlx_info->pr_info.v6only) { int on = 1; /* restrict to IPv6 communications only */ if (tlx_setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof (on)) == -1) { (void) t_close(fd); return (-1); } } if (t_bind(fd, NULL, NULL) == -1) { error_msg("t_bind: %s", t_strerror(t_errno)); (void) t_close(fd); return (-1); } /* * Get the next connection indication - first try the pending * queue, then, if none there, get a new one from the file descriptor. */ if ((conind = uu_list_first(tlx_info->conn_ind_queue)) != NULL) { debug_msg("taking con off queue"); call = conind->call; } else if ((call = get_new_conind(listen_fd)) == NULL) { (void) t_close(fd); return (-1); } /* * Accept the connection indication on the newly created endpoint. * If we fail, and it's the result of a tlook, queue the indication * if it isn't already, and go and process the t_look. */ if (t_accept(listen_fd, fd, call) == -1) { if (t_errno == TLOOK) { if (uu_list_first(tlx_info->conn_ind_queue) == NULL) { /* * We are first one to have to defer accepting * and start the pending connections list. */ if (queue_conind(tlx_info->conn_ind_queue, call) == -1) { error_msg(gettext( "Failed to queue connection " "indication for instance %s"), fmri); (void) t_free((char *)call, T_CALL); return (-1); } } (void) process_tlook(fmri, tlx_info); } else { /* non-TLOOK accept failure */ error_msg("%s: %s", "t_accept failed", t_strerror(t_errno)); /* * If we were accepting a queued connection, dequeue * it. */ if (uu_list_first(tlx_info->conn_ind_queue) != NULL) (void) dequeue_conind(tlx_info->conn_ind_queue); (void) t_free((char *)call, T_CALL); } (void) t_close(fd); return (-1); } /* Copy remote address into address parameter */ (void) memcpy(remote_addr, call->addr.buf, MIN(call->addr.len, sizeof (*remote_addr))); /* If we were accepting a queued connection, dequeue it. */ if (uu_list_first(tlx_info->conn_ind_queue) != NULL) (void) dequeue_conind(tlx_info->conn_ind_queue); (void) t_free((char *)call, T_CALL); return (fd); } /* protocol independent network fd close routine */ void close_net_fd(instance_t *inst, int fd) { if (inst->config->basic->istlx) { (void) t_close(fd); } else { (void) close(fd); } } /* * Consume some data from the given endpoint of the given wait-based instance. */ void consume_wait_data(instance_t *inst, int fd) { int flag; char buf[50]; /* same arbitrary size as old inetd */ if (inst->config->basic->istlx) { (void) t_rcv(fd, buf, sizeof (buf), &flag); } else { (void) recv(fd, buf, sizeof (buf), 0); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * General utility routines. */ #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* size of buffer used in msg() to expand printf() like messages into */ #define MSG_BUF_SIZE 1024 /* number of pollfd we grow the pollfd array by at a time in set_pollfd() */ #define POLLFDS_GROWTH_SIZE 16 /* enumeration of message types supported by msg() */ typedef enum { MT_ERROR, MT_DEBUG, MT_WARN } si_msg_type_t; /* * Collection of information for each method type. * NOTE: This table is indexed into using the instance_method_t * enumeration, so the ordering needs to be kept in synch. */ method_type_info_t methods[] = { {IM_START, START_METHOD_NAME, IIS_NONE}, {IM_ONLINE, ONLINE_METHOD_NAME, IIS_ONLINE}, {IM_OFFLINE, OFFLINE_METHOD_NAME, IIS_OFFLINE}, {IM_DISABLE, DISABLE_METHOD_NAME, IIS_DISABLED}, {IM_REFRESH, REFRESH_METHOD_NAME, IIS_ONLINE}, {IM_NONE, "none", IIS_NONE} }; struct pollfd *poll_fds = NULL; nfds_t num_pollfds; boolean_t syslog_open = B_FALSE; boolean_t debug_enabled = B_FALSE; void msg_init(void) { openlog(SYSLOG_IDENT, LOG_PID|LOG_CONS, LOG_DAEMON); syslog_open = B_TRUE; } void msg_fini(void) { syslog_open = B_FALSE; closelog(); } /* * Outputs a msg. If 'type' is set tp MT_ERROR or MT_WARN the message goes * to syslog with severitys LOG_ERROR and LOG_WARN respectively. For all * values of 'type' the message is written to the debug log file, if it * was openable when inetd started. */ static void msg(si_msg_type_t type, const char *format, va_list ap) { /* * Use a stack buffer so we stand more chance of reporting a * memory shortage failure. */ char buf[MSG_BUF_SIZE]; if (!syslog_open) return; (void) vsnprintf(buf, sizeof (buf), format, ap); /* * Log error and warning messages to syslog with appropriate severity. */ if (type == MT_ERROR) { syslog(LOG_ERR, "%s", buf); } else if (type == MT_WARN) { syslog(LOG_WARNING, "%s", buf); } else if (debug_enabled && type == MT_DEBUG) { syslog(LOG_DEBUG, "%s", buf); } } /* * Output a warning message. Unlike error_msg(), syslog doesn't get told * to log to the console if syslogd isn't around. */ void warn_msg(const char *format, ...) { va_list ap; closelog(); openlog(SYSLOG_IDENT, LOG_PID, LOG_DAEMON); va_start(ap, format); msg(MT_WARN, format, ap); va_end(ap); closelog(); openlog(SYSLOG_IDENT, LOG_PID|LOG_CONS, LOG_DAEMON); } void debug_msg(const char *format, ...) { va_list ap; va_start(ap, format); msg(MT_DEBUG, format, ap); va_end(ap); } void error_msg(const char *format, ...) { va_list ap; va_start(ap, format); msg(MT_ERROR, format, ap); va_end(ap); } void poll_fini(void) { if (poll_fds != NULL) { free(poll_fds); poll_fds = NULL; } } struct pollfd * find_pollfd(int fd) { nfds_t n; for (n = 0; n < num_pollfds; n++) { if (poll_fds[n].fd == fd) return (&(poll_fds[n])); } return (NULL); } int set_pollfd(int fd, uint16_t events) { struct pollfd *p; int i; p = find_pollfd(fd); if ((p == NULL) && ((p = find_pollfd(-1)) == NULL)) { if ((p = realloc(poll_fds, ((num_pollfds + POLLFDS_GROWTH_SIZE) * sizeof (struct pollfd)))) == NULL) { return (-1); } poll_fds = p; for (i = 1; i < POLLFDS_GROWTH_SIZE; i++) poll_fds[num_pollfds + i].fd = -1; p = &poll_fds[num_pollfds]; num_pollfds += POLLFDS_GROWTH_SIZE; } p->fd = fd; p->events = events; p->revents = 0; return (0); } void clear_pollfd(int fd) { struct pollfd *p; if ((p = find_pollfd(fd)) != NULL) { p->fd = -1; p->events = 0; p->revents = 0; } } boolean_t isset_pollfd(int fd) { struct pollfd *p = find_pollfd(fd); return ((p != NULL) && (p->revents & POLLIN)); } /* * An extension of read() that keeps retrying until either the full request has * completed, the other end of the connection/pipe is closed, no data is * readable for a non-blocking socket/pipe, or an unexpected error occurs. * Returns 0 if the data is successfully read, 1 if the other end of the pipe/ * socket is closed or there's nothing to read from a non-blocking socket/pipe, * else -1 if an unexpected error occurs. */ int safe_read(int fd, void *buf, size_t sz) { int ret; size_t cnt = 0; char *cp = (char *)buf; if (sz == 0) return (0); do { switch (ret = read(fd, cp + cnt, sz - cnt)) { case 0: /* other end of pipe/socket closed */ return (1); case -1: if (errno == EAGAIN) { /* nothing to read */ return (1); } else if (errno != EINTR) { error_msg(gettext("Unexpected read error: %s"), strerror(errno)); return (-1); } break; default: cnt += ret; } } while (cnt != sz); return (0); } /* * Return B_TRUE if instance 'inst' has exceeded its configured maximum * concurrent copies limit, else B_FALSE. */ boolean_t copies_limit_exceeded(instance_t *inst) { /* any value <=0 means that copies limits are disabled */ return ((inst->config->basic->max_copies > 0) && (inst->copies >= inst->config->basic->max_copies)); } /* * Cancel the method/con-rate offline timer associated with the instance. */ void cancel_inst_timer(instance_t *inst) { (void) iu_cancel_timer(timer_queue, inst->timer_id, NULL); inst->timer_id = -1; } /* * Cancel the bind retry timer associated with the instance. */ void cancel_bind_timer(instance_t *inst) { (void) iu_cancel_timer(timer_queue, inst->bind_timer_id, NULL); inst->bind_timer_id = -1; } void enable_blocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); (void) fcntl(fd, F_SETFL, (flags & ~O_NONBLOCK)); } void disable_blocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); (void) fcntl(fd, F_SETFL, (flags | O_NONBLOCK)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This file contains a set of routines used to perform wait based method * reaping. */ #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* inetd's open file limit, set in method_init() */ #define INETD_NOFILE_LIMIT RLIM_INFINITY /* structure used to represent an active method process */ typedef struct { int fd; /* fd of process's /proc psinfo file */ /* associated contract id if known, else -1 */ ctid_t cid; pid_t pid; instance_t *inst; /* pointer to associated instance */ instance_method_t method; /* the method type running */ /* associated endpoint protocol name if known, else NULL */ char *proto_name; uu_list_node_t link; } method_el_t; static void unregister_method(method_el_t *); /* list of currently executing method processes */ static uu_list_pool_t *method_pool = NULL; static uu_list_t *method_list = NULL; /* * File limit saved during initialization before modification, so that it can * be reverted back to for inetd's exec'd methods. */ static struct rlimit saved_file_limit; /* * Setup structures used for method termination monitoring. * Returns -1 if an allocation failure occurred, else 0. */ int method_init(void) { struct rlimit rl; /* * Save aside the old file limit and impose one large enough to support * all the /proc file handles we could have open. */ (void) getrlimit(RLIMIT_NOFILE, &saved_file_limit); rl.rlim_cur = rl.rlim_max = INETD_NOFILE_LIMIT; if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { error_msg("Failed to set file limit: %s", strerror(errno)); return (-1); } if ((method_pool = uu_list_pool_create("method_pool", sizeof (method_el_t), offsetof(method_el_t, link), NULL, UU_LIST_POOL_DEBUG)) == NULL) { error_msg("%s: %s", gettext("Failed to create method pool"), uu_strerror(uu_error())); return (-1); } if ((method_list = uu_list_create(method_pool, NULL, 0)) == NULL) { error_msg("%s: %s", gettext("Failed to create method list"), uu_strerror(uu_error())); /* let method_fini() clean-up */ return (-1); } return (0); } /* * Tear-down structures created in method_init(). */ void method_fini(void) { if (method_list != NULL) { method_el_t *me; while ((me = uu_list_first(method_list)) != NULL) unregister_method(me); (void) uu_list_destroy(method_list); method_list = NULL; } if (method_pool != NULL) { (void) uu_list_pool_destroy(method_pool); method_pool = NULL; } /* revert file limit */ method_preexec(); } /* * Revert file limit back to pre-initialization one. This shouldn't fail as * long as its called *after* descriptor cleanup. */ void method_preexec(void) { (void) setrlimit(RLIMIT_NOFILE, &saved_file_limit); } /* * Callback function that handles the timeout of an instance's method. * 'arg' points at the method_el_t representing the method. */ /* ARGSUSED0 */ static void method_timeout(iu_tq_t *tq, void *arg) { method_el_t *mp = arg; error_msg(gettext("The %s method of instance %s timed-out"), methods[mp->method].name, mp->inst->fmri); mp->inst->timer_id = -1; if (mp->method == IM_START) { process_start_term(mp->inst, mp->proto_name); } else { process_non_start_term(mp->inst, IMRET_FAILURE); } unregister_method(mp); } /* * Registers the attributes of a running method passed as arguments so that * the method's termination is noticed and any further processing of the * associated instance is carried out. The function also sets up any * necessary timers so we can detect hung methods. * Returns -1 if either it failed to open the /proc psinfo file which is used * to monitor the method process, it failed to setup a required timer or * memory allocation failed; else 0. */ int register_method(instance_t *ins, pid_t pid, ctid_t cid, instance_method_t mthd, char *proto_name) { char path[MAXPATHLEN]; int fd; method_el_t *me; /* open /proc psinfo file of process to listen for POLLHUP events on */ (void) snprintf(path, sizeof (path), "/proc/%u/psinfo", pid); for (;;) { if ((fd = open(path, O_RDONLY)) >= 0) { break; } else if (errno != EINTR) { /* * Don't output an error for ENOENT; we get this * if a method has gone away whilst we were stopped, * and we're now trying to re-listen for it. */ if (errno != ENOENT) { error_msg(gettext("Failed to open %s: %s"), path, strerror(errno)); } return (-1); } } /* add method record to in-memory list */ if ((me = calloc(1, sizeof (method_el_t))) == NULL) { error_msg(strerror(errno)); (void) close(fd); return (-1); } me->fd = fd; me->inst = (instance_t *)ins; me->method = mthd; me->pid = pid; me->cid = cid; if (proto_name != NULL) { if ((me->proto_name = strdup(proto_name)) == NULL) { error_msg(strerror(errno)); free(me); (void) close(fd); return (-1); } } else me->proto_name = NULL; /* register a timeout for the method, if required */ if (mthd != IM_START) { method_info_t *mi = ins->config->methods[mthd]; if (mi->timeout > 0) { assert(ins->timer_id == -1); ins->timer_id = iu_schedule_timer(timer_queue, mi->timeout, method_timeout, me); if (ins->timer_id == -1) { error_msg(gettext( "Failed to schedule method timeout")); if (me->proto_name != NULL) free(me->proto_name); free(me); (void) close(fd); return (-1); } } } /* * Add fd of psinfo file to poll set, but pass 0 for events to * poll for, so we should only get a POLLHUP event on the fd. */ if (set_pollfd(fd, 0) == -1) { cancel_inst_timer(ins); if (me->proto_name != NULL) free(me->proto_name); free(me); (void) close(fd); return (-1); } uu_list_node_init(me, &me->link, method_pool); (void) uu_list_insert_after(method_list, NULL, me); return (0); } /* * A counterpart to register_method(), this function stops the monitoring of a * method process for its termination. */ static void unregister_method(method_el_t *me) { /* cancel any timer associated with the method */ if (me->inst->timer_id != -1) cancel_inst_timer(me->inst); /* stop polling on the psinfo file fd */ clear_pollfd(me->fd); (void) close(me->fd); /* remove method record from list */ uu_list_remove(method_list, me); if (me->proto_name != NULL) free(me->proto_name); free(me); } /* * Unregister all methods associated with instance 'inst'. */ void unregister_instance_methods(const instance_t *inst) { method_el_t *me = uu_list_first(method_list); while (me != NULL) { if (me->inst == inst) { method_el_t *tmp = me; me = uu_list_next(method_list, me); unregister_method(tmp); } else { me = uu_list_next(method_list, me); } } } /* * Process any terminated methods. For each method determined to have * terminated, the function determines its return value and calls the * appropriate handling function, depending on the type of the method. */ void process_terminated_methods(void) { method_el_t *me = uu_list_first(method_list); while (me != NULL) { struct pollfd *pfd; pid_t pid; int status; int ret; method_el_t *tmp; pfd = find_pollfd(me->fd); /* * We expect to get a POLLHUP back on the fd of the process's * open psinfo file from /proc when the method terminates. * A POLLERR could(?) mask a POLLHUP, so handle this * also. */ if ((pfd->revents & (POLLHUP|POLLERR)) == 0) { me = uu_list_next(method_list, me); continue; } /* get the method's exit code (no need to loop for EINTR) */ pid = waitpid(me->pid, &status, WNOHANG); switch (pid) { case 0: /* child still around */ /* * Either poll() is sending us invalid POLLHUP events * or is flagging a POLLERR on the fd. Neither should * happen, but in the event they do, ignore this fd * this time around and wait out the termination * of its associated method. This may result in * inetd swiftly looping in event_loop(), but means * we don't miss the termination of a method. */ me = uu_list_next(method_list, me); continue; case -1: /* non-existent child */ assert(errno == ECHILD); /* * the method must not be owned by inetd due to it * persisting over an inetd restart. Let's assume the * best, that it was successful. */ ret = IMRET_SUCCESS; break; default: /* child terminated */ if (WIFEXITED(status)) { ret = WEXITSTATUS(status); debug_msg("process %ld of instance %s returned " "%d", pid, me->inst->fmri, ret); } else if (WIFSIGNALED(status)) { /* * Terminated by signal. This may be due * to a kill that we sent from a disable or * offline event. We flag it as a failure, but * this flagged failure will only be processed * in the case of non-start methods, or when * the instance is still enabled. */ debug_msg("process %ld of instance %s exited " "due to signal %d", pid, me->inst->fmri, WTERMSIG(status)); ret = IMRET_FAILURE; } else { /* * Can we actually get here? Don't think so. * Treat it as a failure, anyway. */ debug_msg("waitpid() for %s method of " "instance %s returned %d", methods[me->method].name, me->inst->fmri, status); ret = IMRET_FAILURE; } } remove_method_ids(me->inst, me->pid, me->cid, me->method); /* continue state transition processing of the instance */ if (me->method != IM_START) { process_non_start_term(me->inst, ret); } else { process_start_term(me->inst, me->proto_name); } if (me->cid != -1) (void) abandon_contract(me->cid); tmp = me; me = uu_list_next(method_list, me); unregister_method(tmp); } }