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