xref: /netbsd-src/usr.sbin/npf/npfctl/npf_show.c (revision cb624a4b465c5a8f1d4975ee06f22d9ed05e39c1)
14e592132Srmind /*-
2b899bfd9Srmind  * Copyright (c) 2013-2020 The NetBSD Foundation, Inc.
34e592132Srmind  * All rights reserved.
44e592132Srmind  *
54e592132Srmind  * This code is derived from software contributed to The NetBSD Foundation
64e592132Srmind  * by Mindaugas Rasiukevicius.
74e592132Srmind  *
84e592132Srmind  * Redistribution and use in source and binary forms, with or without
94e592132Srmind  * modification, are permitted provided that the following conditions
104e592132Srmind  * are met:
114e592132Srmind  * 1. Redistributions of source code must retain the above copyright
124e592132Srmind  *    notice, this list of conditions and the following disclaimer.
134e592132Srmind  * 2. Redistributions in binary form must reproduce the above copyright
144e592132Srmind  *    notice, this list of conditions and the following disclaimer in the
154e592132Srmind  *    documentation and/or other materials provided with the distribution.
164e592132Srmind  *
174e592132Srmind  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
184e592132Srmind  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
194e592132Srmind  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
204e592132Srmind  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
214e592132Srmind  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
224e592132Srmind  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
234e592132Srmind  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
244e592132Srmind  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
254e592132Srmind  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
264e592132Srmind  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
274e592132Srmind  * POSSIBILITY OF SUCH DAMAGE.
284e592132Srmind  */
294e592132Srmind 
304e592132Srmind /*
314e592132Srmind  * NPF configuration printing.
324e592132Srmind  *
334e592132Srmind  * Each rule having BPF byte-code has a binary description.
344e592132Srmind  */
354e592132Srmind 
364e592132Srmind #include <sys/cdefs.h>
37*cb624a4bSmlelstv __RCSID("$NetBSD: npf_show.c,v 1.34 2025/01/27 07:54:30 mlelstv Exp $");
384e592132Srmind 
394e592132Srmind #include <sys/socket.h>
40f75d79ebSchristos #define	__FAVOR_BSD
414e592132Srmind #include <netinet/in.h>
424e592132Srmind #include <netinet/tcp.h>
434e592132Srmind #include <net/if.h>
444e592132Srmind 
454e592132Srmind #include <stdio.h>
464e592132Srmind #include <stdlib.h>
474e592132Srmind #include <string.h>
484e592132Srmind #include <stdbool.h>
494e592132Srmind #include <inttypes.h>
504e592132Srmind #include <errno.h>
514e592132Srmind #include <err.h>
524e592132Srmind 
534e592132Srmind #include "npfctl.h"
544e592132Srmind 
55b899bfd9Srmind #define	SEEN_PROTO	0x01
56b899bfd9Srmind 
57b899bfd9Srmind typedef struct {
58b899bfd9Srmind 	char **		values;
59b899bfd9Srmind 	unsigned	count;
60b899bfd9Srmind } elem_list_t;
61b899bfd9Srmind 
62b899bfd9Srmind enum {
63b899bfd9Srmind 	LIST_PROTO = 0, LIST_SADDR, LIST_DADDR, LIST_SPORT, LIST_DPORT,
64b899bfd9Srmind 	LIST_COUNT,
65b899bfd9Srmind };
663250dbf2Srmind 
674e592132Srmind typedef struct {
681e7342c1Srmind 	nl_config_t *	conf;
69b899bfd9Srmind 	bool		validating;
70b899bfd9Srmind 
714e592132Srmind 	FILE *		fp;
724e592132Srmind 	long		fpos;
73b899bfd9Srmind 	long		fposln;
74b899bfd9Srmind 	int		glevel;
754e592132Srmind 
76b899bfd9Srmind 	unsigned	flags;
77b899bfd9Srmind 	uint32_t	curmark;
78b899bfd9Srmind 	uint64_t	seen_marks;
79b899bfd9Srmind 	elem_list_t	list[LIST_COUNT];
80b899bfd9Srmind 
81b899bfd9Srmind } npf_conf_info_t;
824e592132Srmind 
834e592132Srmind static void	print_linesep(npf_conf_info_t *);
844e592132Srmind 
85b899bfd9Srmind static npf_conf_info_t *
86f75d79ebSchristos npfctl_show_init(void)
87f75d79ebSchristos {
88b899bfd9Srmind 	static npf_conf_info_t stdout_ctx;
89b899bfd9Srmind 	memset(&stdout_ctx, 0, sizeof(npf_conf_info_t));
90b899bfd9Srmind 	stdout_ctx.glevel = -1;
91f75d79ebSchristos 	stdout_ctx.fp = stdout;
92b899bfd9Srmind 	return &stdout_ctx;
93b899bfd9Srmind }
94b899bfd9Srmind 
95b899bfd9Srmind static void
96b899bfd9Srmind list_push(elem_list_t *list, char *val)
97b899bfd9Srmind {
98b899bfd9Srmind 	const unsigned n = list->count;
99b899bfd9Srmind 	char **values;
100b899bfd9Srmind 
101b899bfd9Srmind 	if ((values = calloc(n + 1, sizeof(char *))) == NULL) {
102b899bfd9Srmind 		err(EXIT_FAILURE, "calloc");
103b899bfd9Srmind 	}
104b899bfd9Srmind 	for (unsigned i = 0; i < n; i++) {
105b899bfd9Srmind 		values[i] = list->values[i];
106b899bfd9Srmind 	}
107b899bfd9Srmind 	values[n] = val;
108b899bfd9Srmind 	free(list->values);
109b899bfd9Srmind 	list->values = values;
110b899bfd9Srmind 	list->count++;
111b899bfd9Srmind }
112b899bfd9Srmind 
113b899bfd9Srmind static char *
114b899bfd9Srmind list_join_free(elem_list_t *list, const bool use_br, const char *sep)
115b899bfd9Srmind {
116b899bfd9Srmind 	char *s, buf[2048];
117b899bfd9Srmind 
118b899bfd9Srmind 	if (!join(buf, sizeof(buf), list->count, list->values, sep)) {
119b899bfd9Srmind 		errx(EXIT_FAILURE, "out of memory while parsing the rule");
120b899bfd9Srmind 	}
121b899bfd9Srmind 	easprintf(&s, (use_br && list->count > 1) ? "{ %s }" : "%s", buf);
122b899bfd9Srmind 	for (unsigned i = 0; i < list->count; i++) {
123b899bfd9Srmind 		free(list->values[i]);
124b899bfd9Srmind 	}
125b899bfd9Srmind 	free(list->values);
126b899bfd9Srmind 	list->values = NULL;
127b899bfd9Srmind 	list->count = 0;
128b899bfd9Srmind 	return s;
129f75d79ebSchristos }
130f75d79ebSchristos 
1314e592132Srmind /*
1324e592132Srmind  * Helper routines to print various pieces of information.
1334e592132Srmind  */
1344e592132Srmind 
1354e592132Srmind static void
1363d9a792dSrmind print_indent(npf_conf_info_t *ctx, unsigned level)
1374e592132Srmind {
138b899bfd9Srmind 	if (ctx->glevel >= 0 && level <= (unsigned)ctx->glevel) {
1393d9a792dSrmind 		/*
1403d9a792dSrmind 		 * Level decrease -- end of the group.
1413d9a792dSrmind 		 * Print the group closing curly bracket.
1423d9a792dSrmind 		 */
143b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "}\n\n");
144b899bfd9Srmind 		ctx->glevel = -1;
1453d9a792dSrmind 	}
1463d9a792dSrmind 	while (level--) {
147b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "\t");
1484e592132Srmind 	}
1493d9a792dSrmind }
1504e592132Srmind 
1514e592132Srmind static void
1524e592132Srmind print_linesep(npf_conf_info_t *ctx)
1534e592132Srmind {
154b899bfd9Srmind 	if (ctx->fpos != ctx->fposln) {
155b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "\n");
156b899bfd9Srmind 		ctx->fposln = ctx->fpos;
1574e592132Srmind 	}
1584e592132Srmind }
1594e592132Srmind 
1604e592132Srmind static size_t
161b899bfd9Srmind tcpflags2string(char *buf, unsigned tfl)
1624e592132Srmind {
163b899bfd9Srmind 	unsigned i = 0;
1644e592132Srmind 
1654e592132Srmind 	if (tfl & TH_FIN)	buf[i++] = 'F';
1664e592132Srmind 	if (tfl & TH_SYN)	buf[i++] = 'S';
1674e592132Srmind 	if (tfl & TH_RST)	buf[i++] = 'R';
1684e592132Srmind 	if (tfl & TH_PUSH)	buf[i++] = 'P';
1694e592132Srmind 	if (tfl & TH_ACK)	buf[i++] = 'A';
1704e592132Srmind 	if (tfl & TH_URG)	buf[i++] = 'U';
1714e592132Srmind 	if (tfl & TH_ECE)	buf[i++] = 'E';
17242e1450dSchristos 	if (tfl & TH_CWR)	buf[i++] = 'W';
1734e592132Srmind 	buf[i] = '\0';
1744e592132Srmind 	return i;
1754e592132Srmind }
1764e592132Srmind 
1774e592132Srmind static char *
17839013e66Srmind print_family(npf_conf_info_t *ctx __unused, const uint32_t *words)
1794e592132Srmind {
1804e592132Srmind 	const int af = words[0];
1814e592132Srmind 
1824e592132Srmind 	switch (af) {
1834e592132Srmind 	case AF_INET:
1848b83480dSrmind 		return estrdup("inet4");
1854e592132Srmind 	case AF_INET6:
1864e592132Srmind 		return estrdup("inet6");
1874e592132Srmind 	default:
1884e592132Srmind 		errx(EXIT_FAILURE, "invalid byte-code mark (family)");
1894e592132Srmind 	}
1904e592132Srmind 	return NULL;
1914e592132Srmind }
1924e592132Srmind 
1934e592132Srmind static char *
19439013e66Srmind print_address(npf_conf_info_t *ctx __unused, const uint32_t *words)
1954e592132Srmind {
1964e592132Srmind 	const int af = *words++;
197b899bfd9Srmind 	const unsigned mask = *words++;
1984e592132Srmind 	const npf_addr_t *addr;
1994e592132Srmind 	int alen = 0;
2004e592132Srmind 
2014e592132Srmind 	switch (af) {
2024e592132Srmind 	case AF_INET:
2034e592132Srmind 		alen = 4;
2044e592132Srmind 		break;
2054e592132Srmind 	case AF_INET6:
2064e592132Srmind 		alen = 16;
2074e592132Srmind 		break;
2084e592132Srmind 	default:
2094e592132Srmind 		errx(EXIT_FAILURE, "invalid byte-code mark (address)");
2104e592132Srmind 	}
2114e592132Srmind 	addr = (const npf_addr_t *)words;
212a0cedf0dSchristos 	return npfctl_print_addrmask(alen, "%a", addr, mask);
2134e592132Srmind }
2144e592132Srmind 
2154e592132Srmind static char *
21639013e66Srmind print_number(npf_conf_info_t *ctx __unused, const uint32_t *words)
2174e592132Srmind {
2184e592132Srmind 	char *p;
2194e592132Srmind 	easprintf(&p, "%u", words[0]);
2204e592132Srmind 	return p;
2214e592132Srmind }
2224e592132Srmind 
2234e592132Srmind static char *
2241e7342c1Srmind print_table(npf_conf_info_t *ctx, const uint32_t *words)
2251e7342c1Srmind {
2263d9a792dSrmind 	const unsigned tid = words[0];
2273d9a792dSrmind 	const char *tname;
2283d9a792dSrmind 	char *s = NULL;
2293d9a792dSrmind 	bool ifaddr;
2301e7342c1Srmind 
2313d9a792dSrmind 	tname = npfctl_table_getname(ctx->conf, tid, &ifaddr);
2323d9a792dSrmind 	easprintf(&s, ifaddr ? "ifaddrs(%s)" : "<%s>", tname);
2333d9a792dSrmind 	return s;
2341e7342c1Srmind }
2351e7342c1Srmind 
2361e7342c1Srmind static char *
2371e7342c1Srmind print_proto(npf_conf_info_t *ctx, const uint32_t *words)
2384e592132Srmind {
239b899bfd9Srmind 	ctx->flags |= SEEN_PROTO;
2404e592132Srmind 	switch (words[0]) {
2414e592132Srmind 	case IPPROTO_TCP:
2424e592132Srmind 		return estrdup("tcp");
2434e592132Srmind 	case IPPROTO_UDP:
2444e592132Srmind 		return estrdup("udp");
2454e592132Srmind 	case IPPROTO_ICMP:
2464e592132Srmind 		return estrdup("icmp");
2474e592132Srmind 	case IPPROTO_ICMPV6:
2484e592132Srmind 		return estrdup("ipv6-icmp");
2494e592132Srmind 	}
2501e7342c1Srmind 	return print_number(ctx, words);
2514e592132Srmind }
2524e592132Srmind 
2534e592132Srmind static char *
25439013e66Srmind print_tcpflags(npf_conf_info_t *ctx __unused, const uint32_t *words)
2554e592132Srmind {
256b899bfd9Srmind 	const unsigned tf = words[0], tf_mask = words[1];
257b899bfd9Srmind 	char buf[32];
258b899bfd9Srmind 	size_t n;
2594e592132Srmind 
260b899bfd9Srmind 	if ((ctx->flags & SEEN_PROTO) == 0) {
261b899bfd9Srmind 		/*
262b899bfd9Srmind 		 * Note: the TCP flag matching might be without 'proto tcp'
263b899bfd9Srmind 		 * when using a plain 'stateful' rule.  In such case, just
264b899bfd9Srmind 		 * skip showing of the flags as they are implicit.
265b899bfd9Srmind 		 */
266b899bfd9Srmind 		return NULL;
267b899bfd9Srmind 	}
268b899bfd9Srmind 	n = tcpflags2string(buf, tf);
2694e592132Srmind 	if (tf != tf_mask) {
2704e592132Srmind 		buf[n++] = '/';
2714e592132Srmind 		tcpflags2string(buf + n, tf_mask);
2724e592132Srmind 	}
2734e592132Srmind 	return estrdup(buf);
2744e592132Srmind }
2754e592132Srmind 
2764e592132Srmind static char *
27764b4ec43Srmind print_portrange(npf_conf_info_t *ctx __unused, const uint32_t *words)
2784e592132Srmind {
279b899bfd9Srmind 	unsigned fport = words[0], tport = words[1];
2804e592132Srmind 	char *p;
2814e592132Srmind 
2824e592132Srmind 	if (fport != tport) {
28364b4ec43Srmind 		easprintf(&p, "%u-%u", fport, tport);
2844e592132Srmind 	} else {
28564b4ec43Srmind 		easprintf(&p, "%u", fport);
2864e592132Srmind 	}
2874e592132Srmind 	return p;
2884e592132Srmind }
2894e592132Srmind 
2904e592132Srmind /*
2914e592132Srmind  * The main keyword mapping tables defining the syntax:
2924e592132Srmind  * - Mapping of rule attributes (flags) to the keywords.
2934e592132Srmind  * - Mapping of the byte-code marks to the keywords.
2944e592132Srmind  */
2954e592132Srmind 
2964e592132Srmind #define	F(name)		__CONCAT(NPF_RULE_, name)
297dadc88e3Srmind #define	STATEFUL_ALL	(NPF_RULE_STATEFUL | NPF_RULE_GSTATEFUL)
2984e592132Srmind #define	NAME_AT		2
2994e592132Srmind 
3004e592132Srmind static const struct attr_keyword_mapent {
3014e592132Srmind 	uint32_t	mask;
3024e592132Srmind 	uint32_t	flags;
3034e592132Srmind 	const char *	val;
3044e592132Srmind } attr_keyword_map[] = {
3054e592132Srmind 	{ F(GROUP)|F(DYNAMIC),	F(GROUP),		"group"		},
306b899bfd9Srmind 	{ F(GROUP)|F(DYNAMIC),	F(GROUP)|F(DYNAMIC),	"ruleset"	},
3074e592132Srmind 	{ F(GROUP)|F(PASS),	0,			"block"		},
3084e592132Srmind 	{ F(GROUP)|F(PASS),	F(PASS),		"pass"		},
3094e592132Srmind 	{ F(RETRST)|F(RETICMP),	F(RETRST)|F(RETICMP),	"return"	},
3104e592132Srmind 	{ F(RETRST)|F(RETICMP),	F(RETRST),		"return-rst"	},
3114e592132Srmind 	{ F(RETRST)|F(RETICMP),	F(RETICMP),		"return-icmp"	},
312dadc88e3Srmind 	{ STATEFUL_ALL,		F(STATEFUL),		"stateful"	},
313dadc88e3Srmind 	{ STATEFUL_ALL,		STATEFUL_ALL,		"stateful-all"	},
3144e592132Srmind 	{ F(DIMASK),		F(IN),			"in"		},
3154e592132Srmind 	{ F(DIMASK),		F(OUT),			"out"		},
3164e592132Srmind 	{ F(FINAL),		F(FINAL),		"final"		},
3174e592132Srmind };
3184e592132Srmind 
3194e592132Srmind static const struct mark_keyword_mapent {
320b899bfd9Srmind 	unsigned	mark;
321b899bfd9Srmind 	const char *	format;
322b899bfd9Srmind 	int		list_id;
3231e7342c1Srmind 	char *		(*printfn)(npf_conf_info_t *, const uint32_t *);
324b899bfd9Srmind 	unsigned	fwords;
3254e592132Srmind } mark_keyword_map[] = {
326b899bfd9Srmind 	{ BM_IPVER,	"family %s",	LIST_PROTO,	print_family,	1 },
327b899bfd9Srmind 	{ BM_PROTO,	"proto %s",	LIST_PROTO,	print_proto,	1 },
328b899bfd9Srmind 	{ BM_TCPFL,	"flags %s",	LIST_PROTO,	print_tcpflags,	2 },
329b899bfd9Srmind 	{ BM_ICMP_TYPE,	"icmp-type %s",	LIST_PROTO,	print_number,	1 },
330b899bfd9Srmind 	{ BM_ICMP_CODE,	"code %s",	LIST_PROTO,	print_number,	1 },
3314e592132Srmind 
332b899bfd9Srmind 	{ BM_SRC_NEG,	NULL,		-1,		NULL,		0 },
333b899bfd9Srmind 	{ BM_SRC_CIDR,	NULL,		LIST_SADDR,	print_address,	6 },
334b899bfd9Srmind 	{ BM_SRC_TABLE,	NULL,		LIST_SADDR,	print_table,	1 },
335b899bfd9Srmind 	{ BM_SRC_PORTS,	NULL,		LIST_SPORT,	print_portrange,2 },
3364e592132Srmind 
337b899bfd9Srmind 	{ BM_DST_NEG,	NULL,		-1,		NULL,		0 },
338b899bfd9Srmind 	{ BM_DST_CIDR,	NULL,		LIST_DADDR,	print_address,	6 },
339b899bfd9Srmind 	{ BM_DST_TABLE,	NULL,		LIST_DADDR,	print_table,	1 },
340b899bfd9Srmind 	{ BM_DST_PORTS,	NULL,		LIST_DPORT,	print_portrange,2 },
3414e592132Srmind };
3424e592132Srmind 
3434e592132Srmind static const char * __attribute__((format_arg(2)))
3444e592132Srmind verified_fmt(const char *fmt, const char *t __unused)
3454e592132Srmind {
3464e592132Srmind 	return fmt;
3474e592132Srmind }
3484e592132Srmind 
349b899bfd9Srmind static void
3504e592132Srmind scan_marks(npf_conf_info_t *ctx, const struct mark_keyword_mapent *mk,
3514e592132Srmind     const uint32_t *marks, size_t mlen)
3524e592132Srmind {
353b899bfd9Srmind 	elem_list_t sublist, *target_list;
354b899bfd9Srmind 
355b899bfd9Srmind 	/*
356b899bfd9Srmind 	 * If format is used for this mark, then collect multiple elements
357b899bfd9Srmind 	 * in into the list, merge and re-push the set into the target list.
358b899bfd9Srmind 	 *
359b899bfd9Srmind 	 * Currently, this is applicable only for 'proto { tcp, udp }'.
360b899bfd9Srmind 	 */
361b899bfd9Srmind 	memset(&sublist, 0, sizeof(elem_list_t));
362b899bfd9Srmind 	target_list = mk->format ? &sublist : &ctx->list[mk->list_id];
3634e592132Srmind 
3644e592132Srmind 	/* Scan for the marks and extract the values. */
3654e592132Srmind 	mlen /= sizeof(uint32_t);
3664e592132Srmind 	while (mlen > 2) {
3674e592132Srmind 		const uint32_t m = *marks++;
368b899bfd9Srmind 		const unsigned nwords = *marks++;
3694e592132Srmind 
3704e592132Srmind 		if ((mlen -= 2) < nwords) {
3714e592132Srmind 			errx(EXIT_FAILURE, "byte-code marking inconsistency");
3724e592132Srmind 		}
3734e592132Srmind 		if (m == mk->mark) {
374b899bfd9Srmind 			/*
375b899bfd9Srmind 			 * Set the current mark and note it as seen.
376b899bfd9Srmind 			 * Value is processed by the print function,
377b899bfd9Srmind 			 * otherwise we just need to note the mark.
378b899bfd9Srmind 			 */
379b899bfd9Srmind 			ctx->curmark = m;
380b899bfd9Srmind 			assert(BM_COUNT < (sizeof(uint64_t) * CHAR_BIT));
381*cb624a4bSmlelstv 			ctx->seen_marks |= UINT64_C(1) << m;
382b899bfd9Srmind 			assert(mk->fwords == nwords);
383b899bfd9Srmind 
384b899bfd9Srmind 			if (mk->printfn) {
38564b4ec43Srmind 				char *val;
38664b4ec43Srmind 
38764b4ec43Srmind 				if ((val = mk->printfn(ctx, marks)) != NULL) {
388b899bfd9Srmind 					list_push(target_list, val);
389b899bfd9Srmind 				}
39064b4ec43Srmind 			}
3914e592132Srmind 		}
3924e592132Srmind 		marks += nwords;
3934e592132Srmind 		mlen -= nwords;
3944e592132Srmind 	}
3954e592132Srmind 
396b899bfd9Srmind 	if (sublist.count) {
397b899bfd9Srmind 		char *val, *elements;
3984e592132Srmind 
399b899bfd9Srmind 		elements = list_join_free(&sublist, true, ", ");
400b899bfd9Srmind 		easprintf(&val, verified_fmt(mk->format, "%s"), elements );
401b899bfd9Srmind 		list_push(&ctx->list[mk->list_id], val);
402b899bfd9Srmind 		free(elements);
4034e592132Srmind 	}
4044e592132Srmind }
4054e592132Srmind 
4064e592132Srmind static void
407f8006a40Schristos npfctl_print_id(npf_conf_info_t *ctx, nl_rule_t *rl)
408f8006a40Schristos {
409b899bfd9Srmind 	const uint64_t id = npf_rule_getid(rl);
410b899bfd9Srmind 
411b899bfd9Srmind 	if (id) {
412b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "# id=\"%" PRIx64 "\" ", id);
413b899bfd9Srmind 	}
414f8006a40Schristos }
415f8006a40Schristos 
416f8006a40Schristos static void
417b899bfd9Srmind npfctl_print_filter_generic(npf_conf_info_t *ctx)
418b899bfd9Srmind {
419b899bfd9Srmind 	elem_list_t *list = &ctx->list[LIST_PROTO];
420b899bfd9Srmind 
421b899bfd9Srmind 	if (list->count) {
422b899bfd9Srmind 		char *elements = list_join_free(list, false, " ");
423b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "%s ", elements);
424b899bfd9Srmind 		free(elements);
425b899bfd9Srmind 	}
426b899bfd9Srmind }
427b899bfd9Srmind 
428b899bfd9Srmind static bool
429b899bfd9Srmind npfctl_print_filter_seg(npf_conf_info_t *ctx, unsigned which)
430b899bfd9Srmind {
431b899bfd9Srmind 	static const struct {
432b899bfd9Srmind 		const char *	keyword;
433b899bfd9Srmind 		unsigned	alist;
434b899bfd9Srmind 		unsigned	plist;
435b899bfd9Srmind 		unsigned	negbm;
436b899bfd9Srmind 	} refs[] = {
437b899bfd9Srmind 		[NPF_SRC] = {
438b899bfd9Srmind 			.keyword	= "from",
439b899bfd9Srmind 			.alist		= LIST_SADDR,
440b899bfd9Srmind 			.plist		= LIST_SPORT,
441b899bfd9Srmind 			.negbm		= UINT64_C(1) << BM_SRC_NEG,
442b899bfd9Srmind 		},
443b899bfd9Srmind 		[NPF_DST] = {
444b899bfd9Srmind 			.keyword	= "to",
445b899bfd9Srmind 			.alist		= LIST_DADDR,
446b899bfd9Srmind 			.plist		= LIST_DPORT,
447b899bfd9Srmind 			.negbm		= UINT64_C(1) << BM_DST_NEG,
448b899bfd9Srmind 		}
449b899bfd9Srmind 	};
450b899bfd9Srmind 	const char *neg = !!(ctx->seen_marks & refs[which].negbm) ? "! " : "";
451b899bfd9Srmind 	const char *kwd = refs[which].keyword;
452b899bfd9Srmind 	bool seen_filter = false;
453b899bfd9Srmind 	elem_list_t *list;
454b899bfd9Srmind 	char *elements;
455b899bfd9Srmind 
456b899bfd9Srmind 	list = &ctx->list[refs[which].alist];
457b899bfd9Srmind 	if (list->count != 0) {
458b899bfd9Srmind 		seen_filter = true;
459b899bfd9Srmind 		elements = list_join_free(list, true, ", ");
460b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "%s %s%s ", kwd, neg, elements);
461b899bfd9Srmind 		free(elements);
462b899bfd9Srmind 	}
463b899bfd9Srmind 
464b899bfd9Srmind 	list = &ctx->list[refs[which].plist];
465b899bfd9Srmind 	if (list->count != 0) {
466b899bfd9Srmind 		if (!seen_filter) {
467b899bfd9Srmind 			ctx->fpos += fprintf(ctx->fp, "%s any ", kwd);
468b899bfd9Srmind 			seen_filter = true;
469b899bfd9Srmind 		}
470b899bfd9Srmind 		elements = list_join_free(list, true, ", ");
471b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "port %s ", elements);
472b899bfd9Srmind 		free(elements);
473b899bfd9Srmind 	}
474b899bfd9Srmind 	return seen_filter;
475b899bfd9Srmind }
476b899bfd9Srmind 
477b899bfd9Srmind static bool
4784e592132Srmind npfctl_print_filter(npf_conf_info_t *ctx, nl_rule_t *rl)
4794e592132Srmind {
4804e592132Srmind 	const void *marks;
481f56b8821Srmind 	size_t mlen, len;
482f56b8821Srmind 	const void *code;
483b899bfd9Srmind 	bool seenf = false;
484f56b8821Srmind 	int type;
4854e592132Srmind 
4864e592132Srmind 	marks = npf_rule_getinfo(rl, &mlen);
487f56b8821Srmind 	if (!marks && (code = npf_rule_getcode(rl, &type, &len)) != NULL) {
488f56b8821Srmind 		/*
489f56b8821Srmind 		 * No marks, but the byte-code is present.  This must
490f56b8821Srmind 		 * have been filled by libpcap(3) or possibly an unknown
491f56b8821Srmind 		 * to us byte-code.
492f56b8821Srmind 		 */
493b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "%s ", type == NPF_CODE_BPF ?
494f56b8821Srmind 		    "pcap-filter \"...\"" : "unrecognized-bytecode");
495b899bfd9Srmind 		return true;
496f56b8821Srmind 	}
497d6bf72e9Srmind 	ctx->flags = 0;
498f56b8821Srmind 
499f56b8821Srmind 	/*
500f56b8821Srmind 	 * BPF filter criteria described by the byte-code marks.
501f56b8821Srmind 	 */
502*cb624a4bSmlelstv 	ctx->seen_marks = 0;
503b899bfd9Srmind 	for (unsigned i = 0; i < __arraycount(mark_keyword_map); i++) {
5044e592132Srmind 		const struct mark_keyword_mapent *mk = &mark_keyword_map[i];
505b899bfd9Srmind 		scan_marks(ctx, mk, marks, mlen);
5064e592132Srmind 	}
507b899bfd9Srmind 	npfctl_print_filter_generic(ctx);
508b899bfd9Srmind 	seenf |= npfctl_print_filter_seg(ctx, NPF_SRC);
509b899bfd9Srmind 	seenf |= npfctl_print_filter_seg(ctx, NPF_DST);
510b899bfd9Srmind 	return seenf;
5114e592132Srmind }
5124e592132Srmind 
5134e592132Srmind static void
514b899bfd9Srmind npfctl_print_rule(npf_conf_info_t *ctx, nl_rule_t *rl, unsigned level)
5154e592132Srmind {
5164e592132Srmind 	const uint32_t attr = npf_rule_getattr(rl);
517a79812eaSrmind 	const char *rproc, *ifname, *name;
518b899bfd9Srmind 	bool dyn_ruleset;
5194e592132Srmind 
5204e592132Srmind 	/* Rule attributes/flags. */
521b899bfd9Srmind 	for (unsigned i = 0; i < __arraycount(attr_keyword_map); i++) {
5224e592132Srmind 		const struct attr_keyword_mapent *ak = &attr_keyword_map[i];
5234e592132Srmind 
5244e592132Srmind 		if (i == NAME_AT && (name = npf_rule_getname(rl)) != NULL) {
525b899bfd9Srmind 			ctx->fpos += fprintf(ctx->fp, "\"%s\" ", name);
5264e592132Srmind 		}
5274e592132Srmind 		if ((attr & ak->mask) == ak->flags) {
528b899bfd9Srmind 			ctx->fpos += fprintf(ctx->fp, "%s ", ak->val);
5294e592132Srmind 		}
5304e592132Srmind 	}
531a79812eaSrmind 	if ((ifname = npf_rule_getinterface(rl)) != NULL) {
532b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "on %s ", ifname);
5334e592132Srmind 	}
5343d9a792dSrmind 	if (attr == (NPF_RULE_GROUP | NPF_RULE_IN | NPF_RULE_OUT) && !ifname) {
5353d9a792dSrmind 		/* The default group is a special case. */
536b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "default ");
5373d9a792dSrmind 	}
538f56b8821Srmind 	if ((attr & NPF_DYNAMIC_GROUP) == NPF_RULE_GROUP) {
5394e592132Srmind 		/* Group; done. */
540b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "{ ");
541b899bfd9Srmind 		ctx->glevel = level;
542f8006a40Schristos 		goto out;
5434e592132Srmind 	}
5444e592132Srmind 
5454e592132Srmind 	/* Print filter criteria. */
546b899bfd9Srmind 	dyn_ruleset = (attr & NPF_DYNAMIC_GROUP) == NPF_DYNAMIC_GROUP;
547b899bfd9Srmind 	if (!npfctl_print_filter(ctx, rl) && !dyn_ruleset) {
548b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "all ");
549b899bfd9Srmind 	}
5504e592132Srmind 
5514e592132Srmind 	/* Rule procedure. */
5524e592132Srmind 	if ((rproc = npf_rule_getproc(rl)) != NULL) {
553b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "apply \"%s\" ", rproc);
5544e592132Srmind 	}
555f8006a40Schristos out:
556f8006a40Schristos 	npfctl_print_id(ctx, rl);
557b899bfd9Srmind 	ctx->fpos += fprintf(ctx->fp, "\n");
5584e592132Srmind }
5594e592132Srmind 
5604e592132Srmind static void
5614e592132Srmind npfctl_print_nat(npf_conf_info_t *ctx, nl_nat_t *nt)
5624e592132Srmind {
563dadc88e3Srmind 	const unsigned dynamic_natset = NPF_RULE_GROUP | NPF_RULE_DYNAMIC;
5644e592132Srmind 	nl_rule_t *rl = (nl_nat_t *)nt;
5653d9a792dSrmind 	const char *ifname, *algo, *seg1, *seg2, *arrow;
5663d9a792dSrmind 	const npf_addr_t *addr;
5673d9a792dSrmind 	npf_netmask_t mask;
5684e592132Srmind 	in_port_t port;
569a79812eaSrmind 	size_t alen;
570dadc88e3Srmind 	unsigned flags;
571a79812eaSrmind 	char *seg;
5724e592132Srmind 
573dadc88e3Srmind 	/* Get flags and the interface. */
574dadc88e3Srmind 	flags = npf_nat_getflags(nt);
575a79812eaSrmind 	ifname = npf_rule_getinterface(rl);
576a79812eaSrmind 	assert(ifname != NULL);
5774e592132Srmind 
578dadc88e3Srmind 	if ((npf_rule_getattr(rl) & dynamic_natset) == dynamic_natset) {
579dadc88e3Srmind 		const char *name = npf_rule_getname(rl);
580b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp,
581b899bfd9Srmind 		    "map ruleset \"%s\" on %s\n", name, ifname);
582dadc88e3Srmind 		return;
583dadc88e3Srmind 	}
584dadc88e3Srmind 
5853d9a792dSrmind 	/* Get the translation address or table (and port, if used). */
5863d9a792dSrmind 	addr = npf_nat_getaddr(nt, &alen, &mask);
5873d9a792dSrmind 	if (addr) {
5883d9a792dSrmind 		seg = npfctl_print_addrmask(alen, "%a", addr, mask);
5893d9a792dSrmind 	} else {
5903d9a792dSrmind 		const unsigned tid = npf_nat_gettable(nt);
5913d9a792dSrmind 		const char *tname;
5923d9a792dSrmind 		bool ifaddr;
5933d9a792dSrmind 
5943d9a792dSrmind 		tname = npfctl_table_getname(ctx->conf, tid, &ifaddr);
5953d9a792dSrmind 		easprintf(&seg, ifaddr ? "ifaddrs(%s)" : "<%s>", tname);
5963d9a792dSrmind 	}
5973d9a792dSrmind 
5983d9a792dSrmind 	if ((port = npf_nat_getport(nt)) != 0) {
5994e592132Srmind 		char *p;
60027b83b3dSrmind 		easprintf(&p, "%s port %u", seg, ntohs(port));
6014e592132Srmind 		free(seg), seg = p;
6024e592132Srmind 	}
6034e592132Srmind 	seg1 = seg2 = "any";
6044e592132Srmind 
6054e592132Srmind 	/* Get the NAT type and determine the translation segment. */
6064e592132Srmind 	switch (npf_nat_gettype(nt)) {
6074e592132Srmind 	case NPF_NATIN:
6084e592132Srmind 		arrow = "<-";
6094e592132Srmind 		seg1 = seg;
6104e592132Srmind 		break;
6114e592132Srmind 	case NPF_NATOUT:
6124e592132Srmind 		arrow = "->";
6134e592132Srmind 		seg2 = seg;
6144e592132Srmind 		break;
6154e592132Srmind 	default:
6168274d601Srmind 		abort();
6174e592132Srmind 	}
6184e592132Srmind 
6193d9a792dSrmind 	/* NAT algorithm. */
6203d9a792dSrmind 	switch (npf_nat_getalgo(nt)) {
6213d9a792dSrmind 	case NPF_ALGO_NETMAP:
6223d9a792dSrmind 		algo = "algo netmap ";
6233d9a792dSrmind 		break;
6243d9a792dSrmind 	case NPF_ALGO_IPHASH:
6253d9a792dSrmind 		algo = "algo ip-hash ";
6263d9a792dSrmind 		break;
6273d9a792dSrmind 	case NPF_ALGO_RR:
6283d9a792dSrmind 		algo = "algo round-robin ";
6293d9a792dSrmind 		break;
6303d9a792dSrmind 	case NPF_ALGO_NPT66:
6313d9a792dSrmind 		algo = "algo npt66 ";
6323d9a792dSrmind 		break;
6333d9a792dSrmind 	default:
6343d9a792dSrmind 		algo = "";
6353d9a792dSrmind 		break;
6363d9a792dSrmind 	}
6373d9a792dSrmind 
638b899bfd9Srmind 	/* XXX also handle "any" */
6393d9a792dSrmind 
6404e592132Srmind 	/* Print out the NAT policy with the filter criteria. */
641b899bfd9Srmind 	ctx->fpos += fprintf(ctx->fp, "map %s %s %s%s%s %s %s pass ",
6428274d601Srmind 	    ifname, (flags & NPF_NAT_STATIC) ? "static" : "dynamic",
6433d9a792dSrmind 	    algo, (flags & NPF_NAT_PORTS) ? "" : "no-ports ",
6448274d601Srmind 	    seg1, arrow, seg2);
6454e592132Srmind 	npfctl_print_filter(ctx, rl);
646f8006a40Schristos 	npfctl_print_id(ctx, rl);
647b899bfd9Srmind 	ctx->fpos += fprintf(ctx->fp, "\n");
6484e592132Srmind 	free(seg);
6494e592132Srmind }
6504e592132Srmind 
6514e592132Srmind static void
6524e592132Srmind npfctl_print_table(npf_conf_info_t *ctx, nl_table_t *tl)
6534e592132Srmind {
6541e7342c1Srmind 	const char *name = npf_table_getname(tl);
6551e2389edSrmind 	const unsigned type = npf_table_gettype(tl);
6561e2389edSrmind 	const char *table_types[] = {
6573d9a792dSrmind 		[NPF_TABLE_IPSET]	= "ipset",
6583d9a792dSrmind 		[NPF_TABLE_LPM]		= "lpm",
6593d9a792dSrmind 		[NPF_TABLE_CONST]	= "const",
6601e2389edSrmind 	};
6614e592132Srmind 
662d116583eSrmind 	if (name[0] == '.') {
663d116583eSrmind 		/* Internal tables use dot and are hidden. */
664d116583eSrmind 		return;
665d116583eSrmind 	}
6661e2389edSrmind 	assert(type < __arraycount(table_types));
667b899bfd9Srmind 	ctx->fpos += fprintf(ctx->fp,
668b899bfd9Srmind 	    "table <%s> type %s\n", name, table_types[type]);
669b899bfd9Srmind }
670b899bfd9Srmind 
671b899bfd9Srmind static void
672b899bfd9Srmind npfctl_print_params(npf_conf_info_t *ctx, nl_config_t *ncf)
673b899bfd9Srmind {
674b899bfd9Srmind 	nl_iter_t i = NPF_ITER_BEGIN;
675b899bfd9Srmind 	int val, defval, *dval;
676b899bfd9Srmind 	const char *name;
677b899bfd9Srmind 
678b899bfd9Srmind 	dval = ctx->validating ? NULL : &defval;
679b899bfd9Srmind 	while ((name = npf_param_iterate(ncf, &i, &val, dval)) != NULL) {
680b899bfd9Srmind 		if (dval && val == *dval) {
681b899bfd9Srmind 			continue;
682b899bfd9Srmind 		}
683b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp, "set %s %d\n", name, val);
684b899bfd9Srmind 	}
685b899bfd9Srmind 	print_linesep(ctx);
6864e592132Srmind }
6874e592132Srmind 
6884e592132Srmind int
6894e592132Srmind npfctl_config_show(int fd)
6904e592132Srmind {
691b899bfd9Srmind 	npf_conf_info_t *ctx = npfctl_show_init();
6924e592132Srmind 	nl_config_t *ncf;
693f75d79ebSchristos 	bool loaded;
6944e592132Srmind 
6954e592132Srmind 	if (fd) {
696f75d79ebSchristos 		ncf = npf_config_retrieve(fd);
6974e592132Srmind 		if (ncf == NULL) {
6984e592132Srmind 			return errno;
6994e592132Srmind 		}
700f75d79ebSchristos 		loaded = npf_config_loaded_p(ncf);
701b899bfd9Srmind 		ctx->validating = false;
702b899bfd9Srmind 		ctx->fpos += fprintf(ctx->fp,
703b899bfd9Srmind 		    "# filtering:\t%s\n# config:\t%s\n",
704f75d79ebSchristos 		    npf_config_active_p(ncf) ? "active" : "inactive",
7054e592132Srmind 		    loaded ? "loaded" : "empty");
7064e592132Srmind 		print_linesep(ctx);
7074e592132Srmind 	} else {
7084e592132Srmind 		ncf = npfctl_config_ref();
70907861232Srmind 		npfctl_config_build();
710b899bfd9Srmind 		ctx->validating = true;
7114e592132Srmind 		loaded = true;
7124e592132Srmind 	}
7131e7342c1Srmind 	ctx->conf = ncf;
7144e592132Srmind 
7154e592132Srmind 	if (loaded) {
7164e592132Srmind 		nl_rule_t *rl;
7174e592132Srmind 		nl_rproc_t *rp;
7184e592132Srmind 		nl_nat_t *nt;
7194e592132Srmind 		nl_table_t *tl;
720dadc88e3Srmind 		nl_iter_t i;
7213d9a792dSrmind 		unsigned level;
7224e592132Srmind 
723b899bfd9Srmind 		npfctl_print_params(ctx, ncf);
724b899bfd9Srmind 
725dadc88e3Srmind 		i = NPF_ITER_BEGIN;
726dadc88e3Srmind 		while ((tl = npf_table_iterate(ncf, &i)) != NULL) {
7274e592132Srmind 			npfctl_print_table(ctx, tl);
7284e592132Srmind 		}
7294e592132Srmind 		print_linesep(ctx);
7304e592132Srmind 
731dadc88e3Srmind 		i = NPF_ITER_BEGIN;
732dadc88e3Srmind 		while ((rp = npf_rproc_iterate(ncf, &i)) != NULL) {
7334e592132Srmind 			const char *rpname = npf_rproc_getname(rp);
734b899bfd9Srmind 			ctx->fpos += fprintf(ctx->fp,
735b899bfd9Srmind 			    "procedure \"%s\"\n", rpname);
7364e592132Srmind 		}
7374e592132Srmind 		print_linesep(ctx);
7384e592132Srmind 
739dadc88e3Srmind 		i = NPF_ITER_BEGIN;
740dadc88e3Srmind 		while ((nt = npf_nat_iterate(ncf, &i)) != NULL) {
7414e592132Srmind 			npfctl_print_nat(ctx, nt);
7424e592132Srmind 		}
7434e592132Srmind 		print_linesep(ctx);
7444e592132Srmind 
745dadc88e3Srmind 		i = NPF_ITER_BEGIN;
746dadc88e3Srmind 		while ((rl = npf_rule_iterate(ncf, &i, &level)) != NULL) {
7474e592132Srmind 			print_indent(ctx, level);
748b899bfd9Srmind 			npfctl_print_rule(ctx, rl, level);
7494e592132Srmind 		}
7503d9a792dSrmind 		print_indent(ctx, 0);
7514e592132Srmind 	}
7524e592132Srmind 	npf_config_destroy(ncf);
7534e592132Srmind 	return 0;
7544e592132Srmind }
7554e592132Srmind 
7564e592132Srmind int
7574e592132Srmind npfctl_ruleset_show(int fd, const char *ruleset_name)
7584e592132Srmind {
759b899bfd9Srmind 	npf_conf_info_t *ctx = npfctl_show_init();
7604e592132Srmind 	nl_config_t *ncf;
7614e592132Srmind 	nl_rule_t *rl;
7623d9a792dSrmind 	unsigned level;
763dadc88e3Srmind 	nl_iter_t i;
7644e592132Srmind 	int error;
7654e592132Srmind 
7664e592132Srmind 	ncf = npf_config_create();
7671e7342c1Srmind 	ctx->conf = ncf;
7681e7342c1Srmind 
7694e592132Srmind 	if ((error = _npf_ruleset_list(fd, ruleset_name, ncf)) != 0) {
7704e592132Srmind 		return error;
7714e592132Srmind 	}
772dadc88e3Srmind 	i = NPF_ITER_BEGIN;
773dadc88e3Srmind 	while ((rl = npf_rule_iterate(ncf, &i, &level)) != NULL) {
774b899bfd9Srmind 		npfctl_print_rule(ctx, rl, 0);
7754e592132Srmind 	}
7764e592132Srmind 	npf_config_destroy(ncf);
7774e592132Srmind 	return error;
7784e592132Srmind }
779