/* $NetBSD: parse_v2.c,v 1.7 2024/02/08 20:51:25 andvar Exp $ */ /*- * Copyright (c) 2021 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by James Browning, Gabe Coffland, Alex Gavin, and Solomon Ritzow. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __RCSID("$NetBSD: parse_v2.c,v 1.7 2024/02/08 20:51:25 andvar Exp $"); #include #include #include #include #include #include #include #include #include #include "inetd.h" #include "ipsec.h" typedef enum values_state { VALS_PARSING, VALS_END_KEY, VALS_END_DEF, VALS_ERROR } values_state; /* Values parsing state */ typedef struct val_parse_info { char *cp; /* Used so we can null-terminate values by overwriting ',' and ';' */ //char terminal; values_state state; } val_parse_info, *vlist; /* The result of a call to parse_invoke_handler */ typedef enum invoke_result { INVOKE_SUCCESS, INVOKE_FINISH, INVOKE_ERROR } invoke_result; /* The result of a parse of key handler values */ typedef enum hresult { KEY_HANDLER_FAILURE, KEY_HANDLER_SUCCESS } hresult; /* v2 syntax key-value parsers */ static hresult args_handler(struct servtab *, vlist); static hresult bind_handler(struct servtab *, vlist); static hresult exec_handler(struct servtab *, vlist); static hresult filter_handler(struct servtab *, vlist); static hresult group_handler(struct servtab *, vlist); static hresult service_max_handler(struct servtab *, vlist); static hresult ip_max_handler(struct servtab *, vlist); static hresult protocol_handler(struct servtab *, vlist); static hresult recv_buf_handler(struct servtab *, vlist); static hresult send_buf_handler(struct servtab *, vlist); static hresult socket_type_handler(struct servtab *, vlist); static hresult unknown_handler(struct servtab *, vlist); static hresult user_handler(struct servtab *, vlist); static hresult wait_handler(struct servtab *, vlist); #ifdef IPSEC static hresult ipsec_handler(struct servtab *, vlist); #endif static invoke_result parse_invoke_handler(bool *, char **, struct servtab *); static bool fill_default_values(struct servtab *); static bool parse_quotes(char **); static bool skip_whitespace(char **); static int size_to_bytes(char *); static bool infer_protocol_ip_version(struct servtab *); static bool setup_internal(struct servtab *); static void try_infer_socktype(struct servtab *); static int hex_to_bits(char); #ifdef IPSEC static void setup_ipsec(struct servtab *); #endif static inline void strmove(char *, size_t); /* v2 Key handlers infrastructure */ /* v2 syntax Handler function, which must parse all values for its key */ typedef hresult (*key_handler_func)(struct servtab *, vlist); /* List of v2 syntax key handlers */ static struct key_handler { const char *name; key_handler_func handler; } key_handlers[] = { { "bind", bind_handler }, { "socktype", socket_type_handler }, { "acceptfilter", filter_handler }, { "protocol", protocol_handler }, { "sndbuf", send_buf_handler }, { "recvbuf", recv_buf_handler }, { "wait", wait_handler }, { "service_max", service_max_handler }, { "user", user_handler }, { "group", group_handler }, { "exec", exec_handler }, { "args", args_handler }, { "ip_max", ip_max_handler }, #ifdef IPSEC { "ipsec", ipsec_handler } #endif }; /* Error Not Initialized */ #define ENI(key) ERR("Required option '%s' not specified", (key)) #define WAIT_WRN "Option 'wait' for internal service '%s' was inferred" /* Too Few Arguments (values) */ #define TFA(key) ERR("Option '%s' has too few arguments", (key)) /* Too Many Arguments (values) */ #define TMA(key) ERR("Option '%s' has too many arguments", (key)) /* Too Many Definitions */ #define TMD(key) ERR("Option '%s' is already specified", (key)) #define VALID_SOCKET_TYPES "stream, dgram, rdm, seqpacket, raw" parse_v2_result parse_syntax_v2(struct servtab *sep, char **cpp) { /* Catch multiple semantic errors instead of skipping after one */ bool is_valid_definition = true; /* Line number of service for error logging. */ size_t line_number_start = line_number; for (;;) { switch(parse_invoke_handler(&is_valid_definition, cpp, sep)) { case INVOKE_SUCCESS: /* Keep reading more options in. */ continue; case INVOKE_FINISH: /* * Found a semicolon, do final checks and defaults * and return. * Skip whitespace after semicolon to end of line. */ while (isspace((unsigned char)**cpp)) { (*cpp)++; } if (is_valid_definition && fill_default_values(sep)) { if (**cpp == '\0') { *cpp = nextline(fconfig); } return V2_SUCCESS; } DPRINTCONF("Ignoring invalid definition."); /* Log the error for the starting line of the service */ syslog(LOG_ERR, CONF_ERROR_FMT "Ignoring invalid definition.", CONFIG, line_number_start); if (**cpp == '\0') { *cpp = nextline(fconfig); } return V2_SKIP; case INVOKE_ERROR: DPRINTCONF("Syntax error; Exiting '%s'", CONFIG); return V2_ERROR; } } } /* * Fill in any remaining values that should be inferred * Log an error if a required parameter that isn't * provided by user can't be inferred from other servtab data. * Return true on success, false on failure. */ static bool fill_default_values(struct servtab *sep) { bool is_valid = true; if (sep->se_service_max == SERVTAB_UNSPEC_SIZE_T) { /* Set default to same as in v1 syntax. */ sep->se_service_max = TOOMANY; } if (sep->se_hostaddr == NULL) { /* Set hostaddr to default */ sep->se_hostaddr = newstr(defhost); } try_infer_socktype(sep); if (sep->se_server == NULL) { /* If an executable is not specified, assume internal. */ is_valid = setup_internal(sep) && is_valid; } if (sep->se_socktype == SERVTAB_UNSPEC_VAL) { /* Ensure socktype is specified (either set or inferred) */ ENI("socktype"); is_valid = false; } if (sep->se_wait == SERVTAB_UNSPEC_VAL) { /* Ensure wait is specified */ ENI("wait"); is_valid = false; } if (sep->se_user == NULL) { /* Ensure user is specified */ ENI("user"); is_valid = false; } if (sep->se_proto == NULL) { /* Ensure protocol is specified */ ENI("protocol"); is_valid = false; } else { is_valid = infer_protocol_ip_version(sep) && is_valid; } #ifdef IPSEC setup_ipsec(sep); #endif return is_valid; } /* fill_default_values related functions */ #ifdef IPSEC static void setup_ipsec(struct servtab *sep) { if (sep->se_policy == NULL) { /* Set to default global policy */ sep->se_policy = policy; } else if (*sep->se_policy == '\0') { /* IPsec was intentionally disabled. */ free(sep->se_policy); sep->se_policy = NULL; } } #endif static void try_infer_socktype(struct servtab *sep) { if (sep->se_socktype != SERVTAB_UNSPEC_VAL || sep->se_proto == NULL) { return; } /* Check values of se_proto udp, udp6, tcp, tcp6 to set dgram/stream */ if (strncmp(sep->se_proto, "udp", 3) == 0) { sep->se_socktype = SOCK_DGRAM; } else if (strncmp(sep->se_proto, "tcp", 3) == 0) { sep->se_socktype = SOCK_STREAM; } } static bool setup_internal(struct servtab *sep) { pid_t wait_prev = sep->se_wait; if (parse_server(sep, "internal") != 0) { ENI("exec"); return false; } if (wait_prev != SERVTAB_UNSPEC_VAL && wait_prev != sep->se_wait) { /* If wait was already specified throw an error. */ WRN(WAIT_WRN, sep->se_service); } return true; } static bool infer_protocol_ip_version(struct servtab *sep) { struct in_addr tmp; if (strcmp("tcp", sep->se_proto) != 0 && strcmp("udp", sep->se_proto) != 0 && strcmp("rpc/tcp", sep->se_proto) != 0 && strcmp("rpc/udp", sep->se_proto) != 0) { return true; } if (inet_pton(AF_INET, sep->se_hostaddr, &tmp)) { sep->se_family = AF_INET; return true; } if (inet_pton(AF_INET6, sep->se_hostaddr, &tmp)) { sep->se_family = AF_INET6; return true; } ERR("Address family of %s is ambigous or invalid. " "Explicitly specify protocol", sep->se_hostaddr); return false; } /* * Skips whitespaces, newline characters, and comments, * and returns the next token. Returns false and logs error if an EOF is * encountered. */ static bool skip_whitespace(char **cpp) { char *cp = *cpp; size_t line_start = line_number; for (;;) { while (isblank((unsigned char)*cp)) cp++; if (*cp == '\0' || *cp == '#') { cp = nextline(fconfig); /* Should never expect EOF when skipping whitespace */ if (cp == NULL) { ERR("Early end of file after line %zu", line_start); return false; } continue; } break; } *cpp = cp; return true; } /* Get the key handler function pointer for the given name */ static key_handler_func get_handler(char *name) { /* Call function to handle option parsing. */ for (size_t i = 0; i < __arraycount(key_handlers); i++) { if (strcmp(key_handlers[i].name, name) == 0) { return key_handlers[i].handler; } } return NULL; } static inline void strmove(char *buf, size_t off) { memmove(buf, buf + off, strlen(buf + off) + 1); } /* * Perform an in-place parse of a single-line quoted string * with escape sequences. Sets *cpp to the position after the quoted characters. * Uses shell-style quote parsing. */ static bool parse_quotes(char **cpp) { char *cp = *cpp; char quote = *cp; strmove(cp, 1); while (*cp != '\0' && quote != '\0') { if (*cp == quote) { quote = '\0'; strmove(cp, 1); continue; } if (*cp == '\\') { /* start is location of backslash */ char *start = cp; cp++; switch (*cp) { case 'x': { int hi, lo; if ((hi = hex_to_bits(cp[1])) == -1 || (lo = hex_to_bits(cp[2])) == -1) { ERR("Invalid hexcode sequence '%.4s'", start); return false; } *start = (char)((hi << 4) | lo); strmove(cp, 3); continue; } case '\\': *start = '\\'; break; case 'n': *start = '\n'; break; case 't': *start = '\t'; break; case 'r': *start = '\r'; break; case '\'': *start = '\''; break; case '"': *start = '"'; break; case '\0': ERR("Dangling escape sequence backslash"); return false; default: ERR("Unknown escape sequence '\\%c'", *cp); return false; } strmove(cp, 1); continue; } /* Regular character, advance to the next one. */ cp++; } if (*cp == '\0' && quote != '\0') { ERR("Unclosed quote"); return false; } *cpp = cp; return true; } static int hex_to_bits(char in) { switch(in) { case '0'...'9': return in - '0'; case 'a'...'f': return in - 'a' + 10; case 'A'...'F': return in - 'A' + 10; default: return -1; } } /* * Parse the next value for a key handler and advance list->cp past the found * value. Return NULL if there are no more values or there was an error * during parsing, and set the list->state to the appropriate value. */ static char * next_value(vlist list) { char *cp = list->cp; if (list->state != VALS_PARSING) { /* Already at the end of a values list, or there was an error.*/ return NULL; } if (!skip_whitespace(&cp)) { list->state = VALS_ERROR; return NULL; } if (*cp == ',' || *cp == ';') { /* Found end of args, but not immediately after value */ list->state = (*cp == ',' ? VALS_END_KEY : VALS_END_DEF); list->cp = cp + 1; return NULL; } /* Check for end of line */ if (!skip_whitespace(&cp)) { list->state = VALS_ERROR; return NULL; } /* * Found the start of a potential value. Advance one character * past the end of the value. */ char *start = cp; while (!isblank((unsigned char)*cp) && *cp != '#' && *cp != ',' && *cp != ';' && *cp != '\0' ) { if (*cp == '"' || *cp == '\'') { /* Found a quoted segment */ if (!parse_quotes(&cp)) { list->state = VALS_ERROR; return NULL; } } else { /* Find the end of the value */ cp++; } } /* Handle comments next to unquoted values */ if (*cp == '#') { *cp = '\0'; list->cp = cp; return start; } if (*cp == '\0') { /* * Value ends with end of line, so it is already NUL-terminated */ list->cp = cp; return start; } if (*cp == ',') { list->state = VALS_END_KEY; } else if (*cp == ';') { list->state = VALS_END_DEF; } *cp = '\0'; /* Advance past null so we don't skip the rest of the line */ list->cp = cp + 1; return start; } /* Parse key name and invoke associated handler */ static invoke_result parse_invoke_handler(bool *is_valid_definition, char **cpp, struct servtab *sep) { char *key_name, save, *cp = *cpp; int is_blank; key_handler_func handler; val_parse_info info; /* Skip any whitespace if it exists, otherwise do nothing */ if (!skip_whitespace(&cp)) { return INVOKE_ERROR; } /* Starting character of key */ key_name = cp; /* alphabetical or underscore allowed in name */ while (isalpha((unsigned char)*cp) || *cp == '_') { cp++; } is_blank = isblank((unsigned char)*cp); /* Get key handler and move to start of values */ if (*cp != '=' && !is_blank && *cp != '#') { ERR("Expected '=' but found '%c'", *cp); return INVOKE_ERROR; } save = *cp; *cp = '\0'; cp++; handler = get_handler(key_name); if (handler == NULL) { ERR("Unknown option '%s'", key_name); handler = unknown_handler; } /* If blank or new line, still need to find the '=' or throw error */ if (is_blank || save == '#') { if (save == '#') { cp = nextline(fconfig); } skip_whitespace(&cp); if (*cp != '=') { ERR("Expected '=' but found '%c'", *cp); return INVOKE_ERROR; } cp++; } /* Skip whitespace to start of values */ if (!skip_whitespace(&cp)) { return INVOKE_ERROR; } info = (val_parse_info) {cp, VALS_PARSING}; /* * Read values for key and write into sep. * If parsing is successful, all values for key must be read. */ if (handler(sep, &info) == KEY_HANDLER_FAILURE) { /* * Eat remaining values if an error happened * so more errors can be caught. */ while (next_value(&info) != NULL) continue; *is_valid_definition = false; } if (info.state == VALS_END_DEF) { /* * Exit definition handling for(;;). * Set the position to the end of the definition, * for multi-definition lines. */ *cpp = info.cp; return INVOKE_FINISH; } if (info.state == VALS_ERROR) { /* Parse error, stop reading config */ return INVOKE_ERROR; } *cpp = info.cp; return INVOKE_SUCCESS; } /* Return true if sep must be a built-in service */ static bool is_internal(struct servtab *sep) { return sep->se_bi != NULL; } /* * Key-values handlers */ static hresult /*ARGSUSED*/ unknown_handler(struct servtab *sep, vlist values) { /* Return failure for an unknown service name. */ return KEY_HANDLER_FAILURE; } /* Set listen address for this service */ static hresult bind_handler(struct servtab *sep, vlist values) { if (sep->se_hostaddr != NULL) { TMD("bind"); return KEY_HANDLER_FAILURE; } char *val = next_value(values); sep->se_hostaddr = newstr(val); if (next_value(values) != NULL) { TMA("bind"); return KEY_HANDLER_FAILURE; } return KEY_HANDLER_SUCCESS; } static hresult socket_type_handler(struct servtab *sep, vlist values) { char *type = next_value(values); if (type == NULL) { TFA("socktype"); return KEY_HANDLER_FAILURE; } parse_socktype(type, sep); if (sep->se_socktype == -1) { ERR("Invalid socket type '%s'. Valid: " VALID_SOCKET_TYPES, type); return KEY_HANDLER_FAILURE; } if (next_value(values) != NULL) { TMA("socktype"); return KEY_HANDLER_FAILURE; } return KEY_HANDLER_SUCCESS; } /* Set accept filter SO_ACCEPTFILTER */ static hresult filter_handler(struct servtab *sep, vlist values) { /* * See: SO_ACCEPTFILTER https://man.netbsd.org/setsockopt.2 * An accept filter can have one other argument. * This code currently only supports one accept filter * Also see parse_accept_filter(char* arg, struct servtab*sep) */ char *af_name, *af_arg; af_name = next_value(values); if (af_name == NULL) { TFA("filter"); return KEY_HANDLER_FAILURE; } /* Store af_name in se_accf.af_name, no newstr call */ strlcpy(sep->se_accf.af_name, af_name, sizeof(sep->se_accf.af_name)); af_arg = next_value(values); if (af_arg != NULL) { strlcpy(sep->se_accf.af_arg, af_arg, sizeof(sep->se_accf.af_arg)); if (next_value(values) != NULL) { TMA("filter"); return KEY_HANDLER_FAILURE; } } else { /* Store null string */ sep->se_accf.af_arg[0] = '\0'; } return KEY_HANDLER_SUCCESS; } /* Set protocol (udp, tcp, unix, etc.) */ static hresult protocol_handler(struct servtab *sep, vlist values) { char *val; if ((val = next_value(values)) == NULL) { TFA("protocol"); return KEY_HANDLER_FAILURE; } if (sep->se_type == NORM_TYPE && strncmp(val, "faith/", strlen("faith/")) == 0) { val += strlen("faith/"); sep->se_type = FAITH_TYPE; } sep->se_proto = newstr(val); if (parse_protocol(sep)) return KEY_HANDLER_FAILURE; if ((val = next_value(values)) != NULL) { TMA("protocol"); return KEY_HANDLER_FAILURE; } return KEY_HANDLER_SUCCESS; } /* * Convert a string number possible ending with k or m to an integer. * Based on MALFORMED, GETVAL, and ASSIGN in getconfigent(void). */ static int size_to_bytes(char *arg) { char *tail; int rstatus, count; count = (int)strtoi(arg, &tail, 10, 0, INT_MAX, &rstatus); if (rstatus != 0 && rstatus != ENOTSUP) { ERR("Invalid buffer size '%s': %s", arg, strerror(rstatus)); return -1; } switch(tail[0]) { case 'm': if (__builtin_smul_overflow((int)count, 1024, &count)) { ERR("Invalid buffer size '%s': Result too large", arg); return -1; } /* FALLTHROUGH */ case 'k': if (__builtin_smul_overflow((int)count, 1024, &count)) { ERR("Invalid buffer size '%s': Result too large", arg); return -1; } /* FALLTHROUGH */ case '\0': return count; default: ERR("Invalid buffer size unit prefix"); return -1; } } /* sndbuf size */ static hresult send_buf_handler(struct servtab *sep, vlist values) { char *arg; int buffer_size; if (ISMUX(sep)) { ERR("%s: can't specify buffer sizes for tcpmux services", sep->se_service); return KEY_HANDLER_FAILURE; } if ((arg = next_value(values)) == NULL) { TFA("sndbuf"); return KEY_HANDLER_FAILURE; } buffer_size = size_to_bytes(arg); if (buffer_size == -1) { return KEY_HANDLER_FAILURE; } if ((arg = next_value(values)) != NULL) { TMA("sndbuf"); return KEY_HANDLER_FAILURE; } sep->se_sndbuf = buffer_size; return KEY_HANDLER_SUCCESS; } /* recvbuf size */ static hresult recv_buf_handler(struct servtab *sep, vlist values) { char *arg; int buffer_size; if (ISMUX(sep)) { ERR("%s: Cannot specify buffer sizes for tcpmux services", sep->se_service); return KEY_HANDLER_FAILURE; } if ((arg = next_value(values)) == NULL){ TFA("recvbuf"); return KEY_HANDLER_FAILURE; } buffer_size = size_to_bytes(arg); if (buffer_size == -1) { return KEY_HANDLER_FAILURE; } if ((arg = next_value(values)) != NULL) { TMA("recvbuf"); return KEY_HANDLER_FAILURE; } sep->se_rcvbuf = buffer_size; return KEY_HANDLER_SUCCESS; } /* Same as wait in positional */ static hresult wait_handler(struct servtab *sep, vlist values) { char *val; pid_t wait; /* If 'wait' is specified after internal exec */ if (!is_internal(sep) && sep->se_wait != SERVTAB_UNSPEC_VAL) { /* Prevent duplicate wait keys */ TMD("wait"); return KEY_HANDLER_FAILURE; } val = next_value(values); if (val == NULL) { TFA("wait"); return KEY_HANDLER_FAILURE; } if (strcmp(val, "yes") == 0) { wait = true; } else if (strcmp(val, "no") == 0) { wait = false; } else { ERR("Invalid value '%s' for wait. Valid: yes, no", val); return KEY_HANDLER_FAILURE; } if (is_internal(sep) && wait != sep->se_wait) { /* If wait was set for internal service check for correctness */ WRN(WAIT_WRN, sep->se_service); } else if (parse_wait(sep, wait)) { return KEY_HANDLER_FAILURE; } if ((val = next_value(values)) != NULL) { TMA("wait"); return KEY_HANDLER_FAILURE; } return KEY_HANDLER_SUCCESS; } /* Set max connections in interval rate-limit, same as max in positional */ static hresult service_max_handler(struct servtab *sep, vlist values) { char *count_str; int rstatus; if (sep->se_service_max != SERVTAB_UNSPEC_SIZE_T) { TMD("service_max"); return KEY_HANDLER_FAILURE; } count_str = next_value(values); if (count_str == NULL) { TFA("service_max"); return KEY_HANDLER_FAILURE; } size_t count = (size_t)strtou(count_str, NULL, 10, 0, SERVTAB_COUNT_MAX, &rstatus); if (rstatus != 0) { ERR("Invalid service_max '%s': %s", count_str, strerror(rstatus)); return KEY_HANDLER_FAILURE; } if (next_value(values) != NULL) { TMA("service_max"); return KEY_HANDLER_FAILURE; } sep->se_service_max = count; return KEY_HANDLER_SUCCESS; } static hresult ip_max_handler(struct servtab *sep, vlist values) { char *count_str; int rstatus; if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) { TMD("ip_max"); return KEY_HANDLER_FAILURE; } count_str = next_value(values); if (count_str == NULL) { TFA("ip_max"); return KEY_HANDLER_FAILURE; } size_t count = (size_t)strtou(count_str, NULL, 10, 0, SERVTAB_COUNT_MAX, &rstatus); if (rstatus != 0) { ERR("Invalid ip_max '%s': %s", count_str, strerror(rstatus)); return KEY_HANDLER_FAILURE; } if (next_value(values) != NULL) { TMA("ip_max"); return KEY_HANDLER_FAILURE; } sep->se_ip_max = count; return KEY_HANDLER_SUCCESS; } /* Set user to execute as */ static hresult user_handler(struct servtab *sep, vlist values) { if (sep->se_user != NULL) { TMD("user"); return KEY_HANDLER_FAILURE; } char *name = next_value(values); if (name == NULL) { TFA("user"); return KEY_HANDLER_FAILURE; } sep->se_user = newstr(name); if (next_value(values) != NULL) { TMA("user"); return KEY_HANDLER_FAILURE; } return KEY_HANDLER_SUCCESS; } /* Set group to execute as */ static hresult group_handler(struct servtab *sep, vlist values) { char *name = next_value(values); if (name == NULL) { TFA("group"); return KEY_HANDLER_FAILURE; } sep->se_group = newstr(name); if (next_value(values) != NULL) { TMA("group"); return KEY_HANDLER_FAILURE; } return KEY_HANDLER_SUCCESS; } /* Handle program path or "internal" */ static hresult exec_handler(struct servtab *sep, vlist values) { char *val; if ((val = next_value(values)) == NULL) { TFA("exec"); return KEY_HANDLER_FAILURE; } pid_t wait_prev = sep->se_wait; if (parse_server(sep, val)) return KEY_HANDLER_FAILURE; if (is_internal(sep) && wait_prev != SERVTAB_UNSPEC_VAL) { /* * Warn if the user specifies a value for an internal which * is different */ if (wait_prev != sep->se_wait) { WRN(WAIT_WRN, sep->se_service); } } if ((val = next_value(values)) != NULL) { TMA("exec"); return KEY_HANDLER_FAILURE; } return KEY_HANDLER_SUCCESS; } /* Handle program arguments */ static hresult args_handler(struct servtab *sep, vlist values) { char *val; int argc; if (sep->se_argv[0] != NULL) { TMD("args"); return KEY_HANDLER_FAILURE; } argc = 0; for (val = next_value(values); val != NULL; val = next_value(values)) { if (argc >= MAXARGV) { ERR("Must be fewer than " TOSTRING(MAXARGV) " arguments"); return KEY_HANDLER_FAILURE; } sep->se_argv[argc++] = newstr(val); } while (argc <= MAXARGV) sep->se_argv[argc++] = NULL; return KEY_HANDLER_SUCCESS; } #ifdef IPSEC /* * ipsec_handler currently uses the ipsec.h utilities for parsing, requiring * all policies as a single value. This handler could potentially allow multiple * policies as separate values in the future, but strings would need to be * concatenated so the existing ipsec.h functions continue to work and policies * can continue to be stored in sep->policy. */ static hresult ipsec_handler(struct servtab *sep, vlist values) { if (sep->se_policy != NULL) { TMD("ipsec"); return KEY_HANDLER_FAILURE; } char *ipsecstr = next_value(values); if (ipsecstr != NULL && ipsecsetup_test(ipsecstr) < 0) { ERR("IPsec policy '%s' is invalid", ipsecstr); return KEY_HANDLER_FAILURE; } /* * Use 'ipsec=' with no argument to disable ipsec for this service * An empty string indicates that IPsec was disabled, handled in * fill_default_values. */ sep->se_policy = policy != NULL ? newstr(ipsecstr) : newstr(""); if (next_value(values) != NULL) { TMA("ipsec"); /* Currently only one semicolon separated string is allowed */ return KEY_HANDLER_FAILURE; } return KEY_HANDLER_SUCCESS; } #endif