Bug 21989

Summary: preprocessor for ipfilter rules
Product: Base System Reporter: Gerhard Sittig <Gerhard.Sittig>
Component: binAssignee: Darern Reed <darrenr>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 4.1-STABLE   
Hardware: Any   
OS: Any   

Description Gerhard Sittig 2000-10-14 18:00:01 UTC
When loading (or manipulating) rules with ipf(8) and handing a
rules file to the command, the input has to be in exact ipf
language.  There's no possibility to have
- variables substituted
- loops unrolled
- translation from some more abstract language down to ipf syntax
  done

All of this of couse could be done by specifying "-" as the file
(or omitting the -f option) and feeding ipf's stdin from the
appropriate source.  But this doesn't fit well into the
(typical?) command invocation in the rc.conf and rc.network
environment (or whatever the boot scripts are called on the
platform ipf is used at).

Fix: Apply the following patch.  The code got tested here, but the doc
wasn't.  For some reason "man -M . ipf" refused to work from
within the src/contrib/ipfilter directory.  But it needs
rewording and markup corrections, anyway, and doesn't fit for
immediate application. :)

Since many option flags were in use already I had to do the
passing with one command line string.  But this is not a real
drawback.  And I slowly get the feeling that "-" isn't even an
exception to the passing to the preprocessor.




The test case looks like this:

$ head pp_in.txt
#define WORLD world
#define HELLO hello

HELLO WORLD
EXTDEF works too

$ ./ipf -v -p /usr/bin/cpp -f ./pp_in.txt
open device: Permission denied
[hello  world ]
2: unknown keyword (hello)
[EXTDEF works too]
3: unknown keyword (EXTDEF)
$ ./ipf -v -p '/usr/bin/cpp -DEXTDEF=external' -f ./pp_in.txt
open device: Permission denied
[hello  world ]
2: unknown keyword (hello)
[external  works too]
3: unknown keyword (external)
$

And of course it doesn't touch the ones who don't use -p:

$ ./ipf -v -f ./pp_in.txt
open device: Permission denied
[HELLO WORLD]
3: unknown keyword (HELLO)
[EXTDEF works too]
4: unknown keyword (EXTDEF)
$


virtually yours   82D1 9B9C 01DC 4FB4 D7B4  61BE 3F49 4F77 72DE DA76
Gerhard Sittig   true | mail -s "get gpg key" Gerhard.Sittig@gmx.net
-- 
     If you don't understand or are scared by any of the above
             ask your parents or an adult to help you.--JONoRq05cEUBxNvBbKQ8HDX5H3ED2WajyMPMKZJyTzESRCoD
Content-Type: text/plain; name="file.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="file.diff"

Index: ../../contrib/ipfilter/ipf.c
===================================================================
RCS file: /home/fcvs/src/contrib/ipfilter/ipf.c,v
retrieving revision 1.1.1.9
diff -u -r1.1.1.9 ipf.c
--- ../../contrib/ipfilter/ipf.c	2000/08/13 04:57:48	1.1.1.9
+++ ../../contrib/ipfilter/ipf.c	2000/10/14 15:19:07
@@ -66,7 +66,8 @@
 
 static	int	fd = -1;
 
-static	void	procfile __P((char *, char *)), flushfilter __P((char *));
+static	void	procfile __P((char *, char *, char *));
+static	void	flushfilter __P((char *));
 static	void	set_state __P((u_int)), showstats __P((friostat_t *));
 static	void	packetlogon __P((char *)), swapactive __P((void));
 static	int	opendevice __P((char *));
@@ -81,7 +82,8 @@
 static void usage()
 {
 	fprintf(stderr, "usage: ipf [-6AdDEInoPrsUvVyzZ] %s %s %s\n",
-		"[-l block|pass|nomatch]", "[-F i|o|a|s|S]", "[-f filename]");
+		"[-l block|pass|nomatch]", "[-F i|o|a|s|S]",
+		"[-p preprocessor_command] [-f filename]");
 	exit(1);
 }
 
@@ -91,8 +93,9 @@
 char *argv[];
 {
 	int c;
+	char *pp_name = NULL;
 
-	while ((c = getopt(argc, argv, "6AdDEf:F:Il:noPrsUvVyzZ")) != -1) {
+	while ((c = getopt(argc, argv, "6AdDEf:F:Il:nop:PrsUvVyzZ")) != -1) {
 		switch (c)
 		{
 		case '?' :
@@ -115,7 +118,7 @@
 			opts |= OPT_DEBUG;
 			break;
 		case 'f' :
-			procfile(argv[0], optarg);
+			procfile(argv[0], optarg, pp_name);
 			break;
 		case 'F' :
 			flushfilter(optarg);
@@ -131,6 +134,9 @@
 			break;
 		case 'o' :
 			break;
+		case 'p' :
+			pp_name = optarg;
+			break;
 		case 'P' :
 			ipfname = IPL_AUTH;
 			break;
@@ -221,14 +227,21 @@
 	return;
 }
 
-static	void	procfile(name, file)
-char	*name, *file;
+static	void	procfile(name, file, pp_fn)
+char	*name, *file, *pp_fn;
 {
 	FILE	*fp;
 	char	line[513], *s;
 	struct	frentry	*fr;
 	u_int	add, del;
 	int     linenum = 0;
+#define	PP_ARG_MAX	32
+#define	PP_ARG_SEP	" \t"
+	int	pp_pipe[2];
+	int	pp_pid;
+	int	pp_argc;
+	char	*pp_argv[PP_ARG_MAX + 1];
+	char	*pp_tmp;
 
 	(void) opendevice(ipfname);
 
@@ -250,6 +263,69 @@
 		fprintf(stderr, "%s: fopen(%s) failed: %s\n", name, file,
 			STRERROR(errno));
 		exit(1);
+	}
+
+	/* pipe the rules through a preprocessor if specified */
+	if (pp_fn && *pp_fn) {
+
+		if (pipe(pp_pipe) == -1) {
+			fprintf(stderr, "%s: pipe() failed: %s\n",
+					name, STRERROR(errno));
+			exit(1);
+		}
+
+		/*
+		 * FreeBSD has strsep(3), but others might not;
+		 * maybe we could use system(3) or popen(3)
+		 * and get rid of all the dimensioning/strtok stuff?
+		 */
+		for (	pp_argc = 0, pp_tmp = strtok(pp_fn, PP_ARG_SEP);
+			(pp_tmp) && (pp_argc < PP_ARG_MAX);
+			pp_tmp = strtok(NULL, PP_ARG_SEP)
+		) {
+			/* cope with adjacent delimiters */
+			if (*pp_tmp)
+				pp_argv[pp_argc++] = pp_tmp;
+		}
+		pp_argv[pp_argc] = NULL;
+		if (pp_tmp) {
+			fprintf(stderr, "%s: too many preprocessor args, "
+					"ignored \"%s\" and following\n",
+					name, pp_tmp);
+		}
+
+		switch(pp_pid = fork()) {
+		case -1:
+			fprintf(stderr, "%s: fork() failed: %s\n",
+					name, STRERROR(errno));
+			exit(1);
+
+		case 0:	 /* preprocessor job (child) */
+			if ((dup2(fileno(fp), 0) == -1) ||
+			    (dup2(pp_pipe[1], 1) == -1)) {
+				fprintf(stderr, "%s: dup2() failed: %s\n",
+						name, STRERROR(errno));
+				exit(1);
+			}
+			fclose(fp);
+			close(pp_pipe[1]);
+			close(pp_pipe[0]);
+			execvp(pp_argv[0], pp_argv);
+			fprintf(stderr, "%s: exec(%s) failed: %s\n",
+					name, pp_argv[0], STRERROR(errno));
+			exit(1);
+
+		default: /* ipf job (parent) */
+			fclose(fp);
+			close(pp_pipe[1]);
+			if (! (fp = fdopen(pp_pipe[0], "r"))) {
+				fprintf(stderr, "%s: fdopen() failed: %s\n",
+						name, STRERROR(errno));
+				kill(pp_pid, SIGTERM);
+				exit(1);
+			}
+			break;
+		}
 	}
 
 	while (getline(line, sizeof(line), fp)) {
Index: ../../contrib/ipfilter/man/ipf.8
===================================================================
RCS file: /home/fcvs/src/contrib/ipfilter/man/ipf.8,v
retrieving revision 1.4
diff -u -r1.4 ipf.8
--- ../../contrib/ipfilter/man/ipf.8	2000/05/24 02:19:15	1.4
+++ ../../contrib/ipfilter/man/ipf.8	2000/10/14 15:58:17
@@ -12,6 +12,9 @@
 ] [
 .B \-F
 <i|o|a|s|S>
+] [
+.B \-p
+<\fIpreprocessor\fP>
 ]
 .B \-f
 <\fIfilename\fP>
@@ -84,6 +87,17 @@
 .B \-o
 Force rules by default to be added/deleted to/from the output list, rather
 than the (default) input list.
+.TP
+.BR \-p \0<preprocessor>
+All of the files in subsequent \fB-f\fP options
+are fed into this command's stdin, its stdout
+is read back and processed instead of the file's content.
+This command gets invoked anew for every specified filename.
+This option only applies to real files,
+not to stdin specified with its "-" alias.
+Specifying an empty command will turn this feature off, again.
+Shell escapes are needed when passing arguments to this command.
+Up to 31 parameters (like \fB-D\fP and \fB-U\fP) can be passed.
 .TP
 .B \-P
 Add rules as temporary entries in the authentication rule table.
How-To-Repeat: 
It should be obvious. :)  And reading "man ipfw" with a search
for "/-p" will give the idea that there's something "that would
be useful here, too".
Comment 1 Johan Karlsson freebsd_committer freebsd_triage 2000-10-14 18:43:42 UTC
Responsible Changed
From-To: freebsd-bugs->darrenr

Over to ipfilter maintainer.
Comment 2 Gerhard Sittig 2000-10-15 10:58:29 UTC
On Sat, Oct 14, 2000 at 18:24 +0200, Gerhard Sittig wrote:
> 
> And I slowly get the feeling that "-" isn't even an
> exception to the passing to the preprocessor.

Well, it turns out to be. :(  Following the ipf.8 diff the code
should have
- either suppressed preprocessing when the "file" is stdin
- or taken special care to not clobber its stdin handle

And something else showed up:  Separating pp_name into pp_argv[]
on every procfile() invocation mangled pp_name in a way that the
next loop found one word only.  Therefor I made pp_argv[] global
and introduced a use_pproc flag to scan the command only once
when specified with -p.

And while I were testing, I wasted some time searching for errors
just because ipf didn't tell me option -p was not supported.
That's what the usage() and getopt() arg mangling is for:  When
there's no code for an option, I would like to know about it.

And I wonder if there would be any way of making the manpage stuff
"conditional" either by cpp'ing the troff input or by specifying
"when compiled with this option" in the -6 and -p sections.

Feel free to ignore the patch from the previous message and
consider the following one to be the relevant ipf extension:

Index: ../../contrib/ipfilter/ipf.c
===================================================================
RCS file: /home/fcvs/src/contrib/ipfilter/ipf.c,v
retrieving revision 1.1.1.9
diff -u -r1.1.1.9 ipf.c
--- ../../contrib/ipfilter/ipf.c	2000/08/13 04:57:48	1.1.1.9
+++ ../../contrib/ipfilter/ipf.c	2000/10/15 09:43:19
@@ -66,6 +66,13 @@
 
 static	int	fd = -1;
 
+#ifdef	USE_PPROC
+#define	PP_ARG_MAX	32
+#define	PP_ARG_SEP	" \t"
+static	int	use_pproc = 0;
+static	char	*pp_argv[PP_ARG_MAX + 1] = { NULL, };
+#endif
+
 static	void	procfile __P((char *, char *)), flushfilter __P((char *));
 static	void	set_state __P((u_int)), showstats __P((friostat_t *));
 static	void	packetlogon __P((char *)), swapactive __P((void));
@@ -77,11 +84,32 @@
 static	void	showversion __P((void));
 static	int	get_flags __P((void));
 
+/* cause getopt to barf when code for an option is not present */
+#ifdef	USE_INET6
+  #define SYNOPSIS_INET6 "6"
+  #define GETOPT_INET6 "6"
+#else
+  #define SYNOPSIS_INET6 ""
+  #define GETOPT_INET6 ""
+#endif
+#ifdef	USE_PPROC
+  #define SYNOPSIS_PPROC "[-p preprocessor] "
+  #define GETOPT_PPROC "p:"
+#else
+  #define SYNOPSIS_PPROC ""
+  #define GETOPT_PPROC ""
+#endif
+#define	SYNOPSIS_COMMON "AdDEInoPrsUvVyzZ"
+#define	GETOPT_COMMON "AdDEf:F:Il:noPrsUvVyzZ"
 
+#define	GETOPT_OPTIONS	GETOPT_COMMON GETOPT_INET6 GETOPT_PPROC
+
 static void usage()
 {
-	fprintf(stderr, "usage: ipf [-6AdDEInoPrsUvVyzZ] %s %s %s\n",
-		"[-l block|pass|nomatch]", "[-F i|o|a|s|S]", "[-f filename]");
+	fprintf(stderr, "usage: ipf [-%s] %s %s %s\n",
+		SYNOPSIS_INET6 SYNOPSIS_COMMON,
+		"[-l block|pass|nomatch]", "[-F i|o|a|s|S]",
+		SYNOPSIS_PPROC "[-f filename]");
 	exit(1);
 }
 
@@ -91,8 +119,12 @@
 char *argv[];
 {
 	int c;
+#ifdef	USE_PPROC
+	int pp_argc;
+	char *pp_tmp;
+#endif
 
-	while ((c = getopt(argc, argv, "6AdDEf:F:Il:noPrsUvVyzZ")) != -1) {
+	while ((c = getopt(argc, argv, GETOPT_OPTIONS)) != -1) {
 		switch (c)
 		{
 		case '?' :
@@ -131,6 +163,30 @@
 			break;
 		case 'o' :
 			break;
+#ifdef	USE_PPROC
+		case 'p' :
+			/*
+			 * FreeBSD has strsep(3), but others might not;
+			 * maybe we could use system(3) or popen(3)
+			 * and get rid of all the dimensioning/strtok stuff?
+			 */
+			for (	pp_argc = 0, pp_tmp = strtok(optarg, PP_ARG_SEP);
+				(pp_tmp) && (pp_argc < PP_ARG_MAX);
+				pp_tmp = strtok(NULL, PP_ARG_SEP)
+			) {
+				/* cope with adjacent delimiters */
+				if (*pp_tmp)
+					pp_argv[pp_argc++] = pp_tmp;
+			}
+			pp_argv[pp_argc] = NULL;
+			if (pp_tmp) {
+				fprintf(stderr, "%s: too many preprocessor args, "
+						"ignored \"%s\" and following\n",
+						argv[0], pp_tmp);
+			}
+			use_pproc = (pp_argc && pp_argv[0] && *pp_argv[0]);
+			break;
+#endif
 		case 'P' :
 			ipfname = IPL_AUTH;
 			break;
@@ -229,6 +285,10 @@
 	struct	frentry	*fr;
 	u_int	add, del;
 	int     linenum = 0;
+#ifdef	USE_PPROC
+	int	pp_pipe[2];
+	int	pp_pid;
+#endif
 
 	(void) opendevice(ipfname);
 
@@ -251,6 +311,62 @@
 			STRERROR(errno));
 		exit(1);
 	}
+
+#ifdef	USE_PPROC
+	/* pipe the rules through a preprocessor if specified */
+	if (use_pproc) {
+
+		if (pipe(pp_pipe) == -1) {
+			fprintf(stderr, "%s: pipe() failed: %s\n",
+					name, STRERROR(errno));
+			exit(1);
+		}
+
+		switch(pp_pid = fork()) {
+		case -1:
+			fprintf(stderr, "%s: fork() failed: %s\n",
+					name, STRERROR(errno));
+			exit(1);
+			/* NOTREACHED */
+
+		case 0:	 /* preprocessor job (child) */
+			if (dup2(pp_pipe[1], 1) == -1) {
+				fprintf(stderr, "%s: dup2() failed: %s\n",
+						name, STRERROR(errno));
+				exit(1);
+			}
+			close(pp_pipe[1]);
+			/* only close fp & friends when it's not stdin */
+			if (strcmp(file, "-")) {
+				if (dup2(fileno(fp), 0) == -1) {
+					fprintf(stderr, "%s: dup2() failed: %s\n",
+							name, STRERROR(errno));
+					exit(1);
+				}
+				fclose(fp);
+			}
+			close(pp_pipe[0]);
+			execvp(pp_argv[0], pp_argv);
+			fprintf(stderr, "%s: exec(%s) failed: %s\n",
+					name, pp_argv[0], STRERROR(errno));
+			exit(1);
+			/* NOTREACHED */
+
+		default: /* ipf job (parent) */
+			close(pp_pipe[1]);
+			/* only close fp & friends when it's not stdin */
+			if (strcmp(file, "-"))
+				fclose(fp);
+			if (! (fp = fdopen(pp_pipe[0], "r"))) {
+				fprintf(stderr, "%s: fdopen() failed: %s\n",
+						name, STRERROR(errno));
+				kill(pp_pid, SIGTERM);
+				exit(1);
+			}
+			break;
+		}
+	}
+#endif
 
 	while (getline(line, sizeof(line), fp)) {
 	        linenum++;
Index: ../../contrib/ipfilter/man/ipf.8
===================================================================
RCS file: /home/fcvs/src/contrib/ipfilter/man/ipf.8,v
retrieving revision 1.4
diff -u -r1.4 ipf.8
--- ../../contrib/ipfilter/man/ipf.8	2000/05/24 02:19:15	1.4
+++ ../../contrib/ipfilter/man/ipf.8	2000/10/15 09:27:26
@@ -12,6 +12,9 @@
 ] [
 .B \-F
 <i|o|a|s|S>
+] [
+.B \-p
+<\fIpreprocessor\fP>
 ]
 .B \-f
 <\fIfilename\fP>
@@ -84,6 +87,15 @@
 .B \-o
 Force rules by default to be added/deleted to/from the output list, rather
 than the (default) input list.
+.TP
+.BR \-p \0<preprocessor>
+All of the files in subsequent \fB-f\fP options
+are fed into this command's stdin, its stdout
+is read back and processed instead of the file's content.
+This command gets invoked anew for every specified filename.
+Specifying an empty command will turn this feature off, again.
+Shell escapes are needed when passing arguments to this command.
+Up to 31 parameters (usually some \fB-D\fP and \fB-U\fP) can be passed.
 .TP
 .B \-P
 Add rules as temporary entries in the authentication rule table.


Let's see if some tests still work:

$ cd ~/FreeBSD-source/src-current/sbin/ipf
$ make CFLAGS+=-DUSE_PPROC
[ ... snip ... ]
$ head pp_in*.txt
==> pp_in.txt <==
#define WORLD world
#define HELLO hello

HELLO WORLD
EXTDEF works too

==> pp_in1.txt <==
#define INT internal
file F1 with INT and EXT

==> pp_in2.txt <==
#define INT internal
file F2 with INT and EXT

==> pp_in3.txt <==
#define INT internal
file F3 with INT and EXT
$ ./ipf -v -f pp_in.txt
open device: Permission denied
[HELLO WORLD]
3: unknown keyword (HELLO)
[EXTDEF works too]
4: unknown keyword (EXTDEF)
$ ./ipf -v -f pp_in1.txt -f pp_in2.txt
open device: Permission denied
[file F1 with INT and EXT]
2: unknown keyword (file)
open device: Permission denied
[file F2 with INT and EXT]
2: unknown keyword (file)
$ ./ipf -v -f pp_in1.txt -f - -f pp_in3.txt < pp_in2.txt
open device: Permission denied
[file F1 with INT and EXT]
2: unknown keyword (file)
open device: Permission denied
[file F2 with INT and EXT]
2: unknown keyword (file)
open device: Permission denied
[file F3 with INT and EXT]
2: unknown keyword (file)
$ ./ipf -v -p 'cpp' -f pp_in1.txt -f - -f pp_in3.txt < pp_in2.txt
open device: Permission denied
[file F1 with internal  and EXT]
2: unknown keyword (file)
open device: Permission denied
[file F2 with internal  and EXT]
2: unknown keyword (file)
open device: Permission denied
[file F3 with internal  and EXT]
2: unknown keyword (file)
$ ./ipf -v -p 'cpp -DEXT=external' -f pp_in1.txt -f - -f pp_in3.txt < pp_in2.txt
open device: Permission denied
[file F1 with internal  and external ]
2: unknown keyword (file)
open device: Permission denied
[file F2 with internal  and external ]
2: unknown keyword (file)
open device: Permission denied
[file F3 with internal  and external ]
2: unknown keyword (file)
$ ./ipf -v -p 'cpp -DEXT=external' -f pp_in1.txt -f - -p '' -f pp_in3.txt < pp_in2.txt
open device: Permission denied
[file F1 with internal  and external ]
2: unknown keyword (file)
open device: Permission denied
[file F2 with internal  and external ]
2: unknown keyword (file)
open device: Permission denied
[file F3 with INT and EXT]
2: unknown keyword (file)
$ ./ipf -v -p 'cpp -DEXT=ext1' -f pp_in1.txt -p 'cpp -DEXT=ext2' -f - -p 'cpp -DEXT=ext3' -f pp_in3.txt < pp_in2.txt
open device: Permission denied
[file F1 with internal  and ext1 ]
2: unknown keyword (file)
open device: Permission denied
[file F2 with internal  and ext2 ]
2: unknown keyword (file)
open device: Permission denied
[file F3 with internal  and ext3 ]
2: unknown keyword (file)


Now that was fun. :)


virtually yours   82D1 9B9C 01DC 4FB4 D7B4  61BE 3F49 4F77 72DE DA76
Gerhard Sittig   true | mail -s "get gpg key" Gerhard.Sittig@gmx.net
-- 
     If you don't understand or are scared by any of the above
             ask your parents or an adult to help you.
Comment 3 Darern Reed freebsd_committer freebsd_triage 2000-11-23 22:22:16 UTC
State Changed
From-To: open->closed

I am not interested in putting a preprocessor into ipf