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