xref: /illumos-gate/usr/src/cmd/cmd-inet/lib/nwamd/main.c (revision 0a586cea3ceec7e5e50e7e54c745082a7a333ac2)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <inetcfg.h>
30 #include <libdllink.h>
31 #include <libintl.h>
32 #include <libnwam.h>
33 #include <locale.h>
34 #include <priv.h>
35 #include <pthread.h>
36 #include <signal.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <sys/wait.h>
43 #include <unistd.h>
44 
45 #include <libnwam.h>
46 #include "conditions.h"
47 #include "events.h"
48 #include "llp.h"
49 #include "ncp.h"
50 #include "objects.h"
51 #include "util.h"
52 
53 /*
54  * nwamd - NetWork Auto-Magic Daemon
55  */
56 
57 boolean_t fg = B_FALSE;
58 dladm_handle_t dld_handle = NULL;
59 boolean_t shutting_down = B_FALSE;
60 
61 sigset_t original_sigmask;
62 static sigset_t sigwaitset;
63 
64 static void nwamd_refresh(void);
65 static void graceful_shutdown(void);
66 
67 /*
68  * nwamd
69  *
70  * This is the Network Auto-Magic daemon.  For further high level information
71  * see the Network Auto-Magic project and the Approachability communities
72  * on opensolaris.org, nwamd(1M), and the README in the source directory.
73  *
74  * The general structure of the code is as a set of event source threads
75  * which feed events into the event handling thread. Some of these events
76  * are internal-only (e.g UPGRADE), but some also involve propogation
77  * to external listeners (who register via a door call into the daemon).
78  *
79  * signal management
80  * Due to being threaded, a simple set of signal handlers would not work
81  * very well for nwamd.  Instead nwamd blocks signals in all but the
82  * signal handling thread at startup.
83  *
84  */
85 
86 /*
87  * In this file there are several utility functions which might otherwise
88  * belong in util.c, but since they are only called from main(), they can
89  * live here as static functions:
90  * - nlog set-up
91  * - daemonizing
92  * - looking up SMF(5) properties
93  * - signal handling
94  * - managing privileges(5)
95  */
96 
97 static void
98 start_logging(void)
99 {
100 	openlog("nwamd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
101 }
102 
103 static void
104 daemonize(void)
105 {
106 	pid_t pid;
107 
108 	/*
109 	 * A little bit of magic here.  By the first fork+setsid, we
110 	 * disconnect from our current controlling terminal and become
111 	 * a session group leader.  By forking again without calling
112 	 * setsid again, we make certain that we are not the session
113 	 * group leader and can never reacquire a controlling terminal.
114 	 */
115 	if ((pid = fork()) == (pid_t)-1)
116 		pfail("fork 1 failed");
117 	if (pid != 0) {
118 		(void) wait(NULL);
119 		nlog(LOG_DEBUG, "child %ld exited, daemonizing", pid);
120 		_exit(0);
121 	}
122 	if (setsid() == (pid_t)-1)
123 		pfail("setsid");
124 	if ((pid = fork()) == (pid_t)-1)
125 		pfail("fork 2 failed");
126 	if (pid != 0) {
127 		_exit(0);
128 	}
129 	(void) chdir("/");
130 	(void) umask(022);
131 }
132 
133 /* ARGSUSED */
134 static void *
135 sighandler(void *arg)
136 {
137 	uint64_t propval;
138 	int sig;
139 	uid_t uid = getuid();
140 
141 	while (!shutting_down) {
142 		sig = sigwait(&sigwaitset);
143 		nlog(LOG_DEBUG, "signal %s caught", strsignal(sig));
144 
145 		/*
146 		 * Signal handling is different if the Phase 1 manifest
147 		 * have not been yet been imported.  The two if-statements
148 		 * below highlight this.  Signals must be handled
149 		 * differently because there is no event handling thread
150 		 * and event queue.
151 		 *
152 		 * When manifest-import imports the Phase 1 manifest, it
153 		 * refreshes NWAM.  The NWAM Phase 1 properties must be
154 		 * available.  If not, NWAM receveid a signal too soon.
155 		 * "ncu_wait_time" is a Phase 1 SMF property that did not
156 		 * exist in Phase 0/0.5.
157 		 */
158 		if (uid == 0 &&
159 		    nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
160 		    OUR_NCU_WAIT_TIME_PROP_NAME, &propval) != 0) {
161 			nlog(LOG_ERR, "WARN: Phase 1 properties not available. "
162 			    "Ignoring signal ...");
163 			continue;
164 		}
165 
166 		/*
167 		 *  The Phase 1 manifest has been imported.  If the
168 		 *  "version" property exists (it is added by nwamd upon
169 		 *  upgrade to Phase 1), then it means that we have already
170 		 *  successfully run nwam before.  If it doesn't, then we
171 		 *  arrived here just after the new manifest was imported.
172 		 *  manifest-import refreshes nwam after the import.  Exit
173 		 *  nwamd so that svc.startd(1M) can start nwamd correctly
174 		 *  as specified in the imported manifest.
175 		 */
176 		if (uid == 0 &&
177 		    nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
178 		    OUR_VERSION_PROP_NAME, &propval) != 0) {
179 			if (sig == SIGHUP) {
180 				pfail("WARN: Phase 1 properties available, "
181 				    "but NWAM has not been upgraded.  "
182 				    "Exiting to let svc.startd restart NWAM");
183 			} else {
184 				nlog(LOG_ERR, "WARN: Ignoring signal as NWAM "
185 				    "has not been upgraded yet");
186 				continue;
187 			}
188 		}
189 
190 		switch (sig) {
191 		case SIGTHAW:
192 		case SIGHUP:
193 			/*
194 			 * Resumed from suspend or refresh.  Clear up all
195 			 * objects so their states start from scratch;
196 			 * then refresh().
197 			 */
198 			nwamd_fini_enms();
199 			nwamd_fini_ncus();
200 			nwamd_fini_locs();
201 			nwamd_refresh();
202 			break;
203 		case SIGUSR1:
204 			/*
205 			 * Undocumented "log ncu list" signal.
206 			 */
207 			nwamd_log_ncus();
208 			break;
209 		case SIGTERM:
210 			nlog(LOG_DEBUG, "%s received, shutting down",
211 			    strsignal(sig));
212 			graceful_shutdown();
213 			break;
214 		default:
215 			nlog(LOG_DEBUG, "unexpected signal %s received, "
216 			    "ignoring", strsignal(sig));
217 			break;
218 		}
219 	}
220 	return (NULL);
221 }
222 
223 static void
224 init_signalhandling(void)
225 {
226 	pthread_attr_t attr;
227 	pthread_t sighand;
228 	int err;
229 
230 	(void) pthread_attr_init(&attr);
231 	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
232 	if (err = pthread_create(&sighand, &attr, sighandler, NULL)) {
233 		nlog(LOG_ERR, "pthread_create system: %s", strerror(err));
234 		exit(EXIT_FAILURE);
235 	} else {
236 		nlog(LOG_DEBUG, "signal handler thread: %d", sighand);
237 	}
238 	(void) pthread_attr_destroy(&attr);
239 }
240 
241 /*
242  * Construct the set of signals that we explicitly want to deal with.
243  * We block these while we're still single-threaded; this block will
244  * be inherited by all the threads we create.  When we are ready to
245  * start handling signals, we will start the signal handling thread,
246  * which will sigwait() this same set of signals, and will thus receive
247  * and handle any that are sent to the process.
248  */
249 static void
250 block_signals(void)
251 {
252 	(void) sigemptyset(&sigwaitset);
253 	(void) sigaddset(&sigwaitset, SIGHUP);
254 	(void) sigaddset(&sigwaitset, SIGUSR1);
255 	(void) sigaddset(&sigwaitset, SIGUSR2);
256 	(void) sigaddset(&sigwaitset, SIGTERM);
257 	(void) sigaddset(&sigwaitset, SIGTHAW);
258 	(void) pthread_sigmask(SIG_BLOCK, &sigwaitset, &original_sigmask);
259 }
260 
261 /*
262  * Look up nwamd property values and set daemon variables appropriately.
263  * This function will be called on startup and via the signal handling
264  * thread on receiving a HUP (which occurs when the nwam service is
265  * refreshed).
266  */
267 static void
268 lookup_daemon_properties(void)
269 {
270 	char *active_ncp_tmp;
271 	char *scan_level_tmp;
272 
273 	(void) nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG,
274 	    OUR_DEBUG_PROP_NAME, &debug);
275 	(void) nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG,
276 	    OUR_AUTOCONF_PROP_NAME, &wireless_autoconf);
277 	(void) nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG,
278 	    OUR_STRICT_BSSID_PROP_NAME, &wireless_strict_bssid);
279 
280 	(void) pthread_mutex_lock(&active_ncp_mutex);
281 	if ((active_ncp_tmp = malloc(NWAM_MAX_NAME_LEN)) == NULL ||
282 	    nwamd_lookup_string_property(OUR_FMRI, OUR_PG,
283 	    OUR_ACTIVE_NCP_PROP_NAME, active_ncp_tmp, NWAM_MAX_NAME_LEN) != 0) {
284 		(void) strlcpy(active_ncp, NWAM_NCP_NAME_AUTOMATIC,
285 		    NWAM_MAX_NAME_LEN);
286 	} else {
287 		(void) strlcpy(active_ncp, active_ncp_tmp, NWAM_MAX_NAME_LEN);
288 	}
289 	(void) pthread_mutex_unlock(&active_ncp_mutex);
290 	free(active_ncp_tmp);
291 
292 	if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
293 	    OUR_CONDITION_CHECK_INTERVAL_PROP_NAME,
294 	    &condition_check_interval) != 0)
295 		condition_check_interval = CONDITION_CHECK_INTERVAL_DEFAULT;
296 
297 	if ((scan_level_tmp = malloc(NWAM_MAX_NAME_LEN)) == NULL ||
298 	    nwamd_lookup_string_property(OUR_FMRI, OUR_PG,
299 	    OUR_WIRELESS_SCAN_LEVEL_PROP_NAME, scan_level_tmp,
300 	    NWAM_MAX_NAME_LEN) != 0) {
301 		wireless_scan_level = WIRELESS_SCAN_LEVEL_DEFAULT;
302 	} else {
303 		if (dladm_wlan_str2strength(scan_level_tmp,
304 		    &wireless_scan_level) != DLADM_STATUS_OK)
305 			wireless_scan_level = DLADM_WLAN_STRENGTH_VERY_WEAK;
306 	}
307 	free(scan_level_tmp);
308 
309 	if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
310 	    OUR_WIRELESS_SCAN_INTERVAL_PROP_NAME, &wireless_scan_interval) != 0)
311 		wireless_scan_interval = WIRELESS_SCAN_INTERVAL_DEFAULT;
312 
313 	if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
314 	    OUR_NCU_WAIT_TIME_PROP_NAME, &ncu_wait_time) != 0)
315 		ncu_wait_time = NCU_WAIT_TIME_DEFAULT;
316 
317 	nlog(LOG_DEBUG, "Read daemon configuration properties.");
318 }
319 
320 /*
321  * Re-read the SMF properties.
322  * Reset ncu priority group (since the NCUs will have to walk
323  *   through their state machines again) and schedule a check
324  * Re-read objects from libnwam.
325  * Also, run condition checking for locations and ENMs.
326  */
327 static void
328 nwamd_refresh(void)
329 {
330 	lookup_daemon_properties();
331 
332 	(void) pthread_mutex_lock(&active_ncp_mutex);
333 	current_ncu_priority_group = INVALID_PRIORITY_GROUP;
334 	(void) pthread_mutex_unlock(&active_ncp_mutex);
335 
336 	nwamd_init_ncus();
337 	nwamd_init_enms();
338 	nwamd_init_locs();
339 
340 	nwamd_create_ncu_check_event(0);
341 	nwamd_create_triggered_condition_check_event(0);
342 }
343 
344 static void
345 graceful_shutdown(void)
346 {
347 	nwamd_event_t event;
348 
349 	shutting_down = B_TRUE;
350 	nwamd_event_sources_fini();
351 	nwamd_door_fini();
352 	nwamd_fini_enms();
353 	nwamd_fini_ncus();
354 	nwamd_fini_locs();
355 
356 	event = nwamd_event_init_shutdown();
357 	if (event == NULL)
358 		pfail("nwamd could not create shutdown event, exiting");
359 	nwamd_event_enqueue(event);
360 }
361 
362 int
363 main(int argc, char *argv[])
364 {
365 	int c;
366 	uint64_t version;
367 	nwamd_event_t event;
368 	dladm_status_t rc;
369 	uid_t uid = getuid();
370 
371 	/*
372 	 * Block the signals we care about (and which might cause us to
373 	 * exit based on default disposition) until we're ready to start
374 	 * handling them properly...see init_signalhandling() below.
375 	 */
376 	block_signals();
377 
378 	/*
379 	 * In the first boot after upgrade, manifest-import hasn't run yet.
380 	 * Thus, the NWAM Phase 1 SMF properties are not available yet.  In
381 	 * Phase 0/0.5, nwamd ran as root.  Thus in this case, just
382 	 * daemonize() nwamd.  When manifest-import imports the Phase 1
383 	 * manifest, NWAM is refreshed.  Also, setup signal handling to
384 	 * catch the refresh signal.  Kill nwamd then and let svc.startd(1M)
385 	 * start nwamd again (this time correctly as netadm).
386 	 */
387 	if (uid == 0) {
388 		nlog(LOG_ERR, "Warning: Phase 1 properties not available yet");
389 
390 		daemonize();
391 		init_signalhandling();
392 		(void) pause();
393 
394 		return (EXIT_SUCCESS);
395 	}
396 
397 	if (uid != UID_NETADM) {
398 		/*
399 		 * This shouldn't happen normally.  On upgrade the service might
400 		 * need reloading.
401 		 */
402 		pfail("nwamd should run as uid %d, not uid %d\n", UID_NETADM,
403 		    uid);
404 	}
405 
406 	(void) setlocale(LC_ALL, "");
407 	(void) textdomain(TEXT_DOMAIN);
408 
409 	start_logging();
410 	nlog(LOG_INFO, "nwamd pid %d started", getpid());
411 
412 	while ((c = getopt(argc, argv, "fs:")) != -1) {
413 		switch (c) {
414 			case 'f':
415 				fg = B_TRUE;
416 				break;
417 			default:
418 				nlog(LOG_ERR, "unrecognized option %c",
419 				    optopt);
420 				break;
421 		}
422 	}
423 
424 	lookup_daemon_properties();
425 
426 	if (!fg)
427 		daemonize();
428 
429 	/*
430 	 * The dladm handle *must* be opened before privileges are dropped.
431 	 * The device privilege requirements, which are stored in
432 	 * /etc/security/device_policy, may not be loaded yet, as that's
433 	 * done by svc:/system/filesystem/root.  If they are not loaded,
434 	 * then one must have *all* privs in order to open /dev/dld, which
435 	 * is one of the steps performed in dladm_open().
436 	 */
437 	rc = dladm_open(&dld_handle);
438 	if (rc != DLADM_STATUS_OK) {
439 		char status_str[DLADM_STRSIZE];
440 		(void) dladm_status2str(rc, status_str);
441 		pfail("failed to open dladm handle: %s", status_str);
442 	}
443 
444 	/*
445 	 * Handle upgrade of legacy config.  Absence of version property
446 	 * (which did not exist in phase 0 or 0.5) is the indication that
447 	 * we need to upgrade to phase 1 (version 1).
448 	 */
449 	if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG, OUR_VERSION_PROP_NAME,
450 	    &version) != 0)
451 		nwamd_handle_upgrade(NULL);
452 
453 	/*
454 	 * Initialize lists handling internal representations of objects.
455 	 */
456 	nwamd_object_lists_init();
457 
458 	/*
459 	 * Start the event handling thread before starting event sources,
460 	 * including signal handling, so we are ready to handle incoming
461 	 * events.
462 	 */
463 	nwamd_event_queue_init();
464 
465 	init_signalhandling();
466 
467 	/* Enqueue init event */
468 	event = nwamd_event_init_init();
469 	if (event == NULL)
470 		pfail("nwamd could not create init event, exiting");
471 	nwamd_event_enqueue(event);
472 	/*
473 	 * Collect initial user configuration.
474 	 */
475 
476 	/*
477 	 * Walk the physical interfaces and update the Automatic NCP to
478 	 * contain the IP and link NCUs for the interfaces that exist in
479 	 * the system.
480 	 */
481 	nwamd_walk_physical_configuration();
482 
483 	/*
484 	 * We should initialize the door at the point that we can respond to
485 	 * user requests about the system but before we start actually process
486 	 * state changes or effecting the system.
487 	 */
488 	nwamd_door_init();
489 
490 	/*
491 	 * Initialize data objects.
492 	 *
493 	 * Enabling an NCP involves refreshing nwam, which initializes the
494 	 * objects (ncu, enm, loc, known wlan).  Thus, no need to
495 	 * explicitly initialize these objects here.  The refresh also
496 	 * enqueues and NCU activation checking event.  Location and ENM
497 	 * condition checking are triggered by changes in NCU states.
498 	 */
499 	(void) pthread_mutex_lock(&active_ncp_mutex);
500 	if (nwamd_ncp_action(active_ncp, NWAM_ACTION_ENABLE) != 0)
501 		pfail("Initial enable failed for active NCP %s", active_ncp);
502 	(void) pthread_mutex_unlock(&active_ncp_mutex);
503 
504 	/*
505 	 * Enqueue an event to start periodic checking of activation conditions.
506 	 */
507 	nwamd_create_timed_condition_check_event();
508 
509 	/*
510 	 * These two routines safely minimize our privilege set.  They
511 	 * use reference counting to be safe in a threaded program.  It is
512 	 * gross that we escalate/deescalate to initialize this functionality
513 	 * but a real fix is to add functionality to do fine grained privs
514 	 * (and if necessary set uid to 0) in this threaded daemon.
515 	 */
516 	nwamd_escalate();
517 	nwamd_deescalate();
518 
519 	/*
520 	 * Start the various agents (hooks on fds, threads) which collect events
521 	 */
522 	nwamd_event_sources_init();
523 
524 	/*
525 	 * nwamd_event_handler() only returns on shutdown.
526 	 */
527 	nwamd_event_handler();
528 
529 	dladm_close(dld_handle);
530 
531 	return (EXIT_SUCCESS);
532 }
533