1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2003 Mike Barcroft <mike@FreeBSD.org>
5 * Copyright (c) 2008 Bjoern A. Zeeb <bz@FreeBSD.org>
6 * Copyright (c) 2009 James Gritton <jamie@FreeBSD.org>
7 * Copyright (c) 2015 Emmanuel Vadot <manu@bocal.org>
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33__FBSDID("$FreeBSD$");
34
35#include <sys/param.h>
36#include <sys/jail.h>
37#include <sys/socket.h>
38#include <sys/sysctl.h>
39
40#include <arpa/inet.h>
41#include <netinet/in.h>
42
43#include <err.h>
44#include <errno.h>
45#include <jail.h>
46#include <limits.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <string.h>
50#include <unistd.h>
51#include <libxo/xo.h>
52
53#define	JP_USER		0x01000000
54#define	JP_OPT		0x02000000
55
56#define JLS_XO_VERSION	"2"
57
58#define	PRINT_DEFAULT	0x01
59#define	PRINT_HEADER	0x02
60#define	PRINT_NAMEVAL	0x04
61#define	PRINT_QUOTED	0x08
62#define	PRINT_SKIP	0x10
63#define	PRINT_VERBOSE	0x20
64#define	PRINT_JAIL_NAME	0x40
65
66static struct jailparam *params;
67static int *param_parent;
68static int nparams;
69#ifdef INET6
70static int ip6_ok;
71#endif
72#ifdef INET
73static int ip4_ok;
74#endif
75
76static int add_param(const char *name, void *value, size_t valuelen,
77		struct jailparam *source, unsigned flags);
78static int sort_param(const void *a, const void *b);
79static char *noname(const char *name);
80static char *nononame(const char *name);
81static int print_jail(int pflags, int jflags);
82static int special_print(int pflags, struct jailparam *param);
83static void quoted_print(int pflags, char *name, char *value);
84static void emit_ip_addr_list(int af_family, const char *list_name,
85		struct jailparam *param);
86
87int
88main(int argc, char **argv)
89{
90	char *dot, *ep, *jname, *pname;
91	int c, i, jflags, jid, lastjid, pflags, spc;
92
93	argc = xo_parse_args(argc, argv);
94	if (argc < 0)
95		exit(1);
96
97        xo_set_version(JLS_XO_VERSION);
98	jname = NULL;
99	pflags = jflags = jid = 0;
100	while ((c = getopt(argc, argv, "adj:hNnqsv")) >= 0)
101		switch (c) {
102		case 'a':
103		case 'd':
104			jflags |= JAIL_DYING;
105			break;
106		case 'j':
107			jid = strtoul(optarg, &ep, 10);
108			if (!jid || *ep) {
109				jid = 0;
110				jname = optarg;
111			}
112			break;
113		case 'h':
114			pflags = (pflags & ~(PRINT_SKIP | PRINT_VERBOSE)) |
115			    PRINT_HEADER;
116			break;
117		case 'N':
118			pflags |= PRINT_JAIL_NAME;
119			break;
120		case 'n':
121			pflags = (pflags & ~PRINT_VERBOSE) | PRINT_NAMEVAL;
122			break;
123		case 'q':
124			pflags |= PRINT_QUOTED;
125			break;
126		case 's':
127			pflags = (pflags & ~(PRINT_HEADER | PRINT_VERBOSE)) |
128			    PRINT_NAMEVAL | PRINT_QUOTED | PRINT_SKIP;
129			break;
130		case 'v':
131			pflags = (pflags &
132			    ~(PRINT_HEADER | PRINT_NAMEVAL | PRINT_SKIP)) |
133			    PRINT_VERBOSE;
134			break;
135		default:
136			xo_errx(1, "usage: jls [-dhNnqv] [-j jail] [param ...]");
137		}
138
139#ifdef INET6
140	ip6_ok = feature_present("inet6");
141#endif
142#ifdef INET
143	ip4_ok = feature_present("inet");
144#endif
145
146	/* Add the parameters to print. */
147	if (optind == argc) {
148		if (pflags & (PRINT_HEADER | PRINT_NAMEVAL))
149			add_param("all", NULL, (size_t)0, NULL, JP_USER);
150		else if (pflags & PRINT_VERBOSE) {
151			add_param("jid", NULL, (size_t)0, NULL, JP_USER);
152			add_param("host.hostname", NULL, (size_t)0, NULL,
153			    JP_USER);
154			add_param("path", NULL, (size_t)0, NULL, JP_USER);
155			add_param("name", NULL, (size_t)0, NULL, JP_USER);
156			add_param("dying", NULL, (size_t)0, NULL, JP_USER);
157			add_param("cpuset.id", NULL, (size_t)0, NULL, JP_USER);
158#ifdef INET
159			if (ip4_ok)
160				add_param("ip4.addr", NULL, (size_t)0, NULL,
161				    JP_USER);
162#endif
163#ifdef INET6
164			if (ip6_ok)
165				add_param("ip6.addr", NULL, (size_t)0, NULL,
166				    JP_USER | JP_OPT);
167#endif
168		} else {
169			pflags |= PRINT_DEFAULT;
170			if (pflags & PRINT_JAIL_NAME)
171				add_param("name", NULL, (size_t)0, NULL, JP_USER);
172			else
173				add_param("jid", NULL, (size_t)0, NULL, JP_USER);
174#ifdef INET
175			if (ip4_ok)
176				add_param("ip4.addr", NULL, (size_t)0, NULL,
177				    JP_USER);
178#endif
179			add_param("host.hostname", NULL, (size_t)0, NULL,
180			    JP_USER);
181			add_param("path", NULL, (size_t)0, NULL, JP_USER);
182		}
183	} else {
184		pflags &= ~PRINT_VERBOSE;
185		while (optind < argc)
186			add_param(argv[optind++], NULL, (size_t)0, NULL,
187			    JP_USER);
188	}
189
190	if (pflags & PRINT_SKIP) {
191		/* Check for parameters with jailsys parents. */
192		for (i = 0; i < nparams; i++) {
193			if ((params[i].jp_flags & JP_USER) &&
194			    (dot = strchr(params[i].jp_name, '.'))) {
195				pname = alloca((dot - params[i].jp_name) + 1);
196				strlcpy(pname, params[i].jp_name,
197				    (dot - params[i].jp_name) + 1);
198				param_parent[i] = add_param(pname,
199				    NULL, (size_t)0, NULL, JP_OPT);
200			}
201		}
202	}
203
204	/* Add the index key parameters. */
205	if (jid != 0)
206		add_param("jid", &jid, sizeof(jid), NULL, 0);
207	else if (jname != NULL)
208		add_param("name", jname, strlen(jname), NULL, 0);
209	else
210		add_param("lastjid", &lastjid, sizeof(lastjid), NULL, 0);
211
212	/* Print a header line if requested. */
213	if (pflags & PRINT_VERBOSE) {
214		xo_emit("{T:/%3s}{T:JID}{P:  }{T:Hostname}{Pd:/%22s}{T:Path}\n",
215		        "", "");
216		xo_emit("{P:/%8s}{T:Name}{Pd:/%26s}{T:State}\n", "", "");
217		xo_emit("{P:/%8s}{T:CPUSetID}\n", "");
218		xo_emit("{P:/%8s}{T:IP Address(es)}\n", "");
219	}
220	else if (pflags & PRINT_DEFAULT)
221		if (pflags & PRINT_JAIL_NAME)
222			xo_emit("{P: }{T:JID/%-15s}{P: }{T:IP Address/%-15s}"
223			        "{P: }{T:Hostname/%-29s}{P: }{T:Path}\n");
224		else
225			xo_emit("{T:JID/%6s}{P:  }{T:IP Address}{P:/%6s}"
226			        "{T:Hostname}{P:/%22s}{T:Path}\n", "", "");
227	else if (pflags & PRINT_HEADER) {
228		for (i = spc = 0; i < nparams; i++)
229			if (params[i].jp_flags & JP_USER) {
230				if (spc)
231					xo_emit("{P: }");
232				else
233					spc = 1;
234				xo_emit(params[i].jp_name);
235			}
236		xo_emit("{P:\n}");
237	}
238
239	xo_open_container("jail-information");
240	xo_open_list("jail");
241	/* Fetch the jail(s) and print the parameters. */
242	if (jid != 0 || jname != NULL) {
243		if (print_jail(pflags, jflags) < 0)
244			xo_errx(1, "%s", jail_errmsg);
245	} else {
246		for (lastjid = 0;
247		     (lastjid = print_jail(pflags, jflags)) >= 0; )
248			;
249		if (errno != 0 && errno != ENOENT)
250			xo_errx(1, "%s", jail_errmsg);
251	}
252	xo_close_list("jail");
253	xo_close_container("jail-information");
254	xo_finish();
255	return (0);
256}
257
258static int
259add_param(const char *name, void *value, size_t valuelen,
260    struct jailparam *source, unsigned flags)
261{
262	struct jailparam *param, *tparams;
263	int i, tnparams;
264
265	static int paramlistsize;
266
267	/* The pseudo-parameter "all" scans the list of available parameters. */
268	if (!strcmp(name, "all")) {
269		tnparams = jailparam_all(&tparams);
270		if (tnparams < 0)
271			xo_errx(1, "%s", jail_errmsg);
272		qsort(tparams, (size_t)tnparams, sizeof(struct jailparam),
273		    sort_param);
274		for (i = 0; i < tnparams; i++)
275			add_param(tparams[i].jp_name, NULL, (size_t)0,
276			    tparams + i, flags);
277		free(tparams);
278		return -1;
279	}
280
281	/* Check for repeat parameters. */
282	for (i = 0; i < nparams; i++)
283		if (!strcmp(name, params[i].jp_name)) {
284			if (value != NULL && jailparam_import_raw(params + i,
285			    value, valuelen) < 0)
286				xo_errx(1, "%s", jail_errmsg);
287			params[i].jp_flags |= flags;
288			if (source != NULL)
289				jailparam_free(source, 1);
290			return i;
291		}
292
293	/* Make sure there is room for the new param record. */
294	if (!nparams) {
295		paramlistsize = 32;
296		params = malloc(paramlistsize * sizeof(*params));
297		param_parent = malloc(paramlistsize * sizeof(*param_parent));
298		if (params == NULL || param_parent == NULL)
299			xo_err(1, "malloc");
300	} else if (nparams >= paramlistsize) {
301		paramlistsize *= 2;
302		params = realloc(params, paramlistsize * sizeof(*params));
303		param_parent = realloc(param_parent,
304		    paramlistsize * sizeof(*param_parent));
305		if (params == NULL || param_parent == NULL)
306			xo_err(1, "realloc");
307	}
308
309	/* Look up the parameter. */
310	param_parent[nparams] = -1;
311	param = params + nparams++;
312	if (source != NULL) {
313		*param = *source;
314		param->jp_flags |= flags;
315		return param - params;
316	}
317	if (jailparam_init(param, name) < 0 ||
318	    (value != NULL ? jailparam_import_raw(param, value, valuelen)
319	     : jailparam_import(param, value)) < 0) {
320		if (flags & JP_OPT) {
321			nparams--;
322			return (-1);
323		}
324		xo_errx(1, "%s", jail_errmsg);
325	}
326	param->jp_flags = flags;
327	return param - params;
328}
329
330static int
331sort_param(const void *a, const void *b)
332{
333	const struct jailparam *parama, *paramb;
334	char *ap, *bp;
335
336	/* Put top-level parameters first. */
337	parama = a;
338	paramb = b;
339	ap = strchr(parama->jp_name, '.');
340	bp = strchr(paramb->jp_name, '.');
341	if (ap && !bp)
342		return (1);
343	if (bp && !ap)
344		return (-1);
345	return (strcmp(parama->jp_name, paramb->jp_name));
346}
347
348static char *
349noname(const char *name)
350{
351	char *nname, *p;
352
353	nname = malloc(strlen(name) + 3);
354	if (nname == NULL)
355		xo_err(1, "malloc");
356	p = strrchr(name, '.');
357	if (p != NULL)
358		sprintf(nname, "%.*s.no%s", (int)(p - name), name, p + 1);
359	else
360		sprintf(nname, "no%s", name);
361	return nname;
362}
363
364static char *
365nononame(const char *name)
366{
367	char *nname, *p;
368
369	p = strrchr(name, '.');
370	if (strncmp(p ? p + 1 : name, "no", 2))
371		return NULL;
372	nname = malloc(strlen(name) - 1);
373	if (nname == NULL)
374		xo_err(1, "malloc");
375	if (p != NULL)
376		sprintf(nname, "%.*s.%s", (int)(p - name), name, p + 3);
377	else
378		strcpy(nname, name + 2);
379	return nname;
380}
381
382static int
383print_jail(int pflags, int jflags)
384{
385	char *nname, *xo_nname;
386	char **param_values;
387	int i, jid, n, spc;
388
389	jid = jailparam_get(params, nparams, jflags);
390	if (jid < 0)
391		return jid;
392
393	xo_open_instance("jail");
394
395	if (pflags & PRINT_VERBOSE) {
396		xo_emit("{:jid/%6d}{P:  }{:hostname/%-29.29s/%s}{P: }"
397		    "{:path/%.74s/%s}\n",
398		    *(int *)params[0].jp_value,
399		    (char *)params[1].jp_value,
400		    (char *)params[2].jp_value);
401		xo_emit("{P:        }{:name/%-29.29s/%s}{P: }{:state/%.74s}\n",
402		    (char *)params[3].jp_value,
403		    *(int *)params[4].jp_value ? "DYING" : "ACTIVE");
404		xo_emit("{P:        }{:cpusetid/%d}\n", *(int *)params[5].jp_value);
405		n = 6;
406#ifdef INET
407		if (ip4_ok && !strcmp(params[n].jp_name, "ip4.addr")) {
408			emit_ip_addr_list(AF_INET, "ipv4_addrs", params + n);
409			n++;
410		}
411#endif
412#ifdef INET6
413		if (ip6_ok && !strcmp(params[n].jp_name, "ip6.addr")) {
414			emit_ip_addr_list(AF_INET6, "ipv6_addrs", params + n);
415			n++;
416		}
417#endif
418	} else if (pflags & PRINT_DEFAULT) {
419		if (pflags & PRINT_JAIL_NAME)
420			xo_emit("{P: }{:name/%-15s/%s}{P: }",
421			    (char *)params[0].jp_value);
422		else
423			xo_emit("{:jid/%6d}{P:  }", *(int *)params[0].jp_value);
424		xo_emit("{:ipv4/%-15.15s/%s}{P: }{:hostname/%-29.29s/%s}{P: }{:path/%.74s/%s}\n",
425#ifdef INET
426		    (!ip4_ok || params[1].jp_valuelen == 0) ? ""
427		    : inet_ntoa(*(struct in_addr *)params[1].jp_value),
428		    (char *)params[2-!ip4_ok].jp_value,
429		    (char *)params[3-!ip4_ok].jp_value);
430#else
431		    "-",
432		    (char *)params[1].jp_value,
433		    (char *)params[2].jp_value);
434#endif
435	} else {
436		param_values = alloca(nparams * sizeof(*param_values));
437		for (i = 0; i < nparams; i++) {
438			if (!(params[i].jp_flags & JP_USER))
439				continue;
440			param_values[i] = jailparam_export(params + i);
441			if (param_values[i] == NULL)
442				xo_errx(1, "%s", jail_errmsg);
443		}
444		for (i = spc = 0; i < nparams; i++) {
445			if (!(params[i].jp_flags & JP_USER))
446				continue;
447			if ((pflags & PRINT_SKIP) &&
448			    ((!(params[i].jp_ctltype &
449				(CTLFLAG_WR | CTLFLAG_TUN))) ||
450			     (param_parent[i] >= 0 &&
451			      *(int *)params[param_parent[i]].jp_value !=
452			      JAIL_SYS_NEW)))
453				continue;
454			if (spc)
455				xo_emit("{P: }");
456			else
457				spc = 1;
458			if (pflags & PRINT_NAMEVAL) {
459				/*
460				 * Generally "name=value", but for booleans
461				 * either "name" or "noname".
462				 */
463				if (params[i].jp_flags &
464				    (JP_BOOL | JP_NOBOOL)) {
465					if (*(int *)params[i].jp_value) {
466						asprintf(&xo_nname, "{en:%s/true}", params[i].jp_name);
467						xo_emit(xo_nname);
468						xo_emit("{d:/%s}", params[i].jp_name);
469					}
470					else {
471						nname = (params[i].jp_flags &
472						    JP_NOBOOL) ?
473						    nononame(params[i].jp_name)
474						    : noname(params[i].jp_name);
475						if (params[i].jp_flags & JP_NOBOOL) {
476							asprintf(&xo_nname, "{en:%s/true}", params[i].jp_name);
477							xo_emit(xo_nname);
478						} else {
479							asprintf(&xo_nname, "{en:%s/false}", params[i].jp_name);
480							xo_emit(xo_nname);
481						}
482						xo_emit("{d:/%s}", nname);
483						free(nname);
484					}
485					free(xo_nname);
486					continue;
487				}
488				xo_emit("{d:%s}=", params[i].jp_name);
489			}
490			if (!special_print(pflags, params + i))
491				quoted_print(pflags, params[i].jp_name, param_values[i]);
492		}
493		xo_emit("{P:\n}");
494		for (i = 0; i < nparams; i++)
495			if (params[i].jp_flags & JP_USER)
496				free(param_values[i]);
497	}
498
499	xo_close_instance("jail");
500	return (jid);
501}
502
503static void
504quoted_print(int pflags, char *name, char *value)
505{
506	int qc;
507	char *p = value;
508	char *param_name_value;
509
510	/* An empty string needs quoting. */
511	if (!*p) {
512		asprintf(&param_name_value, "{k:%s}{d:%s/\"\"}", name, name);
513		xo_emit(param_name_value);
514		free(param_name_value);
515		return;
516	}
517
518	asprintf(&param_name_value, "{:%s/%%s}", name);
519	/*
520	 * The value will be surrounded by quotes if it contains spaces
521	 * or quotes.
522	 */
523	qc = strchr(p, '\'') ? '"'
524		: strchr(p, '"') ? '\''
525		: strchr(p, ' ') || strchr(p, '\t') ? '"'
526		: 0;
527
528	if (qc && pflags & PRINT_QUOTED)
529		xo_emit("{P:/%c}", qc);
530
531	xo_emit(param_name_value, value);
532
533	free(param_name_value);
534
535	if (qc && pflags & PRINT_QUOTED)
536		xo_emit("{P:/%c}", qc);
537}
538
539static int
540special_print(int pflags, struct jailparam *param)
541{
542	int ip_as_list;
543
544	switch (xo_get_style(NULL)) {
545	case XO_STYLE_JSON:
546	case XO_STYLE_XML:
547		ip_as_list = 1;
548		break;
549	default:
550		ip_as_list = 0;
551	}
552
553	if (!ip_as_list && param->jp_valuelen == 0) {
554		if (pflags & PRINT_QUOTED)
555			xo_emit("{P:\"\"}");
556		else if (!(pflags & PRINT_NAMEVAL))
557			xo_emit("{P:-}");
558	} else if (ip_as_list && !strcmp(param->jp_name, "ip4.addr")) {
559		emit_ip_addr_list(AF_INET, param->jp_name, param);
560	} else if (ip_as_list && !strcmp(param->jp_name, "ip6.addr")) {
561		emit_ip_addr_list(AF_INET6, param->jp_name, param);
562	} else {
563		return 0;
564	}
565
566	return 1;
567}
568
569static void
570emit_ip_addr_list(int af_family, const char *list_name, struct jailparam *param)
571{
572	char ipbuf[INET6_ADDRSTRLEN];
573	size_t addr_len;
574	const char *emit_str;
575	int ai, count;
576
577	switch (af_family) {
578	case AF_INET:
579		addr_len = sizeof(struct in_addr);
580		emit_str = "{P:        }{ql:ipv4_addr}{P:\n}";
581		break;
582	case AF_INET6:
583		addr_len = sizeof(struct in6_addr);
584		emit_str = "{P:        }{ql:ipv6_addr}{P:\n}";
585		break;
586	default:
587		xo_err(1, "unsupported af_family");
588		return;
589	}
590
591	count = param->jp_valuelen / addr_len;
592
593	xo_open_list(list_name);
594	for (ai = 0; ai < count; ai++) {
595		if (inet_ntop(af_family,
596		    ((uint8_t *)param->jp_value) + addr_len * ai,
597		    ipbuf, sizeof(ipbuf)) == NULL) {
598			xo_err(1, "inet_ntop");
599		} else {
600			xo_emit(emit_str, ipbuf);
601		}
602	}
603	xo_close_list(list_name);
604}
605