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 2014 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 int
130 dlmgmt_door_attach(zoneid_t zoneid, char *rootdir)
131 {
132 int fd;
133 int err = 0;
134 char doorpath[MAXPATHLEN];
135 struct stat statbuf;
136
137 /* Handle running in a non-native branded zone (i.e. has /native) */
138 (void) snprintf(doorpath, sizeof (doorpath), "%s/native%s",
139 rootdir, DLMGMT_TMPFS_DIR);
140 if (stat(doorpath, &statbuf) == 0) {
141 (void) snprintf(doorpath, sizeof (doorpath), "%s/native%s",
142 rootdir, DLMGMT_DOOR);
143 } else {
144 (void) snprintf(doorpath, sizeof (doorpath), "%s%s",
145 rootdir, DLMGMT_DOOR);
146 }
147
148 /*
149 * Create the door file for dlmgmtd.
150 */
151 if ((fd = open(doorpath, O_CREAT|O_RDONLY, 0644)) == -1) {
152 err = errno;
153 dlmgmt_log(LOG_ERR, "open(%s) failed: %s", doorpath,
154 strerror(err));
155 return (err);
156 }
157 (void) close(fd);
158 if (chown(doorpath, UID_DLADM, GID_NETADM) == -1)
159 return (errno);
160
161 /*
162 * fdetach first in case a previous daemon instance exited
163 * ungracefully.
164 */
165 (void) fdetach(doorpath);
166 if (fattach(dlmgmt_door_fd, doorpath) != 0) {
167 err = errno;
168 dlmgmt_log(LOG_ERR, "fattach(%s) failed: %s", doorpath,
169 strerror(err));
170 } else if (zoneid == GLOBAL_ZONEID) {
171 if ((err = dlmgmt_set_doorfd(B_TRUE)) != 0) {
172 dlmgmt_log(LOG_ERR, "cannot set kernel doorfd: %s",
173 strerror(err));
174 }
175 }
176
177 return (err);
178 }
179
180 /*
181 * Create the /etc/svc/volatile/dladm/ directory if it doesn't exist, load the
182 * datalink.conf data for this zone, and create/attach the door rendezvous
183 * file.
184 */
185 int
186 dlmgmt_zone_init(zoneid_t zoneid)
187 {
188 char rootdir[MAXPATHLEN], tmpfsdir[MAXPATHLEN];
189 int err;
190 struct stat statbuf;
191
192 if (zoneid == GLOBAL_ZONEID) {
193 rootdir[0] = '\0';
194 } else if (zone_getattr(zoneid, ZONE_ATTR_ROOT, rootdir,
195 sizeof (rootdir)) < 0) {
196 return (errno);
197 }
198
199 /*
200 * Create the DLMGMT_TMPFS_DIR directory.
201 */
202 (void) snprintf(tmpfsdir, sizeof (tmpfsdir), "%s%s", rootdir,
203 DLMGMT_TMPFS_DIR);
204 if (stat(tmpfsdir, &statbuf) < 0) {
205 if (mkdir(tmpfsdir, (mode_t)0755) < 0) {
206 /*
207 * Handle running in a non-native branded zone
208 * (i.e. has /native)
209 */
210 (void) snprintf(tmpfsdir, sizeof (tmpfsdir),
211 "%s/native%s", rootdir, DLMGMT_TMPFS_DIR);
212 if (mkdir(tmpfsdir, (mode_t)0755) < 0)
213 return (errno);
214 }
215 } else if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
216 return (ENOTDIR);
217 }
218
219 if ((chmod(tmpfsdir, 0755) < 0) ||
220 (chown(tmpfsdir, UID_DLADM, GID_NETADM) < 0)) {
221 return (EPERM);
222 }
223
224 if ((err = dlmgmt_db_init(zoneid, rootdir)) != 0)
225 return (err);
226 return (dlmgmt_door_attach(zoneid, rootdir));
227 }
228
229 /*
230 * Initialize each running zone.
231 */
232 static int
233 dlmgmt_allzones_init(void)
234 {
235 int err, i;
236 zoneid_t *zids = NULL;
237 uint_t nzids, nzids_saved;
238
239 if (zone_list(NULL, &nzids) != 0)
240 return (errno);
241 again:
242 nzids *= 2;
243 if ((zids = malloc(nzids * sizeof (zoneid_t))) == NULL)
244 return (errno);
245 nzids_saved = nzids;
246 if (zone_list(zids, &nzids) != 0) {
247 free(zids);
248 return (errno);
249 }
250 if (nzids > nzids_saved) {
251 free(zids);
252 goto again;
253 }
254
255 for (i = 0; i < nzids; i++) {
256 if ((err = dlmgmt_zone_init(zids[i])) != 0)
257 break;
258 }
259 free(zids);
260 return (err);
261 }
262
263 static int
264 dlmgmt_init(void)
265 {
266 int err;
267 char *fmri, *c;
268 char filename[MAXPATHLEN];
269
270 if (dladm_open(&dld_handle) != DLADM_STATUS_OK) {
271 dlmgmt_log(LOG_ERR, "dladm_open() failed");
272 return (EPERM);
273 }
274
275 if (signal(SIGTERM, dlmgmtd_exit) == SIG_ERR ||
276 signal(SIGINT, dlmgmtd_exit) == SIG_ERR) {
277 err = errno;
278 dlmgmt_log(LOG_ERR, "signal() for SIGTERM/INT failed: %s",
279 strerror(err));
280 return (err);
281 }
282
283 (void) unlink(ZONE_LOCK);
284
285 /*
286 * First derive the name of the cache file from the FMRI name. This
287 * cache name is used to keep active datalink configuration.
288 */
289 if (debug) {
290 (void) snprintf(cachefile, MAXPATHLEN, "%s/%s%s",
291 DLMGMT_TMPFS_DIR, progname, ".debug.cache");
292 } else {
293 if ((fmri = getenv("SMF_FMRI")) == NULL) {
294 dlmgmt_log(LOG_ERR, "dlmgmtd is an smf(5) managed "
295 "service and should not be run from the command "
296 "line.");
297 return (EINVAL);
298 }
299
300 /*
301 * The FMRI name is in the form of
302 * svc:/service/service:instance. We need to remove the
303 * prefix "svc:/" and replace '/' with '-'. The cache file
304 * name is in the form of "service:instance.cache".
305 */
306 if ((c = strchr(fmri, '/')) != NULL)
307 c++;
308 else
309 c = fmri;
310 (void) snprintf(filename, MAXPATHLEN, "%s.cache", c);
311 c = filename;
312 while ((c = strchr(c, '/')) != NULL)
313 *c = '-';
314
315 (void) snprintf(cachefile, MAXPATHLEN, "%s/%s",
316 DLMGMT_TMPFS_DIR, filename);
317 }
318
319 dlmgmt_linktable_init();
320 if ((err = dlmgmt_door_init()) != 0)
321 goto done;
322
323 /*
324 * Load datalink configuration and create dlmgmtd door files for all
325 * currently running zones.
326 */
327 if ((err = dlmgmt_allzones_init()) != 0)
328 dlmgmt_door_fini();
329
330 done:
331 if (err != 0)
332 dlmgmt_linktable_fini();
333 return (err);
334 }
335
336 static void
337 dlmgmt_fini(void)
338 {
339 dlmgmt_door_fini();
340 dlmgmt_linktable_fini();
341 if (dld_handle != NULL) {
342 dladm_close(dld_handle);
343 dld_handle = NULL;
344 }
345 }
346
347 /*
348 * This is called by the child process to inform the parent process to
349 * exit with the given return value.
350 */
351 static void
352 dlmgmt_inform_parent_exit(int rv)
353 {
354 if (debug)
355 return;
356
357 if (write(pfds[1], &rv, sizeof (int)) != sizeof (int)) {
358 dlmgmt_log(LOG_WARNING,
359 "dlmgmt_inform_parent_exit() failed: %s", strerror(errno));
360 (void) close(pfds[1]);
361 exit(EXIT_FAILURE);
362 }
363 (void) close(pfds[1]);
364 }
365
366 /*ARGSUSED*/
367 static void
368 dlmgmtd_exit(int signo)
369 {
370 (void) close(pfds[1]);
371 dlmgmt_fini();
372 exit(EXIT_FAILURE);
373 }
374
375 static void
376 usage(void)
377 {
378 (void) fprintf(stderr, "Usage: %s [-d]\n", progname);
379 exit(EXIT_FAILURE);
380 }
381
382 /*
383 * Restrict privileges to only those needed.
384 */
385 int
386 dlmgmt_drop_privileges(void)
387 {
388 priv_set_t *pset;
389 priv_ptype_t ptype;
390 zoneid_t zoneid = getzoneid();
391 int err = 0;
392
393 if ((pset = priv_allocset()) == NULL)
394 return (errno);
395
396 /*
397 * The global zone needs PRIV_PROC_FORK so that it can fork() when it
398 * issues db ops in non-global zones, PRIV_SYS_CONFIG to post
399 * sysevents, and PRIV_SYS_DL_CONFIG to initialize link properties in
400 * dlmgmt_upcall_linkprop_init().
401 *
402 * We remove non-basic privileges from the permitted (and thus
403 * effective) set. When executing in a non-global zone, dlmgmtd
404 * only needs to read and write to files that it already owns.
405 */
406 priv_basicset(pset);
407 (void) priv_delset(pset, PRIV_PROC_EXEC);
408 (void) priv_delset(pset, PRIV_PROC_INFO);
409 (void) priv_delset(pset, PRIV_PROC_SESSION);
410 (void) priv_delset(pset, PRIV_FILE_LINK_ANY);
411 if (zoneid == GLOBAL_ZONEID) {
412 ptype = PRIV_EFFECTIVE;
413 if (priv_addset(pset, PRIV_SYS_CONFIG) == -1 ||
414 priv_addset(pset, PRIV_SYS_DL_CONFIG) == -1)
415 err = errno;
416 } else {
417 (void) priv_delset(pset, PRIV_PROC_FORK);
418 ptype = PRIV_PERMITTED;
419 }
420 if (err == 0 && setppriv(PRIV_SET, ptype, pset) == -1)
421 err = errno;
422 done:
423 priv_freeset(pset);
424 return (err);
425 }
426
427 int
428 dlmgmt_elevate_privileges(void)
429 {
430 priv_set_t *privset;
431 int err = 0;
432
433 if ((privset = priv_str_to_set("zone", ",", NULL)) == NULL)
434 return (errno);
435 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, privset) == -1)
436 err = errno;
437 priv_freeset(privset);
438 return (err);
439 }
440
441 /*
442 * Set the uid of this daemon to the "dladm" user and drop privileges to only
443 * those needed.
444 */
445 static int
446 dlmgmt_set_privileges(void)
447 {
448 int err;
449
450 (void) setgroups(0, NULL);
451 if (setegid(GID_NETADM) == -1 || seteuid(UID_DLADM) == -1)
452 err = errno;
453 else
454 err = dlmgmt_drop_privileges();
455 done:
456 return (err);
457 }
458
459 /*
460 * Keep the pfds fd open, close other fds.
461 */
462 /*ARGSUSED*/
463 static int
464 closefunc(void *arg, int fd)
465 {
466 if (fd != pfds[1])
467 (void) close(fd);
468 return (0);
469 }
470
471 static boolean_t
472 dlmgmt_daemonize(void)
473 {
474 pid_t pid;
475 int rv;
476
477 if (pipe(pfds) < 0) {
478 (void) fprintf(stderr, "%s: pipe() failed: %s\n",
479 progname, strerror(errno));
480 exit(EXIT_FAILURE);
481 }
482
483 if ((pid = fork()) == -1) {
484 (void) fprintf(stderr, "%s: fork() failed: %s\n",
485 progname, strerror(errno));
486 exit(EXIT_FAILURE);
487 } else if (pid > 0) { /* Parent */
488 (void) close(pfds[1]);
489
490 /*
491 * Read the child process's return value from the pfds.
492 * If the child process exits unexpected, read() returns -1.
493 */
494 if (read(pfds[0], &rv, sizeof (int)) != sizeof (int)) {
495 (void) kill(pid, SIGKILL);
496 rv = EXIT_FAILURE;
497 }
498
499 (void) close(pfds[0]);
500 exit(rv);
501 }
502
503 /* Child */
504 (void) close(pfds[0]);
505 (void) setsid();
506
507 /*
508 * Close all files except pfds[1].
509 */
510 (void) fdwalk(closefunc, NULL);
511 (void) chdir("/");
512 openlog(progname, LOG_PID, LOG_DAEMON);
513 return (B_TRUE);
514 }
515
516 int
517 main(int argc, char *argv[])
518 {
519 int opt, err;
520
521 progname = strrchr(argv[0], '/');
522 if (progname != NULL)
523 progname++;
524 else
525 progname = argv[0];
526
527 /*
528 * Process options.
529 */
530 while ((opt = getopt(argc, argv, "d")) != EOF) {
531 switch (opt) {
532 case 'd':
533 debug = B_TRUE;
534 break;
535 default:
536 usage();
537 }
538 }
539
540 if (!debug && !dlmgmt_daemonize())
541 return (EXIT_FAILURE);
542
543 if ((err = dlmgmt_init()) != 0) {
544 dlmgmt_log(LOG_ERR, "unable to initialize daemon: %s",
545 strerror(err));
546 goto child_out;
547 } else if ((err = dlmgmt_set_privileges()) != 0) {
548 dlmgmt_log(LOG_ERR, "unable to set daemon privileges: %s",
549 strerror(err));
550 dlmgmt_fini();
551 goto child_out;
552 }
553
554 /*
555 * Inform the parent process that it can successfully exit.
556 */
557 dlmgmt_inform_parent_exit(EXIT_SUCCESS);
558
559 for (;;)
560 (void) pause();
561
562 child_out:
563 /* return from main() forcibly exits an MT process */
564 dlmgmt_inform_parent_exit(EXIT_FAILURE);
565 return (EXIT_FAILURE);
566 }