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
* 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 <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>
/* exit codes */
#define EXIT_SUCCESS 0 /* succeeded */
#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 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 */
int rpc_low_version;
int rpc_high_version;
char *rpc_prog;
char *groupname;
char *exec;
char *arg0;
};
struct fileinfo {
char *filename;
int lineno;
int failcnt;
};
static char *progname;
/* start of manifest XML template strings */
static const char xml_header[] =
"<?xml version='1.0'?>\n"
"<!DOCTYPE service_bundle SYSTEM "
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"
" 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 *
{
void *cp;
}
return (cp);
}
static char *
safe_strdup(char *s)
{
char *cp;
}
return (cp);
}
static char *
{
static char *buf;
int c;
char *cp;
/* free any memory allocated by a previous call */
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')) {
}
/* convert any disallowed characters into '_' */
*cp = '_';
}
return (buf);
}
static char *
{
static char *buf;
/* free any memory allocated by a previous call */
/*
* Combine the service and protocol fields to produce a unique
* manifest service name. The syntax of a service name is:
* prop(/prop)*
*/
/*
* 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.
*/
if (*cp == '.')
*cp = '_';
}
return (buf);
}
static boolean_t
{
/* returns true if protocol is an IPv6 only protocol */
return (B_TRUE);
return (B_FALSE);
}
static char *
{
static char
buf[sizeof (" service-name endpoint-type protocol wait-status")];
buf[0] = '\0';
return (buf);
}
static boolean_t
{
prop_size * sizeof (inetd_prop_t));
}
gettext("%s: failed to allocate memory: %s\n"),
}
}
"invalid or inconsistent fields:%s\n"), progname,
}
return (valid);
}
static boolean_t
{
/* set RPC version numbers */
*cp = '\0';
if (*++cp != '\0') {
errno = 0;
if (errno != 0)
goto vererr;
if (*cp == '-') {
if (*++cp == '\0')
goto vererr;
errno = 0;
&endp, 10);
goto vererr;
} else if (*cp == '\0') {
} else {
"%s: Error %s line %d invalid RPC "
"version in service: %s\n"),
}
}
}
}
/* tcp6only and udp6only are not valid in inetd.conf */
"invalid protocol: %s\n"), progname,
}
} else {
gettext("%s: Error %s line %d invalid wait-status: %s\n"),
iconf->wait_status);
}
/* look up the username to set the groupname */
gettext("%s: Error %s line %d unknown user: %s\n"),
} else {
} else {
/* use the group ID if no groupname */
char s[1];
}
}
/* check for internal services */
"%s: Error %s line %d the SUNWcnsr and SUNWcnsu"
" packages contain the internal services\n"),
} else {
"unknown internal service: %s\n"), progname,
}
"%s: Error %s line %d server-program not found: %s\n"),
}
}
static void
{
return;
}
static struct inetconfent *
line_to_inetconfent(char *line)
{
char *cp;
struct inetconfent *iconf;
goto fail;
goto fail;
goto fail;
goto fail;
goto fail;
goto fail;
/* last field is optional */
/* Combine args and server name to construct exec and args fields */
} else {
int len;
else
endp++;
/* only set arg0 property value if needed */
}
}
}
return (iconf);
fail:
return (NULL);
}
static void
{
int c;
/* skip remainder of a line */
;
}
static struct inetconfent *
{
char *cp;
struct inetconfent *iconf;
char line[MAX_SRC_LINELEN];
/* skip empty or commented out lines */
if (*line == '\n')
continue;
if (*line == '#') {
continue;
}
/* check for lines which are too long */
gettext("%s: Error %s line %d too long, skipped\n"),
continue;
}
/* remove in line comments and newline character */
if (cp)
*cp = '\0';
"%s: Error %s line %d too few fields, skipped\n"),
continue;
}
return (iconf);
}
return (NULL);
}
static char *
{
if (val)
return ("true");
return ("false");
}
static int
{
if (fprintf(f, xml_header) < 0)
goto print_err;
if (fprintf(f, xml_comment,
goto print_err;
goto print_err;
goto print_err;
if (fprintf(f, xml_instance) < 0)
goto print_err;
goto print_err;
goto print_err;
}
goto print_err;
goto print_err;
}
if (fprintf(f, xml_exec_method_end) < 0)
goto print_err;
PR_EXEC_NAME) < 0)
goto print_err;
if (fprintf(f, xml_exec_method_end) < 0)
goto print_err;
PR_EXEC_NAME) < 0)
goto print_err;
if (fprintf(f, xml_exec_method_end) < 0)
goto print_err;
}
goto print_err;
if (fprintf(f, xml_property_group_end) < 0)
goto print_err;
goto print_err;
if (fprintf(f, xml_property_group_rpc,
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,
goto print_err;
if (fprintf(f, xml_footer) < 0)
goto print_err;
return (0);
return (-1);
}
static struct fileinfo *
open_srcfile(char *filename)
{
gettext("%s: Error opening %s: %s\n"),
}
} else {
/*
* If no source file specified, do the same as inetd and first
* try /etc/inet/inetd.conf, followed by /etc/inetd.conf.
*/
gettext("%s: Error opening %s: %s\n"),
"%s: Error opening %s: %s\n"), progname,
}
}
}
}
return (finfo);
}
/*
* Opens manifest output file. Returns 0 on success, -1 if the file
* exists, -2 on other errors.
*/
static int
char *destdir,
struct inetconfent *iconf,
{
int fd;
/* if no destdir specified, use appropriate default */
else
}
/* convert any '/' chars in service or protocol to '_' chars */
*cp = '_';
0644);
if (fd == -1) {
gettext("%s: Notice: Service manifest for "
"%s already generated as %s, skipped\n"),
return (-1);
} else {
gettext("%s: Error opening %s: %s\n"),
return (-2);
}
}
/* Clear errno to catch the "no stdio streams" case */
errno = 0;
if (errno == 0)
s = gettext("No stdio streams available");
progname, s);
return (-2);
}
return (0);
}
static int
import_manifest(char *filename)
{
int status;
char *cp;
else
cp++;
gettext("\n%s: fork failed, %s not imported: %s\n"),
}
if (pid == 0) {
/* child */
}
/* parent */
"\n%s: unexpected wait (%d) from import of %s: %s\n"),
return (-1);
}
gettext("\n%s: import failure (%d) for %s\n"),
return (-1);
}
return (0);
}
static int
inetd_config_path(char **path)
{
int fd;
SCF_PROPERTY_EXEC)) == NULL)
return (-1);
return (-1);
}
/*
* Look for the optional configuration file, the syntax is:
*/
return (-1);
}
return (-1);
}
/*
* No configuration file specified, do the same as inetd and
* first try /etc/inet/inetd.conf, followed by /etc/inetd.conf.
*/
else
} else {
/* make sure there are no more arguments */
return (-1);
}
configfile = arg1;
}
/* configuration file must be an absolute pathname */
if (*configfile != '/') {
return (-1);
}
return (0);
}
static int
update_hash(char *srcfile)
{
char *inetd_cpath, *hashstr;
/* determine the config file inetd is using */
return (-1);
}
/* resolve inetconv input filename */
/* if inetconv and inetd are using the same config file, update hash */
return (0);
}
/* generic error message as use of hash is not exposed to the user */
gettext("%s: Error unable to update repository\n"),
progname);
return (-1);
}
/* generic error message as use of hash is not exposed to the user */
gettext("%s: Error updating repository: %s\n"),
return (-1);
}
return (0);
}
static void
{
gettext("Error: Instance %1$s is missing property '%2$s'.\n"),
}
/*
* 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
{
scf_value_t *v = NULL;
return (-1);
/* Get the property group */
/* Not a property of the instance, try the service instead */
ret = -1;
goto out;
}
ret = -1;
goto out;
}
}
ret = -1;
goto out;
}
if (scf_error() != SCF_ERROR_NOT_FOUND) {
ret = -1;
goto out;
}
create = 1;
}
ret = -1;
goto out;
}
do {
ret = -1;
goto out;
}
/* Modify the property */
if (create)
else
if (ret == -1)
goto out;
if ((v = scf_value_create(h)) == NULL) {
ret = -1;
goto out;
}
ret = -1;
goto out;
}
ret = -1;
goto out;
}
if (ret == 0) {
/* Property group was stale, retry */
ret = -1;
goto out;
}
}
} while (ret == 0);
out:
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
{
char *svc_name;
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.
*/
gettext("Error: Unable to obtain FMRI for service %1$s."),
svc_name);
return (SCF_FAILED);
}
SCF_PROPERTY_RESTARTER)) == NULL)
goto out;
goto out;
goto out;
/* Free restarter prop so it can be reused below */
/*
* 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.
*/
PR_SVC_NAME_NAME)) == NULL) ||
goto out;
}
goto out;
}
goto out;
}
PR_EXEC_NAME)) == NULL) ||
}
/* If it's RPC, we truncate off the version portion for comparison */
if (*isrpc) {
char *cp;
*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.
*/
/* Can't update exec on internal services */
/* User had edited the command */
if (!import) {
/* Dry run only */
(void) printf(
gettext("Would update %s to %s %s"),
(void) printf(
gettext(" with %s of %s\n"),
} else {
(void) printf("\n");
}
} else {
/* Update instance's exec property */
gettext("Error: Unable to update "
"%s property of %s, %s\n"),
scf_strerror(scf_error()));
else
(void) printf("%s will %s %s\n",
/* Update arg0 prop, if needed */
if (modify_sprop(h, inst,
gettext("Error: Unable to "
"update %s property of "
"%s, %s\n"), PR_ARG0_NAME,
scf_strerror(scf_error()));
} else {
(void) printf("%s will have an "
"%s of %s\n", svc_name,
}
}
}
}
if (!import) {
/* Dry-run only */
} else {
if (smf_enable_instance(svc_name, 0) != 0)
gettext("Error: Failed to enable %s\n"),
svc_name);
else
}
}
out:
return (SCF_SUCCESS);
}
static void
usage(void)
{
"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);
}
int
{
struct inetconfent *iconf;
(void) textdomain(TEXT_DOMAIN);
else
progname++;
switch (c) {
case 'e':
/* enable services based on existing file config */
break;
case 'f':
/* overwrite existing manifests */
break;
case 'n':
/* don't import manifests, or dry-run enable */
break;
case 'i':
/* alternate input file */
gettext("%s: Error only one -%c allowed\n"),
usage();
}
break;
case 'o':
/* alternate output directory */
gettext("%s: Error only one -%c allowed\n"),
usage();
}
break;
case '?': /*FALLTHROUGH*/
default:
usage();
break;
}
}
/*
* Display usage if extraneous args supplied or enable specified in
* combination with overwrite or destdir
*/
usage();
return (EXIT_ERROR_CONV);
/*
* If we're enabling, then just walk all the services for each
* line and enable those which match.
*/
if (enable) {
if (rval == SCF_FAILED) {
/* Only print msg if framework error */
if (scf_error() != SCF_ERROR_CALLBACK_FAILED)
"Error walking instances: %s.\n"),
scf_strerror(scf_error()));
enable_err++;
break;
}
continue;
}
/* Remainder of loop used for conversion & import */
< 0) {
/*
* Only increment error counter if the failure was
* other than the file already existing.
*/
if (rval == -2)
continue;
}
if (rval == 0) {
if (import &&
import_err++;
} else {
}
}
/* Update hash only if not in enable mode, and only if importing */
import_err++;
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);
}