| 1 | ### -*-sh-*- |
| 2 | ### |
| 3 | ### Utility functions for firewall scripts |
| 4 | ### |
| 5 | ### (c) 2008 Mark Wooding |
| 6 | ### |
| 7 | |
| 8 | ###----- Licensing notice --------------------------------------------------- |
| 9 | ### |
| 10 | ### This program is free software; you can redistribute it and/or modify |
| 11 | ### it under the terms of the GNU General Public License as published by |
| 12 | ### the Free Software Foundation; either version 2 of the License, or |
| 13 | ### (at your option) any later version. |
| 14 | ### |
| 15 | ### This program is distributed in the hope that it will be useful, |
| 16 | ### but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | ### GNU General Public License for more details. |
| 19 | ### |
| 20 | ### You should have received a copy of the GNU General Public License |
| 21 | ### along with this program; if not, write to the Free Software Foundation, |
| 22 | ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 23 | |
| 24 | m4_divert(20)m4_dnl |
| 25 | ###-------------------------------------------------------------------------- |
| 26 | ### Utility functions. |
| 27 | |
| 28 | ## doit COMMAND ARGS... |
| 29 | ## |
| 30 | ## If debugging, print the COMMAND and ARGS. If serious, execute them. |
| 31 | run () { |
| 32 | set -e |
| 33 | if [ "$FW_DEBUG" ]; then echo "* $*"; fi |
| 34 | if ! [ "$FW_NOACT" ]; then "$@"; fi |
| 35 | } |
| 36 | |
| 37 | ## trace MESSAGE... |
| 38 | ## |
| 39 | ## If debugging, print the MESSAGE. |
| 40 | trace () { |
| 41 | set -e |
| 42 | if [ "$FW_DEBUG" ]; then echo "$*"; fi |
| 43 | } |
| 44 | |
| 45 | ## defport NAME NUMBER |
| 46 | ## |
| 47 | ## Define $port_NAME to be NUMBER. |
| 48 | defport () { |
| 49 | name=$1 number=$2 |
| 50 | eval port_$name=$number |
| 51 | } |
| 52 | |
| 53 | ## defproto NAME NUMBER |
| 54 | ## |
| 55 | ## Define $proto_NAME to be NUMBER. |
| 56 | defproto () { |
| 57 | name=$1 number=$2 |
| 58 | eval proto_$name=$number |
| 59 | } |
| 60 | |
| 61 | ## addword VAR WORD |
| 62 | ## |
| 63 | ## Adds WORD to the value of the shell variable VAR, if it's not there |
| 64 | ## already. Words are separated by a single space; no leading or trailing |
| 65 | ## spaces are introduced. |
| 66 | addword () { |
| 67 | var=$1 word=$2 |
| 68 | eval val=\$$var |
| 69 | case " $val " in |
| 70 | *" $word "*) ;; |
| 71 | *) eval "$var=\${$var:+\$val }\$word" ;; |
| 72 | esac |
| 73 | } |
| 74 | |
| 75 | m4_divert(38)m4_dnl |
| 76 | ###-------------------------------------------------------------------------- |
| 77 | ### Utility chains (used by function definitions). |
| 78 | |
| 79 | m4_divert(20)m4_dnl |
| 80 | ###-------------------------------------------------------------------------- |
| 81 | ### Basic chain constructions. |
| 82 | |
| 83 | ## ip46tables ARGS ... |
| 84 | ## |
| 85 | ## Do the same thing for `iptables' and `ip6tables'. |
| 86 | ip46tables () { |
| 87 | set -e |
| 88 | iptables "$@" |
| 89 | ip6tables "$@" |
| 90 | } |
| 91 | |
| 92 | ## clearchain CHAIN CHAIN ... |
| 93 | ## |
| 94 | ## Ensure that the named chains exist and are empty. |
| 95 | clearchain () { |
| 96 | set -e |
| 97 | for chain; do |
| 98 | case $chain in |
| 99 | *:*) table=${chain%:*} chain=${chain#*:} ;; |
| 100 | *) table=filter ;; |
| 101 | esac |
| 102 | run ip46tables -t $table -N $chain 2>/dev/null || : |
| 103 | done |
| 104 | } |
| 105 | |
| 106 | ## makeset SET TYPE [PARAMS] |
| 107 | ## |
| 108 | ## Ensure that the named ipset exists. Don't clear it. |
| 109 | makeset () { |
| 110 | set -e |
| 111 | name=$1; shift |
| 112 | if ipset -nL | grep -q "^Name: $name$"; then |
| 113 | : |
| 114 | else |
| 115 | ipset -N "$name" "$@" |
| 116 | fi |
| 117 | } |
| 118 | |
| 119 | ## errorchain CHAIN ACTION ARGS ... |
| 120 | ## |
| 121 | ## Make a chain which logs a message and then invokes some other action, |
| 122 | ## typically REJECT. Log messages are prefixed by `fw: CHAIN'. |
| 123 | errorchain () { |
| 124 | set -e |
| 125 | chain=$1; shift |
| 126 | case $chain in |
| 127 | *:*) table=${chain%:*} chain=${chain#*:} ;; |
| 128 | *) table=filter ;; |
| 129 | esac |
| 130 | clearchain $table:$chain |
| 131 | run ip46tables -t $table -A $chain -j LOG \ |
| 132 | -m limit --limit 3/minute --limit-burst 10 \ |
| 133 | --log-prefix "fw: $chain " --log-level notice |
| 134 | run ip46tables -t $table -A $chain -j "$@" \ |
| 135 | -m limit --limit 20/second --limit-burst 100 |
| 136 | run ip46tables -t $table -A $chain -j DROP |
| 137 | } |
| 138 | |
| 139 | m4_divert(20)m4_dnl |
| 140 | ###-------------------------------------------------------------------------- |
| 141 | ### Basic option setting. |
| 142 | |
| 143 | ## setopt OPTION VALUE |
| 144 | ## |
| 145 | ## Set an IP sysctl. |
| 146 | setopt () { |
| 147 | set -e |
| 148 | opt=$1 val=$2 |
| 149 | any=nil |
| 150 | for ver in ipv4 ipv6; do |
| 151 | if [ -f /proc/sys/net/$ver/$opt ]; then |
| 152 | run sysctl -q net/$ver/$opt="$val" |
| 153 | any=t |
| 154 | fi |
| 155 | done |
| 156 | case $any in |
| 157 | nil) echo >&2 "$0: unknown IP option $opt"; exit 1 ;; |
| 158 | esac |
| 159 | } |
| 160 | |
| 161 | ## setdevopt OPTION VALUE [INTERFACES ...] |
| 162 | ## |
| 163 | ## Set an IP interface-level sysctl. |
| 164 | setdevopt () { |
| 165 | set -e |
| 166 | opt=$1 val=$2; shift 2 |
| 167 | case "$#,$1" in |
| 168 | 0, | 1,all) |
| 169 | set -- $( |
| 170 | seen=: |
| 171 | for ver in ipv4 ipv6; do |
| 172 | cd /proc/sys/net/$ver/conf |
| 173 | for i in *; do |
| 174 | [ -f $i/$opt ] || continue |
| 175 | case "$seen" in (*:$i:*) continue ;; esac |
| 176 | echo $i |
| 177 | done |
| 178 | done) |
| 179 | ;; |
| 180 | esac |
| 181 | for i in "$@"; do |
| 182 | any=nil |
| 183 | for ver in ipv4 ipv6; do |
| 184 | if [ -f /proc/sys/net/$ver/conf/$i/$opt ]; then |
| 185 | any=t |
| 186 | run sysctl -q net/ipv4/conf/$i/$opt="$val" |
| 187 | fi |
| 188 | done |
| 189 | case $any in |
| 190 | nil) echo >&2 "$0: unknown device option $opt"; exit 1 ;; |
| 191 | esac |
| 192 | done |
| 193 | } |
| 194 | |
| 195 | m4_divert(20)m4_dnl |
| 196 | ###-------------------------------------------------------------------------- |
| 197 | ### Packet filter construction. |
| 198 | |
| 199 | ## conntrack CHAIN |
| 200 | ## |
| 201 | ## Add connection tracking to CHAIN, and allow obvious stuff. |
| 202 | conntrack () { |
| 203 | set -e |
| 204 | chain=$1 |
| 205 | run ip46tables -A $chain -p tcp -m state \ |
| 206 | --state ESTABLISHED,RELATED -j ACCEPT |
| 207 | run ip46tables -A $chain -p tcp ! --syn -g bad-tcp |
| 208 | } |
| 209 | |
| 210 | ## commonrules CHAIN |
| 211 | ## |
| 212 | ## Add standard IP filtering rules to the CHAIN. |
| 213 | commonrules () { |
| 214 | set -e |
| 215 | chain=$1 |
| 216 | |
| 217 | ## Pass fragments through, assuming that the eventual destination will sort |
| 218 | ## things out properly. Except for TCP, that is, which should never be |
| 219 | ## fragmented. This is an extra pain for ip6tables, which doesn't provide |
| 220 | ## a pleasant way to detect non-initial fragments. |
| 221 | run iptables -A $chain -p tcp -f -g tcp-fragment |
| 222 | run iptables -A $chain -f -j ACCEPT |
| 223 | run ip6tables -A $chain -p tcp -g tcp-fragment \ |
| 224 | -m ipv6header --soft --header frag |
| 225 | run ip6tables -A $chain -j accept-non-init-frag |
| 226 | } |
| 227 | |
| 228 | m4_divert(38)m4_dnl |
| 229 | ## Accept a non-initial fragment. This is only needed by IPv6, to work |
| 230 | ## around a deficiency in the option parser. |
| 231 | run ip6tables -N accept-non-init-frag |
| 232 | run ip6tables -A accept-non-init-frag -j RETURN \ |
| 233 | -m frag --fragfirst |
| 234 | run ip6tables -A accept-non-init-frag -j ACCEPT |
| 235 | |
| 236 | m4_divert(20)m4_dnl |
| 237 | ## allowservices CHAIN PROTO SERVICE ... |
| 238 | ## |
| 239 | ## Add rules to allow the SERVICES on the CHAIN. |
| 240 | allowservices () { |
| 241 | set -e |
| 242 | chain=$1 proto=$2; shift 2 |
| 243 | count=0 |
| 244 | list= |
| 245 | for svc; do |
| 246 | case $svc in |
| 247 | *:*) |
| 248 | n=2 |
| 249 | left=${svc%:*} right=${svc#*:} |
| 250 | case $left in *[!0-9]*) eval left=\$port_$left ;; esac |
| 251 | case $right in *[!0-9]*) eval right=\$port_$right ;; esac |
| 252 | svc=$left:$right |
| 253 | ;; |
| 254 | *) |
| 255 | n=1 |
| 256 | case $svc in *[!0-9]*) eval svc=\$port_$svc ;; esac |
| 257 | ;; |
| 258 | esac |
| 259 | case $svc in |
| 260 | *: | :* | "" | *[!0-9:]*) |
| 261 | echo >&2 "Bad service name" |
| 262 | exit 1 |
| 263 | ;; |
| 264 | esac |
| 265 | count=$(( $count + $n )) |
| 266 | if [ $count -gt 15 ]; then |
| 267 | run ip46tables -A $chain -p $proto -m multiport -j ACCEPT \ |
| 268 | --destination-ports ${list#,} |
| 269 | list= count=$n |
| 270 | fi |
| 271 | list=$list,$svc |
| 272 | done |
| 273 | case $list in |
| 274 | "") |
| 275 | ;; |
| 276 | ,*,*) |
| 277 | run ip46tables -A $chain -p $proto -m multiport -j ACCEPT \ |
| 278 | --destination-ports ${list#,} |
| 279 | ;; |
| 280 | *) |
| 281 | run ip46tables -A $chain -p $proto -j ACCEPT \ |
| 282 | --destination-port ${list#,} |
| 283 | ;; |
| 284 | esac |
| 285 | } |
| 286 | |
| 287 | ## ntpclient CHAIN NTPSERVER ... |
| 288 | ## |
| 289 | ## Add rules to CHAIN to allow NTP with NTPSERVERs. |
| 290 | ntpclient () { |
| 291 | set -e |
| 292 | ntpchain=$1; shift |
| 293 | |
| 294 | clearchain ntp-servers |
| 295 | for ntp; do run iptables -A ntp-servers -j ACCEPT -s $ntp; done |
| 296 | run iptables -A $ntpchain -j ntp-servers \ |
| 297 | -p udp --source-port 123 --destination-port 123 |
| 298 | } |
| 299 | |
| 300 | ## dnsresolver CHAIN |
| 301 | ## |
| 302 | ## Add rules to allow CHAIN to be a DNS resolver. |
| 303 | dnsresolver () { |
| 304 | set -e |
| 305 | chain=$1 |
| 306 | for p in tcp udp; do |
| 307 | run ip46tables -A $chain -j ACCEPT \ |
| 308 | -m state --state ESTABLISHED \ |
| 309 | -p $p --source-port 53 |
| 310 | done |
| 311 | } |
| 312 | |
| 313 | ## openports CHAIN [MIN MAX] |
| 314 | ## |
| 315 | ## Add rules to CHAIN to allow the open ports. |
| 316 | openports () { |
| 317 | set -e |
| 318 | chain=$1; shift |
| 319 | [ $# -eq 0 ] && set -- $open_port_min $open_port_max |
| 320 | run ip46tables -A $chain -p tcp -g interesting --destination-port $1:$2 |
| 321 | run ip46tables -A $chain -p udp -g interesting --destination-port $1:$2 |
| 322 | } |
| 323 | |
| 324 | m4_divert(20)m4_dnl |
| 325 | ###-------------------------------------------------------------------------- |
| 326 | ### Packet classification. |
| 327 | ### |
| 328 | ### See `classify.m4' for an explanation of how the firewall machinery for |
| 329 | ### packet classification works. |
| 330 | ### |
| 331 | ### A list of all network names is kept in `allnets'. For each network NET, |
| 332 | ### shell variables are defined describing their properties. |
| 333 | ### |
| 334 | ### net_class_NET The class of the network, as defined by |
| 335 | ### `defnetclass'. |
| 336 | ### net_inet_NET List of IPv4 address ranges in the network. |
| 337 | ### net_inet6_NET List of IPv6 address ranges in the network. |
| 338 | ### net_fwd_NET List of other networks that this one forwards to. |
| 339 | ### net_hosts_NET List of hosts known to be in the network. |
| 340 | ### host_inet_HOST IPv4 address of the named HOST. |
| 341 | ### host_inet6_HOST IPv6 address of the named HOST. |
| 342 | ### |
| 343 | ### Similarly, a list of hosts is kept in `allhosts', and for each host HOST, |
| 344 | ### a shell variables are defined: |
| 345 | ### |
| 346 | ### host_ifaces_HOST List of interfaces for this host and the networks |
| 347 | ### they attach to, in the form IFACE=NET. |
| 348 | |
| 349 | ## defbitfield NAME WIDTH |
| 350 | ## |
| 351 | ## Defines MASK_NAME and BIT_NAME symbolic constants for dealing with |
| 352 | ## bitfields: x << BIT_NAME yields the value x in the correct position, and |
| 353 | ## ff & MASK_NAME extracts the corresponding value. |
| 354 | defbitfield () { |
| 355 | set -e |
| 356 | name=$1 width=$2 |
| 357 | eval MASK_$name=$(( (1 << $width) - 1 << $bitindex )) |
| 358 | eval BIT_$name=$bitindex |
| 359 | bitindex=$(( $bitindex + $width )) |
| 360 | } |
| 361 | |
| 362 | ## Define the layout of the bitfield. |
| 363 | bitindex=0 |
| 364 | defbitfield MASK 16 |
| 365 | defbitfield FROM 4 |
| 366 | defbitfield TO 4 |
| 367 | |
| 368 | ## defnetclass NAME FORWARD-TO... |
| 369 | ## |
| 370 | ## Defines a netclass called NAME, which is allowed to forward to the |
| 371 | ## FORWARD-TO netclasses. |
| 372 | ## |
| 373 | ## For each netclass, constants from_NAME and to_NAME are defined as the |
| 374 | ## appropriate values in the FROM and TO fields (i.e., not including any mask |
| 375 | ## bits). |
| 376 | ## |
| 377 | ## This function also establishes mangle chains mark-from-NAME and |
| 378 | ## mark-to-NAME for applying the appropriate mark bits to the packet. |
| 379 | ## |
| 380 | ## Because it needs to resolve forward references, netclasses must be defined |
| 381 | ## in a two-pass manner, using a loop of the form |
| 382 | ## |
| 383 | ## for pass in 1 2; do netclassindex=0; ...; done |
| 384 | netclassess= |
| 385 | defnetclass () { |
| 386 | set -e |
| 387 | name=$1; shift |
| 388 | case $pass in |
| 389 | 1) |
| 390 | |
| 391 | ## Pass 1. Establish the from_NAME and to_NAME constants, and the |
| 392 | ## netclass's mask bit. |
| 393 | trace "netclass $name = $netclassindex" |
| 394 | eval from_$name=$(( $netclassindex << $BIT_FROM )) |
| 395 | eval to_$name=$(( $netclassindex << $BIT_TO )) |
| 396 | eval _mask_$name=$(( 1 << ($netclassindex + $BIT_MASK) )) |
| 397 | nets="$nets $name" |
| 398 | ;; |
| 399 | 2) |
| 400 | |
| 401 | ## Pass 2. Compute the actual from and to values. We're a little bit |
| 402 | ## clever during source classification, and set the TO field to |
| 403 | ## all-bits-one, so that destination classification needs only a single |
| 404 | ## AND operation. |
| 405 | from=$(( ($netclassindex << $BIT_FROM) + (0xf << $BIT_TO) )) |
| 406 | for net; do |
| 407 | eval bit=\$_mask_$net |
| 408 | from=$(( $from + $bit )) |
| 409 | done |
| 410 | to=$(( ($netclassindex << $BIT_TO) + \ |
| 411 | (0xf << $BIT_FROM) + \ |
| 412 | (1 << ($netclassindex + $BIT_MASK)) )) |
| 413 | trace "from $name --> set $(printf %x $from)" |
| 414 | trace " to $name --> and $(printf %x $from)" |
| 415 | |
| 416 | ## Now establish the mark-from-NAME and mark-to-NAME chains. |
| 417 | clearchain mangle:mark-from-$name mangle:mark-to-$name |
| 418 | run ip46tables -t mangle -A mark-from-$name -j MARK --set-mark $from |
| 419 | run ip46tables -t mangle -A mark-to-$name -j MARK --and-mark $to |
| 420 | ;; |
| 421 | esac |
| 422 | netclassindex=$(( $netclassindex + 1 )) |
| 423 | } |
| 424 | |
| 425 | ## defnet NET CLASS |
| 426 | ## |
| 427 | ## Define a network. Follow by calls to `addr', `forwards', etc. to define |
| 428 | ## properties of the network. Networks are processed in order, so if their |
| 429 | ## addresses overlap then the more specific addresses should be defined |
| 430 | ## earlier. |
| 431 | defnet () { |
| 432 | net=$1 class=$2 |
| 433 | addword allnets $net |
| 434 | eval net_class_$1=\$class |
| 435 | } |
| 436 | |
| 437 | ## addr ADDRESS/LEN ... |
| 438 | ## |
| 439 | ## Define addresses for the network being defined. ADDRESSes are in |
| 440 | ## colon-separated IPv6 or dotted-quad IPv4 form. |
| 441 | addr () { |
| 442 | for i in "$@"; do |
| 443 | case "$i" in |
| 444 | *:*) addword net_inet6_$net $i ;; |
| 445 | *) addword net_inet_$net $i ;; |
| 446 | esac |
| 447 | done |
| 448 | } |
| 449 | |
| 450 | ## forwards NET ... |
| 451 | ## |
| 452 | ## Declare that packets from this network are forwarded to the other NETs. |
| 453 | forwards () { |
| 454 | eval "net_fwd_$net=\"$*\"" |
| 455 | } |
| 456 | |
| 457 | ## noxit NET ... |
| 458 | ## |
| 459 | ## Declare that packets from this network must not be forwarded to the other |
| 460 | ## NETs. |
| 461 | noxit () { |
| 462 | eval "net_noxit_$net=\"$*\"" |
| 463 | } |
| 464 | |
| 465 | ## host HOST ADDR ... |
| 466 | ## |
| 467 | ## Define the address of an individual host on the current network. The |
| 468 | ## ADDRs may be full IPv4 or IPv6 addresses, or offsets from the containing |
| 469 | ## network address, which is a simple number for IPv4, or a suffix beginning |
| 470 | ## with `::' for IPv6. If an IPv6 base address is provided for the network |
| 471 | ## but not for the host then the host's IPv4 address is used as a suffix. |
| 472 | host () { |
| 473 | name=$1; shift |
| 474 | |
| 475 | ## Work out which addresses we've actually been given. |
| 476 | unset a6 |
| 477 | for i in "$@"; do |
| 478 | case "$i" in ::*) a6=$i ;; *) a=$i ;; esac |
| 479 | done |
| 480 | case "${a+t}" in |
| 481 | t) ;; |
| 482 | *) echo >&2 "$0: no address for $name"; exit 1 ;; |
| 483 | esac |
| 484 | case "${a6+t}" in t) ;; *) a6=::$a ;; esac |
| 485 | |
| 486 | ## Work out the IPv4 address. |
| 487 | eval nn=\$net_inet_$net |
| 488 | for n in $nn; do |
| 489 | addr=${n%/*} |
| 490 | base=${addr%.*} |
| 491 | offset=${addr##*.} |
| 492 | case $a in *.*) aa=$a ;; *) aa=$base.$(( $offset + $a )) ;; esac |
| 493 | eval host_inet_$name=$aa |
| 494 | done |
| 495 | |
| 496 | ## Work out the IPv6 address. |
| 497 | eval nn=\$net_inet6_$net |
| 498 | for n in $nn; do |
| 499 | addr=${n%/*} |
| 500 | base=${addr%::*} |
| 501 | case $a6 in ::*) aa=$base$a6 ;; *) aa=$a6 ;; esac |
| 502 | eval host_inet6_$name=$aa |
| 503 | done |
| 504 | |
| 505 | ## Remember the host in the list. |
| 506 | addword net_hosts_$net $name |
| 507 | } |
| 508 | |
| 509 | ## defhost NAME |
| 510 | ## |
| 511 | ## Define a new host. Follow by calls to `iface' to define the host's |
| 512 | ## interfaces. |
| 513 | defhost () { |
| 514 | host=$1 |
| 515 | addword allhosts $host |
| 516 | eval host_type_$host=endsys |
| 517 | } |
| 518 | |
| 519 | ## router |
| 520 | ## |
| 521 | ## Declare the host to be a router, so it should forward packets and so on. |
| 522 | router () { |
| 523 | eval host_type_$host=router |
| 524 | } |
| 525 | |
| 526 | ## iface IFACE NET ... |
| 527 | ## |
| 528 | ## Define a host's interfaces. Specifically, declares that the host has an |
| 529 | ## interface IFACE attached to the listed NETs. |
| 530 | iface () { |
| 531 | name=$1; shift |
| 532 | for net in "$@"; do |
| 533 | addword host_ifaces_$host $name=$net |
| 534 | done |
| 535 | } |
| 536 | |
| 537 | ## net_interfaces HOST NET |
| 538 | ## |
| 539 | ## Determine the interfaces on which packets may plausibly arrive from the |
| 540 | ## named NET. Returns `-' if no such interface exists. |
| 541 | ## |
| 542 | ## This algorithm is not very clever. It's just about barely good enough to |
| 543 | ## deduce transitivity through a simple routed network; with complicated |
| 544 | ## networks, it will undoubtedly give wrong answers. Check the results |
| 545 | ## carefully, and, if necessary, list the connectivity explicitly; use the |
| 546 | ## special interface `-' for networks you know shouldn't send packets to a |
| 547 | ## host. |
| 548 | net_interfaces () { |
| 549 | host=$1 startnet=$2 |
| 550 | |
| 551 | ## Determine the locally attached networks. |
| 552 | targets=: |
| 553 | eval ii=\$host_ifaces_$host |
| 554 | for i in $ii; do targets=$targets$i:; done |
| 555 | |
| 556 | ## Determine the transitivity. |
| 557 | seen=: |
| 558 | nets=$startnet |
| 559 | while :; do |
| 560 | |
| 561 | ## First pass. Determine whether any of the networks we're considering |
| 562 | ## are in the target set. If they are, then return the corresponding |
| 563 | ## interfaces. |
| 564 | found="" |
| 565 | for net in $nets; do |
| 566 | tg=$targets |
| 567 | while :; do |
| 568 | any=nil |
| 569 | case $tg in |
| 570 | *"=$net:"*) |
| 571 | n=${tg%=$net:*}; tg=${n%:*}:; n=${n##*:} |
| 572 | addword found $n |
| 573 | any=t |
| 574 | ;; |
| 575 | esac |
| 576 | case $any in nil) break ;; esac |
| 577 | done |
| 578 | done |
| 579 | case "$found" in ?*) echo $found; return ;; esac |
| 580 | |
| 581 | ## No joy. Determine the set of networks which (a) these ones can |
| 582 | ## forward to, and (b) that we've not considered already. These are the |
| 583 | ## nets we'll consider next time around. |
| 584 | nextnets="" |
| 585 | any=nil |
| 586 | for net in $nets; do |
| 587 | eval fwd=\$net_fwd_$net |
| 588 | for n in $fwd; do |
| 589 | case $seen in *":$n:"*) continue ;; esac |
| 590 | seen=$seen$n: |
| 591 | eval noxit=\$net_noxit_$n |
| 592 | case " $noxit " in *" $startnet "*) continue ;; esac |
| 593 | case " $nextnets " in |
| 594 | *" $n "*) ;; |
| 595 | *) addword nextnets $n; any=t ;; |
| 596 | esac |
| 597 | done |
| 598 | done |
| 599 | |
| 600 | ## If we've run out of networks then there's no reachability. Return a |
| 601 | ## failure. |
| 602 | case $any in nil) echo -; return ;; esac |
| 603 | nets=$nextnets |
| 604 | done |
| 605 | } |
| 606 | |
| 607 | m4_divert(-1) |
| 608 | ###----- That's all, folks -------------------------------------------------- |