xref: /illumos-gate/usr/src/cmd/dlmgmtd/dlmgmt_main.c (revision 34bbc83afbf22a6f8e504cb99d76c97c017cb5f4)
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  * Copyright 2011 Joyent, Inc.  All rights reserved.
26  */
27 
28 /*
29  * The dlmgmtd daemon is started by the datalink-management SMF service.
30  * This daemon is used to manage <link name, linkid> mapping and the
31  * persistent datalink configuration.
32  *
33  * Today, the <link name, linkid> mapping and the persistent configuration
34  * of datalinks is kept in /etc/dladm/datalink.conf, and the daemon keeps
35  * a copy of the datalinks in the memory (see dlmgmt_id_avl and
36  * dlmgmt_name_avl). The active <link name, linkid> mapping is kept in
37  * /etc/svc/volatile/dladm cache file, so that the mapping can be recovered
38  * when dlmgmtd exits for some reason (e.g., when dlmgmtd is accidentally
39  * killed).
40  */
41 
42 #include <assert.h>
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <priv.h>
46 #include <signal.h>
47 #include <stdlib.h>
48 #include <stdio.h>
49 #include <strings.h>
50 #include <syslog.h>
51 #include <zone.h>
52 #include <sys/dld.h>
53 #include <sys/dld_ioc.h>
54 #include <sys/param.h>
55 #include <sys/stat.h>
56 #include <unistd.h>
57 #include <libdladm_impl.h>
58 #include <libdlmgmt.h>
59 #include "dlmgmt_impl.h"
60 
61 const char		*progname;
62 boolean_t		debug;
63 static int		pfds[2];
64 /*
65  * This file descriptor to DLMGMT_DOOR cannot be in the libdladm
66  * handle because the door isn't created when the handle is created.
67  */
68 static int		dlmgmt_door_fd = -1;
69 
70 /*
71  * This libdladm handle is global so that dlmgmt_upcall_linkprop_init() can
72  * pass to libdladm.  The handle is opened with "ALL" privileges, before
73  * privileges are dropped in dlmgmt_drop_privileges().  It is not able to open
74  * DLMGMT_DOOR at that time as it hasn't been created yet.  This door in the
75  * handle is opened in the first call to dladm_door_fd().
76  */
77 dladm_handle_t		dld_handle = NULL;
78 
79 static void		dlmgmtd_exit(int);
80 static int		dlmgmt_init();
81 static void		dlmgmt_fini();
82 static int		dlmgmt_set_privileges();
83 
84 static int
85 dlmgmt_set_doorfd(boolean_t start)
86 {
87 	dld_ioc_door_t did;
88 	int err = 0;
89 
90 	assert(dld_handle != NULL);
91 
92 	did.did_start_door = start;
93 
94 	if (ioctl(dladm_dld_fd(dld_handle), DLDIOC_DOORSERVER, &did) == -1)
95 		err = errno;
96 
97 	return (err);
98 }
99 
100 static int
101 dlmgmt_door_init(void)
102 {
103 	int err = 0;
104 
105 	if ((dlmgmt_door_fd = door_create(dlmgmt_handler, NULL,
106 	    DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) {
107 		err = errno;
108 		dlmgmt_log(LOG_ERR, "door_create() failed: %s",
109 		    strerror(err));
110 		return (err);
111 	}
112 	return (err);
113 }
114 
115 static void
116 dlmgmt_door_fini(void)
117 {
118 	if (dlmgmt_door_fd == -1)
119 		return;
120 
121 	if (door_revoke(dlmgmt_door_fd) == -1) {
122 		dlmgmt_log(LOG_WARNING, "door_revoke(%s) failed: %s",
123 		    DLMGMT_DOOR, strerror(errno));
124 	}
125 	(void) dlmgmt_set_doorfd(B_FALSE);
126 	dlmgmt_door_fd = -1;
127 }
128 
129 static int
130 dlmgmt_door_attach(zoneid_t zoneid, char *rootdir)
131 {
132 	int	fd;
133 	int	err = 0;
134 	char	doorpath[MAXPATHLEN];
135 
136 	(void) snprintf(doorpath, sizeof (doorpath), "%s%s", rootdir,
137 	    DLMGMT_DOOR);
138 
139 	/*
140 	 * Create the door file for dlmgmtd.
141 	 */
142 	if ((fd = open(doorpath, O_CREAT|O_RDONLY, 0644)) == -1) {
143 		err = errno;
144 		dlmgmt_log(LOG_ERR, "open(%s) failed: %s", doorpath,
145 		    strerror(err));
146 		return (err);
147 	}
148 	(void) close(fd);
149 	if (chown(doorpath, UID_DLADM, GID_NETADM) == -1)
150 		return (errno);
151 
152 	/*
153 	 * fdetach first in case a previous daemon instance exited
154 	 * ungracefully.
155 	 */
156 	(void) fdetach(doorpath);
157 	if (fattach(dlmgmt_door_fd, doorpath) != 0) {
158 		err = errno;
159 		dlmgmt_log(LOG_ERR, "fattach(%s) failed: %s", doorpath,
160 		    strerror(err));
161 	} else if (zoneid == GLOBAL_ZONEID) {
162 		if ((err = dlmgmt_set_doorfd(B_TRUE)) != 0) {
163 			dlmgmt_log(LOG_ERR, "cannot set kernel doorfd: %s",
164 			    strerror(err));
165 		}
166 	}
167 
168 	return (err);
169 }
170 
171 /*
172  * Create the /etc/svc/volatile/dladm/ directory if it doesn't exist, load the
173  * datalink.conf data for this zone, and create/attach the door rendezvous
174  * file.
175  */
176 int
177 dlmgmt_zone_init(zoneid_t zoneid)
178 {
179 	char	rootdir[MAXPATHLEN], tmpfsdir[MAXPATHLEN];
180 	int	err;
181 	struct stat statbuf;
182 
183 	if (zoneid == GLOBAL_ZONEID) {
184 		rootdir[0] = '\0';
185 	} else if (zone_getattr(zoneid, ZONE_ATTR_ROOT, rootdir,
186 	    sizeof (rootdir)) < 0) {
187 		return (errno);
188 	}
189 
190 	/*
191 	 * Create the DLMGMT_TMPFS_DIR directory.
192 	 */
193 	(void) snprintf(tmpfsdir, sizeof (tmpfsdir), "%s%s", rootdir,
194 	    DLMGMT_TMPFS_DIR);
195 	if (stat(tmpfsdir, &statbuf) < 0) {
196 		if (mkdir(tmpfsdir, (mode_t)0755) < 0)
197 			return (errno);
198 	} else if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
199 		return (ENOTDIR);
200 	}
201 
202 	if ((chmod(tmpfsdir, 0755) < 0) ||
203 	    (chown(tmpfsdir, UID_DLADM, GID_NETADM) < 0)) {
204 		return (EPERM);
205 	}
206 
207 	if ((err = dlmgmt_db_init(zoneid, rootdir)) != 0)
208 		return (err);
209 	return (dlmgmt_door_attach(zoneid, rootdir));
210 }
211 
212 /*
213  * Initialize each running zone.
214  */
215 static int
216 dlmgmt_allzones_init(void)
217 {
218 	int		i;
219 	zoneid_t	*zids = NULL;
220 	uint_t		nzids, nzids_saved;
221 
222 	if (zone_list(NULL, &nzids) != 0)
223 		return (errno);
224 again:
225 	nzids *= 2;
226 	if ((zids = malloc(nzids * sizeof (zoneid_t))) == NULL)
227 		return (errno);
228 	nzids_saved = nzids;
229 	if (zone_list(zids, &nzids) != 0) {
230 		free(zids);
231 		return (errno);
232 	}
233 	if (nzids > nzids_saved) {
234 		free(zids);
235 		goto again;
236 	}
237 
238 	for (i = 0; i < nzids; i++) {
239 		int res;
240 		zone_status_t status;
241 
242 		/*
243 		 * Skip over zones that have gone away or are going down
244 		 * since we got the list.  Process all zones in the list,
245 		 * logging errors for any that failed.
246 		 */
247 		if (zone_getattr(zids[i], ZONE_ATTR_STATUS, &status,
248 		    sizeof (status)) < 0) {
249 			continue;
250 		}
251 		switch (status) {
252 			case ZONE_IS_SHUTTING_DOWN:
253 			case ZONE_IS_EMPTY:
254 			case ZONE_IS_DOWN:
255 			case ZONE_IS_DYING:
256 			case ZONE_IS_DEAD:
257 			case ZONE_IS_INITIALIZED:
258 			case ZONE_IS_UNINITIALIZED:
259 				continue;
260 			default:
261 				break;
262 		}
263 		if ((res = dlmgmt_zone_init(zids[i])) != 0) {
264 			(void) fprintf(stderr, "zone (%ld) init error %s",
265 			    zids[i], strerror(res));
266 			dlmgmt_log(LOG_ERR, "zone (%d) init error %s",
267 			    zids[i], strerror(res));
268 		}
269 	}
270 	free(zids);
271 	return (0);
272 }
273 
274 static int
275 dlmgmt_init(void)
276 {
277 	int	err;
278 	char	*fmri, *c;
279 	char	filename[MAXPATHLEN];
280 
281 	if (dladm_open(&dld_handle) != DLADM_STATUS_OK) {
282 		dlmgmt_log(LOG_ERR, "dladm_open() failed");
283 		return (EPERM);
284 	}
285 
286 	if (signal(SIGTERM, dlmgmtd_exit) == SIG_ERR ||
287 	    signal(SIGINT, dlmgmtd_exit) == SIG_ERR) {
288 		err = errno;
289 		dlmgmt_log(LOG_ERR, "signal() for SIGTERM/INT failed: %s",
290 		    strerror(err));
291 		return (err);
292 	}
293 
294 	/*
295 	 * First derive the name of the cache file from the FMRI name. This
296 	 * cache name is used to keep active datalink configuration.
297 	 */
298 	if (debug) {
299 		(void) snprintf(cachefile, MAXPATHLEN, "%s/%s%s",
300 		    DLMGMT_TMPFS_DIR, progname, ".debug.cache");
301 	} else {
302 		if ((fmri = getenv("SMF_FMRI")) == NULL) {
303 			dlmgmt_log(LOG_ERR, "dlmgmtd is an smf(7) managed "
304 			    "service and should not be run from the command "
305 			    "line.");
306 			return (EINVAL);
307 		}
308 
309 		/*
310 		 * The FMRI name is in the form of
311 		 * svc:/service/service:instance.  We need to remove the
312 		 * prefix "svc:/" and replace '/' with '-'.  The cache file
313 		 * name is in the form of "service:instance.cache".
314 		 */
315 		if ((c = strchr(fmri, '/')) != NULL)
316 			c++;
317 		else
318 			c = fmri;
319 		(void) snprintf(filename, MAXPATHLEN, "%s.cache", c);
320 		c = filename;
321 		while ((c = strchr(c, '/')) != NULL)
322 			*c = '-';
323 
324 		(void) snprintf(cachefile, MAXPATHLEN, "%s/%s",
325 		    DLMGMT_TMPFS_DIR, filename);
326 	}
327 
328 	dlmgmt_linktable_init();
329 	if ((err = dlmgmt_door_init()) != 0)
330 		goto done;
331 
332 	/*
333 	 * Load datalink configuration and create dlmgmtd door files for all
334 	 * currently running zones.
335 	 */
336 	if ((err = dlmgmt_allzones_init()) != 0)
337 		dlmgmt_door_fini();
338 
339 done:
340 	if (err != 0)
341 		dlmgmt_linktable_fini();
342 	return (err);
343 }
344 
345 static void
346 dlmgmt_fini(void)
347 {
348 	dlmgmt_door_fini();
349 	dlmgmt_linktable_fini();
350 	if (dld_handle != NULL) {
351 		dladm_close(dld_handle);
352 		dld_handle = NULL;
353 	}
354 }
355 
356 /*
357  * This is called by the child process to inform the parent process to
358  * exit with the given return value.
359  */
360 static void
361 dlmgmt_inform_parent_exit(int rv)
362 {
363 	if (debug)
364 		return;
365 
366 	if (write(pfds[1], &rv, sizeof (int)) != sizeof (int)) {
367 		dlmgmt_log(LOG_WARNING,
368 		    "dlmgmt_inform_parent_exit() failed: %s", strerror(errno));
369 		(void) close(pfds[1]);
370 		exit(EXIT_FAILURE);
371 	}
372 	(void) close(pfds[1]);
373 }
374 
375 /*ARGSUSED*/
376 static void
377 dlmgmtd_exit(int signo)
378 {
379 	(void) close(pfds[1]);
380 	dlmgmt_fini();
381 	exit(EXIT_FAILURE);
382 }
383 
384 static void
385 usage(void)
386 {
387 	(void) fprintf(stderr, "Usage: %s [-d]\n", progname);
388 	exit(EXIT_FAILURE);
389 }
390 
391 /*
392  * Restrict privileges to only those needed.
393  */
394 int
395 dlmgmt_drop_privileges(void)
396 {
397 	priv_set_t	*pset;
398 	priv_ptype_t	ptype;
399 	zoneid_t	zoneid = getzoneid();
400 	int		err = 0;
401 
402 	if ((pset = priv_allocset()) == NULL)
403 		return (errno);
404 
405 	/*
406 	 * The global zone needs PRIV_PROC_FORK so that it can fork() when it
407 	 * issues db ops in non-global zones, PRIV_SYS_CONFIG to post
408 	 * sysevents, and PRIV_SYS_DL_CONFIG to initialize link properties in
409 	 * dlmgmt_upcall_linkprop_init().
410 	 *
411 	 * We remove non-basic privileges from the permitted (and thus
412 	 * effective) set.  When executing in a non-global zone, dlmgmtd
413 	 * only needs to read and write to files that it already owns.
414 	 */
415 	priv_basicset(pset);
416 	(void) priv_delset(pset, PRIV_PROC_EXEC);
417 	(void) priv_delset(pset, PRIV_PROC_INFO);
418 	(void) priv_delset(pset, PRIV_PROC_SESSION);
419 	(void) priv_delset(pset, PRIV_FILE_LINK_ANY);
420 	if (zoneid == GLOBAL_ZONEID) {
421 		ptype = PRIV_EFFECTIVE;
422 		if (priv_addset(pset, PRIV_SYS_CONFIG) == -1 ||
423 		    priv_addset(pset, PRIV_SYS_DL_CONFIG) == -1)
424 			err = errno;
425 	} else {
426 		(void) priv_delset(pset, PRIV_PROC_FORK);
427 		ptype = PRIV_PERMITTED;
428 	}
429 	if (err == 0 && setppriv(PRIV_SET, ptype, pset) == -1)
430 		err = errno;
431 done:
432 	priv_freeset(pset);
433 	return (err);
434 }
435 
436 int
437 dlmgmt_elevate_privileges(void)
438 {
439 	priv_set_t	*privset;
440 	int		err = 0;
441 
442 	if ((privset = priv_str_to_set("zone", ",", NULL)) == NULL)
443 		return (errno);
444 	if (setppriv(PRIV_SET, PRIV_EFFECTIVE, privset) == -1)
445 		err = errno;
446 	priv_freeset(privset);
447 	return (err);
448 }
449 
450 /*
451  * Set the uid of this daemon to the "dladm" user and drop privileges to only
452  * those needed.
453  */
454 static int
455 dlmgmt_set_privileges(void)
456 {
457 	int err;
458 
459 	(void) setgroups(0, NULL);
460 	if (setegid(GID_NETADM) == -1 || seteuid(UID_DLADM) == -1)
461 		err = errno;
462 	else
463 		err = dlmgmt_drop_privileges();
464 done:
465 	return (err);
466 }
467 
468 /*
469  * Keep the pfds fd open, close other fds.
470  */
471 /*ARGSUSED*/
472 static int
473 closefunc(void *arg, int fd)
474 {
475 	if (fd != pfds[1])
476 		(void) close(fd);
477 	return (0);
478 }
479 
480 static boolean_t
481 dlmgmt_daemonize(void)
482 {
483 	pid_t pid;
484 	int rv;
485 
486 	if (pipe(pfds) < 0) {
487 		(void) fprintf(stderr, "%s: pipe() failed: %s\n",
488 		    progname, strerror(errno));
489 		exit(EXIT_FAILURE);
490 	}
491 
492 	if ((pid = fork()) == -1) {
493 		(void) fprintf(stderr, "%s: fork() failed: %s\n",
494 		    progname, strerror(errno));
495 		exit(EXIT_FAILURE);
496 	} else if (pid > 0) { /* Parent */
497 		(void) close(pfds[1]);
498 
499 		/*
500 		 * Read the child process's return value from the pfds.
501 		 * If the child process exits unexpected, read() returns -1.
502 		 */
503 		if (read(pfds[0], &rv, sizeof (int)) != sizeof (int)) {
504 			(void) kill(pid, SIGKILL);
505 			rv = EXIT_FAILURE;
506 		}
507 
508 		(void) close(pfds[0]);
509 		exit(rv);
510 	}
511 
512 	/* Child */
513 	(void) close(pfds[0]);
514 	(void) setsid();
515 
516 	/*
517 	 * Close all files except pfds[1].
518 	 */
519 	(void) fdwalk(closefunc, NULL);
520 	(void) chdir("/");
521 	openlog(progname, LOG_PID, LOG_DAEMON);
522 	return (B_TRUE);
523 }
524 
525 int
526 main(int argc, char *argv[])
527 {
528 	int opt, err;
529 
530 	progname = strrchr(argv[0], '/');
531 	if (progname != NULL)
532 		progname++;
533 	else
534 		progname = argv[0];
535 
536 	/*
537 	 * Process options.
538 	 */
539 	while ((opt = getopt(argc, argv, "d")) != EOF) {
540 		switch (opt) {
541 		case 'd':
542 			debug = B_TRUE;
543 			break;
544 		default:
545 			usage();
546 		}
547 	}
548 
549 	if (!debug && !dlmgmt_daemonize())
550 		return (EXIT_FAILURE);
551 
552 	if ((err = dlmgmt_init()) != 0) {
553 		dlmgmt_log(LOG_ERR, "unable to initialize daemon: %s",
554 		    strerror(err));
555 		goto child_out;
556 	} else if ((err = dlmgmt_set_privileges()) != 0) {
557 		dlmgmt_log(LOG_ERR, "unable to set daemon privileges: %s",
558 		    strerror(err));
559 		dlmgmt_fini();
560 		goto child_out;
561 	}
562 
563 	/*
564 	 * Inform the parent process that it can successfully exit.
565 	 */
566 	dlmgmt_inform_parent_exit(EXIT_SUCCESS);
567 
568 	for (;;)
569 		(void) pause();
570 
571 child_out:
572 	/* return from main() forcibly exits an MT process */
573 	dlmgmt_inform_parent_exit(EXIT_FAILURE);
574 	return (EXIT_FAILURE);
575 }
576