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 } 592