jexec.c revision 190694
1/*-
2 * Copyright (c) 2003 Mike Barcroft <mike@FreeBSD.org>
3 * Copyright (c) 2008 Bjoern A. Zeeb <bz@FreeBSD.org>
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 * $FreeBSD: head/usr.sbin/jexec/jexec.c 190694 2009-04-04 16:03:28Z ru $
28 */
29
30#include <sys/param.h>
31#include <sys/jail.h>
32#include <sys/sysctl.h>
33
34#include <netinet/in.h>
35
36#include <err.h>
37#include <errno.h>
38#include <login_cap.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <pwd.h>
43#include <unistd.h>
44
45static void	usage(void);
46
47#ifdef SUPPORT_OLD_XPRISON
48static
49char *lookup_xprison_v1(void *p, char *end, int *id)
50{
51	struct xprison_v1 *xp;
52
53	if (id == NULL)
54		errx(1, "Internal error. Invalid ID pointer.");
55
56	if ((char *)p + sizeof(struct xprison_v1) > end)
57		errx(1, "Invalid length for jail");
58
59	xp = (struct xprison_v1 *)p;
60
61	*id = xp->pr_id;
62	return ((char *)(xp + 1));
63}
64#endif
65
66static
67char *lookup_xprison_v3(void *p, char *end, int *id, char *jailname)
68{
69	struct xprison *xp;
70	char *q;
71	int ok;
72
73	if (id == NULL)
74		errx(1, "Internal error. Invalid ID pointer.");
75
76	if ((char *)p + sizeof(struct xprison) > end)
77		errx(1, "Invalid length for jail");
78
79	xp = (struct xprison *)p;
80	ok = 1;
81
82	/* Jail state and name. */
83	if (xp->pr_state < 0 || xp->pr_state >=
84	    (int)((sizeof(prison_states) / sizeof(struct prison_state))))
85		errx(1, "Invalid jail state.");
86	else if (xp->pr_state != PRISON_STATE_ALIVE)
87		ok = 0;
88	if (jailname != NULL) {
89		if (xp->pr_name[0] == '\0')
90			ok = 0;
91		else if (strcmp(jailname, xp->pr_name) != 0)
92			ok = 0;
93	}
94
95	q = (char *)(xp + 1);
96	/* IPv4 addresses. */
97	q += (xp->pr_ip4s * sizeof(struct in_addr));
98	if ((char *)q > end)
99		errx(1, "Invalid length for jail");
100	/* IPv6 addresses. */
101	q += (xp->pr_ip6s * sizeof(struct in6_addr));
102	if ((char *)q > end)
103		errx(1, "Invalid length for jail");
104
105	if (ok)
106		*id = xp->pr_id;
107	return (q);
108}
109
110static int
111lookup_jail(int jid, char *jailname)
112{
113	size_t i, j, len;
114	void *p, *q;
115	int version, id, xid, count;
116
117	if (sysctlbyname("security.jail.list", NULL, &len, NULL, 0) == -1)
118		err(1, "sysctlbyname(): security.jail.list");
119
120	j = len;
121	for (i = 0; i < 4; i++) {
122		if (len == 0)
123			return (-1);
124		p = q = malloc(len);
125		if (p == NULL)
126			err(1, "malloc()");
127
128		if (sysctlbyname("security.jail.list", q, &len, NULL, 0) == -1) {
129			if (errno == ENOMEM) {
130				free(p);
131				p = NULL;
132				len += j;
133				continue;
134			}
135			err(1, "sysctlbyname(): security.jail.list");
136		}
137		break;
138	}
139	if (p == NULL)
140		err(1, "sysctlbyname(): security.jail.list");
141	if (len < sizeof(int))
142		errx(1, "This is no prison. Kernel and userland out of sync?");
143	version = *(int *)p;
144	if (version > XPRISON_VERSION)
145		errx(1, "Sci-Fi prison. Kernel/userland out of sync?");
146
147	count = 0;
148	xid = -1;
149	for (; q != NULL && (char *)q + sizeof(int) < (char *)p + len;) {
150		version = *(int *)q;
151		if (version > XPRISON_VERSION)
152			errx(1, "Sci-Fi prison. Kernel/userland out of sync?");
153		id = -1;
154		switch (version) {
155#ifdef SUPPORT_OLD_XPRISON
156		case 1:
157			if (jailname != NULL)
158				errx(1, "Version 1 prisons did not "
159				    "support jail names.");
160			q = lookup_xprison_v1(q, (char *)p + len, &id);
161			break;
162		case 2:
163			errx(1, "Version 2 was used by multi-IPv4 jail "
164			    "implementations that never made it into the "
165			    "official kernel.");
166			/* NOTREACHED */
167			break;
168#endif
169		case 3:
170			q = lookup_xprison_v3(q, (char *)p + len, &id, jailname);
171			break;
172		default:
173			errx(1, "Prison unknown. Kernel/userland out of sync?");
174			/* NOTREACHED */
175			break;
176		}
177		/* Possible match; see if we have a jail ID to match as well.  */
178		if (id > 0 && (jid <= 0 || id == jid)) {
179			xid = id;
180			count++;
181		}
182	}
183
184	free(p);
185
186	if (count == 1)
187		return (xid);
188	else if (count > 1)
189		errx(1, "Could not uniquely identify the jail.");
190	else
191		return (-1);
192}
193
194#define GET_USER_INFO do {						\
195	pwd = getpwnam(username);					\
196	if (pwd == NULL) {						\
197		if (errno)						\
198			err(1, "getpwnam: %s", username);		\
199		else							\
200			errx(1, "%s: no such user", username);		\
201	}								\
202	lcap = login_getpwclass(pwd);					\
203	if (lcap == NULL)						\
204		err(1, "getpwclass: %s", username);			\
205	ngroups = NGROUPS;						\
206	if (getgrouplist(username, pwd->pw_gid, groups, &ngroups) != 0)	\
207		err(1, "getgrouplist: %s", username);			\
208} while (0)
209
210int
211main(int argc, char *argv[])
212{
213	int jid;
214	login_cap_t *lcap = NULL;
215	struct passwd *pwd = NULL;
216	gid_t groups[NGROUPS];
217	int ch, ngroups, uflag, Uflag;
218	char *jailname, *username;
219
220	ch = uflag = Uflag = 0;
221	jailname = username = NULL;
222	jid = -1;
223
224	while ((ch = getopt(argc, argv, "i:n:u:U:")) != -1) {
225		switch (ch) {
226		case 'n':
227			jailname = optarg;
228			break;
229		case 'u':
230			username = optarg;
231			uflag = 1;
232			break;
233		case 'U':
234			username = optarg;
235			Uflag = 1;
236			break;
237		default:
238			usage();
239		}
240	}
241	argc -= optind;
242	argv += optind;
243	if (argc < 2)
244		usage();
245	if (strlen(argv[0]) > 0) {
246		jid = (int)strtol(argv[0], NULL, 10);
247		if (errno)
248			err(1, "Unable to parse jail ID.");
249	}
250	if (jid <= 0 && jailname == NULL) {
251		fprintf(stderr, "Neither jail ID nor jail name given.\n");
252		usage();
253	}
254	if (uflag && Uflag)
255		usage();
256	if (uflag)
257		GET_USER_INFO;
258	jid = lookup_jail(jid, jailname);
259	if (jid <= 0)
260		errx(1, "Cannot identify jail.");
261	if (jail_attach(jid) == -1)
262		err(1, "jail_attach(): %d", jid);
263	if (chdir("/") == -1)
264		err(1, "chdir(): /");
265	if (username != NULL) {
266		if (Uflag)
267			GET_USER_INFO;
268		if (setgroups(ngroups, groups) != 0)
269			err(1, "setgroups");
270		if (setgid(pwd->pw_gid) != 0)
271			err(1, "setgid");
272		if (setusercontext(lcap, pwd, pwd->pw_uid,
273		    LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN) != 0)
274			err(1, "setusercontext");
275		login_close(lcap);
276	}
277	if (execvp(argv[1], argv + 1) == -1)
278		err(1, "execvp(): %s", argv[1]);
279	exit(0);
280}
281
282static void
283usage(void)
284{
285
286	fprintf(stderr, "%s%s\n",
287		"usage: jexec [-u username | -U username]",
288		" [-n jailname] jid command ...");
289	exit(1);
290}
291