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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
  24  */
  25 
  26 /*
  27  * SMBFS I/O Daemon (SMF service)
  28  */
  29 
  30 #include <sys/types.h>
  31 #include <sys/stat.h>
  32 #include <sys/note.h>
  33 #include <sys/queue.h>
  34 
  35 #include <errno.h>
  36 #include <fcntl.h>
  37 #include <signal.h>
  38 #include <stdarg.h>
  39 #include <stdio.h>
  40 #include <string.h>
  41 #include <strings.h>
  42 #include <stdlib.h>
  43 #include <synch.h>
  44 #include <time.h>
  45 #include <unistd.h>
  46 #include <ucred.h>
  47 #include <wait.h>
  48 #include <priv_utils.h>
  49 #include <err.h>
  50 #include <door.h>
  51 #include <libscf.h>
  52 #include <locale.h>
  53 #include <thread.h>
  54 #include <assert.h>
  55 
  56 #include <netsmb/smb_lib.h>
  57 
  58 static boolean_t d_flag = B_FALSE;
  59 
  60 /* Keep a list of child processes. */
  61 typedef struct _child {
  62         LIST_ENTRY(_child) list;
  63         pid_t pid;
  64         uid_t uid;
  65 } child_t;
  66 static LIST_HEAD(, _child) child_list = { 0 };
  67 mutex_t cl_mutex = DEFAULTMUTEX;
  68 
  69 static const char smbiod_path[] = "/usr/lib/smbfs/smbiod";
  70 static const char door_path[] = SMBIOD_SVC_DOOR;
  71 
  72 void svc_dispatch(void *cookie, char *argp, size_t argsz,
  73     door_desc_t *dp, uint_t n_desc);
  74 static int cmd_start(uid_t uid, gid_t gid);
  75 static int new_child(uid_t uid, gid_t gid);
  76 static void svc_sigchld(void);
  77 static void child_gone(uid_t, pid_t, int);
  78 static void svc_cleanup(void);
  79 
  80 static child_t *
  81 child_find_by_pid(pid_t pid)
  82 {
  83         child_t *cp;
  84 
  85         assert(MUTEX_HELD(&cl_mutex));
  86         LIST_FOREACH(cp, &child_list, list) {
  87                 if (cp->pid == pid)
  88                         return (cp);
  89         }
  90         return (NULL);
  91 }
  92 
  93 static child_t *
  94 child_find_by_uid(uid_t uid)
  95 {
  96         child_t *cp;
  97 
  98         assert(MUTEX_HELD(&cl_mutex));
  99         LIST_FOREACH(cp, &child_list, list) {
 100                 if (cp->uid == uid)
 101                         return (cp);
 102         }
 103         return (NULL);
 104 }
 105 
 106 /*
 107  * Find out if the service is already running.
 108  * Return: true, false.
 109  */
 110 static boolean_t
 111 already_running(void)
 112 {
 113         door_info_t info;
 114         int fd, rc;
 115 
 116         if ((fd = open(door_path, O_RDONLY)) < 0)
 117                 return (B_FALSE);
 118 
 119         rc = door_info(fd, &info);
 120         close(fd);
 121         if (rc < 0)
 122                 return (B_FALSE);
 123 
 124         return (B_TRUE);
 125 }
 126 
 127 /*
 128  * This function will fork off a child process,
 129  * from which only the child will return.
 130  *
 131  * The parent exit status is taken as the SMF start method
 132  * success or failure, so the parent waits (via pipe read)
 133  * for the child to finish initialization before it exits.
 134  * Use SMF error codes only on exit.
 135  */
 136 static int
 137 daemonize_init(void)
 138 {
 139         int pid, st;
 140         int pfds[2];
 141 
 142         chdir("/");
 143 
 144         if (pipe(pfds) < 0) {
 145                 perror("pipe");
 146                 exit(SMF_EXIT_ERR_FATAL);
 147         }
 148         if ((pid = fork1()) == -1) {
 149                 perror("fork");
 150                 exit(SMF_EXIT_ERR_FATAL);
 151         }
 152 
 153         /*
 154          * If we're the parent process, wait for either the child to send us
 155          * the appropriate exit status over the pipe or for the read to fail
 156          * (presumably with 0 for EOF if our child terminated abnormally).
 157          * If the read fails, exit with either the child's exit status if it
 158          * exited or with SMF_EXIT_ERR_FATAL if it died from a fatal signal.
 159          */
 160         if (pid != 0) {
 161                 /* parent */
 162                 close(pfds[1]);
 163                 if (read(pfds[0], &st, sizeof (st)) == sizeof (st))
 164                         _exit(st);
 165                 if (waitpid(pid, &st, 0) == pid && WIFEXITED(st))
 166                         _exit(WEXITSTATUS(st));
 167                 _exit(SMF_EXIT_ERR_FATAL);
 168         }
 169 
 170         /* child */
 171         close(pfds[0]);
 172 
 173         return (pfds[1]);
 174 }
 175 
 176 static void
 177 daemonize_fini(int pfd, int rc)
 178 {
 179         /* Tell parent we're ready. */
 180         (void) write(pfd, &rc, sizeof (rc));
 181         close(pfd);
 182 }
 183 
 184 int
 185 main(int argc, char **argv)
 186 {
 187         sigset_t oldmask, tmpmask;
 188         struct sigaction sa;
 189         struct rlimit rl;
 190         int door_fd = -1, tmp_fd = -1, pfd = -1;
 191         int c, sig;
 192         int rc = SMF_EXIT_ERR_FATAL;
 193         boolean_t created = B_FALSE, attached = B_FALSE;
 194 
 195         /* set locale and text domain for i18n */
 196         (void) setlocale(LC_ALL, "");
 197         (void) textdomain(TEXT_DOMAIN);
 198 
 199         while ((c = getopt(argc, argv, "d")) != -1) {
 200                 switch (c) {
 201                 case 'd':
 202                         /* Do debug messages. */
 203                         d_flag = B_TRUE;
 204                         break;
 205                 default:
 206                         fprintf(stderr, "Usage: %s [-d]\n", argv[0]);
 207                         return (SMF_EXIT_ERR_CONFIG);
 208                 }
 209         }
 210 
 211         if (already_running()) {
 212                 fprintf(stderr, "%s: already running", argv[0]);
 213                 return (rc);
 214         }
 215 
 216         /*
 217          * Raise the fd limit to max
 218          * errors here are non-fatal
 219          */
 220         if (getrlimit(RLIMIT_NOFILE, &rl) != 0) {
 221                 fprintf(stderr, "getrlimit failed, err %d\n", errno);
 222         } else if (rl.rlim_cur < rl.rlim_max) {
 223                 rl.rlim_cur = rl.rlim_max;
 224                 if (setrlimit(RLIMIT_NOFILE, &rl) != 0)
 225                         fprintf(stderr, "setrlimit "
 226                             "RLIMIT_NOFILE %d, err %d",
 227                             (int)rl.rlim_cur, errno);
 228         }
 229 
 230         /*
 231          * Want all signals blocked, as we're doing
 232          * synchronous delivery via sigwait below.
 233          */
 234         sigfillset(&tmpmask);
 235         sigprocmask(SIG_BLOCK, &tmpmask, &oldmask);
 236 
 237         /*
 238          * Do want SIGCHLD, and will waitpid().
 239          */
 240         sa.sa_flags = SA_NOCLDSTOP;
 241         sa.sa_handler = SIG_DFL;
 242         sigemptyset(&sa.sa_mask);
 243         sigaction(SIGCHLD, &sa, NULL);
 244 
 245         /*
 246          * Daemonize, unless debugging.
 247          */
 248         if (d_flag) {
 249                 /* debug: run in foregound (not a service) */
 250                 putenv("SMBFS_DEBUG=1");
 251         } else {
 252                 /* Non-debug: start daemon in the background. */
 253                 pfd = daemonize_init();
 254         }
 255 
 256         /*
 257          * Create directory for all smbiod doors.
 258          */
 259         if ((mkdir(SMBIOD_RUNDIR, 0755) < 0) && errno != EEXIST) {
 260                 perror(SMBIOD_RUNDIR);
 261                 goto out;
 262         }
 263 
 264         /*
 265          * Create a file for the main service door.
 266          */
 267         unlink(door_path);
 268         tmp_fd = open(door_path, O_RDWR|O_CREAT|O_EXCL, 0644);
 269         if (tmp_fd < 0) {
 270                 perror(door_path);
 271                 goto out;
 272         }
 273         close(tmp_fd);
 274         tmp_fd = -1;
 275         created = B_TRUE;
 276 
 277         /* Setup the door service. */
 278         door_fd = door_create(svc_dispatch, NULL,
 279             DOOR_REFUSE_DESC | DOOR_NO_CANCEL);
 280         if (door_fd == -1) {
 281                 perror("svc door_create");
 282                 goto out;
 283         }
 284         fdetach(door_path);
 285         if (fattach(door_fd, door_path) < 0) {
 286                 fprintf(stderr, "%s: fattach failed, %s\n",
 287                     door_path, strerror(errno));
 288                 goto out;
 289         }
 290         attached = B_TRUE;
 291 
 292         /*
 293          * Initializations done.  Tell start method we're up.
 294          */
 295         rc = SMF_EXIT_OK;
 296         if (pfd != -1) {
 297                 daemonize_fini(pfd, rc);
 298                 pfd = -1;
 299         }
 300 
 301         /*
 302          * Main thread just waits for signals.
 303          */
 304 again:
 305         sig = sigwait(&tmpmask);
 306         if (d_flag)
 307                 fprintf(stderr, "main: sig=%d\n", sig);
 308         switch (sig) {
 309         case SIGINT:
 310         case SIGTERM:
 311                 /*
 312                  * The whole process contract gets a SIGTERM
 313                  * at once.  Give children a chance to exit
 314                  * so we can do normal SIGCHLD cleanup.
 315                  * Prevent new door_open calls.
 316                  */
 317                 fdetach(door_path);
 318                 attached = B_FALSE;
 319                 alarm(2);
 320                 goto again;
 321         case SIGALRM:
 322                 break;  /* normal termination */
 323         case SIGCHLD:
 324                 svc_sigchld();
 325                 goto again;
 326         case SIGCONT:
 327                 goto again;
 328         default:
 329                 /* Unexpected signal. */
 330                 fprintf(stderr, "svc_main: unexpected sig=%d\n", sig);
 331                 break;
 332         }
 333 
 334 out:
 335         if (attached)
 336                 fdetach(door_path);
 337         if (door_fd != -1)
 338                 door_revoke(door_fd);
 339         if (created)
 340                 unlink(door_path);
 341 
 342         /* NB: door threads gone now. */
 343         svc_cleanup();
 344 
 345         /* If startup error, report to parent. */
 346         if (pfd != -1)
 347                 daemonize_fini(pfd, rc);
 348 
 349         return (rc);
 350 }
 351 
 352 /*ARGSUSED*/
 353 void
 354 svc_dispatch(void *cookie, char *argp, size_t argsz,
 355     door_desc_t *dp, uint_t n_desc)
 356 {
 357         ucred_t *ucred = NULL;
 358         uid_t uid;
 359         gid_t gid;
 360         int32_t cmd, rc;
 361 
 362         /*
 363          * Allow a NULL arg call to check if this
 364          * daemon is running.  Just return zero.
 365          */
 366         if (argp == NULL) {
 367                 rc = 0;
 368                 goto out;
 369         }
 370 
 371         /*
 372          * Get the caller's credentials.
 373          * (from client side of door)
 374          */
 375         if (door_ucred(&ucred) != 0) {
 376                 rc = EACCES;
 377                 goto out;
 378         }
 379         uid = ucred_getruid(ucred);
 380         gid = ucred_getrgid(ucred);
 381 
 382         /*
 383          * Arg is just an int command code.
 384          * Reply is also an int.
 385          */
 386         if (argsz != sizeof (cmd)) {
 387                 rc = EINVAL;
 388                 goto out;
 389         }
 390         bcopy(argp, &cmd, sizeof (cmd));
 391         switch (cmd) {
 392         case SMBIOD_START:
 393                 rc = cmd_start(uid, gid);
 394                 break;
 395         default:
 396                 rc = EINVAL;
 397                 goto out;
 398         }
 399 
 400 out:
 401         if (ucred != NULL)
 402                 ucred_free(ucred);
 403 
 404         door_return((void *)&rc, sizeof (rc), NULL, 0);
 405 }
 406 
 407 /*
 408  * Start a per-user smbiod, if not already running.
 409  */
 410 int
 411 cmd_start(uid_t uid, gid_t gid)
 412 {
 413         char door_file[64];
 414         child_t *cp;
 415         int pid, fd = -1;
 416 
 417         mutex_lock(&cl_mutex);
 418         cp = child_find_by_uid(uid);
 419         if (cp != NULL) {
 420                 /* This UID already has an IOD. */
 421                 mutex_unlock(&cl_mutex);
 422                 if (d_flag) {
 423                         fprintf(stderr, "cmd_start: uid %d"
 424                             " already has an iod\n", uid);
 425                 }
 426                 return (0);
 427         }
 428 
 429         /*
 430          * OK, create a new child.
 431          */
 432         cp = malloc(sizeof (*cp));
 433         if (cp == NULL) {
 434                 mutex_unlock(&cl_mutex);
 435                 return (ENOMEM);
 436         }
 437         cp->pid = 0; /* update below */
 438         cp->uid = uid;
 439         LIST_INSERT_HEAD(&child_list, cp, list);
 440         mutex_unlock(&cl_mutex);
 441 
 442         /*
 443          * The child will not have permission to create or
 444          * destroy files in SMBIOD_RUNDIR so do that here.
 445          */
 446         snprintf(door_file, sizeof (door_file),
 447             SMBIOD_USR_DOOR, cp->uid);
 448         unlink(door_file);
 449         fd = open(door_file, O_RDWR|O_CREAT|O_EXCL, 0600);
 450         if (fd < 0) {
 451                 perror(door_file);
 452                 goto errout;
 453         }
 454         if (fchown(fd, uid, gid) < 0) {
 455                 perror(door_file);
 456                 goto errout;
 457         }
 458         close(fd);
 459         fd = -1;
 460 
 461         if ((pid = fork1()) == -1) {
 462                 perror("fork");
 463                 goto errout;
 464         }
 465         if (pid == 0) {
 466                 (void) new_child(uid, gid);
 467                 _exit(1);
 468         }
 469         /* parent */
 470         cp->pid = pid;
 471 
 472         if (d_flag) {
 473                 fprintf(stderr, "cmd_start: uid %d new iod, pid %d\n",
 474                     uid, pid);
 475         }
 476 
 477         return (0);
 478 
 479 errout:
 480         if (fd != -1)
 481                 close(fd);
 482         mutex_lock(&cl_mutex);
 483         LIST_REMOVE(cp, list);
 484         mutex_unlock(&cl_mutex);
 485         free(cp);
 486         return (errno);
 487 }
 488 
 489 /*
 490  * Assume the passed credentials (from the door client),
 491  * drop any extra privileges, and exec the per-user iod.
 492  */
 493 static int
 494 new_child(uid_t uid, gid_t gid)
 495 {
 496         char *argv[2];
 497         int flags, rc;
 498 
 499         flags = PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS;
 500         rc = __init_daemon_priv(flags, uid, gid, PRIV_NET_ACCESS, NULL);
 501         if (rc != 0)
 502                 return (errno);
 503 
 504         argv[0] = "smbiod";
 505         argv[1] = NULL;
 506         (void) execv(smbiod_path, argv);
 507         return (errno);
 508 }
 509 
 510 static void
 511 svc_sigchld(void)
 512 {
 513         child_t *cp;
 514         pid_t pid;
 515         int err, status, found = 0;
 516 
 517         mutex_lock(&cl_mutex);
 518 
 519         while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
 520 
 521                 found++;
 522                 if (d_flag)
 523                         fprintf(stderr, "svc_sigchld: pid %d\n", (int)pid);
 524 
 525                 cp = child_find_by_pid(pid);
 526                 if (cp == NULL) {
 527                         fprintf(stderr, "Unknown pid %d\n", (int)pid);
 528                         continue;
 529                 }
 530                 child_gone(cp->uid, cp->pid, status);
 531                 LIST_REMOVE(cp, list);
 532                 free(cp);
 533         }
 534         err = errno;
 535 
 536         mutex_unlock(&cl_mutex);
 537 
 538         /* ECHILD is the normal end of loop. */
 539         if (pid < 0 && err != ECHILD)
 540                 fprintf(stderr, "svc_sigchld: waitpid err %d\n", err);
 541         if (found == 0)
 542                 fprintf(stderr, "svc_sigchld: no children?\n");
 543 }
 544 
 545 static void
 546 child_gone(uid_t uid, pid_t pid, int status)
 547 {
 548         char door_file[64];
 549         int x;
 550 
 551         if (d_flag)
 552                 fprintf(stderr, "child_gone: uid %d pid %d\n",
 553                     uid, (int)pid);
 554 
 555         snprintf(door_file, sizeof (door_file),
 556             SMBIOD_RUNDIR "/%d", uid);
 557         unlink(door_file);
 558 
 559         if (WIFEXITED(status)) {
 560                 x = WEXITSTATUS(status);
 561                 if (x != 0) {
 562                         fprintf(stderr,
 563                             "uid %d, pid %d exit %d",
 564                             uid, (int)pid, x);
 565                 }
 566         }
 567         if (WIFSIGNALED(status)) {
 568                 x = WTERMSIG(status);
 569                 fprintf(stderr,
 570                     "uid %d, pid %d signal %d",
 571                     uid, (int)pid, x);
 572         }
 573 }
 574 
 575 /*
 576  * Final cleanup before exit.  Unlink child doors, etc.
 577  * Called while single threaded, so no locks needed here.
 578  * The list is normally empty by now due to svc_sigchld
 579  * calls during shutdown.  But in case there were any
 580  * straglers, do cleanup here.  Don't bother freeing any
 581  * list elements here, as we're exiting.
 582  */
 583 static void
 584 svc_cleanup(void)
 585 {
 586         child_t *cp;
 587 
 588         LIST_FOREACH(cp, &child_list, list) {
 589                 child_gone(cp->uid, cp->pid, 0);
 590         }
 591 }