Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sbin/ipfw/main.c
39475 views
1
/*-
2
* Copyright (c) 2002-2003,2010 Luigi Rizzo
3
* Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp
4
* Copyright (c) 1994 Ugen J.S.Antsilevich
5
*
6
* Idea and grammar partially left from:
7
* Copyright (c) 1993 Daniel Boulet
8
*
9
* Redistribution and use in source forms, with and without modification,
10
* are permitted provided that this entire comment appears intact.
11
*
12
* Redistribution in binary form may occur without any restrictions.
13
* Obviously, it would be nice if you gave credit where credit is due
14
* but requiring it would be too onerous.
15
*
16
* This software is provided ``AS IS'' without any warranties of any kind.
17
*
18
* Command line interface for IP firewall facility
19
*/
20
21
#include <sys/wait.h>
22
#include <ctype.h>
23
#include <err.h>
24
#include <errno.h>
25
#include <signal.h>
26
#include <stdio.h>
27
#include <stdlib.h>
28
#include <string.h>
29
#include <sysexits.h>
30
#include <unistd.h>
31
#include <libgen.h>
32
33
#include "ipfw2.h"
34
35
static void
36
help(void)
37
{
38
if (is_ipfw()) {
39
fprintf(stderr,
40
"ipfw syntax summary (but please do read the ipfw(8) manpage):\n\n"
41
"\tipfw [-abcdefhnNqStTv] <command>\n\n"
42
"where <command> is one of the following:\n\n"
43
"add [num] [set N] [prob x] RULE-BODY\n"
44
"{pipe|queue} N config PIPE-BODY\n"
45
"[pipe|queue] {zero|delete|show} [N{,N}]\n"
46
"nat N config {ip IPADDR|if IFNAME|log|deny_in|same_ports|unreg_only|unreg_cgn|udp_eim|\n"
47
" reset|reverse|proxy_only|redirect_addr linkspec|\n"
48
" redirect_port linkspec|redirect_proto linkspec|\n"
49
" port_range lower-upper}\n"
50
"set [disable N... enable N...] | move [rule] X to Y | swap X Y | show\n"
51
"set N {show|list|zero|resetlog|delete} [N{,N}] | flush\n"
52
"table N {add ip[/bits] [value] | delete ip[/bits] | flush | list}\n"
53
"table all {flush | list}\n"
54
"\n"
55
"RULE-BODY: check-state [PARAMS] | ACTION [PARAMS] ADDR [OPTION_LIST]\n"
56
"ACTION: check-state | allow | count | deny | unreach{,6} CODE |\n"
57
" skipto N | {divert|tee} PORT | forward ADDR |\n"
58
" pipe N | queue N | nat N | setfib FIB | reass\n"
59
"PARAMS: [log [logamount LOGLIMIT]] [altq QUEUE_NAME]\n"
60
"ADDR: [ MAC dst src ether_type ] \n"
61
" [ ip from IPADDR [ PORT ] to IPADDR [ PORTLIST ] ]\n"
62
" [ ipv6|ip6 from IP6ADDR [ PORT ] to IP6ADDR [ PORTLIST ] ]\n"
63
"IPADDR: [not] { any | me | ip/bits{x,y,z} | table(t[,v]) | IPLIST }\n"
64
"IP6ADDR: [not] { any | me | me6 | ip6/bits | IP6LIST }\n"
65
"IP6LIST: { ip6 | ip6/bits }[,IP6LIST]\n"
66
"IPLIST: { ip | ip/bits | ip:mask }[,IPLIST]\n"
67
"OPTION_LIST: OPTION [OPTION_LIST]\n"
68
"OPTION: bridged | diverted | diverted-loopback | diverted-output |\n"
69
" {dst-ip|src-ip} IPADDR | {dst-ip6|src-ip6|dst-ipv6|src-ipv6} IP6ADDR |\n"
70
" {dst-port|src-port} LIST |\n"
71
" estab | frag | {gid|uid} N | icmptypes LIST | in | out | ipid LIST |\n"
72
" iplen LIST | ipoptions SPEC | ipprecedence | ipsec | iptos SPEC |\n"
73
" ipttl LIST | ipversion VER | keep-state | layer2 | limit ... |\n"
74
" icmp6types LIST | ext6hdr LIST | flow-id N[,N] | fib FIB |\n"
75
" mac ... | mac-type LIST | proto LIST | {recv|xmit|via} {IF|IPADDR} |\n"
76
" setup | {tcpack|tcpseq|tcpwin} NN | tcpflags SPEC | tcpoptions SPEC |\n"
77
" tcpdatalen LIST | verrevpath | versrcreach | antispoof\n"
78
);
79
} else {
80
fprintf(stderr,
81
"dnctl syntax summary (but please do read the dnctl(8) manpage):\n\n"
82
"\tdnctl [-hnsv] <command>\n\n"
83
"where <command> is one of the following:\n\n"
84
"[pipe|queue|sched] N config PIPE-BODY\n"
85
"[pipe|queue|sched] {delete|list|show} [N{,N}]\n"
86
"\n"
87
);
88
}
89
90
exit(0);
91
}
92
93
/*
94
* Called with the arguments, including program name because getopt
95
* wants it to be present.
96
* Returns 0 if successful, 1 if empty command, errx() in case of errors.
97
* First thing we do is process parameters creating an argv[] array
98
* which includes the program name and a NULL entry at the end.
99
* If we are called with a single string, we split it on whitespace.
100
* Also, arguments with a trailing ',' are joined to the next one.
101
* The pointers (av[]) and data are in a single chunk of memory.
102
* av[0] points to the original program name, all other entries
103
* point into the allocated chunk.
104
*/
105
static int
106
ipfw_main(int oldac, char **oldav)
107
{
108
int ch, ac;
109
const char *errstr;
110
char **av, **save_av;
111
int do_acct = 0; /* Show packet/byte count */
112
int try_next = 0; /* set if pipe cmd not found */
113
int av_size; /* compute the av size */
114
char *av_p; /* used to build the av list */
115
116
#define WHITESP " \t\f\v\n\r"
117
if (oldac < 2)
118
return 1; /* need at least one argument */
119
120
if (oldac == 2) {
121
/*
122
* If we are called with one argument, try to split it into
123
* words for subsequent parsing. Spaces after a ',' are
124
* removed by copying the string in-place.
125
*/
126
char *arg = oldav[1]; /* The string is the first arg. */
127
int l = strlen(arg);
128
int copy = 0; /* 1 if we need to copy, 0 otherwise */
129
int i, j;
130
131
for (i = j = 0; i < l; i++) {
132
if (arg[i] == '#') /* comment marker */
133
break;
134
if (copy) {
135
arg[j++] = arg[i];
136
copy = !strchr("," WHITESP, arg[i]);
137
} else {
138
copy = !strchr(WHITESP, arg[i]);
139
if (copy)
140
arg[j++] = arg[i];
141
}
142
}
143
if (!copy && j > 0) /* last char was a 'blank', remove it */
144
j--;
145
l = j; /* the new argument length */
146
arg[j++] = '\0';
147
if (l == 0) /* empty string! */
148
return 1;
149
150
/*
151
* First, count number of arguments. Because of the previous
152
* processing, this is just the number of blanks plus 1.
153
*/
154
for (i = 0, ac = 1; i < l; i++)
155
if (strchr(WHITESP, arg[i]) != NULL)
156
ac++;
157
158
/*
159
* Allocate the argument list structure as a single block
160
* of memory, containing pointers and the argument
161
* strings. We include one entry for the program name
162
* because getopt expects it, and a NULL at the end
163
* to simplify further parsing.
164
*/
165
ac++; /* add 1 for the program name */
166
av_size = (ac+1) * sizeof(char *) + l + 1;
167
av = safe_calloc(av_size, 1);
168
169
/*
170
* Init the argument pointer to the end of the array
171
* and copy arguments from arg[] to av[]. For each one,
172
* j is the initial character, i is the one past the end.
173
*/
174
av_p = (char *)&av[ac+1];
175
for (ac = 1, i = j = 0; i < l; i++) {
176
if (strchr(WHITESP, arg[i]) != NULL || i == l-1) {
177
if (i == l-1)
178
i++;
179
bcopy(arg+j, av_p, i-j);
180
av[ac] = av_p;
181
av_p += i-j; /* the length of the string */
182
*av_p++ = '\0';
183
ac++;
184
j = i + 1;
185
}
186
}
187
} else {
188
/*
189
* If an argument ends with ',' join with the next one.
190
*/
191
int first, i, l=0;
192
193
/*
194
* Allocate the argument list structure as a single block
195
* of memory, containing both pointers and the argument
196
* strings. We include some space for the program name
197
* because getopt expects it.
198
* We add an extra pointer to the end of the array,
199
* to make simpler further parsing.
200
*/
201
for (i=0; i<oldac; i++)
202
l += strlen(oldav[i]);
203
204
av_size = (oldac+1) * sizeof(char *) + l + oldac;
205
av = safe_calloc(av_size, 1);
206
207
/*
208
* Init the argument pointer to the end of the array
209
* and copy arguments from arg[] to av[]
210
*/
211
av_p = (char *)&av[oldac+1];
212
for (first = i = ac = 1, l = 0; i < oldac; i++) {
213
char *arg = oldav[i];
214
int k = strlen(arg);
215
216
l += k;
217
if (arg[k-1] != ',' || i == oldac-1) {
218
/* Time to copy. */
219
av[ac] = av_p;
220
for (l=0; first <= i; first++) {
221
strcat(av_p, oldav[first]);
222
av_p += strlen(oldav[first]);
223
}
224
*av_p++ = '\0';
225
ac++;
226
l = 0;
227
first = i+1;
228
}
229
}
230
}
231
232
/*
233
* set the progname pointer to the original string
234
* and terminate the array with null
235
*/
236
av[0] = oldav[0];
237
av[ac] = NULL;
238
239
/* Set the force flag for non-interactive processes */
240
if (!g_co.do_force)
241
g_co.do_force = !isatty(STDIN_FILENO);
242
243
#ifdef EMULATE_SYSCTL /* sysctl emulation */
244
if (is_ipfw() && ac >= 2 &&
245
!strcmp(av[1], "sysctl")) {
246
char *s;
247
int i;
248
249
if (ac != 3) {
250
printf( "sysctl emulation usage:\n"
251
" ipfw sysctl name[=value]\n"
252
" ipfw sysctl -a\n");
253
return 0;
254
}
255
s = strchr(av[2], '=');
256
if (s == NULL) {
257
s = !strcmp(av[2], "-a") ? NULL : av[2];
258
sysctlbyname(s, NULL, NULL, NULL, 0);
259
} else { /* ipfw sysctl x.y.z=value */
260
/* assume an INT value, will extend later */
261
if (s[1] == '\0') {
262
printf("ipfw sysctl: missing value\n\n");
263
return 0;
264
}
265
*s = '\0';
266
i = strtol(s+1, NULL, 0);
267
sysctlbyname(av[2], NULL, NULL, &i, sizeof(int));
268
}
269
return 0;
270
}
271
#endif
272
273
/* Save arguments for final freeing of memory. */
274
save_av = av;
275
276
optind = optreset = 1; /* restart getopt() */
277
if (is_ipfw()) {
278
while ((ch = getopt(ac, av, "abcdDefhinNp:qs:STtvx")) != -1)
279
switch (ch) {
280
case 'a':
281
do_acct = 1;
282
break;
283
284
case 'b':
285
g_co.comment_only = 1;
286
g_co.do_compact = 1;
287
break;
288
289
case 'c':
290
g_co.do_compact = 1;
291
break;
292
293
case 'd':
294
g_co.do_dynamic = 1;
295
break;
296
297
case 'D':
298
g_co.do_dynamic = 2;
299
break;
300
301
case 'e':
302
/* nop for compatibility */
303
break;
304
305
case 'f':
306
g_co.do_force = 1;
307
break;
308
309
case 'h': /* help */
310
free(save_av);
311
help();
312
break; /* NOTREACHED */
313
314
case 'i':
315
g_co.do_value_as_ip = 1;
316
break;
317
318
case 'n':
319
g_co.test_only = 1;
320
break;
321
322
case 'N':
323
g_co.do_resolv = 1;
324
break;
325
326
case 'p':
327
errx(EX_USAGE, "An absolute pathname must be used "
328
"with -p option.");
329
/* NOTREACHED */
330
331
case 'q':
332
g_co.do_quiet = 1;
333
break;
334
335
case 's': /* sort */
336
g_co.do_sort = atoi(optarg);
337
break;
338
339
case 'S':
340
g_co.show_sets = 1;
341
break;
342
343
case 't':
344
g_co.do_time = TIMESTAMP_STRING;
345
break;
346
347
case 'T':
348
g_co.do_time = TIMESTAMP_NUMERIC;
349
break;
350
351
case 'v': /* verbose */
352
g_co.verbose = 1;
353
break;
354
355
case 'x': /* debug output */
356
g_co.debug_only = 1;
357
break;
358
359
default:
360
free(save_av);
361
return 1;
362
}
363
} else {
364
while ((ch = getopt(ac, av, "hns:v")) != -1)
365
switch (ch) {
366
367
case 'h': /* help */
368
free(save_av);
369
help();
370
break; /* NOTREACHED */
371
372
case 'n':
373
g_co.test_only = 1;
374
break;
375
376
case 's': /* sort */
377
g_co.do_sort = atoi(optarg);
378
break;
379
380
case 'v': /* verbose */
381
g_co.verbose = 1;
382
break;
383
384
default:
385
free(save_av);
386
return 1;
387
}
388
389
}
390
391
ac -= optind;
392
av += optind;
393
NEED1("bad arguments, for usage summary ``ipfw''");
394
395
/*
396
* An undocumented behaviour of ipfw1 was to allow rule numbers first,
397
* e.g. "100 add allow ..." instead of "add 100 allow ...".
398
* In case, swap first and second argument to get the normal form.
399
*/
400
if (ac > 1 && isdigit(*av[0])) {
401
char *p = av[0];
402
403
av[0] = av[1];
404
av[1] = p;
405
}
406
407
/*
408
* Optional: pipe, queue or nat.
409
*/
410
g_co.do_nat = 0;
411
g_co.do_pipe = 0;
412
g_co.use_set = 0;
413
if (is_ipfw() && !strncmp(*av, "nat", strlen(*av)))
414
g_co.do_nat = 1;
415
else if (!strncmp(*av, "pipe", strlen(*av)))
416
g_co.do_pipe = 1;
417
else if (_substrcmp(*av, "queue") == 0)
418
g_co.do_pipe = 2;
419
else if (_substrcmp(*av, "flowset") == 0)
420
g_co.do_pipe = 2;
421
else if (_substrcmp(*av, "sched") == 0)
422
g_co.do_pipe = 3;
423
else if (is_ipfw() && !strncmp(*av, "set", strlen(*av))) {
424
if (ac > 1 && isdigit(av[1][0])) {
425
g_co.use_set = strtonum(av[1], 0, resvd_set_number,
426
&errstr);
427
if (errstr)
428
errx(EX_DATAERR,
429
"invalid set number %s\n", av[1]);
430
ac -= 2; av += 2; g_co.use_set++;
431
}
432
}
433
434
if (g_co.do_pipe || g_co.do_nat) {
435
ac--;
436
av++;
437
}
438
NEED1("missing command");
439
440
/*
441
* For pipes, queues and nats we normally say 'nat|pipe NN config'
442
* but the code is easier to parse as 'nat|pipe config NN'
443
* so we swap the two arguments.
444
*/
445
if ((g_co.do_pipe || g_co.do_nat) && ac > 1 && isdigit(*av[0])) {
446
char *p = av[0];
447
448
av[0] = av[1];
449
av[1] = p;
450
}
451
452
if (! is_ipfw() && g_co.do_pipe == 0) {
453
help();
454
}
455
456
if (g_co.use_set == 0) {
457
if (is_ipfw() && _substrcmp(*av, "add") == 0)
458
ipfw_add(av);
459
else if (g_co.do_nat && _substrcmp(*av, "show") == 0)
460
ipfw_show_nat(ac, av);
461
else if (g_co.do_pipe && _substrcmp(*av, "config") == 0)
462
ipfw_config_pipe(ac, av);
463
else if (g_co.do_nat && _substrcmp(*av, "config") == 0)
464
ipfw_config_nat(ac, av);
465
else if (is_ipfw() && _substrcmp(*av, "set") == 0)
466
ipfw_sets_handler(av);
467
else if (is_ipfw() && _substrcmp(*av, "table") == 0)
468
ipfw_table_handler(ac, av);
469
else if (is_ipfw() && _substrcmp(*av, "enable") == 0)
470
ipfw_sysctl_handler(av, 1);
471
else if (is_ipfw() && _substrcmp(*av, "disable") == 0)
472
ipfw_sysctl_handler(av, 0);
473
else
474
try_next = 1;
475
}
476
477
if (g_co.use_set || try_next) {
478
if (_substrcmp(*av, "delete") == 0)
479
ipfw_delete(av);
480
else if (is_ipfw() && !strncmp(*av, "nat64clat", strlen(*av)))
481
ipfw_nat64clat_handler(ac, av);
482
else if (is_ipfw() && !strncmp(*av, "nat64stl", strlen(*av)))
483
ipfw_nat64stl_handler(ac, av);
484
else if (is_ipfw() && !strncmp(*av, "nat64lsn", strlen(*av)))
485
ipfw_nat64lsn_handler(ac, av);
486
else if (is_ipfw() && !strncmp(*av, "nptv6", strlen(*av)))
487
ipfw_nptv6_handler(ac, av);
488
else if (_substrcmp(*av, "flush") == 0)
489
ipfw_flush(g_co.do_force);
490
else if (is_ipfw() && _substrcmp(*av, "zero") == 0)
491
ipfw_zero(ac, av, 0 /* IP_FW_ZERO */);
492
else if (is_ipfw() && _substrcmp(*av, "resetlog") == 0)
493
ipfw_zero(ac, av, 1 /* IP_FW_RESETLOG */);
494
else if (_substrcmp(*av, "print") == 0 ||
495
_substrcmp(*av, "list") == 0)
496
ipfw_list(ac, av, do_acct);
497
else if (_substrcmp(*av, "show") == 0)
498
ipfw_list(ac, av, 1 /* show counters */);
499
else if (is_ipfw() && _substrcmp(*av, "table") == 0)
500
ipfw_table_handler(ac, av);
501
else if (is_ipfw() && _substrcmp(*av, "internal") == 0)
502
ipfw_internal_handler(ac, av);
503
else
504
errx(EX_USAGE, "bad command `%s'", *av);
505
}
506
507
/* Free memory allocated in the argument parsing. */
508
free(save_av);
509
return 0;
510
}
511
512
513
static void
514
ipfw_readfile(int ac, char *av[])
515
{
516
#define MAX_ARGS 32
517
char buf[4096];
518
char *progname = av[0]; /* original program name */
519
const char *cmd = NULL; /* preprocessor name, if any */
520
const char *filename = av[ac-1]; /* file to read */
521
int c, lineno=0;
522
FILE *f = NULL;
523
pid_t preproc = 0;
524
525
if (is_ipfw()) {
526
while ((c = getopt(ac, av, "cfNnp:qS")) != -1) {
527
switch(c) {
528
case 'c':
529
g_co.do_compact = 1;
530
break;
531
532
case 'f':
533
g_co.do_force = 1;
534
break;
535
536
case 'N':
537
g_co.do_resolv = 1;
538
break;
539
540
case 'n':
541
g_co.test_only = 1;
542
break;
543
544
case 'p':
545
/*
546
* ipfw -p cmd [args] filename
547
*
548
* We are done with getopt(). All arguments
549
* except the filename go to the preprocessor,
550
* so we need to do the following:
551
* - check that a filename is actually present;
552
* - advance av by optind-1 to skip arguments
553
* already processed;
554
* - decrease ac by optind, to remove the args
555
* already processed and the final filename;
556
* - set the last entry in av[] to NULL so
557
* popen() can detect the end of the array;
558
* - set optind=ac to let getopt() terminate.
559
*/
560
if (optind == ac)
561
errx(EX_USAGE, "no filename argument");
562
cmd = optarg;
563
av[ac-1] = NULL;
564
av += optind - 1;
565
ac -= optind;
566
optind = ac;
567
break;
568
569
case 'q':
570
g_co.do_quiet = 1;
571
break;
572
573
case 'S':
574
g_co.show_sets = 1;
575
break;
576
577
default:
578
errx(EX_USAGE, "bad arguments, for usage"
579
" summary ``ipfw''");
580
}
581
}
582
} else {
583
while ((c = getopt(ac, av, "nq")) != -1) {
584
switch(c) {
585
case 'n':
586
g_co.test_only = 1;
587
break;
588
589
case 'q':
590
g_co.do_quiet = 1;
591
break;
592
593
default:
594
errx(EX_USAGE, "bad arguments, for usage"
595
" summary ``dnctl''");
596
}
597
}
598
}
599
600
if (cmd == NULL && ac != optind + 1)
601
errx(EX_USAGE, "extraneous filename arguments %s", av[ac-1]);
602
603
if ((f = fopen(filename, "r")) == NULL)
604
err(EX_UNAVAILABLE, "fopen: %s", filename);
605
606
if (cmd != NULL) { /* pipe through preprocessor */
607
int pipedes[2];
608
609
if (pipe(pipedes) == -1)
610
err(EX_OSERR, "cannot create pipe");
611
612
preproc = fork();
613
if (preproc == -1)
614
err(EX_OSERR, "cannot fork");
615
616
if (preproc == 0) {
617
/*
618
* Child, will run the preprocessor with the
619
* file on stdin and the pipe on stdout.
620
*/
621
if (dup2(fileno(f), 0) == -1
622
|| dup2(pipedes[1], 1) == -1)
623
err(EX_OSERR, "dup2()");
624
fclose(f);
625
close(pipedes[1]);
626
close(pipedes[0]);
627
execvp(cmd, av);
628
err(EX_OSERR, "execvp(%s) failed", cmd);
629
} else { /* parent, will reopen f as the pipe */
630
fclose(f);
631
close(pipedes[1]);
632
if ((f = fdopen(pipedes[0], "r")) == NULL) {
633
int savederrno = errno;
634
635
(void)kill(preproc, SIGTERM);
636
errno = savederrno;
637
err(EX_OSERR, "fdopen()");
638
}
639
}
640
}
641
642
while (fgets(buf, sizeof(buf), f)) { /* read commands */
643
char linename[20];
644
char *args[2];
645
646
lineno++;
647
snprintf(linename, sizeof(linename), "Line %d", lineno);
648
setprogname(linename); /* XXX */
649
args[0] = progname;
650
args[1] = buf;
651
ipfw_main(2, args);
652
}
653
fclose(f);
654
if (cmd != NULL) {
655
int status;
656
657
if (waitpid(preproc, &status, 0) == -1)
658
errx(EX_OSERR, "waitpid()");
659
if (WIFEXITED(status) && WEXITSTATUS(status) != EX_OK)
660
errx(EX_UNAVAILABLE,
661
"preprocessor exited with status %d",
662
WEXITSTATUS(status));
663
else if (WIFSIGNALED(status))
664
errx(EX_UNAVAILABLE,
665
"preprocessor exited with signal %d",
666
WTERMSIG(status));
667
}
668
}
669
670
int
671
main(int ac, char *av[])
672
{
673
#if defined(_WIN32) && defined(TCC)
674
{
675
WSADATA wsaData;
676
int ret=0;
677
unsigned short wVersionRequested = MAKEWORD(2, 2);
678
ret = WSAStartup(wVersionRequested, &wsaData);
679
if (ret != 0) {
680
/* Tell the user that we could not find a usable */
681
/* Winsock DLL. */
682
printf("WSAStartup failed with error: %d\n", ret);
683
return 1;
684
}
685
}
686
#endif
687
688
if (strcmp("dnctl", basename(av[0])) == 0)
689
g_co.prog = cmdline_prog_dnctl;
690
else
691
g_co.prog = cmdline_prog_ipfw;
692
693
/*
694
* If the last argument is an absolute pathname, interpret it
695
* as a file to be preprocessed.
696
*/
697
698
if (ac > 1 && av[ac - 1][0] == '/') {
699
if (access(av[ac - 1], R_OK) == 0)
700
ipfw_readfile(ac, av);
701
else
702
err(EX_USAGE, "pathname: %s", av[ac - 1]);
703
} else {
704
if (ipfw_main(ac, av)) {
705
errx(EX_USAGE,
706
"usage: %s [options]\n"
707
"do \"%s -h\" or \"man %s\" for details", av[0],
708
av[0], av[0]);
709
}
710
}
711
return EX_OK;
712
}
713
714