diff options
Diffstat (limited to 'usr/src/cmd/cmd-inet/usr.sbin/inetconv/inetconv.c')
-rw-r--r-- | usr/src/cmd/cmd-inet/usr.sbin/inetconv/inetconv.c | 1527 |
1 files changed, 1527 insertions, 0 deletions
diff --git a/usr/src/cmd/cmd-inet/usr.sbin/inetconv/inetconv.c b/usr/src/cmd/cmd-inet/usr.sbin/inetconv/inetconv.c new file mode 100644 index 0000000000..fcc10d0a44 --- /dev/null +++ b/usr/src/cmd/cmd-inet/usr.sbin/inetconv/inetconv.c @@ -0,0 +1,1527 @@ +/* + * 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. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * inetconv - convert inetd.conf entries into smf(5) service manifests, + * import them into smf(5) repository + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> +#include <errno.h> +#include <limits.h> +#include <locale.h> +#include <libintl.h> +#include <libscf.h> +#include <inetsvc.h> +#include <rpc/nettype.h> + +/* exit codes */ +#define EXIT_SUCCESS 0 /* succeeded */ +#define EXIT_USAGE 1 /* bad options */ +#define EXIT_ERROR_CONV 2 /* error(s) coverting inetd.conf entries */ +#define EXIT_ERROR_IMP 3 /* error(s) importing manifests */ +#define EXIT_ERROR_SYS 4 /* system error */ +#define EXIT_ERROR_ENBL 5 /* error(s) enabling services */ + +#ifndef TEXT_DOMAIN +#define TEXT_DOMAIN "SUNW_OST_OSCMD" +#endif + +#define MAIN_CONFIG "/etc/inet/inetd.conf" +#define ALT_CONFIG "/etc/inetd.conf" + +#define MANIFEST_DIR "/var/svc/manifest/network" +#define MANIFEST_RPC_DIR MANIFEST_DIR ## "/rpc" +#define SVCCFG_PATH "/usr/sbin/svccfg" + +/* maximum allowed length of an inetd.conf format line */ +#define MAX_SRC_LINELEN 32768 + +/* Version of inetconv, used as a marker in services we generate */ +#define INETCONV_VERSION 1 + +struct inetconfent { + /* fields as read from inetd.conf format line */ + char *service; + char *endpoint; + char *protocol; + char *wait_status; + char *username; + char *server_program; + char *server_args; + /* information derived from above fields */ + boolean_t wait; + boolean_t isrpc; + int rpc_low_version; + int rpc_high_version; + char *rpc_prog; + char *groupname; + char *exec; + char *arg0; +}; + +struct fileinfo { + FILE *fp; + char *filename; + int lineno; + int failcnt; +}; + +static char *progname; + +static boolean_t import = B_TRUE; + +/* start of manifest XML template strings */ +static const char xml_header[] = +"<?xml version='1.0'?>\n" +"<!DOCTYPE service_bundle SYSTEM " +"'/usr/share/lib/xml/dtd/service_bundle.dtd.1'>\n"; + +static const char xml_comment[] = +"<!--\n" +" Service manifest for the %s service.\n" +"\n" +" Generated by inetconv(1M) from inetd.conf(4).\n" +"-->\n\n"; + +static const char xml_service_bundle[] = +"<service_bundle type='manifest' name='inetconv:%s'>\n\n"; + +static const char xml_service_name[] = +"<service\n" +" name='network/%s'\n" +" type='service'\n" +" version='1'>\n\n"; + +static const char xml_instance[] = +" <create_default_instance enabled='true'/>\n\n"; + +static const char xml_restarter[] = +" <restarter>\n" +" <service_fmri value='%s' />\n" +" </restarter>\n\n"; + +static const char xml_exec_method_start[] = +" <!--\n" +" Set a timeout of 0 to signify to inetd that we don't want to\n" +" timeout this service, since the forked process is the one that\n" +" does the service's work. This is the case for most/all legacy\n" +" inetd services; for services written to take advantage of SMF\n" +" capabilities, the start method should fork off a process to\n" +" handle the request and return a success code.\n" +" -->\n" +" <exec_method\n" +" type='method'\n" +" name='%s'\n" +" %s='%s'\n" +" timeout_seconds='0'>\n" +" <method_context>\n" +" <method_credential %s='%s' group='%s' />\n" +" </method_context>\n"; + +static const char xml_arg0[] = +" <propval name='%s' type='astring'\n" +" value='%s' />\n"; + +static const char xml_exec_method_end[] = +" </exec_method>\n\n"; + +static const char xml_exec_method_disable[] = +" <!--\n" +" Use inetd's built-in kill support to disable services.\n" +" -->\n" +" <exec_method\n" +" type='method'\n" +" name='%s'\n" +" %s=':kill'\n" +" timeout_seconds='0'>\n"; + +static const char xml_exec_method_offline[] = +" <!--\n" +" Use inetd's built-in process kill support to offline wait type\n" +" services.\n" +" -->\n" +" <exec_method\n" +" type='method'\n" +" name='%s'\n" +" %s=':kill_process'\n" +" timeout_seconds='0'>\n"; + +static const char xml_inetconv_group_start[] = +" <!--\n" +" This property group is used to record information about\n" +" how this manifest was created. It is an implementation\n" +" detail which should not be modified or deleted.\n" +" -->\n" +" <property_group name='%s' type='framework'>\n" +" <propval name='%s' type='boolean' value='%s' />\n" +" <propval name='%s' type='integer' value='%d' />\n" +" <propval name='%s' type='astring' value=\n" +"'%s %s %s %s %s %s%s%s'\n" +" />\n"; + +static const char xml_property_group_start[] = +" <property_group name='%s' type='framework'>\n" +" <propval name='%s' type='astring' value='%s' />\n" +" <propval name='%s' type='astring' value='%s' />\n" +" <propval name='%s' type='astring' value='%s' />\n" +" <propval name='%s' type='boolean' value='%s' />\n" +" <propval name='%s' type='boolean' value='%s' />\n"; + +static const char xml_property_group_rpc[] = +" <propval name='%s' type='integer' value='%d' />\n" +" <propval name='%s' type='integer' value='%d' />" +"\n"; + +static const char xml_property_group_end[] = +" </property_group>\n\n"; + +static const char xml_stability[] = +" <stability value='External' />\n\n"; + +static const char xml_template[] = +" <template>\n" +" <common_name>\n" +" <loctext xml:lang='C'>\n" +"%s\n" +" </loctext>\n" +" </common_name>\n" +" </template>\n"; + +static const char xml_footer[] = +"</service>\n" +"\n" +"</service_bundle>\n"; +/* end of manifest XML template strings */ + +static void * +safe_malloc(size_t size) +{ + void *cp; + + if ((cp = malloc(size)) == NULL) { + (void) fprintf(stderr, gettext("%s: malloc failed: %s\n"), + progname, strerror(errno)); + exit(EXIT_ERROR_SYS); + } + return (cp); +} + +static char * +safe_strdup(char *s) +{ + char *cp; + + if ((cp = strdup(s)) == NULL) { + (void) fprintf(stderr, gettext("%s: strdup failed: %s\n"), + progname, strerror(errno)); + exit(EXIT_ERROR_SYS); + } + return (cp); +} + +static char * +propertyname(char *name, char *prefix) +{ + static char *buf; + size_t len; + int c; + char *cp; + + /* free any memory allocated by a previous call */ + free(buf); + + len = strlen(name) + strlen(prefix) + 1; + buf = safe_malloc(len); + buf[0] = '\0'; + + /* + * Property names must match the regular expression: + * ([A-Za-z][_A-Za-z0-9.-]*,)?[A-Za-z][_A-Za-z0-9-]* + */ + + /* + * Make sure the first character is alphabetic, if not insert prefix. + * Can't use isalpha() here as its locale dependent but the property + * name regular expression isn't. + */ + c = name[0]; + if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) { + (void) strlcat(buf, prefix, len); + } + (void) strlcat(buf, name, len); + + /* convert any dissallowed characters into '_' */ + for (cp = buf; *cp != '\0'; cp++) { + if ((*cp < 'A' || *cp > 'Z') && (*cp < 'a' || *cp > 'z') && + (*cp < '0' || *cp > '9') && (*cp != '.') && (*cp != '-')) + *cp = '_'; + } + return (buf); +} + +static char * +servicename(struct inetconfent *iconf) +{ + static char *buf; + size_t len; + char *proto; + + /* free any memory allocated by a previous call */ + free(buf); + + len = strlen(iconf->service) + strlen(iconf->protocol) + + sizeof ("rpc-/visible"); + buf = safe_malloc(len); + + /* + * Combine the service and protocol fields to produce a unique + * manifest service name. The syntax of a service name is: + * prop(/prop)* + */ + (void) strlcpy(buf, propertyname(iconf->service, + iconf->isrpc ? "rpc-": "s-"), len); + (void) strlcat(buf, "/", len); + + proto = iconf->protocol; + if (iconf->isrpc && (strcmp(iconf->protocol, "rpc/*") == 0)) + proto = "rpc/visible"; + (void) strlcat(buf, propertyname(proto, "p-"), len); + return (buf); +} + +static boolean_t +is_v6only(char *protocol) +{ + /* returns true if protocol is an IPv6 only protocol */ + if ((strcmp(protocol, SOCKET_PROTO_TCP6_ONLY) == 0) || + (strcmp(protocol, SOCKET_PROTO_UDP6_ONLY) == 0)) + return (B_TRUE); + return (B_FALSE); +} + +static char * +invalid_props(inetd_prop_t *p) +{ + static char + buf[sizeof (" service-name endpoint-type protocol wait-status")]; + + buf[0] = '\0'; + if ((p[PT_SVC_NAME_INDEX].ip_error == IVE_INVALID) || + (p[PT_SVC_NAME_INDEX].ip_error == IVE_UNSET) || + (p[PT_RPC_LW_VER_INDEX].ip_error == IVE_INVALID) || + (p[PT_RPC_HI_VER_INDEX].ip_error == IVE_INVALID)) + (void) strlcat(buf, " service-name", sizeof (buf)); + if ((p[PT_SOCK_TYPE_INDEX].ip_error == IVE_INVALID) || + (p[PT_SOCK_TYPE_INDEX].ip_error == IVE_UNSET)) + (void) strlcat(buf, " endpoint-type", sizeof (buf)); + if ((p[PT_PROTO_INDEX].ip_error == IVE_INVALID) || + (p[PT_PROTO_INDEX].ip_error == IVE_UNSET) || + (p[PT_ISRPC_INDEX].ip_error == IVE_INVALID)) + (void) strlcat(buf, " protocol", sizeof (buf)); + if (p[PT_ISWAIT_INDEX].ip_error == IVE_INVALID) + (void) strlcat(buf, " wait-status", sizeof (buf)); + return (buf); +} + +/* + * wrapper around put_prop_value() that errors and exits on malloc failures, + * returns -1 on other failures, else returns 0. + */ +static int +my_put_prop_value(inetd_prop_t *props, char *pname, void *value) +{ + if (put_prop_value(props, pname, value) == 0) + return (0); + + if (errno == ENOMEM) { + (void) fprintf(stderr, gettext("%s: malloc failed: %s\n"), + progname, strerror(errno)); + exit(EXIT_ERROR_SYS); + } + return (-1); +} + +static boolean_t +valid_basic_properties(struct inetconfent *iconf, struct fileinfo *finfo) +{ + size_t prop_size; + inetd_prop_t *prop, *inetd_properties; + boolean_t valid = B_TRUE; + char *proto = iconf->protocol; + char *svc_name = iconf->service; + + inetd_properties = get_prop_table(&prop_size); + prop = safe_malloc(prop_size * sizeof (inetd_prop_t)); + (void) memcpy(prop, inetd_properties, + prop_size * sizeof (inetd_prop_t)); + + (void) put_prop_value(prop, PR_ISRPC_NAME, &iconf->isrpc); + (void) put_prop_value(prop, PR_ISWAIT_NAME, &iconf->wait); + if (iconf->isrpc) { + (void) put_prop_value(prop, PR_RPC_LW_VER_NAME, + &iconf->rpc_low_version); + (void) put_prop_value(prop, PR_RPC_HI_VER_NAME, + &iconf->rpc_high_version); + svc_name = iconf->rpc_prog; + proto += 4; /* skip 'rpc/' */ + } + + if ((my_put_prop_value(prop, PR_SOCK_TYPE_NAME, iconf->endpoint) + != 0) || + (my_put_prop_value(prop, PR_SVC_NAME_NAME, svc_name) != 0) || + (my_put_prop_value(prop, PR_PROTO_NAME, proto) != 0)) + valid = B_FALSE; + + if (!valid_props(prop, NULL, NULL, NULL, NULL) || !valid) { + valid = B_FALSE; + (void) fprintf(stderr, gettext("%s: Error %s line %d " + "invalid or inconsistent fields:%s\n"), progname, + finfo->filename, finfo->lineno, + invalid_props(prop)); + } + + free_instance_props(prop); + return (valid); +} + +static boolean_t +valid_inetconfent(struct inetconfent *iconf, struct fileinfo *finfo) +{ + boolean_t valid = B_TRUE; + size_t len; + char *cp, *endp; + struct passwd *pwd; + struct group *grp; + struct stat statb; + char *proto = iconf->protocol; + + iconf->isrpc = B_FALSE; + if (strncmp(iconf->protocol, "rpc/", 4) == 0) { + iconf->isrpc = B_TRUE; + iconf->rpc_prog = safe_strdup(iconf->service); + + /* set RPC version numbers */ + iconf->rpc_low_version = 1; + iconf->rpc_high_version = 1; + if ((cp = strrchr(iconf->rpc_prog, '/')) != NULL) { + *cp = '\0'; + if (*++cp != '\0') { + errno = 0; + iconf->rpc_low_version = strtol(cp, &endp, 10); + if (errno != 0) + goto vererr; + cp = endp; + if (*cp == '-') { + if (*++cp == '\0') + goto vererr; + errno = 0; + iconf->rpc_high_version = strtol(cp, + &endp, 10); + if ((errno != 0) || (*endp != '\0')) + goto vererr; + } else if (*cp == '\0') { + iconf->rpc_high_version = + iconf->rpc_low_version; + } else { +vererr: + (void) fprintf(stderr, gettext( + "%s: Error %s line %d invalid RPC " + "version in service: %s\n"), + progname, finfo->filename, + finfo->lineno, iconf->service); + valid = B_FALSE; + } + } + } + proto += 4; /* skip 'rpc/' */ + } + /* tcp6only and udp6only are not valid in inetd.conf */ + if (is_v6only(proto)) { + (void) fprintf(stderr, gettext("%s: Error %s line %d " + "invalid protocol: %s\n"), progname, + finfo->filename, finfo->lineno, proto); + valid = B_FALSE; + } + + if (strcmp(iconf->wait_status, "wait") == 0) { + iconf->wait = B_TRUE; + } else if (strcmp(iconf->wait_status, "nowait") == 0) { + iconf->wait = B_FALSE; + } else { + (void) fprintf(stderr, + gettext("%s: Error %s line %d invalid wait-status: %s\n"), + progname, finfo->filename, finfo->lineno, + iconf->wait_status); + valid = B_FALSE; + } + + /* look up the username to set the groupname */ + if ((pwd = getpwnam(iconf->username)) == NULL) { + (void) fprintf(stderr, + gettext("%s: Error %s line %d unknown user: %s\n"), + progname, finfo->filename, finfo->lineno, + iconf->username); + valid = B_FALSE; + } else { + if ((grp = getgrgid(pwd->pw_gid)) != NULL) { + iconf->groupname = safe_strdup(grp->gr_name); + } else { + /* use the group ID if no groupname */ + char s[1]; + + len = snprintf(s, 1, "%d", pwd->pw_gid) + 1; + iconf->groupname = safe_malloc(len); + (void) snprintf(iconf->groupname, len, "%d", + pwd->pw_gid); + } + } + + /* check for internal services */ + if (strcmp(iconf->server_program, "internal") == 0) { + valid = B_FALSE; + if ((strcmp(iconf->service, "echo") == 0) || + (strcmp(iconf->service, "discard") == 0) || + (strcmp(iconf->service, "time") == 0) || + (strcmp(iconf->service, "daytime") == 0) || + (strcmp(iconf->service, "chargen") == 0)) { + (void) fprintf(stderr, gettext( + "%s: Error %s line %d the SUNWcnsr and SUNWcnsu" + " packages contain the internal services\n"), + progname, finfo->filename, finfo->lineno); + } else { + (void) fprintf(stderr, gettext("%s: Error %s line %d " + "unknown internal service: %s\n"), progname, + finfo->filename, finfo->lineno, iconf->service); + } + } else if ((stat(iconf->server_program, &statb) == -1) && + (errno == ENOENT)) { + (void) fprintf(stderr, gettext( + "%s: Error %s line %d server-program not found: %s\n"), + progname, finfo->filename, finfo->lineno, + iconf->server_program); + valid = B_FALSE; + } + + return (valid && valid_basic_properties(iconf, finfo)); +} + +static void +free_inetconfent(struct inetconfent *iconf) +{ + if (iconf == NULL) + return; + + free(iconf->service); + free(iconf->endpoint); + free(iconf->protocol); + free(iconf->wait_status); + free(iconf->username); + free(iconf->server_program); + free(iconf->server_args); + free(iconf->rpc_prog); + free(iconf->groupname); + free(iconf->exec); + free(iconf->arg0); + + free(iconf); +} + +static struct inetconfent * +line_to_inetconfent(char *line) +{ + char *cp; + struct inetconfent *iconf; + + iconf = safe_malloc(sizeof (struct inetconfent)); + (void) memset(iconf, 0, sizeof (struct inetconfent)); + + if ((cp = strtok(line, " \t\n")) == NULL) + goto fail; + iconf->service = safe_strdup(cp); + + if ((cp = strtok(NULL, " \t\n")) == NULL) + goto fail; + iconf->endpoint = safe_strdup(cp); + + if ((cp = strtok(NULL, " \t\n")) == NULL) + goto fail; + iconf->protocol = safe_strdup(cp); + + if ((cp = strtok(NULL, " \t\n")) == NULL) + goto fail; + iconf->wait_status = safe_strdup(cp); + + if ((cp = strtok(NULL, " \t\n")) == NULL) + goto fail; + iconf->username = safe_strdup(cp); + + if ((cp = strtok(NULL, " \t\n")) == NULL) + goto fail; + iconf->server_program = safe_strdup(cp); + + /* last field is optional */ + if ((cp = strtok(NULL, "\n")) != NULL) + iconf->server_args = safe_strdup(cp); + + /* Combine args and server name to construct exec and args fields */ + if (iconf->server_args == NULL) { + iconf->exec = safe_strdup(iconf->server_program); + } else { + int len; + char *args, *endp; + + len = strlen(iconf->server_program) + + strlen(iconf->server_args) + 1; + iconf->exec = safe_malloc(len); + (void) strlcpy(iconf->exec, iconf->server_program, len); + + args = safe_strdup(iconf->server_args); + if ((cp = strtok(args, " \t")) != NULL) { + if ((endp = strrchr(iconf->exec, '/')) == NULL) + endp = iconf->exec; + else + endp++; + /* only set arg0 property value if needed */ + if (strcmp(endp, cp) != 0) + iconf->arg0 = safe_strdup(cp); + while ((cp = strtok(NULL, " \t")) != NULL) { + (void) strlcat(iconf->exec, " ", len); + (void) strlcat(iconf->exec, cp, len); + } + } + free(args); + } + + return (iconf); +fail: + free_inetconfent(iconf); + return (NULL); +} + +static void +skipline(FILE *fp) +{ + int c; + + /* skip remainder of a line */ + while (((c = getc(fp)) != EOF) && (c != '\n')) + ; +} + +static struct inetconfent * +fgetinetconfent(struct fileinfo *finfo, boolean_t validate) +{ + char *cp; + struct inetconfent *iconf; + char line[MAX_SRC_LINELEN]; + + while (fgets(line, sizeof (line), finfo->fp) != NULL) { + finfo->lineno++; + + /* skip empty or commented out lines */ + if (*line == '\n') + continue; + if (*line == '#') { + if (line[strlen(line) - 1] != '\n') + skipline(finfo->fp); + continue; + } + /* check for lines which are too long */ + if (line[strlen(line) - 1] != '\n') { + (void) fprintf(stderr, + gettext("%s: Error %s line %d too long, skipped\n"), + progname, finfo->filename, finfo->lineno); + skipline(finfo->fp); + finfo->failcnt++; + continue; + } + /* remove in line comments and newline character */ + if ((cp = strchr(line, '#')) == NULL) + cp = strchr(line, '\n'); + if (cp) + *cp = '\0'; + + if ((iconf = line_to_inetconfent(line)) == NULL) { + (void) fprintf(stderr, gettext( + "%s: Error %s line %d too few fields, skipped\n"), + progname, finfo->filename, finfo->lineno); + finfo->failcnt++; + continue; + } + + if (!validate || valid_inetconfent(iconf, finfo)) + return (iconf); + + finfo->failcnt++; + free_inetconfent(iconf); + } + return (NULL); +} + +static char * +boolstr(boolean_t val) +{ + if (val) + return ("true"); + return ("false"); +} + +static int +print_manifest(FILE *f, char *filename, struct inetconfent *iconf) +{ + if (fprintf(f, xml_header) < 0) + goto print_err; + + if (fprintf(f, xml_comment, + iconf->isrpc ? iconf->rpc_prog : iconf->service) < 0) + goto print_err; + + if (fprintf(f, xml_service_bundle, iconf->service) < 0) + goto print_err; + if (fprintf(f, xml_service_name, servicename(iconf)) < 0) + goto print_err; + if (fprintf(f, xml_instance) < 0) + goto print_err; + if (fprintf(f, xml_restarter, INETD_INSTANCE_FMRI) < 0) + goto print_err; + + if (fprintf(f, xml_exec_method_start, START_METHOD_NAME, PR_EXEC_NAME, + iconf->exec, PR_USER_NAME, iconf->username, iconf->groupname) < 0) + goto print_err; + if (iconf->arg0 != NULL) { + if (fprintf(f, xml_arg0, PR_ARG0_NAME, iconf->arg0) < 0) + goto print_err; + } + if (fprintf(f, xml_exec_method_end) < 0) + goto print_err; + + if (fprintf(f, xml_exec_method_disable, DISABLE_METHOD_NAME, + PR_EXEC_NAME) < 0) + goto print_err; + if (fprintf(f, xml_exec_method_end) < 0) + goto print_err; + + if (iconf->wait) { + if (fprintf(f, xml_exec_method_offline, OFFLINE_METHOD_NAME, + PR_EXEC_NAME) < 0) + goto print_err; + if (fprintf(f, xml_exec_method_end) < 0) + goto print_err; + } + + if (fprintf(f, xml_inetconv_group_start, PG_NAME_INETCONV, + PR_AUTO_CONVERTED_NAME, boolstr(B_TRUE), + PR_VERSION_NAME, INETCONV_VERSION, + PR_SOURCE_LINE_NAME, iconf->service, + iconf->endpoint, iconf->protocol, iconf->wait_status, + iconf->username, iconf->server_program, + iconf->server_args == NULL ? "" : " ", + iconf->server_args == NULL ? "" : iconf->server_args) < 0) + goto print_err; + if (fprintf(f, xml_property_group_end) < 0) + goto print_err; + + if (fprintf(f, xml_property_group_start, PG_NAME_SERVICE_CONFIG, + PR_SVC_NAME_NAME, iconf->isrpc ? iconf->rpc_prog : iconf->service, + PR_SOCK_TYPE_NAME, iconf->endpoint, + PR_PROTO_NAME, iconf->isrpc ? iconf->protocol + 4 : + iconf->protocol, + PR_ISWAIT_NAME, boolstr(iconf->wait), + PR_ISRPC_NAME, boolstr(iconf->isrpc)) < 0) + goto print_err; + if (iconf->isrpc) { + if (fprintf(f, xml_property_group_rpc, + PR_RPC_LW_VER_NAME, iconf->rpc_low_version, + PR_RPC_HI_VER_NAME, iconf->rpc_high_version) < 0) + goto print_err; + } + if (fprintf(f, xml_property_group_end) < 0) + goto print_err; + + if (fprintf(f, xml_stability) < 0) + goto print_err; + if (fprintf(f, xml_template, + iconf->isrpc ? iconf->rpc_prog : iconf->service) < 0) + goto print_err; + if (fprintf(f, xml_footer) < 0) + goto print_err; + + (void) printf("%s -> %s\n", iconf->service, filename); + return (0); + +print_err: + (void) fprintf(stderr, gettext("%s: Error writing manifest %s: %s\n"), + progname, filename, strerror(errno)); + return (-1); +} + +static struct fileinfo * +open_srcfile(char *filename) +{ + struct fileinfo *finfo = NULL; + FILE *fp; + + if (filename != NULL) { + if ((fp = fopen(filename, "r")) == NULL) { + (void) fprintf(stderr, + gettext("%s: Error opening %s: %s\n"), + progname, filename, strerror(errno)); + } + } else { + /* + * If no source file specified, do the same as inetd and first + * try /etc/inet/inetd.conf, followed by /etc/inetd.conf. + */ + filename = MAIN_CONFIG; + if ((fp = fopen(filename, "r")) == NULL) { + (void) fprintf(stderr, + gettext("%s: Error opening %s: %s\n"), + progname, filename, strerror(errno)); + filename = ALT_CONFIG; + if ((fp = fopen(filename, "r")) == NULL) { + (void) fprintf(stderr, gettext( + "%s: Error opening %s: %s\n"), progname, + filename, strerror(errno)); + } + } + } + if (fp != NULL) { + finfo = safe_malloc(sizeof (struct fileinfo)); + finfo->fp = fp; + finfo->filename = filename; + finfo->lineno = 0; + finfo->failcnt = 0; + (void) fcntl(fileno(fp), F_SETFD, FD_CLOEXEC); + } + return (finfo); +} + +/* + * Opens manifest output file. Returns 0 on success, -1 if the file + * exists, -2 on other errors. + */ +static int +open_dstfile( + char *destdir, + boolean_t overwrite, + struct inetconfent *iconf, + struct fileinfo **finfo) +{ + int fd; + size_t len; + char *dstfile, *cp, *proto; + FILE *fp; + + /* if no destdir specified, use appropriate default */ + if (destdir == NULL) { + if (iconf->isrpc) + destdir = MANIFEST_RPC_DIR; + else + destdir = MANIFEST_DIR; + } + + len = strlen(destdir) + strlen(iconf->service) + + strlen(iconf->protocol) + sizeof ("/-visible.xml"); + dstfile = safe_malloc(len); + + (void) strlcpy(dstfile, destdir, len); + if (dstfile[strlen(dstfile) - 1] != '/') + (void) strlcat(dstfile, "/", len); + cp = dstfile + strlen(dstfile); + + (void) strlcat(dstfile, iconf->service, len); + (void) strlcat(dstfile, "-", len); + + proto = iconf->protocol; + if (iconf->isrpc && (strcmp(iconf->protocol, "rpc/*") == 0)) + proto = "rpc/visible"; + + (void) strlcat(dstfile, proto, len); + (void) strlcat(dstfile, ".xml", len); + + /* convert any '/' chars in service or protocol to '_' chars */ + while ((cp = strchr(cp, '/')) != NULL) + *cp = '_'; + + fd = open(dstfile, O_WRONLY|O_CREAT|(overwrite ? O_TRUNC : O_EXCL), + 0644); + if (fd == -1) { + if (!overwrite && (errno == EEXIST)) { + (void) fprintf(stderr, + gettext("%s: Notice: Service manifest for " + "%s already generated as %s, skipped\n"), + progname, iconf->service, dstfile); + free(dstfile); + return (-1); + } else { + (void) fprintf(stderr, + gettext("%s: Error opening %s: %s\n"), + progname, dstfile, strerror(errno)); + free(dstfile); + return (-2); + } + } + /* Clear errno to catch the "no stdio streams" case */ + errno = 0; + if ((fp = fdopen(fd, "w")) == NULL) { + char *s = strerror(errno); + if (errno == 0) + s = gettext("No stdio streams available"); + (void) fprintf(stderr, gettext("%s: Error fdopen failed: %s\n"), + progname, s); + (void) close(fd); + free(dstfile); + return (-2); + } + *finfo = safe_malloc(sizeof (struct fileinfo)); + (*finfo)->fp = fp; + (*finfo)->filename = dstfile; + (*finfo)->lineno = 0; + (*finfo)->failcnt = 0; + return (0); +} + +static int +import_manifest(char *filename) +{ + int status; + pid_t pid, wpid; + char *cp; + + if ((cp = strrchr(filename, '/')) == NULL) + cp = filename; + else + cp++; + (void) printf(gettext("Importing %s ..."), cp); + + if ((pid = fork()) == -1) { + (void) fprintf(stderr, + gettext("\n%s: fork failed, %s not imported: %s\n"), + progname, filename, strerror(errno)); + exit(EXIT_ERROR_SYS); + } + if (pid == 0) { + /* child */ + (void) fclose(stdin); + (void) setenv("SVCCFG_CHECKHASH", "1", 1); + (void) execl(SVCCFG_PATH, "svccfg", "import", filename, NULL); + (void) fprintf(stderr, gettext("\n%s: exec of %s failed: %s"), + progname, SVCCFG_PATH, strerror(errno)); + _exit(EXIT_ERROR_SYS); + } + /* parent */ + if ((wpid = waitpid(pid, &status, 0)) != pid) { + (void) fprintf(stderr, gettext( + "\n%s: unexpected wait (%d) from import of %s: %s\n"), + progname, wpid, filename, strerror(errno)); + return (-1); + } + if (WIFEXITED(status) && (WEXITSTATUS(status) != 0)) { + (void) fprintf(stderr, + gettext("\n%s: import failure (%d) for %s\n"), + progname, WEXITSTATUS(status), filename); + return (-1); + } + (void) printf(gettext("Done\n")); + return (0); +} + +static int +inetd_config_path(char **path) +{ + int fd; + char *arg1, *configfile, *configstr; + scf_simple_prop_t *sp; + char cpath[PATH_MAX]; + + if ((sp = scf_simple_prop_get(NULL, INETD_INSTANCE_FMRI, "start", + SCF_PROPERTY_EXEC)) == NULL) + return (-1); + if ((configstr = scf_simple_prop_next_astring(sp)) == NULL) { + scf_simple_prop_free(sp); + return (-1); + } + configstr = safe_strdup(configstr); + scf_simple_prop_free(sp); + + /* + * Look for the optional configuration file, the syntax is: + * /usr/lib/inet/inetd [config-file] start|stop|refresh|disable|%m + */ + if (strtok(configstr, " \t") == NULL) { + free(configstr); + return (-1); + } + if ((arg1 = strtok(NULL, " \t")) == NULL) { + free(configstr); + return (-1); + } + if (strtok(NULL, " \t") == NULL) { + /* + * No configuration file specified, do the same as inetd and + * first try /etc/inet/inetd.conf, followed by /etc/inetd.conf. + */ + configfile = MAIN_CONFIG; + if ((fd = open(configfile, O_RDONLY)) >= 0) + (void) close(fd); + else + configfile = ALT_CONFIG; + + } else { + /* make sure there are no more arguments */ + if (strtok(NULL, " \t") != NULL) { + free(configstr); + return (-1); + } + configfile = arg1; + } + + /* configuration file must be an absolute pathname */ + if (*configfile != '/') { + free(configstr); + return (-1); + } + + if (realpath(configfile, cpath) == NULL) + (void) strlcpy(cpath, configfile, sizeof (cpath)); + + free(configstr); + *path = safe_strdup(cpath); + return (0); +} + +static int +update_hash(char *srcfile) +{ + scf_error_t rval; + char *inetd_cpath, *hashstr; + char cpath[PATH_MAX]; + + /* determine the config file inetd is using */ + if (inetd_config_path(&inetd_cpath) == -1) { + (void) fprintf(stderr, + gettext("%s: Error reading from repository\n"), progname); + return (-1); + } + + /* resolve inetconv input filename */ + if (realpath(srcfile, cpath) == NULL) + (void) strlcpy(cpath, srcfile, sizeof (cpath)); + + /* if inetconv and inetd are using the same config file, update hash */ + if (strcmp(cpath, inetd_cpath) != 0) { + free(inetd_cpath); + return (0); + } + free(inetd_cpath); + + /* generic error message as use of hash is not exposed to the user */ + if (calculate_hash(cpath, &hashstr) != 0) { + (void) fprintf(stderr, + gettext("%s: Error unable to update repository\n"), + progname); + return (-1); + } + /* generic error message as use of hash is not exposed to the user */ + if ((rval = store_inetd_hash(hashstr)) != SCF_ERROR_NONE) { + (void) fprintf(stderr, + gettext("%s: Error updating repository: %s\n"), + progname, scf_strerror(rval)); + free(hashstr); + return (-1); + } + free(hashstr); + return (0); +} + +static void +property_error(const char *fmri, const char *prop) +{ + (void) fprintf(stderr, + gettext("Error: Instance %1$s is missing property '%2$s'.\n"), + fmri, prop); +} + +/* + * modify_sprop takes a handle, an instance, a property group, a property, + * and an astring value, and modifies the instance (or service's) specified + * property in the repository to the submitted value. + * + * returns -1 on error, 1 on successful transaction completion. + */ + +static int +modify_sprop(scf_handle_t *h, const scf_instance_t *inst, + const char *pg, const char *prop, const char *value) +{ + scf_transaction_t *tx = NULL; + scf_transaction_entry_t *ent = NULL; + scf_propertygroup_t *gpg = NULL; + scf_property_t *eprop = NULL; + scf_value_t *v = NULL; + scf_service_t *svc = NULL; + int ret = 0, create = 0; + + if ((gpg = scf_pg_create(h)) == NULL) + return (-1); + + /* Get the property group */ + if (scf_instance_get_pg(inst, pg, gpg) == -1) { + /* Not a property of the instance, try the service instead */ + if ((svc = scf_service_create(h)) == NULL) { + ret = -1; + goto out; + } + if ((scf_instance_get_parent(inst, svc) == -1) || + (scf_service_get_pg(svc, pg, gpg) == -1)) { + ret = -1; + goto out; + } + } + + if ((eprop = scf_property_create(h)) == NULL) { + ret = -1; + goto out; + } + + if (scf_pg_get_property(gpg, prop, eprop) == -1) { + if (scf_error() != SCF_ERROR_NOT_FOUND) { + ret = -1; + goto out; + } + + create = 1; + } + + if ((tx = scf_transaction_create(h)) == NULL || + (ent = scf_entry_create(h)) == NULL) { + ret = -1; + goto out; + } + + do { + if (scf_transaction_start(tx, gpg) == -1) { + ret = -1; + goto out; + } + + /* Modify the property */ + if (create) + ret = scf_transaction_property_new(tx, ent, prop, + SCF_TYPE_ASTRING); + else + ret = scf_transaction_property_change_type(tx, ent, + prop, SCF_TYPE_ASTRING); + + if (ret == -1) + goto out; + + if ((v = scf_value_create(h)) == NULL) { + ret = -1; + goto out; + } + + if (scf_value_set_astring(v, value) == -1) { + ret = -1; + goto out; + } + + if (scf_entry_add_value(ent, v) == -1) { + ret = -1; + goto out; + } + + ret = scf_transaction_commit(tx); + + if (ret == 0) { + /* Property group was stale, retry */ + if (scf_pg_update(gpg) == -1) { + ret = -1; + goto out; + } + scf_transaction_reset(tx); + } + + } while (ret == 0); +out: + scf_value_destroy(v); + scf_entry_destroy(ent); + scf_transaction_destroy(tx); + scf_property_destroy(eprop); + scf_service_destroy(svc); + scf_pg_destroy(gpg); + + return (ret); +} + +/* + * list_callback is the callback function to be handed to simple_walk_instances + * in main. It is called once on every instance on a machine. If that + * instance is controlled by inetd, we test whether it's the same + * service that we're looking at from the inetd.conf file, and enable it if + * they are the same. + */ + +/*ARGSUSED*/ +static int +list_callback(scf_handle_t *h, scf_instance_t *inst, void *buf) +{ + ssize_t max_name_length; + char *svc_name; + scf_simple_prop_t *prop = NULL; + scf_simple_prop_t *sockprop = NULL; + scf_simple_prop_t *rpcprop = NULL; + scf_simple_prop_t *progprop = NULL; + const char *name, *endpoint, *restart_str, *prog; + struct inetconfent *iconf = (struct inetconfent *)buf; + uint8_t *isrpc; + + max_name_length = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH); + if ((svc_name = malloc(max_name_length + 1)) == NULL) { + (void) fprintf(stderr, gettext("Error: Out of memory.\n")); + return (SCF_FAILED); + } + + /* + * Get the FMRI of the instance, and check if its delegated restarter + * is inetd. A missing or empty restarter property implies that + * svc.startd is the restarter. + */ + + if (scf_instance_to_fmri(inst, svc_name, max_name_length) < 0) { + (void) fprintf(stderr, + gettext("Error: Unable to obtain FMRI for service %1$s."), + svc_name); + free(svc_name); + return (SCF_FAILED); + } + + if ((prop = scf_simple_prop_get(h, svc_name, SCF_PG_GENERAL, + SCF_PROPERTY_RESTARTER)) == NULL) + goto out; + + if ((restart_str = scf_simple_prop_next_ustring(prop)) == NULL) + goto out; + + if (strcmp(restart_str, INETD_INSTANCE_FMRI) != 0) + goto out; + + /* Free restarter prop so it can be reused below */ + scf_simple_prop_free(prop); + + /* + * We know that this instance is managed by inetd. + * Now get the properties needed to decide if it matches this + * line in the old config file. + */ + + if (((prop = scf_simple_prop_get(h, svc_name, PG_NAME_SERVICE_CONFIG, + PR_SVC_NAME_NAME)) == NULL) || + ((name = scf_simple_prop_next_astring(prop)) == NULL)) { + property_error(svc_name, PR_SVC_NAME_NAME); + goto out; + } + + if (((sockprop = scf_simple_prop_get(h, svc_name, + PG_NAME_SERVICE_CONFIG, PR_SOCK_TYPE_NAME)) == NULL) || + ((endpoint = scf_simple_prop_next_astring(sockprop)) == NULL)) { + property_error(svc_name, PR_SOCK_TYPE_NAME); + goto out; + } + + if (((rpcprop = scf_simple_prop_get(h, svc_name, + PG_NAME_SERVICE_CONFIG, PR_ISRPC_NAME)) == NULL) || + ((isrpc = scf_simple_prop_next_boolean(rpcprop)) == NULL)) { + property_error(svc_name, PR_ISRPC_NAME); + goto out; + } + + if (((progprop = scf_simple_prop_get(h, svc_name, START_METHOD_NAME, + PR_EXEC_NAME)) == NULL) || + ((prog = scf_simple_prop_next_astring(progprop)) == NULL)) { + property_error(svc_name, PR_EXEC_NAME); + } + + + /* If it's RPC, we truncate off the version portion for comparison */ + if (*isrpc) { + char *cp; + + cp = strchr(iconf->service, '/'); + if (cp != NULL) + *cp = '\0'; + } + + /* + * If name of this service and endpoint are equal to values from + * iconf fields, and they're either both RPC or both non-RPC, + * then we have a match; update the exec and arg0 properties if + * necessary, then enable it. + * We don't return an error if either operation fails so that we + * continue to try all the other services. + */ + if (strcmp(name, iconf->service) == 0 && + strcmp(endpoint, iconf->endpoint) == 0 && + *isrpc == (strncmp(iconf->protocol, "rpc/", 4) == 0)) { + /* Can't update exec on internal services */ + if ((strcmp(iconf->server_program, "internal") != 0) && + (strcmp(iconf->exec, prog) != 0)) { + /* User had edited the command */ + if (!import) { + /* Dry run only */ + (void) printf( + gettext("Would update %s to %s %s"), + svc_name, PR_EXEC_NAME, iconf->exec); + if (iconf->arg0 != NULL) { + (void) printf( + gettext(" with %s of %s\n"), + PR_ARG0_NAME, iconf->arg0); + } else { + (void) printf("\n"); + } + } else { + /* Update instance's exec property */ + if (modify_sprop(h, inst, START_METHOD_NAME, + PR_EXEC_NAME, iconf->exec) != 1) + (void) fprintf(stderr, + gettext("Error: Unable to update " + "%s property of %s, %s\n"), + PR_EXEC_NAME, svc_name, + scf_strerror(scf_error())); + else + (void) printf("%s will %s %s\n", + svc_name, PR_EXEC_NAME, + iconf->exec); + + /* Update arg0 prop, if needed */ + if (iconf->arg0 != NULL) { + if (modify_sprop(h, inst, + START_METHOD_NAME, PR_ARG0_NAME, + iconf->arg0) != 1) { + (void) fprintf(stderr, + gettext("Error: Unable to " + "update %s property of " + "%s, %s\n"), PR_ARG0_NAME, + svc_name, + scf_strerror(scf_error())); + } else { + (void) printf("%s will have an " + "%s of %s\n", svc_name, + PR_ARG0_NAME, iconf->arg0); + } + } + } + } + + if (!import) { + /* Dry-run only */ + (void) printf("Would enable %s\n", svc_name); + } else { + if (smf_enable_instance(svc_name, 0) != 0) + (void) fprintf(stderr, + gettext("Error: Failed to enable %s\n"), + svc_name); + else + (void) printf("%s enabled\n", svc_name); + } + } + +out: + free(svc_name); + scf_simple_prop_free(prop); + scf_simple_prop_free(sockprop); + scf_simple_prop_free(rpcprop); + scf_simple_prop_free(progprop); + return (SCF_SUCCESS); +} + +static void +usage(void) +{ + (void) fprintf(stderr, gettext( + "Usage: %s [-fn] [-i srcfile] [-o destdir]\n" + " %1$s -e [-n] [-i srcfile]\n" + "-? Display this usage message\n" + "-e Enable smf services which are enabled in the input\n" + " file\n" + "-f Force overwrite of existing manifests\n" + "-n Do not import converted manifests,\n" + " or only display services which would be enabled\n" + "-i srcfile Alternate input file\n" + "-o destdir Alternate output directory for manifests\n"), + progname); + exit(EXIT_USAGE); +} + +int +main(int argc, char *argv[]) +{ + int c, rval, convert_err, import_err = 0, enable_err = 0; + boolean_t overwrite = B_FALSE; + boolean_t enable = B_FALSE; + char *srcfile = NULL; + char *destdir = NULL; + struct fileinfo *srcfinfo, *dstfinfo; + struct inetconfent *iconf; + + setbuf(stdout, NULL); + (void) setlocale(LC_ALL, ""); + (void) textdomain(TEXT_DOMAIN); + + if ((progname = strrchr(argv[0], '/')) == NULL) + progname = argv[0]; + else + progname++; + + while ((c = getopt(argc, argv, "?efni:o:")) != -1) { + switch (c) { + case 'e': + /* enable services based on existing file config */ + enable = B_TRUE; + break; + + case 'f': + /* overwrite existing manifests */ + overwrite = B_TRUE; + break; + case 'n': + /* don't import manifests, or dry-run enable */ + import = B_FALSE; + break; + case 'i': + /* alternate input file */ + if (srcfile != NULL) { + (void) fprintf(stderr, + gettext("%s: Error only one -%c allowed\n"), + progname, optopt); + usage(); + } + srcfile = optarg; + break; + case 'o': + /* alternate output directory */ + if (destdir != NULL) { + (void) fprintf(stderr, + gettext("%s: Error only one -%c allowed\n"), + progname, optopt); + usage(); + } + destdir = optarg; + break; + case '?': /*FALLTHROUGH*/ + default: + usage(); + break; + } + } + + /* + * Display usage if extraneous args supplied or enable specified in + * combination with overwrite or destdir + */ + if ((optind != argc) || (enable && (overwrite || destdir != NULL))) + usage(); + + if ((srcfinfo = open_srcfile(srcfile)) == NULL) + return (EXIT_ERROR_CONV); + + while ((iconf = fgetinetconfent(srcfinfo, !enable)) != NULL) { + /* + * If we're enabling, then just walk all the services for each + * line and enable those which match. + */ + if (enable) { + rval = scf_simple_walk_instances(SCF_STATE_ALL, iconf, + list_callback); + free_inetconfent(iconf); + if (rval == SCF_FAILED) { + /* Only print msg if framework error */ + if (scf_error() != SCF_ERROR_CALLBACK_FAILED) + (void) fprintf(stderr, gettext( + "Error walking instances: %s.\n"), + scf_strerror(scf_error())); + enable_err++; + break; + } + continue; + } + + /* Remainder of loop used for conversion & import */ + if ((rval = open_dstfile(destdir, overwrite, iconf, &dstfinfo)) + < 0) { + /* + * Only increment error counter if the failure was + * other than the file already existing. + */ + if (rval == -2) + srcfinfo->failcnt++; + free_inetconfent(iconf); + continue; + } + rval = print_manifest(dstfinfo->fp, dstfinfo->filename, iconf); + (void) fclose(dstfinfo->fp); + if (rval == 0) { + if (import && + (import_manifest(dstfinfo->filename) != 0)) + import_err++; + } else { + (void) unlink(dstfinfo->filename); + srcfinfo->failcnt++; + } + free(dstfinfo->filename); + free(dstfinfo); + free_inetconfent(iconf); + } + (void) fclose(srcfinfo->fp); + convert_err = srcfinfo->failcnt; + + /* Update hash only if not in enable mode, and only if importing */ + if (!enable && import && (update_hash(srcfinfo->filename) != 0)) + import_err++; + + free(srcfinfo); + + if (enable_err != 0) + return (EXIT_ERROR_ENBL); + if (import_err != 0) + return (EXIT_ERROR_IMP); + if (convert_err != 0) + return (EXIT_ERROR_CONV); + return (EXIT_SUCCESS); +} |