jexec.c revision 185435
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 185435 2008-11-29 14:32:14Z bz $
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 == NULL)
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			exit(0);
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. */
178		if (id > 0) {
179			/* Do we have a jail ID to match as well? */
180			if (jid > 0) {
181				if (jid == id) {
182					xid = id;
183					count++;
184				}
185			} else {
186				xid = id;
187				count++;
188			}
189		}
190	}
191
192	free(p);
193
194	if (count != 1)
195		errx(1, "Could not uniquely identify the jail.");
196
197	return (xid);
198}
199
200#define GET_USER_INFO do {						\
201	pwd = getpwnam(username);					\
202	if (pwd == NULL) {						\
203		if (errno)						\
204			err(1, "getpwnam: %s", username);		\
205		else							\
206			errx(1, "%s: no such user", username);		\
207	}								\
208	lcap = login_getpwclass(pwd);					\
209	if (lcap == NULL)						\
210		err(1, "getpwclass: %s", username);			\
211	ngroups = NGROUPS;						\
212	if (getgrouplist(username, pwd->pw_gid, groups, &ngroups) != 0)	\
213		err(1, "getgrouplist: %s", username);			\
214} while (0)
215
216int
217main(int argc, char *argv[])
218{
219	int jid;
220	login_cap_t *lcap = NULL;
221	struct passwd *pwd = NULL;
222	gid_t groups[NGROUPS];
223	int ch, ngroups, uflag, Uflag;
224	char *jailname, *username;
225
226	ch = uflag = Uflag = 0;
227	jailname = username = NULL;
228	jid = -1;
229
230	while ((ch = getopt(argc, argv, "i:n:u:U:")) != -1) {
231		switch (ch) {
232		case 'n':
233			jailname = optarg;
234			break;
235		case 'u':
236			username = optarg;
237			uflag = 1;
238			break;
239		case 'U':
240			username = optarg;
241			Uflag = 1;
242			break;
243		default:
244			usage();
245		}
246	}
247	argc -= optind;
248	argv += optind;
249	if (argc < 2)
250		usage();
251	if (strlen(argv[0]) > 0) {
252		jid = (int)strtol(argv[0], NULL, 10);
253		if (errno)
254			err(1, "Unable to parse jail ID.");
255	}
256	if (jid <= 0 && jailname == NULL) {
257		fprintf(stderr, "Neither jail ID nor jail name given.\n");
258		usage();
259	}
260	if (uflag && Uflag)
261		usage();
262	if (uflag)
263		GET_USER_INFO;
264	jid = lookup_jail(jid, jailname);
265	if (jid <= 0)
266		errx(1, "Cannot identify jail.");
267	if (jail_attach(jid) == -1)
268		err(1, "jail_attach(): %d", jid);
269	if (chdir("/") == -1)
270		err(1, "chdir(): /");
271	if (username != NULL) {
272		if (Uflag)
273			GET_USER_INFO;
274		if (setgroups(ngroups, groups) != 0)
275			err(1, "setgroups");
276		if (setgid(pwd->pw_gid) != 0)
277			err(1, "setgid");
278		if (setusercontext(lcap, pwd, pwd->pw_uid,
279		    LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN) != 0)
280			err(1, "setusercontext");
281		login_close(lcap);
282	}
283	if (execvp(argv[1], argv + 1) == -1)
284		err(1, "execvp(): %s", argv[1]);
285	exit(0);
286}
287
288static void
289usage(void)
290{
291
292	fprintf(stderr, "%s%s\n",
293		"usage: jexec [-u username | -U username]",
294		" [-n jailname] jid command ...");
295	exit(1);
296}
297