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_NETADM) == -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_NETADM) < 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_NETADM) == -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 }