1#!/usr/local/bin/perl -w
2#
3#	$Id: ipfmeta,v 1.4 2001/01/29 08:37:56 camield Exp $
4#
5# Copyright (c) 2000, 2001 Camiel Dobbelaar <cd@sentia.nl>
6# All rights reserved.
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# 3. The name of the author may not be used to endorse or promote products
17#    derived from this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22# DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
23# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29# POSSIBILITY OF SUCH DAMAGE.
30#
31
32use strict;
33
34# Globals
35my $Group = 0;
36my $Maxrec = 20;
37my $Objfile = "ipf.objs";
38my $Obj; my @Objs;
39my $Val; my %Vals;
40my $Verbose = 0;
41
42my $objfile = shift || $Objfile;
43readobjects($objfile);
44
45my @all_lines = <STDIN>;
46@all_lines = cutw(@all_lines);
47
48my $oline;
49foreach (@all_lines) {
50	if (m/^%/) {
51		command($_);
52		next;
53	}
54
55	if (m/^(#|$)/) {
56		print "$_\n";
57		next;
58	}
59
60	foreach $oline (expand(agroup($_))) {
61		print "$oline\n";
62	}
63}
64
65# end of main
66
67sub agroup
68{
69	my $line = shift;
70
71	if ($Group && ($line !~ m/group/)) {
72		$line = "$line group $Group";
73	}
74	return $line;
75}
76
77sub command
78{
79	my $line = shift;
80
81	for ($line) {
82		m/verbose (\d+)/i and $Verbose = $1;
83		m/group (\d+)/i   and $Group = $1;
84		m/dump/i          and objdump();
85	}
86}
87
88sub cutw
89{
90	my @ret_lines;
91
92	foreach (@_) {
93		push(@ret_lines, join(' ', split));
94	}
95	return @ret_lines;
96}
97
98sub expand
99{
100	my $line = shift;
101	my $level = shift || 0;
102	my @retlines = $line;
103
104	# coarse protection
105	if ($level > $Maxrec) {
106		warn "ERR: recursion exceeds $Maxrec levels\n";
107		return;
108	}
109
110	foreach $Obj (@Objs) {
111		if ($line =~ m/$Obj/) {
112			undef(@retlines);
113			if ($level < $Verbose) {
114				# add metarule as a comment
115				push(@retlines, "# ".$line);
116			}
117			foreach $Val (@{$Vals{$Obj}}) {
118				my $newline = $line;
119				$newline =~ s/$Obj/$Val/;
120				push(@retlines, expand($newline, $level+1));
121			}
122			last;
123		}
124	}
125	return @retlines;
126}
127
128sub objdump
129{
130	unless (@Objs) {
131		print "# dump: no objects\n";
132		return;
133	}
134	foreach $Obj (sort @Objs) {
135		print "# [$Obj]\n";
136		foreach $Val (sort @{$Vals{$Obj}}) {
137			print "# \t '$Val'\n";
138		}
139	}
140}
141
142sub readobjects
143{
144	my $objfile = shift;
145	my @tokens;
146
147	unless (open(FH, "$objfile")) {
148		if ($objfile eq $Objfile) {
149			return;
150		} else {
151			die "cannot open $objfile: $!\n";
152		}
153	}
154	while (<FH>) {
155		push(@tokens, tokenparse(cutw($_)));
156	}
157	close(FH) || die "cannot close $objfile: $!\n";
158	# link objects with their values
159	$Obj = "";
160	while (@tokens) {
161		my $token = shift(@tokens);
162		if ($token =~ m/^\[([^\]]*)\]$/) {
163			# new object
164			$Obj = $1;
165		} else {
166			# new value
167			push(@{$Vals{$Obj}}, $token) unless ($Obj eq "");
168		}
169	}
170	# sort objects: longest first
171	@Objs = sort { length($b) <=> length($a) } keys %Vals;
172}
173
174sub tokenparse
175{
176	my @ret_tokens;
177	$_ = shift;
178
179	while ($_) {
180		next if (s/^\s+//);
181		last if (m/^#/);
182		my $token = "";
183		if (s/^"//) {
184			$token = $1 if (s/^([^"]*)"//);
185		} elsif (s/^\[//) {
186			$token = "[$1]" if (s/^([^\]]*)\]//);
187		} else {
188			$token = $1 if (s/^([^\s]+)//);
189		}
190		push(@ret_tokens, $token) unless ($token eq "");
191	}
192	return @ret_tokens;
193}
194
195__END__
196
197=head1 NAME
198
199B<ipfmeta> - use objects in IPfilter files
200
201=head1 SYNOPSIS
202
203B<ipfmeta> [F<objfile>]
204
205=head1 DESCRIPTION
206
207B<ipfmeta> is used to simplify the maintenance of your IPfilter
208ruleset. It does this through the use of 'objects'. A matching
209object gets replaced by its values at runtime. This is similar to
210what a macro processor like m4 does.
211
212B<ipfmeta> is specifically geared towards IPfilter. It is line
213oriented: if an object has multiple values, the line with the object
214is duplicated and substituted for each value. It is also recursive:
215an object may have another object as a value.
216
217Metarules to be processed are read from stdin, output rules go to
218stdout.
219
220Definition of the objects and their values is done in a separate
221file; the filename defaults to F<ipf.objs>. An object is delimited
222by square brackets. A value is delimited by whitespace, except when
223it is enclosed by double-quotes. Comments start with '#' and end
224with a newline. Empty lines and extraneous whitespace are allowed.
225A value belongs to the object that precedes it.
226
227B<ipfmeta> has a command mode. Metarules starting with '%' are
228passed to the command processor. The commands are listed below.
229
230It is recommended that you use all caps or another distinguishing
231feature for object names. You can use B<ipfmeta> for NAT rules also,
232for instance to keep them in sync with filter rules. Combine
233B<ipfmeta> with a Makefile to save typing.
234
235=head1 COMMANDS
236
237=over 4
238
239=item B<dump>
240
241Include a list of all objects with their values in the output as
242comments. This can be used to verify if the objectfile is parsed
243correctly.
244
245=item B<group> F<n>
246
247Append 'group n' to subsequent output rules. Use 'group 0' to stop
248appending groups to the output. This is also the default.
249
250=item B<verbose> F<level>
251
252Include expanded metarules in output as comments. The default is
2530, do not add any comments. Higher verbosity levels cause deeper
254levels of expanded metarules to be included.
255
256=back
257
258=head1 EXAMPLES
259
260ipfmeta ipf.objs <ipf.metarules >ipf.rules
261
262cat ipf.metarules | ipfmeta | ipf -I -Fa -vf -
263
264=head1 AUTHOR
265
266Camiel Dobbelaar <cd@sentia.nl>
267
268=cut
269