/*
* 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 (c) 2012, Oracle and/or its affiliates. All rights reserved.
*/
#include <assert.h>
#include <auth_attr.h>
#include <door.h>
#include <errno.h>
#include <fcntl.h>
#include <libintl.h>
#include <libscf.h>
#include <pthread.h>
#include <pwd.h>
#include <strings.h>
#include <s10_brand.h>
#include <sys/mman.h>
#include <unistd.h>
#include <zone.h>
#include "libnetcfg.h"
#include "libnetcfg_impl.h"
/*
* Communicate with the backend daemon (running in netcfgd) to retrieve or
* change configuration.
*/
static int netcfgd_door_fd = -1;
pthread_mutex_t door_mutex = PTHREAD_MUTEX_INITIALIZER;
#define NP_FMRI "svc:/network/physical:default"
#define NP_NETCFG_PG "netcfg"
#define NP_ACTIVE_NCP_PROP "active_ncp"
/* error codes and text description */
static struct netcfg_error_info {
netcfg_error_t error_code;
const char *error_desc;
} netcfg_errors[] = {
{ NETCFG_SUCCESS, "no error" },
{ NETCFG_EPERM, "permission denied" },
{ NETCFG_INVALID_ARG, "invalid argument" },
{ NETCFG_EXISTS, "object exists" },
{ NETCFG_NOT_FOUND, "object not found" },
{ NETCFG_WALK_HALTED, "callback function halted walk" },
{ NETCFG_ERROR_BIND, "could not bind to daemon" },
{ NETCFG_NO_MEMORY, "insufficient memory" },
{ NETCFG_FAILURE, "failure" }
};
#define NETCFG_NUM_ERRORS (sizeof (netcfg_errors) / sizeof (*netcfg_errors))
/*
* Returns a message string for the given libnetcfg error code.
*/
const char *
netcfg_strerror(netcfg_error_t code)
{
int i;
for (i = 0; i < NETCFG_NUM_ERRORS; i++) {
if (code == netcfg_errors[i].error_code) {
return (dgettext(TEXT_DOMAIN,
netcfg_errors[i].error_desc));
}
}
return (dgettext(TEXT_DOMAIN, "<unknown error>"));
}
netcfg_error_t
netcfg_errno2error(int error)
{
switch (error) {
case 0:
return (NETCFG_SUCCESS);
case ENOMEM:
case ENOSPC:
return (NETCFG_NO_MEMORY);
case EFAULT:
case EINVAL:
return (NETCFG_INVALID_ARG);
case EEXIST:
return (NETCFG_EXISTS);
case ENOENT:
case ENOTDIR:
return (NETCFG_NOT_FOUND);
case EACCES:
case EPERM:
return (NETCFG_EPERM);
case EBADF:
return (NETCFG_ERROR_BIND);
default:
return (NETCFG_FAILURE);
}
}
int
netcfg_error2errno(netcfg_error_t code)
{
int err;
switch (code) {
case NETCFG_SUCCESS:
err = 0;
break;
case NETCFG_EPERM:
err = EACCES;
break;
case NETCFG_INVALID_ARG:
err = EINVAL;
break;
case NETCFG_EXISTS:
err = EEXIST;
break;
case NETCFG_NOT_FOUND:
err = ENOENT;
break;
case NETCFG_WALK_HALTED:
err = ECANCELED;
break;
case NETCFG_NO_MEMORY:
err = ENOMEM;
break;
case NETCFG_ERROR_BIND:
case NETCFG_FAILURE:
default:
/* no good analog for these */
err = EINVAL;
break;
}
return (err);
}
/*
* Read the "netcfg/active_ncp" property from svc:/network/physical:default
*/
netcfg_error_t
netcfg_active_profile(char *profile, size_t pbuflen)
{
zoneid_t zoneid = getzoneid();
char brand[MAXNAMELEN];
scf_simple_prop_t *prop;
*profile = NULL;
/* Need to skip if we're in an S10-branded zone */
if (zoneid != GLOBAL_ZONEID) {
if (zone_getattr(zoneid, ZONE_ATTR_BRAND, brand,
sizeof (brand)) < 0) {
return (netcfg_errno2error(errno));
}
if (strcmp(brand, S10_BRANDNAME) == 0) {
(void) strlcpy(profile, S10_BRAND_DUMMY_PROFILE,
pbuflen);
return (NETCFG_SUCCESS);
}
}
prop = scf_simple_prop_get(NULL, NP_FMRI, NP_NETCFG_PG,
NP_ACTIVE_NCP_PROP);
if (prop == NULL)
return (NETCFG_FAILURE);
if (scf_simple_prop_numvalues(prop) > 0) {
(void) strlcpy(profile, scf_simple_prop_next_astring(prop),
pbuflen);
}
scf_simple_prop_free(prop);
return (NETCFG_SUCCESS);
}
/*
* Read nvlists from the door argument that is returned by the door call to
* netcfgd. If the arg datalen is non-zero, unpack the object nvlist.
*/
netcfg_error_t
netcfg_read_lists_from_door_arg(netcfgd_door_arg_t *arg, nvlist_t **idlist,
nvlist_t **objp)
{
caddr_t idptr, dataptr;
int nverr;
if (arg->nda_result != NETCFG_SUCCESS)
return (arg->nda_result);
idptr = (caddr_t)arg + sizeof (netcfgd_door_arg_t);
if ((nverr = nvlist_unpack((char *)idptr, arg->nda_idlen, idlist, 0))
!= 0)
return (netcfg_errno2error(nverr));
if (arg->nda_datalen > 0) {
dataptr = idptr + arg->nda_idlen;
if ((nverr = nvlist_unpack((char *)dataptr, arg->nda_datalen,
objp, 0)) != 0)
return (netcfg_errno2error(nverr));
}
return (NETCFG_SUCCESS);
}
/*
* Set up door arguments and make the door_call().
*/
static int
netcfg_make_door_call(void *arg, size_t asize, void **rbufp)
{
door_arg_t door_args;
struct door_info dinfo;
int err;
door_args.data_ptr = arg;
door_args.data_size = asize;
door_args.desc_ptr = NULL;
door_args.desc_num = 0;
/*
* Don't pass in a return buffer. The door server will allocate the
* return buffer. The rsize must be > 0, however, else the door acts
* as if no response was expected and doesn't pass back anything.
*/
door_args.rbuf = NULL;
door_args.rsize = 1;
(void) pthread_mutex_lock(&door_mutex);
if (netcfgd_door_fd != -1) {
/* Check door fd is not old (from previous netcfgd) */
if (door_info(netcfgd_door_fd, &dinfo) != 0 ||
(dinfo.di_attributes & DOOR_REVOKED) != 0) {
(void) close(netcfgd_door_fd);
netcfgd_door_fd = -1;
}
}
if (netcfgd_door_fd == -1) {
if ((netcfgd_door_fd = open(NETCFGD_DOOR_FILE, O_RDONLY))
== -1) {
err = errno;
(void) pthread_mutex_unlock(&door_mutex);
return (err);
}
}
(void) pthread_mutex_unlock(&door_mutex);
if ((err = door_call(netcfgd_door_fd, &door_args)) == -1)
return (errno);
if (door_args.rbuf != NULL) {
/*
* The size of *rbufp was not big enough and the door itself
* rellocated the return buffer. Reallocate *rbufp and copy
* the contents to the new buffer.
*/
if ((*rbufp = malloc(door_args.rsize)) == NULL)
err = ENOMEM;
else
(void) memcpy(*rbufp, door_args.rbuf, door_args.rsize);
/* munmap() the door buffer */
(void) munmap(door_args.rbuf, door_args.rsize);
}
return (err);
}
/*
* Set up arguments for the door call and read in results returned from the
* door call.
*/
static netcfg_error_t
netcfg_door_call(netcfgd_door_cmd_t cmd, nvlist_t **idlist, uint64_t flags,
nvlist_t **obj)
{
netcfgd_door_arg_t *dargp, *rargp = NULL;
char *buf = NULL, *idnvlbuf = NULL, *nvlbuf = NULL;
char *idp, *datap;
size_t bufsize, idnvlsize, nvlsize = 0;
netcfg_error_t err = NETCFG_SUCCESS;
int nverr, ierr;
/* pack the nvlist of identifiers */
nverr = nvlist_pack(*idlist, &idnvlbuf, &idnvlsize, NV_ENCODE_XDR, 0);
if (nverr != 0) {
err = netcfg_errno2error(nverr);
goto done;
}
/* For the commands that pass object data as well, pack the objlist */
if (cmd == NETCFGD_DOOR_CMD_UPDATE_REQ ||
cmd == NETCFGD_DOOR_CMD_RENAME_REQ ||
cmd == NETCFGD_DOOR_CMD_GETPROP_REQ ||
cmd == NETCFGD_DOOR_CMD_DBCREATE_REQ) {
nverr = nvlist_pack(*obj, &nvlbuf, &nvlsize, NV_ENCODE_XDR, 0);
if (nverr != 0) {
err = netcfg_errno2error(nverr);
goto done;
}
/* obj will be filled in when the returned nvlist is unpacked */
if (cmd == NETCFGD_DOOR_CMD_GETPROP_REQ) {
nvlist_free(*obj);
*obj = NULL;
}
}
bufsize = sizeof (netcfgd_door_arg_t) + idnvlsize + nvlsize;
if ((buf = calloc(1, bufsize)) == NULL) {
err = netcfg_errno2error(errno);
goto done;
}
/* populate the door call argument structure */
dargp = (void *)buf;
dargp->nda_cmd = cmd;
dargp->nda_idlen = idnvlsize;
dargp->nda_datalen = nvlsize;
dargp->nda_result = NETCFG_SUCCESS;
dargp->nda_flags = flags;
/* add the packed id nvlist */
idp = buf + sizeof (netcfgd_door_arg_t);
(void) bcopy(idnvlbuf, idp, idnvlsize);
/* and the packed data nvlist for updates */
datap = idp + idnvlsize;
if (nvlsize > 0)
(void) bcopy(nvlbuf, datap, nvlsize);
ierr = netcfg_make_door_call(dargp, bufsize, (void **)&rargp);
if (ierr != 0) {
err = netcfg_errno2error(ierr);
goto done;
}
/* Check for an error return */
if (rargp != NULL && rargp->nda_result != NETCFG_SUCCESS) {
err = rargp->nda_result;
goto done;
}
/* If reading from file, parse out the return buffer */
if ((cmd == NETCFGD_DOOR_CMD_READ_REQ ||
cmd == NETCFGD_DOOR_CMD_GETPROP_REQ) && rargp != NULL) {
nvlist_free(*idlist);
*idlist = NULL;
err = netcfg_read_lists_from_door_arg(rargp, idlist, obj);
}
done:
free(idnvlbuf);
free(nvlbuf);
free(buf);
free(rargp);
return (err);
}
/*
* Read object specified by the passed-in idlist, retrieving an object
* list representation.
*
* If idlist is NULL, obj is a list of string arrays consisting of the list
* of DBs.
*
* If object-db-name is specified, but no object identifiers are included
* in idlist, read all objects in the specified dbname and create an object
* list containing a string array which represents each object.
*
* Otherwise, obj will point to a list of the properties for the object
* specified by idlist.
*/
netcfg_error_t
netcfg_read_object(nvlist_t **idlist, uint64_t flags, nvlist_t **obj)
{
return (netcfg_door_call(NETCFGD_DOOR_CMD_READ_REQ, idlist, flags,
obj));
}
/*
* Read in all objects from the DB and object specified by idlist and update
* with the properties recorded in obj, writing the results to the DB.
*/
netcfg_error_t
netcfg_update_object(nvlist_t **idlist, uint64_t flags, nvlist_t **obj)
{
return (netcfg_door_call(NETCFGD_DOOR_CMD_UPDATE_REQ, idlist, flags,
obj));
}
/*
* Remove the object specified by cur_idlist from the DB, and create a new
* entry with the read-in property list and the passed-in new_idlist.
*/
netcfg_error_t
netcfg_rename_object(nvlist_t **cur_idlist, uint64_t flags,
nvlist_t **new_idlist)
{
return (netcfg_door_call(NETCFGD_DOOR_CMD_RENAME_REQ, cur_idlist,
flags, new_idlist));
}
/*
* Remove specified object from the DB by reading in the list of objects,
* removing the object identified by idlist, and writing the remainder.
*
* If idlist only identifies the DB, remove the DB altogether.
*/
netcfg_error_t
netcfg_remove_object(nvlist_t **idlist, uint64_t flags)
{
return (netcfg_door_call(NETCFGD_DOOR_CMD_REMOVE_REQ, idlist, flags,
NULL));
}
/*
* Read the specified object, then fill in appropriate values for the
* properties passed in in objlist. If a passed-in property is not present
* in the db entry, remove it from the list before returning.
*/
netcfg_error_t
netcfg_get_object_props(nvlist_t **idlist, uint64_t flags, nvlist_t **objlist)
{
return (netcfg_door_call(NETCFGD_DOOR_CMD_GETPROP_REQ, idlist, flags,
objlist));
}
/*
* Call the provided callback for each object in DB (specified by a dbname
* pair in the idlist), passing in the object and the provided argument.
*
* If selcb is specified, it will be called first for each entry, and will
* be passed both the idlist and objlist, as well as the flags. It should
* return 0 or non-zero to signify whether or not cb should be called for
* that particular entry; it may optionally return an alternate object arg
* to be passed to cb instead of the objlist. This allows libraries which
* consume this generic interface to implement library-specific filtering
* for walk functions which they in turn export.
*/
netcfg_error_t
netcfg_walk_db(nvlist_t **idlist, uint64_t flags, netcfg_walkcb_t *cb,
void *arg, netcfg_selectcb_t *selcb, netcfg_freecb_t *freecb, int *retp)
{
netcfg_error_t nerr;
int nverr, i;
uint_t objcnt;
nvlist_t *nvlist, *objp;
nvpair_t *objlist;
nvlist_t **objids;
boolean_t xzoneop;
zoneid_t dbzoneid, myzoneid = getzoneid();
assert(cb != NULL);
/*
* Calling netcfg_read_object() with an idlist containing just a
* dbname pair (and optional zoneid pair) returns an nvlist with
* one member: the NETCFG_OBJECT_ID_LIST; this object's value is
* an array of nvlists, where each nvlist is the list of
* identifiers for an object in the db.
*/
if ((nerr = netcfg_read_object(idlist, flags, &nvlist))
!= NETCFG_SUCCESS)
return (nerr);
if ((nverr = nvlist_lookup_nvpair(nvlist, NETCFG_OBJECT_ID_LIST,
&objlist)) != 0 || (nverr = nvpair_value_nvlist_array(objlist,
&objids, &objcnt)) != 0) {
nvlist_free(nvlist);
return (netcfg_errno2error(nverr));
}
dbzoneid = zoneid_from_idlist(*idlist);
xzoneop = (dbzoneid <= MAX_ZONEID && dbzoneid != myzoneid);
if (xzoneop && myzoneid != GLOBAL_ZONEID) {
nvlist_free(nvlist);
return (NETCFG_INVALID_ARG);
}
for (i = 0; i < objcnt; i++) {
int cbret;
void *cbobjp = NULL;
if (xzoneop) {
nverr = nvlist_add_int64(objids[i],
NETCFG_OBJECT_DB_ZONE, dbzoneid);
if (nverr != 0)
goto done;
}
nerr = netcfg_read_object(&objids[i], flags, &objp);
if (nerr == NETCFG_NOT_FOUND)
continue;
if (nerr != NETCFG_SUCCESS)
goto done;
if (selcb == NULL ||
selcb(objids[i], objp, flags, &cbobjp) == 0) {
cbret = cb(cbobjp == NULL ? objp : cbobjp, arg);
/* now call the freecb() routine */
if (freecb != NULL)
freecb(&cbobjp);
if (cbret != 0) {
if (retp != NULL)
*retp = cbret;
nerr = NETCFG_WALK_HALTED;
nvlist_free(objp);
goto done;
}
}
nvlist_free(objp);
}
done:
nvlist_free(nvlist);
return (nerr);
}
/*
* Walk a db, calling the provided callback for each link in the file.
* Passes the raw data string representing the current line to the
* callback, as well as a caller-specified argument.
*/
netcfg_error_t
netcfg_walk_db_raw(nvlist_t **idlist, uint64_t flags, netcfg_rawcb_t cb,
void *arg)
{
netcfg_error_t nerr;
int nverr, i;
uint_t bufcnt;
nvlist_t *nvlist;
nvpair_t *objlist;
char **bufs;
netcfg_error_t retval = NETCFG_SUCCESS;
assert(cb != NULL);
/*
* Calling netcfg_read_object() with an idlist containing just a
* dbname pair and NETCFG_DB_RAW_FLAG set returns an nvlist with
* one member: the NETCFG_OBJECT_RAW_BUFS; this object's value is
* an array of strings, where each string in the array maps to a
* single line from the file.
*/
flags |= NETCFG_DB_RAW_FLAG;
if ((nerr = netcfg_read_object(idlist, flags, &nvlist))
!= NETCFG_SUCCESS) {
if (nerr == NETCFG_NOT_FOUND) {
/*
* The dbname container is not present; this is not
* an error case, it just means the db is empty, so
* there is nothing to walk.
*/
return (NETCFG_SUCCESS);
}
return (nerr);
}
if (((nverr = nvlist_lookup_nvpair(nvlist, NETCFG_OBJECT_RAW_BUFS,
&objlist)) != 0) || ((nverr = nvpair_value_string_array(objlist,
&bufs, &bufcnt)) != 0)) {
nvlist_free(nvlist);
return (netcfg_errno2error(nverr));
}
for (i = 0; i < bufcnt; i++) {
int cbret;
cbret = cb(bufs[i], arg);
if (cbret != 0) {
retval = NETCFG_WALK_HALTED;
break;
}
}
done:
nvlist_free(nvlist);
return (retval);
}
/*
* Create a new (or replace an existing) database. The new db is identified
* by the passed-in idlist; its contents are specified in the dblist param.
* This nvlist should contain an nvpair for each db entry; the name should
* be the string representation of the entry's idlist (the output of
* netcfg_idl2idstr()), the value should be an nvlist containing the entry
* properties.
*/
netcfg_error_t
netcfg_create_db(nvlist_t **idlist, uint64_t flags, nvlist_t **dblist)
{
return (netcfg_door_call(NETCFGD_DOOR_CMD_DBCREATE_REQ, idlist, flags,
dblist));
}
/*
* Destroy the database specified by the passed-in idlist.
*/
netcfg_error_t
netcfg_destroy_db(nvlist_t **idlist)
{
return (netcfg_door_call(NETCFGD_DOOR_CMD_DBDESTROY_REQ, idlist, 0,
NULL));
}
netcfg_error_t
netcfg_backup_db(nvlist_t **idlist)
{
return (netcfg_door_call(NETCFGD_DOOR_CMD_DBBACKUP_REQ, idlist, 0,
NULL));
}
netcfg_error_t
netcfg_restore_db(nvlist_t **idlist)
{
return (netcfg_door_call(NETCFGD_DOOR_CMD_DBRESTORE_REQ, idlist, 0,
NULL));
}
netcfg_error_t
netcfg_destroy_backup_db(nvlist_t **idlist)
{
return (netcfg_door_call(NETCFGD_DOOR_CMD_DBDESTROYBACKUP_REQ, idlist,
0, NULL));
}