mirror of
https://gitlab.com/shorewall/code.git
synced 2024-12-24 07:08:53 +01:00
372f436520
git-svn-id: https://shorewall.svn.sourceforge.net/svnroot/shorewall/trunk@3601 fbd18981-670d-0410-9b5c-8dc0c1a9a2bb
577 lines
22 KiB
XML
577 lines
22 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
|
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
|
<article lang="en" status="">
|
|
<!--$Id$-->
|
|
|
|
<articleinfo>
|
|
<title>Macros</title>
|
|
|
|
<authorgroup>
|
|
<author>
|
|
<firstname>Tom</firstname>
|
|
|
|
<surname>Eastep</surname>
|
|
</author>
|
|
|
|
<author>
|
|
<firstname>Cristian</firstname>
|
|
|
|
<surname>Rodríguez</surname>
|
|
</author>
|
|
</authorgroup>
|
|
|
|
<pubdate>2005-11-18</pubdate>
|
|
|
|
<copyright>
|
|
<year>2005</year>
|
|
|
|
<holder>Thomas M. Eastep</holder>
|
|
</copyright>
|
|
|
|
<legalnotice>
|
|
<para>Permission is granted to copy, distribute and/or modify this
|
|
document under the terms of the GNU Free Documentation License, Version
|
|
1.2 or any later version published by the Free Software Foundation; with
|
|
no Invariant Sections, with no Front-Cover, and with no Back-Cover
|
|
Texts. A copy of the license is included in the section entitled
|
|
<quote><ulink url="GnuCopyright.htm">GNU Free Documentation
|
|
License</ulink></quote>.</para>
|
|
</legalnotice>
|
|
</articleinfo>
|
|
|
|
<caution>
|
|
<para><emphasis role="bold">This article applies to Shorewall 3.0 and
|
|
later. If you are running a version of Shorewall earlier than Shorewall
|
|
3.0.0 then please see the documentation for that
|
|
release.</emphasis></para>
|
|
</caution>
|
|
|
|
<section>
|
|
<title>Overview of Shorewall Macros?</title>
|
|
|
|
<para>Shorewall macros allow a symbolic name to be associated with a
|
|
series of one or more iptables rules. The symbolic name may appear in the
|
|
ACTION column of an <filename><ulink
|
|
url="Documentation.htm#Rules">/etc/shorewall/rules</ulink></filename> file
|
|
entry and in the TARGET column of an action in which case, the traffic
|
|
matching that rules file entry will be passed to the series of iptables
|
|
rules named by the macro.</para>
|
|
|
|
<para>Macros can be thought of as templates. When a macro is invoked in an
|
|
<filename>/etc/shorewall/rules</filename> entry, it may be qualified by a
|
|
logging specification (log level and optionally a log tag). The presence
|
|
of the log level/tag causes a modified series of rules to be generated in
|
|
which each packet/rule match within the macro causes a log message to be
|
|
generated.</para>
|
|
|
|
<para>There are two types of Shorewall macros:</para>
|
|
|
|
<orderedlist>
|
|
<listitem>
|
|
<para>Standard Macros. These macros are released as part of Shorewall.
|
|
They are defined in macros.* files in <filename
|
|
class="directory">/usr/share/shorewall</filename>. Each
|
|
<filename>macros.*</filename> file has a comment at the beginning of
|
|
the file that describes what the macro does. As an example, here is
|
|
the definition of the <firstterm>SMB</firstterm> standard
|
|
macro.</para>
|
|
|
|
<programlisting>#
|
|
# Shorewall 3.0 /usr/share/shorewall/macro.SMB
|
|
#
|
|
# Handle Microsoft SMB traffic. You need to invoke this macro in
|
|
# both directions.
|
|
#
|
|
######################################################################################
|
|
#TARGET SOURCE DEST PROTO DEST SOURCE RATE USER/
|
|
# PORT PORT(S) LIMIT GROUP
|
|
PARAM - - udp 135,445
|
|
PARAM - - udp 137:139
|
|
PARAM - - udp 1024: 137
|
|
PARAM - - tcp 135,139,445
|
|
#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE</programlisting>
|
|
|
|
<para>If you wish to modify one of the standard macros, do not modify
|
|
the definition in /usr/share/shorewall. Rather, copy the file to
|
|
<filename class="directory">/etc/shorewall</filename> (or somewhere
|
|
else on your CONFIG_PATH) and modify the copy.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>User-defined Macros. These macros are created by end-users. They
|
|
are defined in macros.* files in /etc/shorewall or in another
|
|
directory listed in your CONFIG_PATH (defined in <ulink
|
|
url="Documentation.htm#Conf">/etc/shorewall/shorewall.conf</ulink>).</para>
|
|
</listitem>
|
|
</orderedlist>
|
|
|
|
<para>Most Standard Macros are <firstterm>parameterized</firstterm>. That
|
|
means that you specify what you want to do (ACCEPT, DROP, REJECT, etc.)
|
|
when you invoke the macro. The SMB macro shown above is parameterized
|
|
(note PARAM in the TARGET column). When invoking a parameterized macro,
|
|
you follow the name of the macro with a slash ("/") and the action that
|
|
you want to substitute for PARAM.</para>
|
|
|
|
<para>Example:</para>
|
|
|
|
<blockquote>
|
|
<para>/etc/shorewall/rules:</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
SMB/ACCEPT loc fw </programlisting>
|
|
|
|
<para>The above is equivalent to coding the following series of
|
|
rules:</para>
|
|
|
|
<programlisting>#TARGET SOURCE DEST PROTO DEST PORT(s)
|
|
ACCEPT loc fw udp 135,445
|
|
ACCEPT loc fw udp 137:139
|
|
ACCEPT loc fw udp 1024: 137
|
|
ACCEPT loc fw tcp 135,139,445</programlisting>
|
|
</blockquote>
|
|
|
|
<para>Logging is covered in <link linkend="Logging">a following
|
|
section</link>. The other columns are treated as follows:</para>
|
|
|
|
<variablelist>
|
|
<varlistentry>
|
|
<term>SOURCE and DEST</term>
|
|
|
|
<listitem>
|
|
<para>If a value other than "-" appears in both the macro body and
|
|
in the invocation of the macro, then the value in the invocation is
|
|
examined and the appropriate action is taken (you will want to be
|
|
running Shorewall 3.0.1 or later). If the value in the invocation
|
|
appears to be an address (IP or MAC) or the name of an ipset, then
|
|
it is placed after the value in the macro body. Otherwise, it is
|
|
placed before the value in the macro body.</para>
|
|
|
|
<para>Example 1:</para>
|
|
|
|
<blockquote>
|
|
<para>/etc/shorewall/macro.SMTP</para>
|
|
|
|
<programlisting>#TARGET SOURCE DEST PROTO DEST PORT(S)
|
|
PARAM - loc tcp 25</programlisting>
|
|
|
|
<para>/etc/shorewall/rules</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
SMTP/DNAT:info net 192.168.1.5</programlisting>
|
|
|
|
<para>This would be equivalent to coding the following directly in
|
|
/etc/shorewall/rules</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
DNAT:info net loc:192.168.1.5 tcp 25</programlisting>
|
|
</blockquote>
|
|
|
|
<para>Example 2:</para>
|
|
|
|
<blockquote>
|
|
<para>/etc/shorewall/macro.SMTP</para>
|
|
|
|
<programlisting>#TARGET SOURCE DEST PROTO DEST PORT(S)
|
|
PARAM - 192.168.1.5 tcp 25</programlisting>
|
|
|
|
<para>/etc/shorewall/rules</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
SMTP/DNAT:info net loc</programlisting>
|
|
|
|
<para>This would be equivalent to coding the following directly in
|
|
/etc/shorewall/rules</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
DNAT:info net loc:192.168.1.5 tcp 25</programlisting>
|
|
</blockquote>
|
|
|
|
<para>Beginning with Shorewall 3.1, you may also specify SOURCE or
|
|
DEST in the SOURCE and DEST columns. This allows you to define
|
|
macros that work in both directions.</para>
|
|
|
|
<para>Example 3:</para>
|
|
|
|
<blockquote>
|
|
<para><filename>/etc/shorewall/macro.SMBBI</filename> (Note: there
|
|
is already a macro like this released as part of Shorewall 3.1 and
|
|
later):</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST SOURCE ORIGINAL RATE USER/
|
|
# PORT PORT(S) DEST LIMIT GROUP
|
|
PARAM - - udp 135,445
|
|
PARAM - - udp 137:139
|
|
PARAM - - udp 1024: 137
|
|
PARAM - - tcp 135,139,445
|
|
PARAM DEST SOURCE udp 135,445
|
|
PARAM DEST SOURCE udp 137:139
|
|
PARAM DEST SOURCE udp 1024: 137
|
|
PARAM DEST SOURCE tcp 135,139,445
|
|
#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE</programlisting>
|
|
|
|
<para>/etc/shorewall/rules:</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
SMBBI/ACCEPT loc fw</programlisting>
|
|
|
|
<para>This would be equivalent to coding the following directly in
|
|
/etc/shorewall/rules</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
ACCEPT loc fw udp 135,445
|
|
ACCEPT loc fw udp 137:139
|
|
ACCEPT loc fw udp 1024: 137
|
|
ACCEPT loc fw tcp 135,139,445
|
|
ACCEPT fw loc udp 135,445
|
|
ACCEPT fw loc udp 137:139
|
|
ACCEPT fw loc udp 1024: 137
|
|
ACCEPT fw loc tcp 135,139,445</programlisting>
|
|
</blockquote>
|
|
</listitem>
|
|
</varlistentry>
|
|
|
|
<varlistentry>
|
|
<term>Remaining columns</term>
|
|
|
|
<listitem>
|
|
<para>Any value in the invocation replaces the value in the rule in
|
|
the macro.</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
</variablelist>
|
|
|
|
<para>One remaining restriction must be mentioned: macros that are invoked
|
|
from actions cannot themselves invoke other actions.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Defining your own Macros</title>
|
|
|
|
<para>To define a new macro:</para>
|
|
|
|
<orderedlist>
|
|
<listitem>
|
|
<para>Macro names must be valid shell variable names ((must begin with
|
|
a letter and be composed of letters, digits and underscore characters)
|
|
as well as valid Netfilter chain names.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Copy /usr/share/shorewall/macro.template to
|
|
<filename>/etc/shorewall/macro.MacroName</filename> (for example, if
|
|
your new macro name is <quote>Foo</quote> then copy
|
|
<filename>/usr/share/shorewall/macro.template</filename> to
|
|
<filename>/etc/shorewall/macro.Foo</filename>).</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Now modify the new file to define the new macro.</para>
|
|
</listitem>
|
|
</orderedlist>
|
|
|
|
<para>Columns in the macro.template file are as follows:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>ACTION - ACCEPT, DROP, REJECT, DNAT, DNAT-, REDIRECT, CONTINUE,
|
|
LOG, QUEUE, PARAM or an action name. Note that a macro may not invoke
|
|
another macro.</para>
|
|
|
|
<simplelist>
|
|
<member>ACCEPT - allow the connection request</member>
|
|
|
|
<member>ACCEPT+ - like ACCEPT but also excludes the connection from
|
|
any subsequent DNAT[-] or REDIRECT[-] rules.</member>
|
|
|
|
<member>NONAT - Excludes the connection from any subsequent DNAT[-]
|
|
or REDIRECT[-] rules but doesn't generate a rule to accept the
|
|
traffic.</member>
|
|
|
|
<member>DROP - ignore the request</member>
|
|
|
|
<member>REJECT - disallow the request and return an icmp unreachable
|
|
or an RST packet.</member>
|
|
|
|
<member>DNAT - Forward the request to another address (and
|
|
optionally another port).</member>
|
|
|
|
<member>DNAT- - Advanced users only. Like DNAT but only generates
|
|
the DNAT iptables rule and not the companion ACCEPT rule.</member>
|
|
|
|
<member>SAME - Similar to DNAT except that the port may not be
|
|
remapped and when multiple server addresses are listed, all requests
|
|
from a given remote system go to the same server.</member>
|
|
|
|
<member>SAME- - Advanced users only. Like SAME but only generates
|
|
the SAME iptables rule and not the companion ACCEPT rule.</member>
|
|
|
|
<member>REDIRECT - Redirect the request to a local port on the
|
|
firewall.</member>
|
|
|
|
<member>REDIRECT- - Advanced users only. Like REDIRECT but only
|
|
generates the REDIRECT iptables rule and not the companion ACCEPT
|
|
rule.</member>
|
|
|
|
<member>CONTINUE - (For experts only). Do not process any of the
|
|
following rules for this (source zone,destination zone). If The
|
|
source and/or destination If the address falls into a zone defined
|
|
later in /etc/shorewall/zones, this connection request will be
|
|
passed to the rules defined for that (those) zone(s).</member>
|
|
|
|
<member>LOG - Simply log the packet and continue.</member>
|
|
|
|
<member>QUEUE - Queue the packet to a user-space application such as
|
|
ftwall (http://p2pwall.sf.net).</member>
|
|
</simplelist>
|
|
|
|
<para>The ACTION may optionally be followed by ":" and a syslog log
|
|
level (e.g, REJECT:info or DNAT:debug). This causes the packet to be
|
|
logged at the specified level.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>SOURCE - Source hosts to which the rule applies. A
|
|
comma-separated list of subnets and/or hosts. Hosts may be specified
|
|
by IP or MAC address; mac addresses must begin with <quote>~</quote>
|
|
and must use <quote>-</quote> as a separator.</para>
|
|
|
|
<para>Alternatively, clients may be specified by interface name. For
|
|
example, eth1 specifies a client that communicates with the firewall
|
|
system through eth1. This may be optionally followed by another colon
|
|
(<quote>:</quote>) and an IP/MAC/subnet address as described above
|
|
(e.g. eth1:192.168.1.5).</para>
|
|
|
|
<para>May also contain 'DEST' as described above.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>DEST - Location of Server. Same as above with the exception that
|
|
MAC addresses are not allowed.</para>
|
|
|
|
<para>Unlike in the SOURCE column, you may specify a range of up to
|
|
256 IP addresses using the syntax <<emphasis>first
|
|
ip</emphasis>>-<<emphasis>last ip</emphasis>>.</para>
|
|
|
|
<para>May also contain 'SOURCE' as described above.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>PROTO - Protocol - Must be <quote>tcp</quote>,
|
|
<quote>udp</quote>, <quote>icmp</quote>, a number, or
|
|
<quote>all</quote>.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>DEST PORT(S) - Destination Ports. A comma-separated list of Port
|
|
names (from <filename>/etc/services</filename>), port numbers or port
|
|
ranges; if the protocol is <quote>icmp</quote>, this column is
|
|
interpreted as the destination icmp-type(s).</para>
|
|
|
|
<para>A port range is expressed as <<emphasis>low
|
|
port</emphasis>>:<<emphasis>high port</emphasis>>.</para>
|
|
|
|
<para>This column is ignored if PROTOCOL = all but must be entered if
|
|
any of the following fields are supplied. In that case, it is
|
|
suggested that this field contain <quote>-</quote>.</para>
|
|
|
|
<para>If your kernel contains multi-port match support, then only a
|
|
single Netfilter rule will be generated if in this list and in the
|
|
CLIENT PORT(S) list below:</para>
|
|
|
|
<orderedlist>
|
|
<listitem>
|
|
<para>There are 15 or less ports listed.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>No port ranges are included.</para>
|
|
</listitem>
|
|
</orderedlist>
|
|
|
|
<para>Otherwise, a separate rule will be generated for each
|
|
port.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>SOURCE PORT(S) - Port(s) used by the client. If omitted, any
|
|
source port is acceptable. Specified as a comma-separated list of port
|
|
names, port numbers or port ranges.</para>
|
|
|
|
<para>If you don't want to restrict client ports but need to specify
|
|
an ADDRESS in the next column, then place "-" in this column.</para>
|
|
|
|
<para>If your kernel contains multi-port match support, then only a
|
|
single Netfilter rule will be generated if in this list and in the
|
|
DEST PORT(S) list above:</para>
|
|
|
|
<orderedlist>
|
|
<listitem>
|
|
<para>There are 15 or less ports listed.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>No port ranges are included.</para>
|
|
</listitem>
|
|
</orderedlist>
|
|
|
|
<para>Otherwise, a separate rule will be generated for each
|
|
port.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>RATE LIMIT - You may rate-limit the rule by placing a value in
|
|
this column:</para>
|
|
|
|
<para><programlisting> <<emphasis>rate</emphasis>>/<<emphasis>interval</emphasis>>[:<<emphasis>burst</emphasis>>]</programlisting>where
|
|
<<emphasis>rate</emphasis>> is the number of connections per
|
|
<<emphasis>interval</emphasis>> (<quote>sec</quote> or
|
|
<quote>min</quote>) and <<emphasis>burst</emphasis>> is the
|
|
largest burst permitted. If no <<emphasis>burst</emphasis>> is
|
|
given, a value of 5 is assumed. There may be no whitespace embedded in
|
|
the specification.</para>
|
|
|
|
<para><programlisting> Example: 10/sec:20</programlisting></para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>USER/GROUP - For output rules (those with the firewall as their
|
|
source), you may control connections based on the effective UID and/or
|
|
GID of the process requesting the connection. This column can contain
|
|
any of the following:</para>
|
|
|
|
<simplelist>
|
|
<member>[!]<<emphasis>user number</emphasis>>[:]</member>
|
|
|
|
<member>[!]<<emphasis>user name</emphasis>>[:]</member>
|
|
|
|
<member>[!]:<<emphasis>group number</emphasis>></member>
|
|
|
|
<member>[!]:<<emphasis>group name</emphasis>></member>
|
|
|
|
<member>[!]<<emphasis>user
|
|
number</emphasis>>:<<emphasis>group
|
|
number</emphasis>></member>
|
|
|
|
<member>[!]<<emphasis>user
|
|
name</emphasis>>:<<emphasis>group
|
|
number</emphasis>></member>
|
|
|
|
<member>[!]<<emphasis>user
|
|
inumber</emphasis>>:<<emphasis>group
|
|
name</emphasis>></member>
|
|
|
|
<member>[!]<<emphasis>user
|
|
name</emphasis>>:<<emphasis>group name</emphasis>></member>
|
|
|
|
<member>[!]+<<emphasis>program name</emphasis>> (Note: support
|
|
for this form was removed from Netfilter in kernel version
|
|
2.6.14).</member>
|
|
</simplelist>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>Omitted column entries should be entered using a dash ("-:).</para>
|
|
|
|
<para>Example:</para>
|
|
|
|
<para><phrase><filename>/etc/shorewall/macro.LogAndAccept</filename></phrase><programlisting> LOG:info
|
|
ACCEPT</programlisting></para>
|
|
|
|
<para>To use your macro, in <filename>/etc/shorewall/rules</filename> you
|
|
might do something like:</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
LogAndAccept loc $FW tcp 22</programlisting>
|
|
</section>
|
|
|
|
<section id="Logging">
|
|
<title>Macros and Logging</title>
|
|
|
|
<para>Specifying a log level in a rule that invokes a user- or
|
|
Shorewall-defined action will cause each rule in the macro to be logged
|
|
with the specified level (and tag).</para>
|
|
|
|
<para>The extent to which logging of macro rules occur is governed by the
|
|
following:</para>
|
|
|
|
<orderedlist>
|
|
<listitem>
|
|
<para>When you invoke a macro and specify a log level, only those
|
|
rules in the macro that have no log level will be changed to log at
|
|
the level specified at the action invocation.</para>
|
|
|
|
<para>Example:</para>
|
|
|
|
<para>/etc/shorewall/macro.foo</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
ACCEPT - - tcp 22
|
|
bar:info</programlisting>
|
|
|
|
<para>/etc/shorewall/rules:</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
foo:debug $FW net</programlisting>
|
|
|
|
<para>Logging in the invoked 'foo' macro will be as if foo had been
|
|
defined as:</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
ACCEPT:debug - - tcp 22
|
|
bar:info</programlisting>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>If you follow the log level with "!" then logging will be at
|
|
that level for all rules recursively invoked by the macro.</para>
|
|
|
|
<para>Example:</para>
|
|
|
|
<para>/etc/shorewall/macro.foo</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
ACCEPT - - tcp 22
|
|
bar:info</programlisting>
|
|
|
|
<para>/etc/shorewall/rules:</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
foo:debug! $FW net</programlisting>
|
|
|
|
<para>Logging in the invoked 'foo' macro will be as if foo had been
|
|
defined as:</para>
|
|
|
|
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
|
|
ACCEPT:debug - - tcp 22
|
|
bar:debug</programlisting>
|
|
</listitem>
|
|
</orderedlist>
|
|
</section>
|
|
|
|
<section>
|
|
<title>How do I know if I should create an Action or a Macro?</title>
|
|
|
|
<para>While actions and macros perform similar functions, in any given
|
|
case you will generally find that one is more appropriate than the
|
|
other.</para>
|
|
|
|
<orderedlist>
|
|
<listitem>
|
|
<para>You can not associate an Extension Script with a macro <ulink
|
|
url="Actions.html#Extension">the way that you can with an
|
|
Action</ulink>. So if you need access to iptables features not
|
|
directly supported by Shorewall then you must use an action.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Macros are expanded in-line while each action is it's own chain.
|
|
So if there are a lot of rules involved in your new action/macro then
|
|
it is generally better to use an action than a macro. Only the packets
|
|
selected when you invoke the action are directed to the corresponding
|
|
chain. On the other hand, if there are only one or two rules involved
|
|
in what you want to do then a macro is more efficient.</para>
|
|
</listitem>
|
|
</orderedlist>
|
|
</section>
|
|
</article> |