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