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