From olli@lurza.secnetix.de  Fri Oct 18 14:11:21 2002
Return-Path: <olli@lurza.secnetix.de>
Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125])
	by hub.freebsd.org (Postfix) with ESMTP id D429C37B401
	for <FreeBSD-gnats-submit@freebsd.org>; Fri, 18 Oct 2002 14:11:21 -0700 (PDT)
Received: from lurza.secnetix.de (lurza.secnetix.de [212.66.1.130])
	by mx1.FreeBSD.org (Postfix) with ESMTP id 9995443E3B
	for <FreeBSD-gnats-submit@freebsd.org>; Fri, 18 Oct 2002 14:11:20 -0700 (PDT)
	(envelope-from olli@lurza.secnetix.de)
Received: from lurza.secnetix.de (localhost [IPv6:::1])
	by lurza.secnetix.de (8.12.5/8.12.5) with ESMTP id g9ILBGmC049619
	for <FreeBSD-gnats-submit@freebsd.org>; Fri, 18 Oct 2002 23:11:18 +0200 (CEST)
	(envelope-from oliver.fromme@secnetix.de)
Received: (from olli@localhost)
	by lurza.secnetix.de (8.12.5/8.12.5/Submit) id g9ILBG0d049618;
	Fri, 18 Oct 2002 23:11:16 +0200 (CEST)
Message-Id: <200210182111.g9ILBG0d049618@lurza.secnetix.de>
Date: Fri, 18 Oct 2002 23:11:16 +0200 (CEST)
From: Oliver Fromme <olli@secnetix.de>
Reply-To: Oliver Fromme <olli@secnetix.de>
To: FreeBSD-gnats-submit@freebsd.org
Cc:
Subject: [PATCH] syntax-check option for ipfw2
X-Send-Pr-Version: 3.113
X-GNATS-Notify:

>Number:         44238
>Category:       bin
>Synopsis:       [PATCH] syntax-check option for ipfw2
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    luigi
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Fri Oct 18 14:20:01 PDT 2002
>Closed-Date:    Tue Jul 08 01:53:54 PDT 2003
>Last-Modified:  Tue Jul 08 01:53:54 PDT 2003
>Originator:     Oliver Fromme
>Release:        FreeBSD 4.7-RELEASE i386
>Organization:
secnetix GmbH & Co. KG
>Environment:
System: FreeBSD monos.secnetix.net 4.7-RELEASE FreeBSD 4.7-RELEASE #0: Thu Oct 17 12:56:21 CEST 2002 olli@monos.secnetix.net:/usr/src/sys/compile/MONOS i386
Version of src/sbin/ipfw/ipfw2.c is 1.4.2.5 2002/08/21 05:46:14.

>Description:

I was surprised to discover that there is not already an
option in ipfw (or ipfw2) to perform a syntax-check on the
command line (or on the rules contained within a file),
without actually changing anything.

As I needed such an option urgently for a setup where a
script dynamically creates rules from a set of configured
services, I implemented it myself.  I guess that others
might have use for this as well, so I'm submitting the
patch hereby.

It implements an option -T.  When this option is specified,
no changes will be made to the kernel's firewall rules,
but the command line will be parsed completely and checked
for syntax errors.  If a pathname is specified, it will be
preprocessed and parsed as usual, also checking for syntax
errors.  Because no raw IP socket is opened when the -T
option is used, the ipfw utility can be executed by
non-root users.

The patch is against ipfw2.c (I didn't bother to hack on
the "old" ipfw.c, as I guess it'll become obsolete soon)
in 4.7-Release.  An appropriate patch for the manpage
to document the new option is included (might need some
fixes, as English is not my native language).

These are the CVS revisions agans which my patches apply:
$FreeBSD: src/sbin/ipfw/ipfw2.c,v 1.4.2.5 2002/08/21 05:46:14 luigi Exp $
$FreeBSD: src/sbin/ipfw/ipfw.8,v 1.63.2.28 2002/09/30 20:57:05 blackend Exp $

>How-To-Repeat:

n/a

>Fix:

--- ipfw2.c.diff begins here ---
--- src/sbin/ipfw/ipfw2.c.orig	Wed Aug 21 07:46:14 2002
+++ src/sbin/ipfw/ipfw2.c	Thu Oct 17 13:17:50 2002
@@ -54,7 +54,7 @@
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
 
-int		s,			/* main RAW socket */
+int		s = -1,			/* main RAW socket */
 		do_resolv,		/* Would try to resolve all */
 		do_acct,		/* Show packet/byte count */
 		do_time,		/* Show time stamps */
@@ -65,6 +65,7 @@
 		do_dynamic,		/* display dynamic rules */
 		do_expired,		/* display expired dynamic rules */
 		do_compact,		/* show rules in compact mode */
+		do_test,		/* parse and check syntax only */
 		show_sets,		/* display rule sets */
 		verbose;
 
@@ -1432,6 +1433,8 @@
 		nbytes = sizeof(struct ip_fw);
 		if ((data = malloc(nbytes)) == NULL)
 			err(EX_OSERR, "malloc");
+		if (do_test)
+			return;
 		if (getsockopt(s, IPPROTO_IP, IP_FW_GET, data, &nbytes) < 0)
 			err(EX_OSERR, "getsockopt(IP_FW_GET)");
 		set_disable = (u_int32_t)(((struct ip_fw *)data)->next_rule);
@@ -1459,6 +1462,8 @@
 		if (!isdigit(*(av[1])) || new_set > 30)
 			errx(EX_DATAERR, "invalid set number %s\n", av[1]);
 		masks[0] = (4 << 24) | (new_set << 16) | (rulenum);
+		if (do_test)
+			return;
 		i = setsockopt(s, IPPROTO_IP, IP_FW_DEL,
 			masks, sizeof(u_int32_t));
 	} else if (!strncmp(*av, "move", strlen(*av))) {
@@ -1478,6 +1483,8 @@
 		if (!isdigit(*(av[2])) || new_set > 30)
 			errx(EX_DATAERR, "invalid dest. set %s\n", av[1]);
 		masks[0] = (cmd << 24) | (new_set << 16) | (rulenum);
+		if (do_test)
+			return;
 		i = setsockopt(s, IPPROTO_IP, IP_FW_DEL,
 			masks, sizeof(u_int32_t));
 	} else if (!strncmp(*av, "disable", strlen(*av)) ||
@@ -1506,6 +1513,8 @@
 		if ( (masks[0] & masks[1]) != 0 )
 			errx(EX_DATAERR,
 			    "cannot enable and disable the same set\n");
+		if (do_test)
+			return;
 
 		i = setsockopt(s, IPPROTO_IP, IP_FW_DEL, masks, sizeof(masks));
 		if (i)
@@ -1538,6 +1547,9 @@
 	/* get rules or pipes from kernel, resizing array as necessary */
 	nbytes = nalloc;
 
+	if (do_test)
+		return;
+
 	while (nbytes >= nalloc) {
 		nalloc = nalloc * 2 + 200;
 		nbytes = nalloc;
@@ -1870,8 +1882,11 @@
 				pipe.pipe_nr = i;
 			else
 				pipe.fs.fs_nr = i;
-			i = setsockopt(s, IPPROTO_IP, IP_DUMMYNET_DEL,
-			    &pipe, sizeof pipe);
+			if (do_test)
+				i = 0;
+			else
+				i = setsockopt(s, IPPROTO_IP, IP_DUMMYNET_DEL,
+				    &pipe, sizeof pipe);
 			if (i) {
 				exitval = 1;
 				warn("rule %u: setsockopt(IP_DUMMYNET_DEL)",
@@ -1880,8 +1895,11 @@
 			}
 		} else {
 			rulenum =  (i & 0xffff) | (do_set << 24);
-			i = setsockopt(s, IPPROTO_IP, IP_FW_DEL, &rulenum,
-			    sizeof rulenum);
+			if (do_test)
+				i = 0;
+			else
+				i = setsockopt(s, IPPROTO_IP, IP_FW_DEL,
+				    &rulenum, sizeof rulenum);
 			if (i) {
 				exitval = EX_UNAVAILABLE;
 				warn("rule %u: setsockopt(IP_FW_DEL)",
@@ -2255,8 +2273,11 @@
 			weight *= weight;
 		pipe.fs.lookup_weight = (int)(weight * (1 << SCALE_RED));
 	}
-	i = setsockopt(s, IPPROTO_IP, IP_DUMMYNET_CONFIGURE, &pipe,
-			    sizeof pipe);
+	if (do_test)
+		i = 0;
+	else
+		i = setsockopt(s, IPPROTO_IP, IP_DUMMYNET_CONFIGURE, &pipe,
+				    sizeof pipe);
 	if (i)
 		err(1, "setsockopt(%s)", "IP_DUMMYNET_CONFIGURE");
 }
@@ -3178,8 +3199,9 @@
 
 	rule->cmd_len = (u_int32_t *)dst - (u_int32_t *)(rule->cmd);
 	i = (void *)dst - (void *)rule;
-	if (getsockopt(s, IPPROTO_IP, IP_FW_ADD, rule, &i) == -1)
-		err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_ADD");
+	if (!do_test)
+		if (getsockopt(s, IPPROTO_IP, IP_FW_ADD, rule, &i) == -1)
+			err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_ADD");
 	if (!do_quiet)
 		show_ipfw(rule);
 }
@@ -3194,6 +3216,8 @@
 
 	if (!ac) {
 		/* clear all entries */
+		if (do_test)
+			return;
 		if (setsockopt(s, IPPROTO_IP, IP_FW_ZERO, NULL, 0) < 0)
 			err(EX_UNAVAILABLE, "setsockopt(%s)", "IP_FW_ZERO");
 		if (!do_quiet)
@@ -3208,6 +3232,8 @@
 			rulenum = atoi(*av);
 			av++;
 			ac--;
+			if (do_test)
+				continue;
 			if (setsockopt(s, IPPROTO_IP,
 			    IP_FW_ZERO, &rulenum, sizeof rulenum)) {
 				warn("rule %u: setsockopt(IP_FW_ZERO)",
@@ -3233,6 +3259,8 @@
 
 	if (!ac) {
 		/* clear all entries */
+		if (do_test)
+			return;
 		if (setsockopt(s, IPPROTO_IP, IP_FW_RESETLOG, NULL, 0) < 0)
 			err(EX_UNAVAILABLE, "setsockopt(IP_FW_RESETLOG)");
 		if (!do_quiet)
@@ -3247,6 +3275,8 @@
 			rulenum = atoi(*av);
 			av++;
 			ac--;
+			if (do_test)
+				continue;
 			if (setsockopt(s, IPPROTO_IP,
 			    IP_FW_RESETLOG, &rulenum, sizeof rulenum)) {
 				warn("rule %u: setsockopt(IP_FW_RESETLOG)",
@@ -3283,6 +3313,8 @@
 		if (c == 'N')	/* user said no */
 			return;
 	}
+	if (do_test)
+		return;
 	if (setsockopt(s, IPPROTO_IP, cmd, NULL, 0) < 0)
 		err(EX_UNAVAILABLE, "setsockopt(IP_%s_FLUSH)",
 		    do_pipe ? "DUMMYNET" : "FW");
@@ -3302,7 +3334,7 @@
 	do_force = !isatty(STDIN_FILENO);
 
 	optind = optreset = 1;
-	while ((ch = getopt(ac, av, "hs:acdefNqStv")) != -1)
+	while ((ch = getopt(ac, av, "hs:acdefNqSTtv")) != -1)
 		switch (ch) {
 		case 'h': /* help */
 			help();
@@ -3335,6 +3367,9 @@
 		case 'S':
 			show_sets = 1;
 			break;
+		case 'T':
+			do_test = 1;
+			break;
 		case 't':
 			do_time = 1;
 			break;
@@ -3363,6 +3398,12 @@
 	}
 	NEED1("missing command");
 
+	if (!do_test && s < 0) {
+		s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+		if (s < 0)
+			err(EX_UNAVAILABLE, "socket");
+	}
+
 	/*
 	 * for pipes and queues we normally say 'pipe NN config'
 	 * but the code is easier to parse as 'pipe config NN'
@@ -3412,7 +3453,7 @@
 	pid_t	preproc = 0;
 	int	c;
 
-	while ((c = getopt(ac, av, "D:U:p:q")) != -1)
+	while ((c = getopt(ac, av, "D:U:p:Tq")) != -1)
 		switch(c) {
 		case 'D':
 			if (!pflag)
@@ -3441,6 +3482,10 @@
 			i = 1;
 			break;
 
+		case 'T':
+			do_test = 1;
+			break;
+
 		case 'q':
 			qflag = 1;
 			break;
@@ -3455,6 +3500,12 @@
 	if (ac != 1)
 		errx(EX_USAGE, "extraneous filename arguments");
 
+	if (!do_test) {
+		s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+		if (s < 0)
+			err(EX_UNAVAILABLE, "socket");
+	}
+
 	if ((f = fopen(av[0], "r")) == NULL)
 		err(EX_UNAVAILABLE, "fopen: %s", av[0]);
 
@@ -3538,10 +3589,6 @@
 int
 main(int ac, char *av[])
 {
-	s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
-	if (s < 0)
-		err(EX_UNAVAILABLE, "socket");
-
 	/*
 	 * If the last argument is an absolute pathname, interpret it
 	 * as a file to be preprocessed.
--- ipfw2.c.diff ends here ---

--- ipfw.8.diff begins here ---
--- src/sbin/ipfw/ipfw.8.orig	Fri Oct  4 19:22:36 2002
+++ src/sbin/ipfw/ipfw.8	Fri Oct 18 23:02:54 2002
@@ -14,46 +14,52 @@
 .Nd IP firewall and traffic shaper control program
 .Sh SYNOPSIS
 .Nm
-.Op Fl cq
+.Op Fl Tcq
 .Cm add
 .Ar rule
 .Nm
-.Op Fl acdeftNS
+.Op Fl TacdeftNS
 .Brq Cm list | show
 .Op Ar number ...
 .Nm
+.Op Fl T
 .Op Fl f | q
 .Cm flush
 .Nm
-.Op Fl q
+.Op Fl Tq
 .Brq Cm delete | zero | resetlog
 .Op Cm set
 .Op Ar number ...
 .Pp
 .Nm
+.Op Fl T
 .Cm set Oo Cm disable Ar number ... Oc Op Cm enable Ar number ...
 .Nm
+.Op Fl T
 .Cm set move
 .Op Cm rule
 .Ar number Cm to Ar number
 .Nm
+.Op Fl T
 .Cm set swap Ar number number
 .Nm
+.Op Fl T
 .Cm set show
 .Pp
 .Nm
+.Op Fl T
 .Brq Cm pipe | queue
 .Ar number
 .Cm config
 .Ar config-options
 .Nm
-.Op Fl s Op Ar field
+.Op Fl Ts Op Ar field
 .Brq Cm pipe | queue
 .Brq Cm delete | list | show
 .Op Ar number ...
 .Pp
 .Nm
-.Op Fl q
+.Op Fl Tq
 .Oo
 .Fl p Ar preproc
 .Oo Fl D
@@ -272,6 +278,12 @@
 .It Fl s Op Ar field
 While listing pipes, sort according to one of the four
 counters (total and current packets or bytes).
+.It Fl T
+Parse and check everything for syntax errors, but do not
+actually modify the kernel's ruleset or even read from it.
+With this flag,
+.Nm
+can be executed by non-root users.
 .It Fl t
 While listing, show last match timestamp.
 .El
--- ipfw.8.diff ends here ---


>Release-Note:
>Audit-Trail:
Responsible-Changed-From-To: freebsd-bugs->luigi 
Responsible-Changed-By: johan 
Responsible-Changed-When: Fri Oct 25 01:44:32 PDT 2002 
Responsible-Changed-Why:  
Over to ipfw2 maintainer. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=44238 
State-Changed-From-To: open->closed 
State-Changed-By: luigi 
State-Changed-When: Tue Jul 8 01:52:46 PDT 2003 
State-Changed-Why:  
committed similar functionality, using the same flag (-n) 
used by /bin/sh 


http://www.freebsd.org/cgi/query-pr.cgi?pr=44238 
>Unformatted:
