1/*-
2 * Copyright (c) 2009-2020 The NetBSD Foundation, Inc.
3 * All rights reserved.
4 *
5 * This material is based upon work partially supported by The
6 * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__RCSID("$NetBSD: npf_cmd.c,v 1.1 2020/05/30 14:16:56 rmind Exp $");
32
33#include <stdio.h>
34#include <string.h>
35#include <stdlib.h>
36#include <unistd.h>
37#include <errno.h>
38#include <err.h>
39
40#ifdef __NetBSD__
41#include <sha1.h>
42#define SHA_DIGEST_LENGTH SHA1_DIGEST_LENGTH
43#else
44#include <openssl/sha.h>
45#endif
46
47#include "npfctl.h"
48
49////////////////////////////////////////////////////////////////////////////
50//
51// NPFCTL RULE COMMANDS
52//
53
54#ifdef __NetBSD__
55static unsigned char *
56SHA1(const unsigned char *d, size_t l, unsigned char *md)
57{
58	SHA1_CTX c;
59
60	SHA1Init(&c);
61	SHA1Update(&c, d, l);
62	SHA1Final(md, &c);
63	return md;
64}
65#endif
66
67static void
68npfctl_generate_key(nl_rule_t *rl, void *key)
69{
70	void *meta;
71	size_t len;
72
73	if ((meta = npf_rule_export(rl, &len)) == NULL) {
74		errx(EXIT_FAILURE, "error generating rule key");
75	}
76	__CTASSERT(NPF_RULE_MAXKEYLEN >= SHA_DIGEST_LENGTH);
77	memset(key, 0, NPF_RULE_MAXKEYLEN);
78	SHA1(meta, len, key);
79	free(meta);
80}
81
82int
83npfctl_nat_ruleset_p(const char *name, bool *natset)
84{
85	const size_t preflen = sizeof(NPF_RULESET_MAP_PREF) - 1;
86	*natset = strncmp(name, NPF_RULESET_MAP_PREF, preflen) == 0;
87	return (*natset && strlen(name) <= preflen) ? -1 : 0;
88}
89
90static nl_rule_t *
91npfctl_parse_rule(int argc, char **argv, parse_entry_t entry)
92{
93	char rule_string[1024];
94	nl_rule_t *rl;
95
96	/* Get the rule string and parse it. */
97	if (!join(rule_string, sizeof(rule_string), argc, argv, " ")) {
98		errx(EXIT_FAILURE, "command too long");
99	}
100	npfctl_parse_string(rule_string, entry);
101	if ((rl = npfctl_rule_ref()) == NULL) {
102		errx(EXIT_FAILURE, "could not parse the rule");
103	}
104	return rl;
105}
106
107void
108npfctl_rule(int fd, int argc, char **argv)
109{
110	static const struct ruleops_s {
111		const char *	cmd;
112		int		action;
113		bool		extra_arg;
114	} ruleops[] = {
115		{ "add",	NPF_CMD_RULE_ADD,	true	},
116		{ "rem",	NPF_CMD_RULE_REMKEY,	true	},
117		{ "del",	NPF_CMD_RULE_REMKEY,	true	},
118		{ "rem-id",	NPF_CMD_RULE_REMOVE,	true	},
119		{ "list",	NPF_CMD_RULE_LIST,	false	},
120		{ "flush",	NPF_CMD_RULE_FLUSH,	false	},
121		{ NULL,		0,			0	}
122	};
123	uint8_t key[NPF_RULE_MAXKEYLEN];
124	const char *ruleset_name = argv[0];
125	const char *cmd = argv[1];
126	int error, action = 0;
127	bool extra_arg, natset;
128	parse_entry_t entry;
129	uint64_t rule_id;
130	nl_rule_t *rl;
131
132	for (unsigned n = 0; ruleops[n].cmd != NULL; n++) {
133		if (strcmp(cmd, ruleops[n].cmd) == 0) {
134			action = ruleops[n].action;
135			extra_arg = ruleops[n].extra_arg;
136			break;
137		}
138	}
139	argc -= 2;
140	argv += 2;
141
142	if (!action || (extra_arg && argc == 0)) {
143		usage();
144	}
145
146	if (npfctl_nat_ruleset_p(ruleset_name, &natset) != 0) {
147		errx(EXIT_FAILURE,
148		    "invalid NAT ruleset name (note: the name must be "
149		    "prefixed with `" NPF_RULESET_MAP_PREF "`)");
150	}
151	entry = natset ? NPFCTL_PARSE_MAP : NPFCTL_PARSE_RULE;
152
153	switch (action) {
154	case NPF_CMD_RULE_ADD:
155		rl = npfctl_parse_rule(argc, argv, entry);
156		npfctl_generate_key(rl, key);
157		npf_rule_setkey(rl, key, sizeof(key));
158		error = npf_ruleset_add(fd, ruleset_name, rl, &rule_id);
159		break;
160	case NPF_CMD_RULE_REMKEY:
161		rl = npfctl_parse_rule(argc, argv, entry);
162		npfctl_generate_key(rl, key);
163		error = npf_ruleset_remkey(fd, ruleset_name, key, sizeof(key));
164		break;
165	case NPF_CMD_RULE_REMOVE:
166		rule_id = strtoull(argv[0], NULL, 16);
167		error = npf_ruleset_remove(fd, ruleset_name, rule_id);
168		break;
169	case NPF_CMD_RULE_LIST:
170		error = npfctl_ruleset_show(fd, ruleset_name);
171		break;
172	case NPF_CMD_RULE_FLUSH:
173		error = npf_ruleset_flush(fd, ruleset_name);
174		break;
175	default:
176		abort();
177	}
178
179	switch (error) {
180	case 0:
181		/* Success. */
182		break;
183	case ESRCH:
184		errx(EXIT_FAILURE, "ruleset \"%s\" not found", ruleset_name);
185	case ENOENT:
186		errx(EXIT_FAILURE, "rule was not found");
187	default:
188		errx(EXIT_FAILURE, "rule operation: %s", strerror(error));
189	}
190	if (action == NPF_CMD_RULE_ADD) {
191		printf("OK %" PRIx64 "\n", rule_id);
192	}
193}
194
195////////////////////////////////////////////////////////////////////////////
196//
197// NPFCTL TABLE COMMANDS
198//
199
200static int
201npfctl_table_type(const char *typename)
202{
203	static const struct tbltype_s {
204		const char *	name;
205		unsigned	type;
206	} tbltypes[] = {
207		{ "ipset",	NPF_TABLE_IPSET	},
208		{ "lpm",	NPF_TABLE_LPM	},
209		{ "const",	NPF_TABLE_CONST	},
210		{ NULL,		0		}
211	};
212
213	for (unsigned i = 0; tbltypes[i].name != NULL; i++) {
214		if (strcmp(typename, tbltypes[i].name) == 0) {
215			return tbltypes[i].type;
216		}
217	}
218	return 0;
219}
220
221void
222npfctl_table_replace(int fd, int argc, char **argv)
223{
224	const char *name, *newname, *path, *typename = NULL;
225	nl_config_t *ncf;
226	nl_table_t *t;
227	unsigned type = 0;
228	int c, tid = -1;
229	FILE *fp;
230
231	name = newname = argv[0];
232	optind = 2;
233	while ((c = getopt(argc, argv, "n:t:")) != -1) {
234		switch (c) {
235		case 't':
236			typename = optarg;
237			break;
238		case 'n':
239			newname = optarg;
240			break;
241		default:
242			errx(EXIT_FAILURE,
243			    "Usage: %s table \"table-name\" replace "
244			    "[-n \"name\"] [-t <type>] <table-file>\n",
245			    getprogname());
246		}
247	}
248	argc -= optind;
249	argv += optind;
250
251	if (typename && (type = npfctl_table_type(typename)) == 0) {
252		errx(EXIT_FAILURE, "unsupported table type '%s'", typename);
253	}
254
255	if (argc != 1) {
256		usage();
257	}
258
259	path = argv[0];
260	if (strcmp(path, "-") == 0) {
261		path = "stdin";
262		fp = stdin;
263	} else if ((fp = fopen(path, "r")) == NULL) {
264		err(EXIT_FAILURE, "open '%s'", path);
265	}
266
267	/* Get existing config to lookup ID of existing table */
268	if ((ncf = npf_config_retrieve(fd)) == NULL) {
269		err(EXIT_FAILURE, "npf_config_retrieve()");
270	}
271	if ((t = npfctl_table_getbyname(ncf, name)) == NULL) {
272		errx(EXIT_FAILURE,
273		    "table '%s' not found in the active configuration", name);
274	}
275	tid = npf_table_getid(t);
276	if (!type) {
277		type = npf_table_gettype(t);
278	}
279	npf_config_destroy(ncf);
280
281	if ((t = npfctl_load_table(newname, tid, type, path, fp)) == NULL) {
282		err(EXIT_FAILURE, "table load failed");
283	}
284
285	if (npf_table_replace(fd, t, NULL)) {
286		err(EXIT_FAILURE, "npf_table_replace(<%s>)", name);
287	}
288}
289
290void
291npfctl_table(int fd, int argc, char **argv)
292{
293	static const struct tblops_s {
294		const char *	cmd;
295		int		action;
296	} tblops[] = {
297		{ "add",	NPF_CMD_TABLE_ADD		},
298		{ "rem",	NPF_CMD_TABLE_REMOVE		},
299		{ "del",	NPF_CMD_TABLE_REMOVE		},
300		{ "test",	NPF_CMD_TABLE_LOOKUP		},
301		{ "list",	NPF_CMD_TABLE_LIST		},
302		{ "flush",	NPF_CMD_TABLE_FLUSH		},
303		{ NULL,		0				}
304	};
305	npf_ioctl_table_t nct;
306	fam_addr_mask_t fam;
307	size_t buflen = 512;
308	char *cmd, *arg;
309	int n, alen;
310
311	/* Default action is list. */
312	memset(&nct, 0, sizeof(npf_ioctl_table_t));
313	nct.nct_name = argv[0];
314	cmd = argv[1];
315
316	for (n = 0; tblops[n].cmd != NULL; n++) {
317		if (strcmp(cmd, tblops[n].cmd) != 0) {
318			continue;
319		}
320		nct.nct_cmd = tblops[n].action;
321		break;
322	}
323	if (tblops[n].cmd == NULL) {
324		errx(EXIT_FAILURE, "invalid command '%s'", cmd);
325	}
326
327	switch (nct.nct_cmd) {
328	case NPF_CMD_TABLE_LIST:
329	case NPF_CMD_TABLE_FLUSH:
330		arg = NULL;
331		break;
332	default:
333		if (argc < 3) {
334			usage();
335		}
336		arg = argv[2];
337	}
338
339again:
340	switch (nct.nct_cmd) {
341	case NPF_CMD_TABLE_LIST:
342		nct.nct_data.buf.buf = ecalloc(1, buflen);
343		nct.nct_data.buf.len = buflen;
344		break;
345	case NPF_CMD_TABLE_FLUSH:
346		break;
347	default:
348		if (!npfctl_parse_cidr(arg, &fam, &alen)) {
349			errx(EXIT_FAILURE, "invalid CIDR '%s'", arg);
350		}
351		nct.nct_data.ent.alen = alen;
352		memcpy(&nct.nct_data.ent.addr, &fam.fam_addr, alen);
353		nct.nct_data.ent.mask = fam.fam_mask;
354	}
355
356	if (ioctl(fd, IOC_NPF_TABLE, &nct) != -1) {
357		errno = 0;
358	}
359	switch (errno) {
360	case 0:
361		break;
362	case EEXIST:
363		errx(EXIT_FAILURE, "entry already exists or is conflicting");
364	case ENOENT:
365		errx(EXIT_FAILURE, "not found");
366	case EINVAL:
367		errx(EXIT_FAILURE, "invalid address, mask or table ID");
368	case ENOMEM:
369		if (nct.nct_cmd == NPF_CMD_TABLE_LIST) {
370			/* XXX */
371			free(nct.nct_data.buf.buf);
372			buflen <<= 1;
373			goto again;
374		}
375		/* FALLTHROUGH */
376	default:
377		err(EXIT_FAILURE, "ioctl(IOC_NPF_TABLE)");
378	}
379
380	if (nct.nct_cmd == NPF_CMD_TABLE_LIST) {
381		npf_ioctl_ent_t *ent = nct.nct_data.buf.buf;
382		char *buf;
383
384		while (nct.nct_data.buf.len--) {
385			if (!ent->alen)
386				break;
387			buf = npfctl_print_addrmask(ent->alen, "%a",
388			    &ent->addr, ent->mask);
389			puts(buf);
390			ent++;
391		}
392		free(nct.nct_data.buf.buf);
393	} else {
394		printf("%s: %s\n", getprogname(),
395		    nct.nct_cmd == NPF_CMD_TABLE_LOOKUP ?
396		    "match" : "success");
397	}
398}
399
400////////////////////////////////////////////////////////////////////////////
401//
402// NPFCTL CONNECTION COMMANDS
403//
404
405typedef struct {
406	FILE *		fp;
407	unsigned	alen;
408	const char *	ifname;
409	bool		nat;
410	bool		nowide;
411	bool		name;
412
413	bool		v4;
414	unsigned	pwidth;
415} npf_conn_filter_t;
416
417static int
418npfctl_conn_print(unsigned alen, const npf_addr_t *a, const in_port_t *p,
419    const char *ifname, void *arg)
420{
421	const npf_conn_filter_t *fil = arg;
422	char *addrstr, *src, *dst;
423	const char *fmt;
424	FILE *fp = fil->fp;
425	bool nat_conn;
426
427	/*
428	 * Filter connection entries by IP version, interface and/or
429	 * applicability of NAT.
430	 */
431	if (alen != fil->alen) {
432		return 0;
433	}
434	if (fil->ifname && (!ifname || strcmp(ifname, fil->ifname) != 0)) {
435		return 0;
436	}
437	nat_conn = !npfctl_addr_iszero(&a[2]) || p[2] != 0;
438	if (fil->nat && !nat_conn) {
439		return 0;
440	}
441
442	fmt = fil->name ? "%A" : (fil->v4 ? "%a" : "[%a]");
443
444	addrstr = npfctl_print_addrmask(alen, fmt, &a[0], NPF_NO_NETMASK);
445	easprintf(&src, "%s:%d", addrstr, p[0]);
446	free(addrstr);
447
448	addrstr = npfctl_print_addrmask(alen, fmt, &a[1], NPF_NO_NETMASK);
449	easprintf(&dst, "%s:%d", addrstr, p[1]);
450	free(addrstr);
451
452	fprintf(fp, "%-*s %-*s ", fil->pwidth, src, fil->pwidth, dst);
453	free(src);
454	free(dst);
455
456	fprintf(fp, "%-10s ", ifname ? ifname : "-");
457	if (nat_conn) {
458		addrstr = npfctl_print_addrmask(alen, fmt, &a[2], NPF_NO_NETMASK);
459		fprintf(fp, "%s", addrstr);
460		free(addrstr);
461		if (p[2]) {
462			fprintf(fp, ":%d", p[2]);
463		}
464	}
465	fputc('\n', fp);
466	return 1;
467}
468
469static void
470npf_conn_list_v(int fd, unsigned alen, npf_conn_filter_t *f)
471{
472	f->alen = alen;
473	f->v4 = alen == sizeof(struct in_addr);
474	f->pwidth = f->nowide ? 0 : ((f->v4 ? 15 : 40) + 1 + 5);
475	if (npf_conn_list(fd, npfctl_conn_print, f) != 0) {
476		err(EXIT_FAILURE, "npf_conn_list");
477	}
478}
479
480int
481npfctl_conn_list(int fd, int argc, char **argv)
482{
483	npf_conn_filter_t f;
484	bool header = true;
485	unsigned alen = 0;
486	int c;
487
488	argc--;
489	argv++;
490
491	memset(&f, 0, sizeof(f));
492	f.fp = stdout;
493
494	while ((c = getopt(argc, argv, "46hi:nNW")) != -1) {
495		switch (c) {
496		case '4':
497			alen = sizeof(struct in_addr);
498			break;
499		case '6':
500			alen = sizeof(struct in6_addr);
501			break;
502		case 'h':
503			header = false;
504			break;
505		case 'i':
506			f.ifname = optarg;
507			break;
508		case 'n':
509			f.nat = true;
510			break;
511		case 'N':
512			f.name = true;
513			break;
514		case 'W':
515			f.nowide = true;
516			break;
517		default:
518			errx(EXIT_FAILURE,
519			    "Usage: %s list [-46hnNW] [-i <ifname>]\n",
520			    getprogname());
521		}
522	}
523
524	if (header) {
525		fprintf(f.fp, "# %-*s %-*s %-*s %s\n",
526		    21 - 2, "src-addr:port",
527		    21, "dst-addr:port",
528		    10, "interface",
529		    "nat-addr:port");
530	}
531
532	if (!alen || alen == sizeof(struct in_addr)) {
533		npf_conn_list_v(fd, sizeof(struct in_addr), &f);
534	}
535	if (!alen || alen == sizeof(struct in6_addr)) {
536		npf_conn_list_v(fd, sizeof(struct in6_addr), &f);
537	}
538
539	return 0;
540}
541