1
0
mirror of https://gitlab.com/shorewall/code.git synced 2025-04-25 11:49:44 +02:00

Allow conditional compilation

Signed-off-by: Tom Eastep <teastep@shorewall.net>
This commit is contained in:
Tom Eastep 2012-03-16 08:16:12 -07:00
parent 8e413a7bf0
commit ab13fbe95e
4 changed files with 236 additions and 42 deletions
Shorewall
Perl/Shorewall
manpages
Shorewall6/manpages
docs

View File

@ -425,6 +425,13 @@ my %deprecated = ( LOGRATE => '' ,
# #
my %converted = ( WIDE_TC_MARKS => 1, my %converted = ( WIDE_TC_MARKS => 1,
HIGH_ROUTE_MARKS => 1 ); HIGH_ROUTE_MARKS => 1 );
#
# Variables involved in ?IF, ?ELSE ?ENDIF processing
#
my $omitting;
my @ifstack;
my $ifstack;
# #
# Rather than initializing globals in an INIT block or during declaration, # Rather than initializing globals in an INIT block or during declaration,
# we initialize them in a function. This is done for two reasons: # we initialize them in a function. This is done for two reasons:
@ -458,6 +465,9 @@ sub initialize( $ ) {
$tempfile = ''; # Temporary File Name $tempfile = ''; # Temporary File Name
$sillyname = $sillyname =
$sillyname1 = ''; # Temporary ipchains $sillyname1 = ''; # Temporary ipchains
$omitting = 0;
$ifstack = 0;
@ifstack = ();
# #
# Misc Globals # Misc Globals
@ -756,7 +766,7 @@ my @abbr = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
sub warning_message sub warning_message
{ {
my $linenumber = $currentlinenumber || 1; my $linenumber = $currentlinenumber || 1;
my $currentlineinfo = $currentfile ? " : $currentfilename (line $linenumber)" : ''; my $currentlineinfo = $currentfile ? " : $currentfilename " . ( $linenumber eq 'EOF' ? '(EOF)' : "(line $linenumber)" ) : '';
our @localtime; our @localtime;
$| = 1; #Reset output buffering (flush any partially filled buffers). $| = 1; #Reset output buffering (flush any partially filled buffers).
@ -812,7 +822,7 @@ sub cleanup() {
# #
sub fatal_error { sub fatal_error {
my $linenumber = $currentlinenumber || 1; my $linenumber = $currentlinenumber || 1;
my $currentlineinfo = $currentfile ? " : $currentfilename (line $linenumber)" : ''; my $currentlineinfo = $currentfile ? " : $currentfilename " . ( $linenumber eq 'EOF' ? '(EOF)' : "(line $linenumber)" ) : '';
$| = 1; #Reset output buffering (flush any partially filled buffers). $| = 1; #Reset output buffering (flush any partially filled buffers).
@ -1481,10 +1491,17 @@ sub open_file( $ ) {
sub pop_include() { sub pop_include() {
my $arrayref = pop @includestack; my $arrayref = pop @includestack;
unless ( $ifstack == @ifstack ) {
my $lastref = $ifstack[-1];
$currentlinenumber = 'EOF';
fatal_error qq(Missing "?END" to match ?IF at line number $lastref->[2])
}
if ( $arrayref ) { if ( $arrayref ) {
( $currentfile, $currentfilename, $currentlinenumber ) = @$arrayref; ( $currentfile, $currentfilename, $currentlinenumber, $ifstack ) = @$arrayref;
} else { } else {
$currentfile = undef; $currentfile = undef;
$currentlinenumber = 'EOF';
} }
} }
@ -1607,7 +1624,7 @@ sub copy1( $ ) {
fatal_error "Directory ($filename) not allowed in INCLUDE" if -d _; fatal_error "Directory ($filename) not allowed in INCLUDE" if -d _;
if ( -s _ ) { if ( -s _ ) {
push @includestack, [ $currentfile, $currentfilename, $currentlinenumber ]; push @includestack, [ $currentfile, $currentfilename, $currentlinenumber, $ifstack ];
$currentfile = undef; $currentfile = undef;
do_open_file $filename; do_open_file $filename;
} else { } else {
@ -1723,7 +1740,7 @@ EOF
# #
sub push_open( $ ) { sub push_open( $ ) {
push @includestack, [ $currentfile, $currentfilename, $currentlinenumber ]; push @includestack, [ $currentfile, $currentfilename, $currentlinenumber, $ifstack = @ifstack ];
my @a = @includestack; my @a = @includestack;
push @openstack, \@a; push @openstack, \@a;
@includestack = (); @includestack = ();
@ -1798,7 +1815,7 @@ sub embedded_shell( $ ) {
while ( <$currentfile> ) { while ( <$currentfile> ) {
$currentlinenumber++; $currentlinenumber++;
last if $last = s/^\s*END(\s+SHELL)?\s*;?//; last if $last = s/^\s*\??END(\s+SHELL)?\s*;?//;
$command .= $_; $command .= $_;
} }
@ -1808,7 +1825,7 @@ sub embedded_shell( $ ) {
$command .= q('); $command .= q(');
push @includestack, [ $currentfile, $currentfilename, $currentlinenumber ]; push @includestack, [ $currentfile, $currentfilename, $currentlinenumber, $ifstack = @ifstack ];
$currentfile = undef; $currentfile = undef;
open $currentfile , '-|', $command or fatal_error qq(Shell Command failed); open $currentfile , '-|', $command or fatal_error qq(Shell Command failed);
$currentfilename = "SHELL\@$currentfilename:$currentlinenumber"; $currentfilename = "SHELL\@$currentfilename:$currentlinenumber";
@ -1832,7 +1849,7 @@ sub embedded_perl( $ ) {
while ( <$currentfile> ) { while ( <$currentfile> ) {
$currentlinenumber++; $currentlinenumber++;
last if $last = s/^\s*END(\s+PERL)?\s*;?//; last if $last = s/^\s*\??END(\s+PERL)?\s*;?//;
$command .= $_; $command .= $_;
} }
@ -1864,7 +1881,7 @@ sub embedded_perl( $ ) {
$perlscript = undef; $perlscript = undef;
push @includestack, [ $currentfile, $currentfilename, $currentlinenumber ]; push @includestack, [ $currentfile, $currentfilename, $currentlinenumber , $ifstack = @ifstack ];
$currentfile = undef; $currentfile = undef;
open $currentfile, '<', $perlscriptname or fatal_error "Unable to open Perl Script $perlscriptname"; open $currentfile, '<', $perlscriptname or fatal_error "Unable to open Perl Script $perlscriptname";
@ -1980,6 +1997,7 @@ sub expand_variables( \$ ) {
# - Handle embedded SHELL and PERL scripts # - Handle embedded SHELL and PERL scripts
# - Expand shell variables from %params and %ENV. # - Expand shell variables from %params and %ENV.
# - Handle INCLUDE <filename> # - Handle INCLUDE <filename>
# - Handle ?IF, ?ELSE, ?ENDIF
# #
sub read_a_line(;$$$) { sub read_a_line(;$$$) {
@ -2019,6 +2037,52 @@ sub read_a_line(;$$$) {
# #
$currentline = '', $currentlinenumber = 0, next if $currentline =~ /^\s*$/; $currentline = '', $currentlinenumber = 0, next if $currentline =~ /^\s*$/;
# #
# Line not blank -- Handle conditionals
#
if ( $currentline =~ /^\s*\?(IF\s+|ELSE|ENDIF)(.*)$/ ) {
my $rest = $2;
$rest = '' unless supplied $rest;
if ( $1 =~ /^IF/ ) {
fatal_error "Missing IF variable" unless $rest;
my $invert = $rest =~ s/^!\s*//;
fatal_error "Invalid IF variable ($rest)" unless $rest =~ s/^\$// && $rest =~ /^\w+$/;
push @ifstack, [ 'IF', $omitting, $currentlinenumber ];
if ( $rest eq '__IPV6' ) {
$omitting = $family == F_IPV4;
} elsif ( $rest eq '__IPV4' ) {
$omitting = $family == F_IPV6;
} else {
$omitting = ! ( exists $ENV{$rest} ? $ENV{$rest} :
exists $params{$rest} ? $params{$rest} :
exists $config{$rest} ? $config{$rest} : 0 );
}
$omitting = ! $omitting if $invert;
} elsif ( $1 eq 'ELSE' ) {
fatal_error "Invalid ?ELSE" unless $rest eq '';
my ( $last, $omit, $lineno ) = @{pop @ifstack};
fatal_error q(Unexpected "?ELSE" without matching ?IF) unless defined $last && $last eq 'IF';
push @ifstack, [ 'ELSE', $omitting = ! $omit, $lineno ];
} else {
fatal_error "Invalid ?END" unless $rest eq '';
fatal_error q(Unexpected "?END" without matching ?IF or ?ELSE) if @ifstack <= $ifstack;
(my $last, $omitting ) = @{pop @ifstack};
}
$currentline='', next;
}
if ( $omitting ) {
progress_message " OMITTED: $currentline";
$currentline='';
next;
}
#
# Line not blank -- Handle any first-entry message/capabilities check # Line not blank -- Handle any first-entry message/capabilities check
# #
if ( $first_entry ) { if ( $first_entry ) {
@ -2033,12 +2097,12 @@ sub read_a_line(;$$$) {
# Must check for shell/perl before doing variable expansion # Must check for shell/perl before doing variable expansion
# #
if ( $embedded_enabled ) { if ( $embedded_enabled ) {
if ( $currentline =~ s/^\s*(BEGIN\s+)?SHELL\s*;?// ) { if ( $currentline =~ s/^\s*\??(BEGIN\s+)?SHELL\s*;?// ) {
embedded_shell( $1 ); embedded_shell( $1 );
next; next;
} }
if ( $currentline =~ s/^\s*(BEGIN\s+)?PERL\s*\;?// ) { if ( $currentline =~ s/^\s*\??(BEGIN\s+)?PERL\s*\;?// ) {
embedded_perl( $1 ); embedded_perl( $1 );
next; next;
} }
@ -2050,7 +2114,7 @@ sub read_a_line(;$$$) {
# #
expand_variables( $currentline ) if $expand_variables; expand_variables( $currentline ) if $expand_variables;
if ( $currentline =~ /^\s*INCLUDE\s/ ) { if ( $currentline =~ /^\s*\??INCLUDE\s/ ) {
my @line = split ' ', $currentline; my @line = split ' ', $currentline;
@ -2063,7 +2127,7 @@ sub read_a_line(;$$$) {
fatal_error "Directory ($filename) not allowed in INCLUDE" if -d _; fatal_error "Directory ($filename) not allowed in INCLUDE" if -d _;
if ( -s _ ) { if ( -s _ ) {
push @includestack, [ $currentfile, $currentfilename, $currentlinenumber ]; push @includestack, [ $currentfile, $currentfilename, $currentlinenumber, $ifstack = @ifstack ];
$currentfile = undef; $currentfile = undef;
do_open_file $filename; do_open_file $filename;
} else { } else {
@ -2367,7 +2431,7 @@ sub load_kernel_modules( ) {
my @suffixes = split /\s+/ , $config{MODULE_SUFFIX}; my @suffixes = split /\s+/ , $config{MODULE_SUFFIX};
while ( read_a_line ) { while ( read_a_line1 ) {
fatal_error "Invalid modules file entry" unless ( $currentline =~ /^loadmodule\s+([a-zA-Z]\w*)\s*(.*)$/ ); fatal_error "Invalid modules file entry" unless ( $currentline =~ /^loadmodule\s+([a-zA-Z]\w*)\s*(.*)$/ );
my ( $module, $arguments ) = ( $1, $2 ); my ( $module, $arguments ) = ( $1, $2 );
unless ( $loadedmodules{ $module } ) { unless ( $loadedmodules{ $module } ) {
@ -3235,7 +3299,7 @@ sub process_shorewall_conf( $$ ) {
# #
# Don't expand shell variables or allow embedded scripting # Don't expand shell variables or allow embedded scripting
# #
while ( read_a_line( 0, 0 ) ) { while ( read_a_line1 ) {
if ( $currentline =~ /^\s*([a-zA-Z]\w*)=(.*?)\s*$/ ) { if ( $currentline =~ /^\s*([a-zA-Z]\w*)=(.*?)\s*$/ ) {
my ($var, $val) = ($1, $2); my ($var, $val) = ($1, $2);

View File

@ -23,7 +23,11 @@
<refsect1> <refsect1>
<title>Description</title> <title>Description</title>
<para>Assign any shell variables that you need in this file.</para> <para>Assign any shell variables that you need in this file. The file is
always processed by <filename>/bin/sh</filename> or by the shell specified
through SHOREWALL_SHELL in <ulink
url="shorewall.conf.html">shorewall.conf</ulink> (5) so the full range of
shell capabilities may be used.</para>
<para>It is suggested that variable names begin with an upper case letter <para>It is suggested that variable names begin with an upper case letter
to distinguish them from variables used internally within the Shorewall to distinguish them from variables used internally within the Shorewall
@ -128,12 +132,13 @@ net eth0 130.252.100.255 routefilter,norfc1918</programlisting>
url="http://www.shorewall.net/configuration_file_basics.htm#Variables?">http://www.shorewall.net/configuration_file_basics.htm#Variables</ulink></para> url="http://www.shorewall.net/configuration_file_basics.htm#Variables?">http://www.shorewall.net/configuration_file_basics.htm#Variables</ulink></para>
<para>shorewall(8), shorewall-accounting(5), shorewall-actions(5), <para>shorewall(8), shorewall-accounting(5), shorewall-actions(5),
shorewall-blacklist(5), shorewall-hosts(5), shorewall_interfaces(5), shorewall-ipsets(5), shorewall-blacklist(5), shorewall-hosts(5), shorewall_interfaces(5),
shorewall-maclist(5), shorewall-masq(5), shorewall-nat(5), shorewall-ipsets(5), shorewall-maclist(5), shorewall-masq(5),
shorewall-netmap(5), shorewall-policy(5), shorewall-providers(5), shorewall-nat(5), shorewall-netmap(5), shorewall-policy(5),
shorewall-proxyarp(5), shorewall-rtrules(5), shorewall-providers(5), shorewall-proxyarp(5), shorewall-rtrules(5),
shorewall-routestopped(5), shorewall-rules(5), shorewall.conf(5), shorewall-secmarks(5), shorewall-routestopped(5), shorewall-rules(5), shorewall.conf(5),
shorewall-tcclasses(5), shorewall-tcdevices(5), shorewall-tcrules(5), shorewall-secmarks(5), shorewall-tcclasses(5), shorewall-tcdevices(5),
shorewall-tos(5), shorewall-tunnels(5), shorewall-zones(5)</para> shorewall-tcrules(5), shorewall-tos(5), shorewall-tunnels(5),
shorewall-zones(5)</para>
</refsect1> </refsect1>
</refentry> </refentry>

View File

@ -23,7 +23,11 @@
<refsect1> <refsect1>
<title>Description</title> <title>Description</title>
<para>Assign any shell variables that you need in this file.</para> <para>Assign any shell variables that you need in this file. The file is
always processed by <filename>/bin/sh</filename> or by the shell specified
through SHOREWALL_SHELL in <ulink
url="shorewall6.conf.html">shorewall6.conf</ulink> (5) so the full range
of shell capabilities may be used.</para>
<para>It is suggested that variable names begin with an upper case letter <para>It is suggested that variable names begin with an upper case letter
to distinguish them from variables used internally within the Shorewall to distinguish them from variables used internally within the Shorewall
@ -130,8 +134,8 @@ net eth0 - dhcp,nosmurfs</programlisting>
<para>shorewall6(8), shorewall6-accounting(5), shorewall6-actions(5), <para>shorewall6(8), shorewall6-accounting(5), shorewall6-actions(5),
shorewall6-blacklist(5), shorewall6-hosts(5), shorewall6-interfaces(5), shorewall6-blacklist(5), shorewall6-hosts(5), shorewall6-interfaces(5),
shorewall6-maclist(5), shorewall6-policy(5), shorewall6-providers(5), shorewall6-maclist(5), shorewall6-policy(5), shorewall6-providers(5),
shorewall6-rtrules(5), shorewall6-routestopped(5), shorewall6-rtrules(5), shorewall6-routestopped(5), shorewall6-rules(5),
shorewall6-rules(5), shorewall6.conf(5), shorewall6-secmarks(5), shorewall6-tcclasses(5), shorewall6.conf(5), shorewall6-secmarks(5), shorewall6-tcclasses(5),
shorewall6-tcdevices(5), shorewall6-tcrules(5), shorewall6-tos(5), shorewall6-tcdevices(5), shorewall6-tcrules(5), shorewall6-tos(5),
shorewall6-tunnels(5), shorewall6-zones(5)</para> shorewall6-tunnels(5), shorewall6-zones(5)</para>
</refsect1> </refsect1>

View File

@ -70,7 +70,10 @@
<listitem> <listitem>
<para><filename>/etc/shorewall/params</filename> - use this file to <para><filename>/etc/shorewall/params</filename> - use this file to
set shell variables that you will expand in other files.</para> set shell variables that you will expand in other files. It is
always processed by /bin/sh or by the shell specified through
SHOREWALL_SHELL in
<filename>/etc/shorewall/shorewall.conf.</filename></para>
</listitem> </listitem>
<listitem> <listitem>
@ -1044,6 +1047,16 @@ SHELL cat /etc/shorewall/rules.d/*.rules</programlisting></para>
<programlisting>SECTION NEW <programlisting>SECTION NEW
SHELL cat /etc/shorewall/rules.d/*.rules 2&gt; /dev/null || true</programlisting> SHELL cat /etc/shorewall/rules.d/*.rules 2&gt; /dev/null || true</programlisting>
<para>Beginning with Shorewall 4.5.2, in files other than
<filename>/etc/shorewall/params</filename> and
<filename>/etc/shorewall/conf</filename>, INCLUDE may be immediately
preceeded with '?' to signal that the line is a compiler directive and
not configuration data.</para>
<para>Example:</para>
<programlisting>?INCLUDE common.rules</programlisting>
</example> </example>
</section> </section>
@ -1427,6 +1440,112 @@ SHELL cat /etc/shorewall/rules.d/*.rules 2&gt; /dev/null || true</programlisting
</note> </note>
</section> </section>
<section id="Conditional">
<title>Conditional Entries</title>
<para>Beginning with Shorewall 4.5.2, lines in configuration files may be
conditionally included or omitted based on the setting of <link
linkend="Variables">Shell variables</link>.</para>
<para>The general form is:</para>
<programlisting>?IF <replaceable>$variable
</replaceable>&lt;lines to be included if $variable is non-empty and non-zero&gt;
?ELSE
&lt;lines to be omitted if $variable is non-empty and non-zero&gt;
?ENDIF</programlisting>
<para>The compiler predefines two special
<replaceable>variable</replaceable>s that may only be used in ?IF
lines:</para>
<variablelist>
<varlistentry>
<term>__IPV4</term>
<listitem>
<para>True if this is an IPv4 compilation</para>
</listitem>
</varlistentry>
<varlistentry>
<term>__IPV6</term>
<listitem>
<para>True if this is an IPv6 compilation.</para>
</listitem>
</varlistentry>
</variablelist>
<para>Unless <replaceable>variable</replaceable> is one of these
pre-defined ones, it is searched for in the compiler's environmental
variables, in variables set in <filename>/etc/shorewall/params</filename>,
and in options set in <filename>/etc/shorewall/shorewall.conf</filename>
in that order. If it is not found in any of those places, the
<replaceable>variable</replaceable> is assumed to have a value of 0
(false).</para>
<para>The setting in <filename>/etc/shorewall/params</filename> by be
overridden at runtime, provided the setting in
<filename>/etc/shorewall/params</filename> is done like this:</para>
<programlisting>[ -n "${<replaceable>variable</replaceable>:=0}" ]</programlisting>
<para>or like this:</para>
<programlisting>[ -n "${<replaceable>variable</replaceable>}" ] || <replaceable>variable</replaceable>=0</programlisting>
<para>Either of those will set variable to 0 if it is not set to a
non-empty value in the environment. The setting can be overridden at
runtime:</para>
<programlisting><replaceable>variable</replaceable>=1 shorewall restart -c # use -c to force recompilation if AUTOMAKE=Yes in /etc/shorewall/shorewall.conf</programlisting>
<para>The ?ELSE may be omitted if there are no lines to be omitted.</para>
<para>The test may also be inverted using '!':</para>
<programlisting>?IF ! <replaceable>$variable
</replaceable>&lt;lines to be omitted if $variable is non-empty and non-zero&gt;
?ELSE
&lt;lines to be included if $variable is non-empty and non-zero&gt;
?ENDIF</programlisting>
<para>Conditional entries may be nested but the number of ?IFs must match
the number of ?ENDs in any give file. <link linkend="INCLUDE">INCLUDE
directives</link> are ignored in omitted lines.</para>
<programlisting>?IF <replaceable>$variable1
</replaceable>&lt;lines to be included if $variable1 is non-empty and non-zero&gt;
?IF $variable2
&lt;lines to be included if $variable1 and $variable2 are non-empty and non-zero&gt;
?ELSE
&lt;lines to be omitted if $variable1 is non-empty and non-zero and if $variable2 is empty or zero&gt;
?ENDIF
<replaceable>
</replaceable>&lt;lines to be included if $variable1 is non-empty and non-zero&gt;
?ELSE
&lt;lines to be omitted if $variable is non-empty and non-zero&gt;
?ENDIF</programlisting>
</section>
<section id="Embedded"> <section id="Embedded">
<title>Embedded Shell and Perl</title> <title>Embedded Shell and Perl</title>
@ -1442,21 +1561,27 @@ SHELL cat /etc/shorewall/rules.d/*.rules 2&gt; /dev/null || true</programlisting
configuration files except <filename>/etc/shorewall/params</filename> and configuration files except <filename>/etc/shorewall/params</filename> and
<filename>/etc/shorewall/shorewall.conf</filename>.</para> <filename>/etc/shorewall/shorewall.conf</filename>.</para>
<para>Embedded scripts can be either single-line or multi-line. Single <para><emphasis role="bold">Note:</emphasis>In this section, '[' and ']'
line scripts take one of the following forms:</para> are meta-characters which indicate that what they enclose is optional and
may be omitted.</para>
<para>Single line scripts take one of the following forms:</para>
<itemizedlist> <itemizedlist>
<listitem> <listitem>
<para><emphasis role="bold">PERL</emphasis> &lt;<emphasis>perl <para><emphasis role="bold">[?]PERL</emphasis> &lt;<emphasis>perl
script</emphasis>&gt;</para> script</emphasis>&gt;</para>
</listitem> </listitem>
<listitem> <listitem>
<para><emphasis role="bold">SHELL</emphasis> &lt;<emphasis>shell <para><emphasis role="bold">[?]SHELL</emphasis> &lt;<emphasis>shell
script</emphasis>&gt;</para> script</emphasis>&gt;</para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
<para>The optional '?' is allowed in Shorewall versions 4.5.2 and
later.</para>
<para>Shell scripts run in a child shell process and their output is piped <para>Shell scripts run in a child shell process and their output is piped
back to the compiler which processes that output as if it were embedded at back to the compiler which processes that output as if it were embedded at
the point of the script.</para> the point of the script.</para>
@ -1501,18 +1626,14 @@ use Shorewall::Config ( qw/shorewall/ );</programlisting>
</orderedlist> </orderedlist>
<para>Multi-line scripts use one of the following forms:<programlisting><emphasis <para>Multi-line scripts use one of the following forms:<programlisting><emphasis
role="bold">BEGIN SHELL</emphasis> role="bold">[?]BEGIN SHELL</emphasis>
&lt;<emphasis>shell script</emphasis>&gt; &lt;<emphasis>shell script</emphasis>&gt;
<emphasis role="bold">END</emphasis> [ <emphasis role="bold">SHELL</emphasis> ]</programlisting><programlisting><emphasis <emphasis role="bold">[?]END</emphasis> [ <emphasis role="bold">SHELL</emphasis> ]</programlisting><programlisting><emphasis
role="bold">BEGIN PERL</emphasis> [;] role="bold">[?]BEGIN PERL</emphasis> [;]
&lt;<emphasis>perl script</emphasis>&gt; &lt;<emphasis>perl script</emphasis>&gt;
<emphasis role="bold">END</emphasis> [ <emphasis role="bold">PERL</emphasis> ] [<emphasis <emphasis role="bold">[?]END</emphasis> [ <emphasis role="bold">PERL</emphasis> ] [<emphasis
role="bold">;</emphasis>]</programlisting></para> role="bold">;</emphasis>]</programlisting>As above, the optional
leading '?' is allowed in Shorewall versions 4.5.2 and later.</para>
<para><emphasis role="bold">Note: </emphasis>The '[' and ']' above are
meta-characters which indicate that what they enclose is optional and may
be omitted. So you may follow PERL with a semicolon ( ';') or you may omit
the semicolon.</para>
</section> </section>
<section id="dnsnames"> <section id="dnsnames">