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__
55 static unsigned char *
SHA1(const unsigned char * d,size_t l,unsigned char * md)56 SHA1(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 
67 static void
npfctl_generate_key(nl_rule_t * rl,void * key)68 npfctl_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 
82 int
npfctl_nat_ruleset_p(const char * name,bool * natset)83 npfctl_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 
90 static nl_rule_t *
npfctl_parse_rule(int argc,char ** argv,parse_entry_t entry)91 npfctl_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 
107 void
npfctl_rule(int fd,int argc,char ** argv)108 npfctl_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 
200 static int
npfctl_table_type(const char * typename)201 npfctl_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 
221 void
npfctl_table_replace(int fd,int argc,char ** argv)222 npfctl_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 
290 void
npfctl_table(int fd,int argc,char ** argv)291 npfctl_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 
339 again:
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 
405 typedef 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 
417 static int
npfctl_conn_print(unsigned alen,const npf_addr_t * a,const in_port_t * p,const char * ifname,void * arg)418 npfctl_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 
469 static void
npf_conn_list_v(int fd,unsigned alen,npf_conn_filter_t * f)470 npf_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 
480 int
npfctl_conn_list(int fd,int argc,char ** argv)481 npfctl_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