xref: /illumos-gate/usr/src/cmd/fs.d/smbclnt/smbiod-svc/smbiod-svc.c (revision 8c69cc8fbe729fa7b091e901c4b50508ccc6bb33)
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