jail.c revision 208586
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: head/usr.sbin/jail/jail.c 208586 2010-05-27 03:15:04Z cperciva $");
30
31#include <sys/param.h>
32#include <sys/jail.h>
33#include <sys/socket.h>
34#include <sys/sysctl.h>
35
36#include <arpa/inet.h>
37#include <netinet/in.h>
38
39#include <ctype.h>
40#include <err.h>
41#include <errno.h>
42#include <grp.h>
43#include <jail.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
53static struct jailparam *params;
54static char **param_values;
55static int nparams;
56
57static char *ip4_addr;
58#ifdef INET6
59static char *ip6_addr;
60#endif
61
62static void add_ip_addr(char **addrp, char *newaddr);
63#ifdef INET6
64static void add_ip_addr46(char *newaddr);
65#endif
66static void add_ip_addrinfo(int ai_flags, char *value);
67static void quoted_print(FILE *fp, char *str);
68static void set_param(const char *name, char *value);
69static void usage(void);
70
71static const char *perm_sysctl[][3] = {
72	{ "security.jail.set_hostname_allowed",
73	  "allow.noset_hostname", "allow.set_hostname" },
74	{ "security.jail.sysvipc_allowed",
75	  "allow.nosysvipc", "allow.sysvipc" },
76	{ "security.jail.allow_raw_sockets",
77	  "allow.noraw_sockets", "allow.raw_sockets" },
78	{ "security.jail.chflags_allowed",
79	  "allow.nochflags", "allow.chflags" },
80	{ "security.jail.mount_allowed",
81	  "allow.nomount", "allow.mount" },
82	{ "security.jail.socket_unixiproute_only",
83	  "allow.socket_af", "allow.nosocket_af" },
84};
85
86extern char **environ;
87
88#define GET_USER_INFO do {						\
89	pwd = getpwnam(username);					\
90	if (pwd == NULL) {						\
91		if (errno)						\
92			err(1, "getpwnam: %s", username);		\
93		else							\
94			errx(1, "%s: no such user", username);		\
95	}								\
96	lcap = login_getpwclass(pwd);					\
97	if (lcap == NULL)						\
98		err(1, "getpwclass: %s", username);			\
99	ngroups = ngroups_max;						\
100	if (getgrouplist(username, pwd->pw_gid, groups, &ngroups) != 0)	\
101		err(1, "getgrouplist: %s", username);			\
102} while (0)
103
104int
105main(int argc, char **argv)
106{
107	login_cap_t *lcap = NULL;
108	struct passwd *pwd = NULL;
109	gid_t *groups;
110	size_t sysvallen;
111	int ch, cmdarg, i, jail_set_flags, jid, ngroups, sysval;
112	int hflag, iflag, Jflag, lflag, rflag, uflag, Uflag;
113	long ngroups_max;
114	unsigned pi;
115	char *jailname, *securelevel, *username, *JidFile;
116	char enforce_statfs[4];
117	static char *cleanenv;
118	const char *shell, *p = NULL;
119	FILE *fp;
120
121	hflag = iflag = Jflag = lflag = rflag = uflag = Uflag =
122	    jail_set_flags = 0;
123	cmdarg = jid = -1;
124	jailname = securelevel = username = JidFile = cleanenv = NULL;
125	fp = NULL;
126
127	ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;
128	if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
129		err(1, "malloc");
130
131	while ((ch = getopt(argc, argv, "cdhilmn:r:s:u:U:J:")) != -1) {
132		switch (ch) {
133		case 'd':
134			jail_set_flags |= JAIL_DYING;
135			break;
136		case 'h':
137			hflag = 1;
138			break;
139		case 'i':
140			iflag = 1;
141			break;
142		case 'J':
143			JidFile = optarg;
144			Jflag = 1;
145			break;
146		case 'n':
147			jailname = optarg;
148			break;
149		case 's':
150			securelevel = optarg;
151			break;
152		case 'u':
153			username = optarg;
154			uflag = 1;
155			break;
156		case 'U':
157			username = optarg;
158			Uflag = 1;
159			break;
160		case 'l':
161			lflag = 1;
162			break;
163		case 'c':
164			jail_set_flags |= JAIL_CREATE;
165			break;
166		case 'm':
167			jail_set_flags |= JAIL_UPDATE;
168			break;
169		case 'r':
170			jid = jail_getid(optarg);
171			if (jid < 0)
172				errx(1, "%s", jail_errmsg);
173			rflag = 1;
174			break;
175		default:
176			usage();
177		}
178	}
179	argc -= optind;
180	argv += optind;
181	if (rflag) {
182		if (argc > 0 || iflag || Jflag || lflag || uflag || Uflag)
183			usage();
184		if (jail_remove(jid) < 0)
185			err(1, "jail_remove");
186		exit (0);
187	}
188	if (argc == 0)
189		usage();
190	if (uflag && Uflag)
191		usage();
192	if (lflag && username == NULL)
193		usage();
194	if (uflag)
195		GET_USER_INFO;
196
197	if (jailname)
198		set_param("name", jailname);
199	if (securelevel)
200		set_param("securelevel", securelevel);
201	if (jail_set_flags) {
202		for (i = 0; i < argc; i++) {
203			if (!strncmp(argv[i], "command=", 8)) {
204				cmdarg = i;
205				argv[cmdarg] += 8;
206				jail_set_flags |= JAIL_ATTACH;
207				break;
208			}
209			if (hflag) {
210				if (!strncmp(argv[i], "ip4.addr=", 9)) {
211					add_ip_addr(&ip4_addr, argv[i] + 9);
212					break;
213				}
214#ifdef INET6
215				if (!strncmp(argv[i], "ip6.addr=", 9)) {
216					add_ip_addr(&ip6_addr, argv[i] + 9);
217					break;
218				}
219#endif
220				if (!strncmp(argv[i], "host.hostname=", 14))
221					add_ip_addrinfo(0, argv[i] + 14);
222			}
223			set_param(NULL, argv[i]);
224		}
225	} else {
226		if (argc < 4 || argv[0][0] != '/')
227			errx(1, "%s\n%s",
228			   "no -c or -m, so this must be an old-style command.",
229			   "But it doesn't look like one.");
230		set_param("path", argv[0]);
231		set_param("host.hostname", argv[1]);
232		if (hflag)
233			add_ip_addrinfo(0, argv[1]);
234		if (argv[2][0] != '\0')
235#ifdef INET6
236			add_ip_addr46(argv[2]);
237#else
238			add_ip_addr(&ip4_addr, argv[2]);
239#endif
240		cmdarg = 3;
241		/* Emulate the defaults from security.jail.* sysctls */
242		sysvallen = sizeof(sysval);
243		if (sysctlbyname("security.jail.jailed", &sysval, &sysvallen,
244		    NULL, 0) == 0 && sysval == 0) {
245			for (pi = 0; pi < sizeof(perm_sysctl) /
246			     sizeof(perm_sysctl[0]); pi++) {
247				sysvallen = sizeof(sysval);
248				if (sysctlbyname(perm_sysctl[pi][0],
249				    &sysval, &sysvallen, NULL, 0) == 0)
250					set_param(perm_sysctl[pi]
251					    [sysval ? 2 : 1], NULL);
252			}
253			sysvallen = sizeof(sysval);
254			if (sysctlbyname("security.jail.enforce_statfs",
255			    &sysval, &sysvallen, NULL, 0) == 0) {
256				snprintf(enforce_statfs,
257				    sizeof(enforce_statfs), "%d", sysval);
258				set_param("enforce_statfs", enforce_statfs);
259			}
260		}
261	}
262	if (ip4_addr != NULL)
263		set_param("ip4.addr", ip4_addr);
264#ifdef INET6
265	if (ip6_addr != NULL)
266		set_param("ip6.addr", ip6_addr);
267#endif
268
269	if (Jflag) {
270		fp = fopen(JidFile, "w");
271		if (fp == NULL)
272			errx(1, "Could not create JidFile: %s", JidFile);
273	}
274	jid = jailparam_set(params, nparams,
275	    jail_set_flags ? jail_set_flags : JAIL_CREATE | JAIL_ATTACH);
276	if (jid < 0)
277		errx(1, "%s", jail_errmsg);
278	if (iflag) {
279		printf("%d\n", jid);
280		fflush(stdout);
281	}
282	if (Jflag) {
283		if (jail_set_flags) {
284			fprintf(fp, "jid=%d", jid);
285			for (i = 0; i < nparams; i++)
286				if (strcmp(params[i].jp_name, "jid")) {
287					fprintf(fp, " %s",
288					    (char *)params[i].jp_name);
289					if (param_values[i]) {
290						putc('=', fp);
291						quoted_print(fp,
292						    param_values[i]);
293					}
294				}
295			fprintf(fp, "\n");
296		} else {
297			for (i = 0; i < nparams; i++)
298				if (!strcmp(params[i].jp_name, "path"))
299					break;
300#ifdef INET6
301			fprintf(fp, "%d\t%s\t%s\t%s%s%s\t%s\n",
302			    jid, i < nparams
303			    ? (char *)params[i].jp_value : argv[0],
304			    argv[1], ip4_addr ? ip4_addr : "",
305			    ip4_addr && ip4_addr[0] && ip6_addr && ip6_addr[0]
306			    ? "," : "", ip6_addr ? ip6_addr : "", argv[3]);
307#else
308			fprintf(fp, "%d\t%s\t%s\t%s\t%s\n",
309			    jid, i < nparams
310			    ? (char *)params[i].jp_value : argv[0],
311			    argv[1], ip4_addr ? ip4_addr : "", argv[3]);
312#endif
313		}
314		(void)fclose(fp);
315	}
316	if (cmdarg < 0)
317		exit(0);
318	if (username != NULL) {
319		if (Uflag)
320			GET_USER_INFO;
321		if (lflag) {
322			p = getenv("TERM");
323			environ = &cleanenv;
324		}
325		if (setgroups(ngroups, groups) != 0)
326			err(1, "setgroups");
327		if (setgid(pwd->pw_gid) != 0)
328			err(1, "setgid");
329		if (setusercontext(lcap, pwd, pwd->pw_uid,
330		    LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN) != 0)
331			err(1, "setusercontext");
332		login_close(lcap);
333	}
334	if (lflag) {
335		if (*pwd->pw_shell)
336			shell = pwd->pw_shell;
337		else
338			shell = _PATH_BSHELL;
339		if (chdir(pwd->pw_dir) < 0)
340			errx(1, "no home directory");
341		setenv("HOME", pwd->pw_dir, 1);
342		setenv("SHELL", shell, 1);
343		setenv("USER", pwd->pw_name, 1);
344		if (p)
345			setenv("TERM", p, 1);
346	}
347	execvp(argv[cmdarg], argv + cmdarg);
348	err(1, "execvp: %s", argv[cmdarg]);
349}
350
351static void
352add_ip_addr(char **addrp, char *value)
353{
354	int addrlen;
355	char *addr;
356
357	if (!*addrp) {
358		*addrp = strdup(value);
359		if (!*addrp)
360			err(1, "malloc");
361	} else if (value[0]) {
362		addrlen = strlen(*addrp) + strlen(value) + 2;
363		addr = malloc(addrlen);
364		if (!addr)
365			err(1, "malloc");
366		snprintf(addr, addrlen, "%s,%s", *addrp, value);
367		free(*addrp);
368		*addrp = addr;
369	}
370}
371
372#ifdef INET6
373static void
374add_ip_addr46(char *value)
375{
376	char *p, *np;
377
378	for (p = value;; p = np + 1)
379	{
380		np = strchr(p, ',');
381		if (np)
382			*np = '\0';
383		add_ip_addrinfo(AI_NUMERICHOST, p);
384		if (!np)
385			break;
386	}
387}
388#endif
389
390static void
391add_ip_addrinfo(int ai_flags, char *value)
392{
393	struct addrinfo hints, *ai0, *ai;
394	struct in_addr addr4;
395	size_t size;
396	int error, ip4ok;
397	int mib[4];
398	char avalue4[INET_ADDRSTRLEN];
399#ifdef INET6
400	struct in6_addr addr6;
401	int ip6ok;
402	char avalue6[INET6_ADDRSTRLEN];
403#endif
404
405	/* Look up the hostname (or get the address) */
406	memset(&hints, 0, sizeof(hints));
407	hints.ai_socktype = SOCK_STREAM;
408#ifdef INET6
409	hints.ai_family = PF_UNSPEC;
410#else
411	hints.ai_family = PF_INET;
412#endif
413	hints.ai_flags = ai_flags;
414	error = getaddrinfo(value, NULL, &hints, &ai0);
415	if (error != 0)
416		errx(1, "hostname %s: %s", value, gai_strerror(error));
417
418	/*
419	 * Silently ignore unsupported address families from DNS lookups.
420	 * But if this is a numeric address, let the kernel give the error.
421	 */
422	if (ai_flags & AI_NUMERICHOST)
423		ip4ok =
424#ifdef INET6
425		    ip6ok =
426#endif
427		    1;
428	else {
429		size = 4;
430		ip4ok = (sysctlnametomib("security.jail.param.ip4", mib,
431		    &size) == 0);
432#ifdef INET6
433		size = 4;
434		ip6ok = (sysctlnametomib("security.jail.param.ip6", mib,
435		    &size) == 0);
436#endif
437	}
438
439	/* Convert the addresses to ASCII so set_param can convert them back. */
440	for (ai = ai0; ai; ai = ai->ai_next)
441		switch (ai->ai_family) {
442		case AF_INET:
443			if (!ip4ok)
444				break;
445			memcpy(&addr4, &((struct sockaddr_in *)
446			    (void *)ai->ai_addr)->sin_addr, sizeof(addr4));
447			if (inet_ntop(AF_INET, &addr4, avalue4,
448			    INET_ADDRSTRLEN) == NULL)
449				err(1, "inet_ntop");
450			add_ip_addr(&ip4_addr, avalue4);
451			break;
452#ifdef INET6
453		case AF_INET6:
454			if (!ip6ok)
455				break;
456			memcpy(&addr6, &((struct sockaddr_in6 *)
457			    (void *)ai->ai_addr)->sin6_addr, sizeof(addr6));
458			if (inet_ntop(AF_INET6, &addr6, avalue6,
459			    INET6_ADDRSTRLEN) == NULL)
460				err(1, "inet_ntop");
461			add_ip_addr(&ip6_addr, avalue6);
462			break;
463#endif
464		}
465	freeaddrinfo(ai0);
466}
467
468static void
469quoted_print(FILE *fp, char *str)
470{
471	int c, qc;
472	char *p = str;
473
474	/* An empty string needs quoting. */
475	if (!*p) {
476		fputs("\"\"", fp);
477		return;
478	}
479
480	/*
481	 * The value will be surrounded by quotes if it contains spaces
482	 * or quotes.
483	 */
484	qc = strchr(p, '\'') ? '"'
485	    : strchr(p, '"') ? '\''
486	    : strchr(p, ' ') || strchr(p, '\t') ? '"'
487	    : 0;
488	if (qc)
489		putc(qc, fp);
490	while ((c = *p++)) {
491		if (c == '\\' || c == qc)
492			putc('\\', fp);
493		putc(c, fp);
494	}
495	if (qc)
496		putc(qc, fp);
497}
498
499static void
500set_param(const char *name, char *value)
501{
502	struct jailparam *param;
503	int i;
504
505	static int paramlistsize;
506
507	/* Separate the name from the value, if not done already. */
508	if (name == NULL) {
509		name = value;
510		if ((value = strchr(value, '=')))
511			*value++ = '\0';
512	}
513
514	/* jail_set won't chdir along with its chroot, so do it here. */
515	if (!strcmp(name, "path") && chdir(value) < 0)
516		err(1, "chdir: %s", value);
517
518	/* Check for repeat parameters */
519	for (i = 0; i < nparams; i++)
520		if (!strcmp(name, params[i].jp_name)) {
521			jailparam_free(params + i, 1);
522			memcpy(params + i, params + i + 1,
523			    (--nparams - i) * sizeof(struct jailparam));
524			break;
525		}
526
527	/* Make sure there is room for the new param record. */
528	if (!nparams) {
529		paramlistsize = 32;
530		params = malloc(paramlistsize * sizeof(*params));
531		param_values = malloc(paramlistsize * sizeof(*param_values));
532		if (params == NULL || param_values == NULL)
533			err(1, "malloc");
534	} else if (nparams >= paramlistsize) {
535		paramlistsize *= 2;
536		params = realloc(params, paramlistsize * sizeof(*params));
537		param_values = realloc(param_values,
538		    paramlistsize * sizeof(*param_values));
539		if (params == NULL)
540			err(1, "realloc");
541	}
542
543	/* Look up the paramter. */
544	param_values[nparams] = value;
545	param = params + nparams++;
546	if (jailparam_init(param, name) < 0 ||
547	    jailparam_import(param, value) < 0)
548		errx(1, "%s", jail_errmsg);
549}
550
551static void
552usage(void)
553{
554
555	(void)fprintf(stderr,
556	    "usage: jail [-d] [-h] [-i] [-J jid_file] "
557			"[-l -u username | -U username]\n"
558	    "            [-c | -m] param=value ... [command=command ...]\n"
559	    "       jail [-r jail]\n");
560	exit(1);
561}
562