xref: /freebsd/usr.sbin/jail/jail.c (revision 195ebc7e9e4b129de810833791a19dfb4349d6a9)
1 /*-
2  * Copyright (c) 1999 Poul-Henning Kamp.
3  * Copyright (c) 2009 James Gritton
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <sys/jail.h>
33 #include <sys/socket.h>
34 #include <sys/sysctl.h>
35 #include <sys/uio.h>
36 
37 #include <arpa/inet.h>
38 #include <netinet/in.h>
39 
40 #include <ctype.h>
41 #include <err.h>
42 #include <errno.h>
43 #include <grp.h>
44 #include <login_cap.h>
45 #include <netdb.h>
46 #include <paths.h>
47 #include <pwd.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52 
53 #define	SJPARAM		"security.jail.param"
54 #define	ERRMSG_SIZE	256
55 
56 struct param {
57 	struct iovec name;
58 	struct iovec value;
59 };
60 
61 static struct param *params;
62 static char **param_values;
63 static int nparams;
64 
65 static char *ip4_addr;
66 #ifdef INET6
67 static char *ip6_addr;
68 #endif
69 
70 static void add_ip_addr(char **addrp, char *newaddr);
71 #ifdef INET6
72 static void add_ip_addr46(char *newaddr);
73 #endif
74 static void add_ip_addrinfo(int ai_flags, char *value);
75 static void quoted_print(FILE *fp, char *str);
76 static void set_param(const char *name, char *value);
77 static void usage(void);
78 
79 extern char **environ;
80 
81 #define GET_USER_INFO do {						\
82 	pwd = getpwnam(username);					\
83 	if (pwd == NULL) {						\
84 		if (errno)						\
85 			err(1, "getpwnam: %s", username);		\
86 		else							\
87 			errx(1, "%s: no such user", username);		\
88 	}								\
89 	lcap = login_getpwclass(pwd);					\
90 	if (lcap == NULL)						\
91 		err(1, "getpwclass: %s", username);			\
92 	ngroups = NGROUPS;						\
93 	if (getgrouplist(username, pwd->pw_gid, groups, &ngroups) != 0)	\
94 		err(1, "getgrouplist: %s", username);			\
95 } while (0)
96 
97 int
98 main(int argc, char **argv)
99 {
100 	login_cap_t *lcap = NULL;
101 	struct iovec rparams[2];
102 	struct passwd *pwd = NULL;
103 	gid_t groups[NGROUPS];
104 	int ch, cmdarg, i, jail_set_flags, jid, ngroups;
105 	int hflag, iflag, Jflag, lflag, rflag, uflag, Uflag;
106 	char *ep, *jailname, *securelevel, *username, *JidFile;
107 	char errmsg[ERRMSG_SIZE];
108 	static char *cleanenv;
109 	const char *shell, *p = NULL;
110 	FILE *fp;
111 
112 	hflag = iflag = Jflag = lflag = rflag = uflag = Uflag =
113 	    jail_set_flags = 0;
114 	cmdarg = jid = -1;
115 	jailname = securelevel = username = JidFile = cleanenv = NULL;
116 	fp = NULL;
117 
118 	while ((ch = getopt(argc, argv, "cdhilmn:r:s:u:U:J:")) != -1) {
119 		switch (ch) {
120 		case 'd':
121 			jail_set_flags |= JAIL_DYING;
122 			break;
123 		case 'h':
124 			hflag = 1;
125 			break;
126 		case 'i':
127 			iflag = 1;
128 			break;
129 		case 'J':
130 			JidFile = optarg;
131 			Jflag = 1;
132 			break;
133 		case 'n':
134 			jailname = optarg;
135 			break;
136 		case 's':
137 			securelevel = optarg;
138 			break;
139 		case 'u':
140 			username = optarg;
141 			uflag = 1;
142 			break;
143 		case 'U':
144 			username = optarg;
145 			Uflag = 1;
146 			break;
147 		case 'l':
148 			lflag = 1;
149 			break;
150 		case 'c':
151 			jail_set_flags |= JAIL_CREATE;
152 			break;
153 		case 'm':
154 			jail_set_flags |= JAIL_UPDATE;
155 			break;
156 		case 'r':
157 			jid = strtoul(optarg, &ep, 10);
158 			if (!*optarg || *ep) {
159 				*(const void **)&rparams[0].iov_base = "name";
160 				rparams[0].iov_len = sizeof("name");
161 				rparams[1].iov_base = optarg;
162 				rparams[1].iov_len = strlen(optarg) + 1;
163 				jid = jail_get(rparams, 2, 0);
164 				if (jid < 0)
165 					errx(1, "unknown jail: %s", optarg);
166 			}
167 			rflag = 1;
168 			break;
169 		default:
170 			usage();
171 		}
172 	}
173 	argc -= optind;
174 	argv += optind;
175 	if (rflag) {
176 		if (argc > 0 || iflag || Jflag || lflag || uflag || Uflag)
177 			usage();
178 		if (jail_remove(jid) < 0)
179 			err(1, "jail_remove");
180 		exit (0);
181 	}
182 	if (argc == 0)
183 		usage();
184 	if (uflag && Uflag)
185 		usage();
186 	if (lflag && username == NULL)
187 		usage();
188 	if (uflag)
189 		GET_USER_INFO;
190 
191 	/*
192 	 * If the first argument (path) starts with a slash, and the third
193 	 * argument (IP address) starts with a digit, it is likely to be
194 	 * an old-style fixed-parameter command line.
195 	 */
196 	if (jailname)
197 		set_param("name", jailname);
198 	if (securelevel)
199 		set_param("securelevel", securelevel);
200 	if (jail_set_flags) {
201 		for (i = 0; i < argc; i++) {
202 			if (!strncmp(argv[i], "command=", 8)) {
203 				cmdarg = i;
204 				argv[cmdarg] += 8;
205 				jail_set_flags |= JAIL_ATTACH;
206 				break;
207 			}
208 			if (hflag) {
209 				if (!strncmp(argv[i], "ip4.addr=", 9)) {
210 					add_ip_addr(&ip4_addr, argv[i] + 9);
211 					break;
212 				}
213 #ifdef INET6
214 				if (!strncmp(argv[i], "ip6.addr=", 9)) {
215 					add_ip_addr(&ip6_addr, argv[i] + 9);
216 					break;
217 				}
218 #endif
219 				if (!strncmp(argv[i], "host.hostname=", 14))
220 					add_ip_addrinfo(0, argv[i] + 14);
221 			}
222 			set_param(NULL, argv[i]);
223 		}
224 	} else {
225 		if (argc < 4 || argv[0][0] != '/')
226 			errx(1, "%s\n%s",
227 			   "no -c or -m, so this must be an old-style command.",
228 			   "But it doesn't look like one.");
229 		set_param("path", argv[0]);
230 		set_param("host.hostname", argv[1]);
231 		if (hflag)
232 			add_ip_addrinfo(0, argv[1]);
233 #ifdef INET6
234 		add_ip_addr46(argv[2]);
235 #else
236 		add_ip_addr(&ip4_addr, argv[2]);
237 #endif
238 		cmdarg = 3;
239 	}
240 	if (ip4_addr != NULL)
241 		set_param("ip4.addr", ip4_addr);
242 #ifdef INET6
243 	if (ip6_addr != NULL)
244 		set_param("ip6.addr", ip6_addr);
245 #endif
246 	errmsg[0] = 0;
247 	set_param("errmsg", errmsg);
248 
249 	if (Jflag) {
250 		fp = fopen(JidFile, "w");
251 		if (fp == NULL)
252 			errx(1, "Could not create JidFile: %s", JidFile);
253 	}
254 	jid = jail_set(&params->name, 2 * nparams,
255 	    jail_set_flags ? jail_set_flags : JAIL_CREATE | JAIL_ATTACH);
256 	if (jid < 0) {
257 		if (errmsg[0] != '\0')
258 			errx(1, "%s", errmsg);
259 		err(1, "jail_set");
260 	}
261 	if (iflag) {
262 		printf("%d\n", jid);
263 		fflush(stdout);
264 	}
265 	if (Jflag) {
266 		if (jail_set_flags) {
267 			fprintf(fp, "jid=%d", jid);
268 			for (i = 0; i < nparams; i++)
269 				if (strcmp(params[i].name.iov_base, "jid") &&
270 				    strcmp(params[i].name.iov_base, "errmsg")) {
271 					fprintf(fp, " %s",
272 					    (char *)params[i].name.iov_base);
273 					if (param_values[i]) {
274 						putc('=', fp);
275 						quoted_print(fp,
276 						    param_values[i]);
277 					}
278 				}
279 			fprintf(fp, "\n");
280 		} else {
281 			for (i = 0; i < nparams; i++)
282 				if (!strcmp(params[i].name.iov_base, "path"))
283 					break;
284 #ifdef INET6
285 			fprintf(fp, "%d\t%s\t%s\t%s%s%s\t%s\n",
286 			    jid, i < nparams
287 			    ? (char *)params[i].value.iov_base : argv[0],
288 			    argv[1], ip4_addr ? ip4_addr : "",
289 			    ip4_addr && ip4_addr[0] && ip6_addr && ip6_addr[0]
290 			    ? "," : "", ip6_addr ? ip6_addr : "", argv[3]);
291 #else
292 			fprintf(fp, "%d\t%s\t%s\t%s\t%s\n",
293 			    jid, i < nparams
294 			    ? (char *)params[i].value.iov_base : argv[0],
295 			    argv[1], ip4_addr ? ip4_addr : "", argv[3]);
296 #endif
297 		}
298 		(void)fclose(fp);
299 	}
300 	if (cmdarg < 0)
301 		exit(0);
302 	if (username != NULL) {
303 		if (Uflag)
304 			GET_USER_INFO;
305 		if (lflag) {
306 			p = getenv("TERM");
307 			environ = &cleanenv;
308 		}
309 		if (setgroups(ngroups, groups) != 0)
310 			err(1, "setgroups");
311 		if (setgid(pwd->pw_gid) != 0)
312 			err(1, "setgid");
313 		if (setusercontext(lcap, pwd, pwd->pw_uid,
314 		    LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN) != 0)
315 			err(1, "setusercontext");
316 		login_close(lcap);
317 	}
318 	if (lflag) {
319 		if (*pwd->pw_shell)
320 			shell = pwd->pw_shell;
321 		else
322 			shell = _PATH_BSHELL;
323 		if (chdir(pwd->pw_dir) < 0)
324 			errx(1, "no home directory");
325 		setenv("HOME", pwd->pw_dir, 1);
326 		setenv("SHELL", shell, 1);
327 		setenv("USER", pwd->pw_name, 1);
328 		if (p)
329 			setenv("TERM", p, 1);
330 	}
331 	execvp(argv[cmdarg], argv + cmdarg);
332 	err(1, "execvp: %s", argv[cmdarg]);
333 }
334 
335 static void
336 add_ip_addr(char **addrp, char *value)
337 {
338 	int addrlen;
339 	char *addr;
340 
341 	if (!*addrp) {
342 		*addrp = strdup(value);
343 		if (!*addrp)
344 			err(1, "malloc");
345 	} else if (value[0]) {
346 		addrlen = strlen(*addrp) + strlen(value) + 2;
347 		addr = malloc(addrlen);
348 		if (!addr)
349 			err(1, "malloc");
350 		snprintf(addr, addrlen, "%s,%s", *addrp, value);
351 		free(*addrp);
352 		*addrp = addr;
353 	}
354 }
355 
356 #ifdef INET6
357 static void
358 add_ip_addr46(char *value)
359 {
360 	char *p, *np;
361 
362 	if (!value[0]) {
363 		add_ip_addr(&ip4_addr, value);
364 		add_ip_addr(&ip6_addr, value);
365 		return;
366 	}
367 	for (p = value;; p = np + 1)
368 	{
369 		np = strchr(p, ',');
370 		if (np)
371 			*np = '\0';
372 		add_ip_addrinfo(AI_NUMERICHOST, p);
373 		if (!np)
374 			break;
375 	}
376 }
377 #endif
378 
379 static void
380 add_ip_addrinfo(int ai_flags, char *value)
381 {
382 	struct addrinfo hints, *ai0, *ai;
383 	struct in_addr addr4;
384 	int error;
385 	char avalue4[INET_ADDRSTRLEN];
386 #ifdef INET6
387 	struct in6_addr addr6;
388 	char avalue6[INET6_ADDRSTRLEN];
389 #endif
390 
391 	/* Look up the hostname (or get the address) */
392 	memset(&hints, 0, sizeof(hints));
393 	hints.ai_socktype = SOCK_STREAM;
394 #ifdef INET6
395 	hints.ai_family = PF_UNSPEC;
396 #else
397 	hints.ai_family = PF_INET;
398 #endif
399 	hints.ai_flags = ai_flags;
400 	error = getaddrinfo(value, NULL, &hints, &ai0);
401 	if (error != 0)
402 		errx(1, "hostname %s: %s", value, gai_strerror(error));
403 
404 	/* Convert the addresses to ASCII so set_param can convert them back. */
405 	for (ai = ai0; ai; ai = ai->ai_next)
406 		switch (ai->ai_family) {
407 		case AF_INET:
408 			memcpy(&addr4, &((struct sockaddr_in *)
409 			    (void *)ai->ai_addr)->sin_addr, sizeof(addr4));
410 			if (inet_ntop(AF_INET, &addr4, avalue4,
411 			    INET_ADDRSTRLEN) == NULL)
412 				err(1, "inet_ntop");
413 			add_ip_addr(&ip4_addr, avalue4);
414 			break;
415 #ifdef INET6
416 		case AF_INET6:
417 			memcpy(&addr6, &((struct sockaddr_in6 *)
418 			    (void *)ai->ai_addr)->sin6_addr, sizeof(addr6));
419 			if (inet_ntop(AF_INET6, &addr6, avalue6,
420 			    INET6_ADDRSTRLEN) == NULL)
421 				err(1, "inet_ntop");
422 			add_ip_addr(&ip6_addr, avalue6);
423 			break;
424 #endif
425 		}
426 	freeaddrinfo(ai0);
427 }
428 
429 static void
430 quoted_print(FILE *fp, char *str)
431 {
432 	int c, qc;
433 	char *p = str;
434 
435 	/* An empty string needs quoting. */
436 	if (!*p) {
437 		fputs("\"\"", fp);
438 		return;
439 	}
440 
441 	/*
442 	 * The value will be surrounded by quotes if it contains spaces
443 	 * or quotes.
444 	 */
445 	qc = strchr(p, '\'') ? '"'
446 	    : strchr(p, '"') ? '\''
447 	    : strchr(p, ' ') || strchr(p, '\t') ? '"'
448 	    : 0;
449 	if (qc)
450 		putc(qc, fp);
451 	while ((c = *p++)) {
452 		if (c == '\\' || c == qc)
453 			putc('\\', fp);
454 		putc(c, fp);
455 	}
456 	if (qc)
457 		putc(qc, fp);
458 }
459 
460 static void
461 set_param(const char *name, char *value)
462 {
463 	struct param *param;
464 	char *ep, *p;
465 	size_t buflen, mlen;
466 	int i, nval, mib[CTL_MAXNAME];
467 	struct {
468 		int i;
469 		char s[MAXPATHLEN];
470 	} buf;
471 
472 	static int paramlistsize;
473 
474 	/* Separate the name from the value, if not done already. */
475 	if (name == NULL) {
476 		name = value;
477 		if ((value = strchr(value, '=')))
478 			*value++ = '\0';
479 	}
480 
481 	/* Check for repeat parameters */
482 	for (i = 0; i < nparams; i++)
483 		if (!strcmp(name, params[i].name.iov_base)) {
484 			memcpy(params + i, params + i + 1,
485 			    (--nparams - i) * sizeof(struct param));
486 			break;
487 		}
488 
489 	/* Make sure there is room for the new param record. */
490 	if (!nparams) {
491 		paramlistsize = 32;
492 		params = malloc(paramlistsize * sizeof(*params));
493 		param_values = malloc(paramlistsize * sizeof(*param_values));
494 		if (params == NULL || param_values == NULL)
495 			err(1, "malloc");
496 	} else if (nparams >= paramlistsize) {
497 		paramlistsize *= 2;
498 		params = realloc(params, paramlistsize * sizeof(*params));
499 		param_values = realloc(param_values,
500 		    paramlistsize * sizeof(*param_values));
501 		if (params == NULL)
502 			err(1, "realloc");
503 	}
504 
505 	/* Look up the paramter. */
506 	param_values[nparams] = value;
507 	param = params + nparams++;
508 	*(const void **)&param->name.iov_base = name;
509 	param->name.iov_len = strlen(name) + 1;
510 	/* Trivial values - no value or errmsg. */
511 	if (value == NULL) {
512 		param->value.iov_base = NULL;
513 		param->value.iov_len = 0;
514 		return;
515 	}
516 	if (!strcmp(name, "errmsg")) {
517 		param->value.iov_base = value;
518 		param->value.iov_len = ERRMSG_SIZE;
519 		return;
520 	}
521 	mib[0] = 0;
522 	mib[1] = 3;
523 	snprintf(buf.s, sizeof(buf.s), SJPARAM ".%s", name);
524 	mlen = sizeof(mib) - 2 * sizeof(int);
525 	if (sysctl(mib, 2, mib + 2, &mlen, buf.s, strlen(buf.s)) < 0)
526 		errx(1, "unknown parameter: %s", name);
527 	mib[1] = 4;
528 	buflen = sizeof(buf);
529 	if (sysctl(mib, (mlen / sizeof(int)) + 2, &buf, &buflen, NULL, 0) < 0)
530 		err(1, "sysctl(0.4.%s)", name);
531 	/*
532 	 * See if this is an array type.
533 	 * Treat non-arrays as an array of one.
534 	 */
535 	p = strchr(buf.s, '\0');
536 	nval = 1;
537 	if (p - 2 >= buf.s && !strcmp(p - 2, ",a")) {
538 		if (value[0] == '\0' ||
539 		    (value[0] == '-' && value[1] == '\0')) {
540 			param->value.iov_base = value;
541 			param->value.iov_len = 0;
542 			return;
543 		}
544 		p[-2] = 0;
545 		for (p = strchr(value, ','); p; p = strchr(p + 1, ',')) {
546 			*p = '\0';
547 			nval++;
548 		}
549 	}
550 
551 	/* Set the values according to the parameter type. */
552 	switch (buf.i & CTLTYPE) {
553 	case CTLTYPE_INT:
554 	case CTLTYPE_UINT:
555 		param->value.iov_len = nval * sizeof(int);
556 		break;
557 	case CTLTYPE_LONG:
558 	case CTLTYPE_ULONG:
559 		param->value.iov_len = nval * sizeof(long);
560 		break;
561 	case CTLTYPE_STRUCT:
562 		if (!strcmp(buf.s, "S,in_addr"))
563 			param->value.iov_len = nval * sizeof(struct in_addr);
564 #ifdef INET6
565 		else if (!strcmp(buf.s, "S,in6_addr"))
566 			param->value.iov_len = nval * sizeof(struct in6_addr);
567 #endif
568 		else
569 			errx(1, "%s: unknown parameter structure (%s)",
570 			    name, buf.s);
571 		break;
572 	case CTLTYPE_STRING:
573 		if (!strcmp(name, "path")) {
574 			param->value.iov_base = malloc(MAXPATHLEN);
575 			if (param->value.iov_base == NULL)
576 				err(1, "malloc");
577 			if (realpath(value, param->value.iov_base) == NULL)
578 				err(1, "%s: realpath(%s)", name, value);
579 			if (chdir(param->value.iov_base) != 0)
580 				err(1, "chdir: %s",
581 				    (char *)param->value.iov_base);
582 		} else
583 			param->value.iov_base = value;
584 		param->value.iov_len = strlen(param->value.iov_base) + 1;
585 		return;
586 	default:
587 		errx(1, "%s: unknown parameter type %d (%s)",
588 		    name, buf.i, buf.s);
589 	}
590 	param->value.iov_base = malloc(param->value.iov_len);
591 	for (i = 0; i < nval; i++) {
592 		switch (buf.i & CTLTYPE) {
593 		case CTLTYPE_INT:
594 			((int *)param->value.iov_base)[i] =
595 			    strtol(value, &ep, 10);
596 			if (ep[0] != '\0')
597 				errx(1, "%s: non-integer value \"%s\"",
598 				    name, value);
599 			break;
600 		case CTLTYPE_UINT:
601 			((unsigned *)param->value.iov_base)[i] =
602 			    strtoul(value, &ep, 10);
603 			if (ep[0] != '\0')
604 				errx(1, "%s: non-integer value \"%s\"",
605 				    name, value);
606 			break;
607 		case CTLTYPE_LONG:
608 			((long *)param->value.iov_base)[i] =
609 			    strtol(value, &ep, 10);
610 			if (ep[0] != '\0')
611 			    errx(1, "%s: non-integer value \"%s\"",
612 				name, value);
613 			break;
614 		case CTLTYPE_ULONG:
615 			((unsigned long *)param->value.iov_base)[i] =
616 			    strtoul(value, &ep, 10);
617 			if (ep[0] != '\0')
618 			    errx(1, "%s: non-integer value \"%s\"",
619 				name, value);
620 			break;
621 		case CTLTYPE_STRUCT:
622 			if (!strcmp(buf.s, "S,in_addr")) {
623 				if (inet_pton(AF_INET, value,
624 				    &((struct in_addr *)
625 				    param->value.iov_base)[i]) != 1)
626 					errx(1, "%s: not an IPv4 address: %s",
627 					    name, value);
628 			}
629 #ifdef INET6
630 			else if (!strcmp(buf.s, "S,in6_addr")) {
631 				if (inet_pton(AF_INET6, value,
632 				    &((struct in6_addr *)
633 				    param->value.iov_base)[i]) != 1)
634 					errx(1, "%s: not an IPv6 address: %s",
635 					    name, value);
636 			}
637 #endif
638 		}
639 		if (i > 0)
640 			value[-1] = ',';
641 		value = strchr(value, '\0') + 1;
642 	}
643 }
644 
645 static void
646 usage(void)
647 {
648 
649 	(void)fprintf(stderr,
650 	    "usage: jail [-d] [-h] [-i] [-J jid_file] "
651 			"[-l -u username | -U username]\n"
652 	    "            [-c | -m] param=value ... [command=command ...]\n"
653 	    "       jail [-r jail]\n");
654 	exit(1);
655 }
656