/* * 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. */ /* * inetconv - convert inetd.conf entries into smf(5) service manifests, * import them into smf(5) repository */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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 "/lib/svc/manifest/network" #define MANIFEST_RPC_DIR MANIFEST_DIR "/rpc" #define SVCCFG_PATH "/usr/sbin/svccfg" #define RPCBIND_FMRI "svc:/network/rpc/bind" /* 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[] = "\n" "\n"; static const char xml_comment[] = "\n\n"; static const char xml_service_bundle[] = "\n\n"; static const char xml_service_name[] = "\n\n"; static const char xml_dependency[] = " \n" " \n" " \n\n"; static const char xml_instance[] = " \n\n"; static const char xml_restarter[] = " \n" " \n" " \n\n"; static const char xml_exec_method_start[] = " \n" " \n" " \n" " \n" " \n"; static const char xml_arg0[] = " \n"; static const char xml_exec_method_end[] = " \n\n"; static const char xml_exec_method_disable[] = " \n" " \n"; static const char xml_exec_method_offline[] = " \n" " \n"; static const char xml_inetconv_group_start[] = " \n" " \n" " \n" " \n" " \n"; static const char xml_property_group_start[] = " \n" " \n" " \n" " \n" " \n" " \n"; static const char xml_property_group_rpc[] = " \n" " " "\n"; static const char xml_property_group_end[] = " \n\n"; static const char xml_stability[] = " \n\n"; static const char xml_template[] = " \n"; static const char xml_footer[] = "\n" "\n" "\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 it's 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 disallowed 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 *cp, *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"; /* * SMF service names may not contain '.', but IANA services do * allow its use, and property names can contain '.' as returned * by propertyname(). So if the resultant SMF service name * would contain a '.' we fix it here. */ for (cp = buf; *cp != '\0'; cp++) { if (*cp == '.') *cp = '_'; } (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); } 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)); put_prop_value_boolean(prop, PR_ISRPC_NAME, iconf->isrpc); put_prop_value_boolean(prop, PR_ISWAIT_NAME, iconf->wait); if (iconf->isrpc) { put_prop_value_int(prop, PR_RPC_LW_VER_NAME, iconf->rpc_low_version); put_prop_value_int(prop, PR_RPC_HI_VER_NAME, iconf->rpc_high_version); svc_name = iconf->rpc_prog; proto += 4; /* skip 'rpc/' */ } if (!put_prop_value_string(prop, PR_SOCK_TYPE_NAME, iconf->endpoint) || !put_prop_value_string(prop, PR_SVC_NAME_NAME, svc_name)) { valid = B_FALSE; if (errno == ENOMEM) { (void) fprintf(stderr, gettext("%s: failed to allocate memory: %s\n"), progname, strerror(errno)); exit(EXIT_ERROR_SYS); } } put_prop_value_string_list(prop, PR_PROTO_NAME, get_protos(proto)); 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 (iconf->isrpc) { if (fprintf(f, xml_dependency, "rpcbind", RPCBIND_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); }