/*
* 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) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*/
#include <errno.h>
#include <fcntl.h>
#include <libdllink.h>
#include <libintl.h>
#include <libnwam.h>
#include <locale.h>
#include <priv.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <libnwam.h>
#include "conditions.h"
#include "events.h"
#include "llp.h"
#include "ncp.h"
#include "objects.h"
#include "util.h"
/*
* nwamd - NetWork Auto-Magic Daemon
*/
boolean_t fg = B_FALSE;
dladm_handle_t dld_handle = NULL;
ipadm_handle_t ipadm_handle = NULL;
boolean_t shutting_down = B_FALSE;
sigset_t original_sigmask;
static sigset_t sigwaitset;
static void nwamd_refresh(void);
static void graceful_shutdown(void);
/*
* nwamd
*
* This is the Network Auto-Magic daemon. For further high level information
* see the Network Auto-Magic project and the Approachability communities
* on opensolaris.org, nwamd(1M), and the README in the source directory.
*
* The general structure of the code is as a set of event source threads
* which feed events into the event handling thread. Some of these events
* are internal-only (e.g UPGRADE), but some also involve propogation
* to external listeners (who register via a door call into the daemon).
*
* signal management
* Due to being threaded, a simple set of signal handlers would not work
* very well for nwamd. Instead nwamd blocks signals in all but the
* signal handling thread at startup.
*
*/
/*
* In this file there are several utility functions which might otherwise
* belong in util.c, but since they are only called from main(), they can
* live here as static functions:
* - nlog set-up
* - daemonizing
* - looking up SMF(5) properties
* - signal handling
* - managing privileges(5)
*/
static void
start_logging(void)
{
openlog("nwamd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
}
static void
daemonize(void)
{
pid_t pid;
/*
* A little bit of magic here. By the first fork+setsid, we
* disconnect from our current controlling terminal and become
* a session group leader. By forking again without calling
* setsid again, we make certain that we are not the session
* group leader and can never reacquire a controlling terminal.
*/
if ((pid = fork()) == (pid_t)-1)
pfail("fork 1 failed");
if (pid != 0) {
(void) wait(NULL);
nlog(LOG_DEBUG, "child %ld exited, daemonizing", pid);
_exit(0);
}
if (setsid() == (pid_t)-1)
pfail("setsid");
if ((pid = fork()) == (pid_t)-1)
pfail("fork 2 failed");
if (pid != 0) {
_exit(0);
}
(void) chdir("/");
(void) umask(022);
}
/* ARGSUSED */
static void *
sighandler(void *arg)
{
int sig;
while (!shutting_down) {
sig = sigwait(&sigwaitset);
nlog(LOG_DEBUG, "signal %s caught", strsignal(sig));
switch (sig) {
case SIGTHAW:
case SIGHUP:
/*
* Resumed from suspend or refresh. Clear up all
* objects so their states start from scratch;
* then refresh().
*/
nwamd_fini_enms();
nwamd_fini_ncus();
nwamd_fini_locs();
nwamd_refresh();
break;
case SIGUSR1:
/*
* Undocumented "log ncu list" signal.
*/
nwamd_log_ncus();
break;
case SIGTERM:
nlog(LOG_DEBUG, "%s received, shutting down",
strsignal(sig));
graceful_shutdown();
break;
default:
nlog(LOG_DEBUG, "unexpected signal %s received, "
"ignoring", strsignal(sig));
break;
}
}
return (NULL);
}
static void
init_signalhandling(void)
{
pthread_attr_t attr;
pthread_t sighand;
int err;
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (err = pthread_create(&sighand, &attr, sighandler, NULL)) {
nlog(LOG_ERR, "pthread_create system: %s", strerror(err));
exit(EXIT_FAILURE);
} else {
nlog(LOG_DEBUG, "signal handler thread: %d", sighand);
}
(void) pthread_attr_destroy(&attr);
}
/*
* Construct the set of signals that we explicitly want to deal with.
* We block these while we're still single-threaded; this block will
* be inherited by all the threads we create. When we are ready to
* start handling signals, we will start the signal handling thread,
* which will sigwait() this same set of signals, and will thus receive
* and handle any that are sent to the process.
*/
static void
block_signals(void)
{
(void) sigemptyset(&sigwaitset);
(void) sigaddset(&sigwaitset, SIGHUP);
(void) sigaddset(&sigwaitset, SIGUSR1);
(void) sigaddset(&sigwaitset, SIGUSR2);
(void) sigaddset(&sigwaitset, SIGTERM);
(void) sigaddset(&sigwaitset, SIGTHAW);
(void) pthread_sigmask(SIG_BLOCK, &sigwaitset, &original_sigmask);
}
/*
* Look up nwamd property values and set daemon variables appropriately.
* This function will be called on startup and via the signal handling
* thread on receiving a HUP (which occurs when the nwam service is
* refreshed).
*/
static void
lookup_daemon_properties(void)
{
char *active_ncp_tmp;
char *scan_level_tmp;
(void) nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG,
OUR_DEBUG_PROP_NAME, &debug);
(void) nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG,
OUR_AUTOCONF_PROP_NAME, &wireless_autoconf);
(void) nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG,
OUR_STRICT_BSSID_PROP_NAME, &wireless_strict_bssid);
(void) pthread_mutex_lock(&active_ncp_mutex);
if ((active_ncp_tmp = malloc(NWAM_MAX_NAME_LEN)) == NULL ||
nwamd_lookup_string_property(OUR_FMRI, OUR_PG,
OUR_ACTIVE_NCP_PROP_NAME, active_ncp_tmp, NWAM_MAX_NAME_LEN) != 0) {
(void) strlcpy(active_ncp, NWAM_NCP_NAME_AUTOMATIC,
NWAM_MAX_NAME_LEN);
} else {
(void) strlcpy(active_ncp, active_ncp_tmp, NWAM_MAX_NAME_LEN);
}
(void) pthread_mutex_unlock(&active_ncp_mutex);
free(active_ncp_tmp);
if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
OUR_CONDITION_CHECK_INTERVAL_PROP_NAME,
&condition_check_interval) != 0)
condition_check_interval = CONDITION_CHECK_INTERVAL_DEFAULT;
if ((scan_level_tmp = malloc(NWAM_MAX_NAME_LEN)) == NULL ||
nwamd_lookup_string_property(OUR_FMRI, OUR_PG,
OUR_WIRELESS_SCAN_LEVEL_PROP_NAME, scan_level_tmp,
NWAM_MAX_NAME_LEN) != 0) {
wireless_scan_level = WIRELESS_SCAN_LEVEL_DEFAULT;
} else {
if (dladm_wlan_str2strength(scan_level_tmp,
&wireless_scan_level) != DLADM_STATUS_OK)
wireless_scan_level = DLADM_WLAN_STRENGTH_VERY_WEAK;
}
free(scan_level_tmp);
if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
OUR_WIRELESS_SCAN_INTERVAL_PROP_NAME, &wireless_scan_interval) != 0)
wireless_scan_interval = WIRELESS_SCAN_INTERVAL_DEFAULT;
if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
OUR_NCU_WAIT_TIME_PROP_NAME, &ncu_wait_time) != 0)
ncu_wait_time = NCU_WAIT_TIME_DEFAULT;
nlog(LOG_DEBUG, "Read daemon configuration properties.");
}
/*
* Re-read the SMF properties.
* Reset ncu priority group (since the NCUs will have to walk
* through their state machines again) and schedule a check
* Re-read objects from libnwam.
* Also, run condition checking for locations and ENMs.
*/
static void
nwamd_refresh(void)
{
lookup_daemon_properties();
(void) pthread_mutex_lock(&active_ncp_mutex);
current_ncu_priority_group = INVALID_PRIORITY_GROUP;
(void) pthread_mutex_unlock(&active_ncp_mutex);
nwamd_init_ncus();
nwamd_init_enms();
nwamd_init_locs();
nwamd_create_ncu_check_event(0);
nwamd_create_triggered_condition_check_event(0);
}
static void
graceful_shutdown(void)
{
nwamd_event_t event;
shutting_down = B_TRUE;
nwamd_event_sources_fini();
nwamd_door_fini();
nwamd_fini_enms();
nwamd_fini_ncus();
nwamd_fini_locs();
event = nwamd_event_init_shutdown();
if (event == NULL)
pfail("nwamd could not create shutdown event, exiting");
nwamd_event_enqueue(event);
}
int
main(int argc, char *argv[])
{
int c;
uint64_t version;
nwamd_event_t event;
dladm_status_t drc;
ipadm_status_t irc;
uid_t uid = getuid();
/*
* Block the signals we care about (and which might cause us to
* exit based on default disposition) until we're ready to start
* handling them properly...see init_signalhandling() below.
*/
block_signals();
if (uid != UID_NETADM && uid != 0) {
/*
* This shouldn't happen normally. On upgrade the service might
* need reloading.
*/
pfail("nwamd should run as uid %d, not uid %d\n", UID_NETADM,
uid);
}
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
start_logging();
nlog(LOG_INFO, "nwamd pid %d started", getpid());
while ((c = getopt(argc, argv, "fs:")) != -1) {
switch (c) {
case 'f':
fg = B_TRUE;
break;
default:
nlog(LOG_ERR, "unrecognized option %c",
optopt);
break;
}
}
lookup_daemon_properties();
if (!fg)
daemonize();
/*
* The dladm handle *must* be opened before privileges are dropped.
* The device privilege requirements, which are stored in
* /etc/security/device_policy, may not be loaded yet, as that's
* done by svc:/system/filesystem/root. If they are not loaded,
* then one must have *all* privs in order to open /dev/dld, which
* is one of the steps performed in dladm_open().
*/
drc = dladm_open(&dld_handle);
if (drc != DLADM_STATUS_OK) {
char status_str[DLADM_STRSIZE];
pfail("failed to open dladm handle: %s",
dladm_status2str(drc, status_str));
}
irc = ipadm_open(&ipadm_handle, 0);
if (irc != IPADM_SUCCESS)
pfail("failed to open ipadm handle: %s", ipadm_status2str(irc));
/*
* Create the event queue before starting event sources, including
* signal handling, so we are ready to handle incoming events. Also
* start before attempting to upgrade, in case there's a problem
* upgrading and we need to retry (in which case we schedule an event
* to do so).
*/
nwamd_event_queue_init();
/*
* Handle upgrade of legacy config. Absence of version property
* (which did not exist in phase 0 or 0.5) is the indication that
* we need to upgrade to phase 1 (version 1).
*/
if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG, OUR_VERSION_PROP_NAME,
&version) != 0)
nwamd_handle_upgrade(NULL);
/*
* Initialize lists handling internal representations of objects.
*/
nwamd_object_lists_init();
init_signalhandling();
/* Enqueue init event */
event = nwamd_event_init_init();
if (event == NULL)
pfail("nwamd could not create init event, exiting");
nwamd_event_enqueue(event);
/*
* Collect initial user configuration.
*/
/*
* Walk the physical interfaces and update the Automatic NCP to
* contain the IP and link NCUs for the interfaces that exist in
* the system.
*/
nwamd_walk_physical_configuration();
/*
* We should initialize the door at the point that we can respond to
* user requests about the system but before we start actually process
* state changes or effecting the system.
*/
nwamd_door_init();
/*
* Initialize data objects.
*
* Enabling an NCP involves refreshing nwam, which initializes the
* objects (ncu, enm, loc, known wlan). Thus, no need to
* explicitly initialize these objects here. The refresh also
* enqueues and NCU activation checking event. Location and ENM
* condition checking are triggered by changes in NCU states.
*/
(void) pthread_mutex_lock(&active_ncp_mutex);
if (nwamd_ncp_action(active_ncp, NWAM_ACTION_ENABLE) != 0)
pfail("Initial enable failed for active NCP %s", active_ncp);
(void) pthread_mutex_unlock(&active_ncp_mutex);
/*
* Enqueue an event to start periodic checking of activation conditions.
*/
nwamd_create_timed_condition_check_event();
/*
* These two routines safely minimize our privilege set. They
* use reference counting to be safe in a threaded program. It is
* gross that we escalate/deescalate to initialize this functionality
* but a real fix is to add functionality to do fine grained privs
* (and if necessary set uid to 0) in this threaded daemon.
*/
nwamd_escalate();
nwamd_deescalate();
/*
* Start the various agents (hooks on fds, threads) which collect events
*/
nwamd_event_sources_init();
/*
* nwamd_event_handler() only returns on shutdown.
*/
nwamd_event_handler();
ipadm_close(ipadm_handle);
dladm_close(dld_handle);
return (EXIT_SUCCESS);
}