1d71dbb73Sjbeck /* 2d71dbb73Sjbeck * CDDL HEADER START 3d71dbb73Sjbeck * 4d71dbb73Sjbeck * The contents of this file are subject to the terms of the 5d71dbb73Sjbeck * Common Development and Distribution License (the "License"). 6d71dbb73Sjbeck * You may not use this file except in compliance with the License. 7d71dbb73Sjbeck * 8d71dbb73Sjbeck * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9d71dbb73Sjbeck * or http://www.opensolaris.org/os/licensing. 10d71dbb73Sjbeck * See the License for the specific language governing permissions 11d71dbb73Sjbeck * and limitations under the License. 12d71dbb73Sjbeck * 13d71dbb73Sjbeck * When distributing Covered Code, include this CDDL HEADER in each 14d71dbb73Sjbeck * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15d71dbb73Sjbeck * If applicable, add the following below this CDDL HEADER, with the 16d71dbb73Sjbeck * fields enclosed by brackets "[]" replaced with your own identifying 17d71dbb73Sjbeck * information: Portions Copyright [yyyy] [name of copyright owner] 18d71dbb73Sjbeck * 19d71dbb73Sjbeck * CDDL HEADER END 20d71dbb73Sjbeck */ 21d71dbb73Sjbeck 22d71dbb73Sjbeck /* 23*b00044a2SJames Carlson * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24d71dbb73Sjbeck * Use is subject to license terms. 25d71dbb73Sjbeck */ 26d71dbb73Sjbeck 27d71dbb73Sjbeck /* 28d71dbb73Sjbeck * nwamd - NetWork Auto-Magic Daemon 29d71dbb73Sjbeck */ 30d71dbb73Sjbeck 31d71dbb73Sjbeck #include <fcntl.h> 32d71dbb73Sjbeck #include <priv.h> 33d71dbb73Sjbeck #include <pthread.h> 34d71dbb73Sjbeck #include <pwd.h> 35d71dbb73Sjbeck #include <stdio.h> 36d71dbb73Sjbeck #include <stdlib.h> 37d71dbb73Sjbeck #include <string.h> 38d71dbb73Sjbeck #include <signal.h> 39d71dbb73Sjbeck #include <sys/stat.h> 40d71dbb73Sjbeck #include <sys/types.h> 41d71dbb73Sjbeck #include <sys/wait.h> 42d71dbb73Sjbeck #include <syslog.h> 43d71dbb73Sjbeck #include <unistd.h> 44d71dbb73Sjbeck #include <locale.h> 45d71dbb73Sjbeck #include <libintl.h> 46d71dbb73Sjbeck #include <errno.h> 47d71dbb73Sjbeck 48d71dbb73Sjbeck #include "defines.h" 49d71dbb73Sjbeck #include "structures.h" 50d71dbb73Sjbeck #include "functions.h" 51d71dbb73Sjbeck #include "variables.h" 52d71dbb73Sjbeck 53d71dbb73Sjbeck #define TIMESPECGT(x, y) ((x.tv_sec > y.tv_sec) || \ 54d71dbb73Sjbeck ((x.tv_sec == y.tv_sec) && (x.tv_nsec > y.tv_nsec))) 55d71dbb73Sjbeck 56d71dbb73Sjbeck const char *OUR_FMRI = "svc:/network/physical:nwam"; 57d71dbb73Sjbeck const char *OUR_PG = "nwamd"; 58d71dbb73Sjbeck 59d71dbb73Sjbeck boolean_t fg = B_FALSE; 60d71dbb73Sjbeck boolean_t shutting_down; 61d71dbb73Sjbeck sigset_t original_sigmask; 62d71dbb73Sjbeck char zonename[ZONENAME_MAX]; 63*b00044a2SJames Carlson pthread_mutex_t machine_lock = PTHREAD_MUTEX_INITIALIZER; 64d71dbb73Sjbeck 65d71dbb73Sjbeck /* 66d71dbb73Sjbeck * nwamd 67d71dbb73Sjbeck * 68d71dbb73Sjbeck * This is the Network Auto-Magic daemon. For further high level information 69d71dbb73Sjbeck * see the Network Auto-Magic project and the Approachability communities 70d71dbb73Sjbeck * on opensolaris.org, and nwamd(1M). 71d71dbb73Sjbeck * 72d71dbb73Sjbeck * The general structure of the code is as a set of threads collecting 73d71dbb73Sjbeck * system events which are fed into a state machine which alters system 74d71dbb73Sjbeck * state based on configuration. 75d71dbb73Sjbeck * 76d71dbb73Sjbeck * signal management 77d71dbb73Sjbeck * Due to being threaded, a simple set of signal handlers would not work 78d71dbb73Sjbeck * very well for nwamd. Instead nwamd blocks signals at startup and 79d71dbb73Sjbeck * then starts a thread which sits in sigwait(2) waiting for signals. 80d71dbb73Sjbeck * When a signal is received the signal handling thread dispatches it. 81d71dbb73Sjbeck * It handles: 82d71dbb73Sjbeck * - shutting down, done by creating an event which is passed through the 83d71dbb73Sjbeck * system allowing the various subsystems to do any necessary cleanup. 84d71dbb73Sjbeck * - SIGALRM for timers. 85d71dbb73Sjbeck * - SIGHUP for instance refresh, which tells us to look up various 86d71dbb73Sjbeck * properties from SMF(5). 87d71dbb73Sjbeck * 88d71dbb73Sjbeck * subprocess management 89d71dbb73Sjbeck * nwamd starts several different subprocesses to manage the system. Some 90d71dbb73Sjbeck * of those start other processes (e.g. `ifconfig <if> dhcp` ends up starting 91d71dbb73Sjbeck * dhcpagent if necessary). Due to the way we manage signals if we started 92d71dbb73Sjbeck * those up without doing anything special their signal mask would mostly 93d71dbb73Sjbeck * block signals. So we restore the signal mask when we start subprocesses. 94d71dbb73Sjbeck * This is especially important with respect to DHCP as later when we exit 95d71dbb73Sjbeck * we need to kill the dhcpagent process which we started; for details, see 96d71dbb73Sjbeck * the block comment in state_machine.c in its cleanup() function. 97d71dbb73Sjbeck */ 98d71dbb73Sjbeck 99d71dbb73Sjbeck /* 100d71dbb73Sjbeck * In this file there are several utility functions which might otherwise 101d71dbb73Sjbeck * belong in util.c, but since they are only called from main(), they can 102d71dbb73Sjbeck * live here as static functions: 103d71dbb73Sjbeck * - syslog set-up 104d71dbb73Sjbeck * - daemonizing 105d71dbb73Sjbeck * - looking up SMF(5) properties 106d71dbb73Sjbeck * - signal handling 107d71dbb73Sjbeck * - managing privileges(5) 108d71dbb73Sjbeck */ 109d71dbb73Sjbeck 110d71dbb73Sjbeck static void 111d71dbb73Sjbeck start_logging(void) 112d71dbb73Sjbeck { 113d71dbb73Sjbeck openlog("nwamd", LOG_PID | LOG_NDELAY, LOG_DAEMON); 114d71dbb73Sjbeck } 115d71dbb73Sjbeck 116d71dbb73Sjbeck static void 117d71dbb73Sjbeck daemonize(void) 118d71dbb73Sjbeck { 119d71dbb73Sjbeck pid_t pid; 120d71dbb73Sjbeck 121d71dbb73Sjbeck /* 122d71dbb73Sjbeck * A little bit of magic here. By the first fork+setsid, we 123d71dbb73Sjbeck * disconnect from our current controlling terminal and become 124d71dbb73Sjbeck * a session group leader. By forking again without calling 125d71dbb73Sjbeck * setsid again, we make certain that we are not the session 126d71dbb73Sjbeck * group leader and can never reacquire a controlling terminal. 127d71dbb73Sjbeck */ 128d71dbb73Sjbeck if ((pid = fork()) == (pid_t)-1) { 129d71dbb73Sjbeck syslog(LOG_ERR, "fork 1 failed"); 130d71dbb73Sjbeck exit(EXIT_FAILURE); 131d71dbb73Sjbeck } 132d71dbb73Sjbeck if (pid != 0) { 133d71dbb73Sjbeck (void) wait(NULL); 13410e672b7Smeem dprintf("child %ld exited, daemonizing", pid); 135d71dbb73Sjbeck _exit(0); 136d71dbb73Sjbeck } 137d71dbb73Sjbeck if (setsid() == (pid_t)-1) { 138d71dbb73Sjbeck syslog(LOG_ERR, "setsid"); 139d71dbb73Sjbeck exit(EXIT_FAILURE); 140d71dbb73Sjbeck } 141d71dbb73Sjbeck if ((pid = fork()) == (pid_t)-1) { 142d71dbb73Sjbeck syslog(LOG_ERR, "fork 2 failed"); 143d71dbb73Sjbeck exit(EXIT_FAILURE); 144d71dbb73Sjbeck } 145d71dbb73Sjbeck if (pid != 0) { 146d71dbb73Sjbeck _exit(0); 147d71dbb73Sjbeck } 148d71dbb73Sjbeck (void) chdir("/"); 149d71dbb73Sjbeck (void) umask(022); 150d71dbb73Sjbeck } 151d71dbb73Sjbeck 152d71dbb73Sjbeck /* 153d71dbb73Sjbeck * Look up nwamd property values and set daemon variables appropriately. 154d71dbb73Sjbeck * This function will be called on startup and via the signal handling 155d71dbb73Sjbeck * thread on receiving a HUP (which occurs when the nwam service is 156d71dbb73Sjbeck * refreshed). 157d71dbb73Sjbeck */ 158d71dbb73Sjbeck static void 159d71dbb73Sjbeck lookup_daemon_properties(void) 160d71dbb73Sjbeck { 161d71dbb73Sjbeck boolean_t debug_set; 162d71dbb73Sjbeck uint64_t scan_interval; 163*b00044a2SJames Carlson uint64_t idle_time; 164d71dbb73Sjbeck 165d71dbb73Sjbeck if (lookup_boolean_property(OUR_PG, "debug", &debug_set) == 0) 166d71dbb73Sjbeck debug = debug_set; 167d71dbb73Sjbeck if (lookup_count_property(OUR_PG, "scan_interval", &scan_interval) == 0) 168d71dbb73Sjbeck wlan_scan_interval = scan_interval; 169*b00044a2SJames Carlson if (lookup_count_property(OUR_PG, "idle_time", &idle_time) == 0) 170*b00044a2SJames Carlson door_idle_time = idle_time; 171d71dbb73Sjbeck dprintf("Read daemon configuration properties."); 172d71dbb73Sjbeck } 173d71dbb73Sjbeck 174d71dbb73Sjbeck /* ARGSUSED */ 175d71dbb73Sjbeck static void * 176d71dbb73Sjbeck sighandler(void *arg) 177d71dbb73Sjbeck { 178d71dbb73Sjbeck sigset_t sigset; 179d71dbb73Sjbeck int sig; 180d71dbb73Sjbeck uint32_t now; 181d71dbb73Sjbeck 182d71dbb73Sjbeck (void) sigfillset(&sigset); 183d71dbb73Sjbeck 184*b00044a2SJames Carlson while (!shutting_down) { 185d71dbb73Sjbeck sig = sigwait(&sigset); 186d71dbb73Sjbeck dprintf("signal %d caught", sig); 187d71dbb73Sjbeck switch (sig) { 188d71dbb73Sjbeck case SIGALRM: 189d71dbb73Sjbeck /* 190d71dbb73Sjbeck * We may have multiple interfaces with 191d71dbb73Sjbeck * scheduled timers; walk the list and 192d71dbb73Sjbeck * create a timer event for each one. 193d71dbb73Sjbeck */ 194d71dbb73Sjbeck timer_expire = TIMER_INFINITY; 195d71dbb73Sjbeck now = NSEC_TO_SEC(gethrtime()); 196*b00044a2SJames Carlson check_interface_timers(now); 197*b00044a2SJames Carlson check_door_life(now); 198d71dbb73Sjbeck break; 199d71dbb73Sjbeck case SIGHUP: 200d71dbb73Sjbeck /* 201d71dbb73Sjbeck * Refresh action - reread configuration properties. 202d71dbb73Sjbeck */ 203d71dbb73Sjbeck lookup_daemon_properties(); 204d71dbb73Sjbeck break; 205*b00044a2SJames Carlson case SIGINT: 206*b00044a2SJames Carlson /* 207*b00044a2SJames Carlson * Undocumented "print debug status" signal. 208*b00044a2SJames Carlson */ 209*b00044a2SJames Carlson print_llp_status(); 210*b00044a2SJames Carlson print_interface_status(); 211*b00044a2SJames Carlson print_wireless_status(); 212*b00044a2SJames Carlson break; 213d71dbb73Sjbeck default: 214d71dbb73Sjbeck syslog(LOG_NOTICE, "%s received, shutting down", 215d71dbb73Sjbeck strsignal(sig)); 216d71dbb73Sjbeck shutting_down = B_TRUE; 217*b00044a2SJames Carlson if (!np_queue_add_event(EV_SHUTDOWN, NULL)) { 218d71dbb73Sjbeck dprintf("could not allocate shutdown event"); 219d71dbb73Sjbeck cleanup(); 220d71dbb73Sjbeck exit(EXIT_FAILURE); 221d71dbb73Sjbeck } 222d71dbb73Sjbeck break; 223d71dbb73Sjbeck } 224d71dbb73Sjbeck } 225*b00044a2SJames Carlson return (NULL); 226d71dbb73Sjbeck } 227d71dbb73Sjbeck 228d71dbb73Sjbeck static void 229d71dbb73Sjbeck init_signalhandling(void) 230d71dbb73Sjbeck { 231d71dbb73Sjbeck pthread_attr_t attr; 232d71dbb73Sjbeck pthread_t sighand; 233d71dbb73Sjbeck int err; 234d71dbb73Sjbeck sigset_t new; 235d71dbb73Sjbeck 236d71dbb73Sjbeck (void) sigfillset(&new); 237d71dbb73Sjbeck (void) pthread_sigmask(SIG_BLOCK, &new, &original_sigmask); 238d71dbb73Sjbeck (void) pthread_attr_init(&attr); 239d71dbb73Sjbeck (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 240d71dbb73Sjbeck if (err = pthread_create(&sighand, &attr, sighandler, NULL)) { 241d71dbb73Sjbeck syslog(LOG_ERR, "pthread_create system: %s", strerror(err)); 242d71dbb73Sjbeck exit(EXIT_FAILURE); 243d71dbb73Sjbeck } else { 244d71dbb73Sjbeck dprintf("signal handler thread: %d", sighand); 245d71dbb73Sjbeck } 246d71dbb73Sjbeck (void) pthread_attr_destroy(&attr); 247d71dbb73Sjbeck } 248d71dbb73Sjbeck 249d71dbb73Sjbeck static void 250d71dbb73Sjbeck change_user_set_privs(void) 251d71dbb73Sjbeck { 252d71dbb73Sjbeck priv_set_t *priv_set; 253d71dbb73Sjbeck 254d71dbb73Sjbeck priv_set = priv_allocset(); 255d71dbb73Sjbeck if (getppriv(PRIV_PERMITTED, priv_set) == -1) { 256d71dbb73Sjbeck dprintf("getppriv %s", strerror(errno)); 257d71dbb73Sjbeck } else { 258d71dbb73Sjbeck char *p; 259d71dbb73Sjbeck 260d71dbb73Sjbeck p = priv_set_to_str(priv_set, ',', 0); 261d71dbb73Sjbeck dprintf("started with privs %s", p != NULL ? p : "Unknown"); 262d71dbb73Sjbeck free(p); 263d71dbb73Sjbeck } 2649b38c944Sokie priv_freeset(priv_set); 265d71dbb73Sjbeck 2669b38c944Sokie /* always start with the basic set */ 2679b38c944Sokie priv_set = priv_str_to_set("basic", ",", NULL); 2689b38c944Sokie if (priv_set == NULL) { 2699b38c944Sokie syslog(LOG_ERR, "converting basic privilege set: %m"); 2709b38c944Sokie exit(EXIT_FAILURE); 2719b38c944Sokie } 2729b38c944Sokie (void) priv_addset(priv_set, PRIV_FILE_CHOWN_SELF); 2739b38c944Sokie (void) priv_addset(priv_set, PRIV_FILE_DAC_READ); 2749b38c944Sokie (void) priv_addset(priv_set, PRIV_FILE_DAC_WRITE); 2759b38c944Sokie (void) priv_addset(priv_set, PRIV_NET_PRIVADDR); 2769b38c944Sokie (void) priv_addset(priv_set, PRIV_NET_RAWACCESS); 277*b00044a2SJames Carlson (void) priv_addset(priv_set, PRIV_PROC_AUDIT); 2789b38c944Sokie (void) priv_addset(priv_set, PRIV_PROC_OWNER); 2799b38c944Sokie (void) priv_addset(priv_set, PRIV_PROC_SETID); 280*b00044a2SJames Carlson (void) priv_addset(priv_set, PRIV_SYS_CONFIG); 2819b38c944Sokie (void) priv_addset(priv_set, PRIV_SYS_IP_CONFIG); 2829b38c944Sokie (void) priv_addset(priv_set, PRIV_SYS_IPC_CONFIG); 2839b38c944Sokie (void) priv_addset(priv_set, PRIV_SYS_NET_CONFIG); 2849b38c944Sokie (void) priv_addset(priv_set, PRIV_SYS_RES_CONFIG); 2859b38c944Sokie (void) priv_addset(priv_set, PRIV_SYS_RESOURCE); 286d71dbb73Sjbeck 287d71dbb73Sjbeck if (setppriv(PRIV_SET, PRIV_INHERITABLE, priv_set) == -1) { 288d71dbb73Sjbeck syslog(LOG_ERR, "setppriv inheritable: %m"); 289d71dbb73Sjbeck priv_freeset(priv_set); 290d71dbb73Sjbeck exit(EXIT_FAILURE); 291d71dbb73Sjbeck } 292d71dbb73Sjbeck 293d71dbb73Sjbeck if (setppriv(PRIV_SET, PRIV_PERMITTED, priv_set) == -1) { 294d71dbb73Sjbeck syslog(LOG_ERR, "setppriv permitted: %m"); 295d71dbb73Sjbeck priv_freeset(priv_set); 296d71dbb73Sjbeck exit(EXIT_FAILURE); 297d71dbb73Sjbeck } 298d71dbb73Sjbeck 299d71dbb73Sjbeck if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_set) == -1) { 300d71dbb73Sjbeck syslog(LOG_ERR, "setppriv effective: %m"); 301d71dbb73Sjbeck priv_freeset(priv_set); 302d71dbb73Sjbeck exit(EXIT_FAILURE); 303d71dbb73Sjbeck } 304d71dbb73Sjbeck 305d71dbb73Sjbeck priv_freeset(priv_set); 306d71dbb73Sjbeck } 307d71dbb73Sjbeck 308*b00044a2SJames Carlson static void 309*b00044a2SJames Carlson init_machine_mutex(void) 310*b00044a2SJames Carlson { 311*b00044a2SJames Carlson pthread_mutexattr_t attrs; 312*b00044a2SJames Carlson 313*b00044a2SJames Carlson (void) pthread_mutexattr_init(&attrs); 314*b00044a2SJames Carlson (void) pthread_mutexattr_settype(&attrs, PTHREAD_MUTEX_ERRORCHECK); 315*b00044a2SJames Carlson if (pthread_mutex_init(&machine_lock, &attrs) != 0) { 316*b00044a2SJames Carlson syslog(LOG_ERR, "unable to set up machine lock"); 317*b00044a2SJames Carlson exit(EXIT_FAILURE); 318*b00044a2SJames Carlson } 319*b00044a2SJames Carlson (void) pthread_mutexattr_destroy(&attrs); 320*b00044a2SJames Carlson } 321*b00044a2SJames Carlson 322d71dbb73Sjbeck int 323d71dbb73Sjbeck main(int argc, char *argv[]) 324d71dbb73Sjbeck { 325d71dbb73Sjbeck int c; 326d71dbb73Sjbeck int scan_lev; 327d71dbb73Sjbeck struct np_event *e; 328*b00044a2SJames Carlson enum np_event_type etype; 329d71dbb73Sjbeck 330d71dbb73Sjbeck (void) setlocale(LC_ALL, ""); 331d71dbb73Sjbeck (void) textdomain(TEXT_DOMAIN); 332d71dbb73Sjbeck 333d71dbb73Sjbeck shutting_down = B_FALSE; 334d71dbb73Sjbeck start_logging(); 335d71dbb73Sjbeck syslog(LOG_INFO, "nwamd pid %d started", getpid()); 336d71dbb73Sjbeck 337d71dbb73Sjbeck while ((c = getopt(argc, argv, "fs:")) != -1) { 338d71dbb73Sjbeck switch (c) { 339d71dbb73Sjbeck case 'f': 340d71dbb73Sjbeck fg = B_TRUE; 341d71dbb73Sjbeck break; 342d71dbb73Sjbeck case 's': 343d71dbb73Sjbeck scan_lev = atoi(optarg); 344d71dbb73Sjbeck if (scan_lev >= DLADM_WLAN_STRENGTH_VERY_WEAK && 345d71dbb73Sjbeck scan_lev <= DLADM_WLAN_STRENGTH_EXCELLENT) { 346d71dbb73Sjbeck wireless_scan_level = scan_lev; 347d71dbb73Sjbeck } else { 348d71dbb73Sjbeck syslog(LOG_ERR, "invalid signal " 349d71dbb73Sjbeck "strength: %s", optarg); 350d71dbb73Sjbeck } 351d71dbb73Sjbeck break; 352d71dbb73Sjbeck default: 353d71dbb73Sjbeck syslog(LOG_ERR, "unrecognized option %c", 354d71dbb73Sjbeck optopt); 355d71dbb73Sjbeck break; 356d71dbb73Sjbeck } 357d71dbb73Sjbeck } 358d71dbb73Sjbeck 359d71dbb73Sjbeck lookup_daemon_properties(); 360d71dbb73Sjbeck 361d71dbb73Sjbeck change_user_set_privs(); 362d71dbb73Sjbeck 363d71dbb73Sjbeck if (!fg) 364d71dbb73Sjbeck daemonize(); 365d71dbb73Sjbeck 366*b00044a2SJames Carlson initialize_llp(); 367*b00044a2SJames Carlson 368d71dbb73Sjbeck init_signalhandling(); 369d71dbb73Sjbeck 370*b00044a2SJames Carlson initialize_wireless(); 371d71dbb73Sjbeck 372d71dbb73Sjbeck lookup_zonename(zonename, sizeof (zonename)); 373d71dbb73Sjbeck 374*b00044a2SJames Carlson init_machine_mutex(); 375*b00044a2SJames Carlson 376d71dbb73Sjbeck initialize_interfaces(); 377d71dbb73Sjbeck 378d71dbb73Sjbeck llp_parse_config(); 379d71dbb73Sjbeck 380*b00044a2SJames Carlson initialize_door(); 381*b00044a2SJames Carlson 382d71dbb73Sjbeck (void) start_event_collection(); 383d71dbb73Sjbeck 384*b00044a2SJames Carlson while ((e = np_queue_get_event()) != NULL) { 385d71dbb73Sjbeck 386*b00044a2SJames Carlson etype = e->npe_type; 387*b00044a2SJames Carlson syslog(LOG_INFO, "got event type %s", npe_type_str(etype)); 388*b00044a2SJames Carlson if (etype == EV_SHUTDOWN) 389*b00044a2SJames Carlson terminate_door(); 390*b00044a2SJames Carlson if (pthread_mutex_lock(&machine_lock) != 0) { 391*b00044a2SJames Carlson syslog(LOG_ERR, "mutex lock"); 392*b00044a2SJames Carlson exit(EXIT_FAILURE); 393*b00044a2SJames Carlson } 394d71dbb73Sjbeck state_machine(e); 395*b00044a2SJames Carlson (void) pthread_mutex_unlock(&machine_lock); 396d71dbb73Sjbeck free_event(e); 397*b00044a2SJames Carlson if (etype == EV_SHUTDOWN) 398d71dbb73Sjbeck break; 399*b00044a2SJames Carlson } 400*b00044a2SJames Carlson syslog(LOG_DEBUG, "terminating routing and scanning threads"); 401d71dbb73Sjbeck (void) pthread_cancel(routing); 402d71dbb73Sjbeck (void) pthread_cancel(scan); 403d71dbb73Sjbeck (void) pthread_join(routing, NULL); 404d71dbb73Sjbeck (void) pthread_join(scan, NULL); 405d71dbb73Sjbeck syslog(LOG_INFO, "nwamd shutting down"); 406*b00044a2SJames Carlson return (EXIT_SUCCESS); 407d71dbb73Sjbeck } 408