inetconv.c revision 4421e67684faea98cd9bffa503bdc3779557762f
/*
* 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 2007 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"
#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[] =
"<?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_dependency[] =
" <dependency\n"
" name='%s'\n"
" grouping='require_all'\n"
" restart_on='restart'\n"
" type='service'>\n"
" <service_fmri value='%s' />\n"
" </dependency>\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 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);
}