xref: /illumos-gate/usr/src/cmd/svc/shell/ipf_include.sh (revision 2e67aa296fc3707ae8e2b532f67387daf0823499)
1#!/sbin/sh
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
24#
25
26IPFILTER_FMRI="svc:/network/ipfilter:default"
27ETC_IPF_DIR=/etc/ipf
28IPNATCONF=`/usr/bin/svcprop -p config/ipnat_config_file $IPFILTER_FMRI \
29    2>/dev/null`
30if [ $? -eq 1 ]; then
31	IPNATCONF=$ETC_IPF_DIR/ipnat.conf
32fi
33IPPOOLCONF=`/usr/bin/svcprop -p config/ippool_config_file $IPFILTER_FMRI \
34    2>/dev/null`
35if [ $? -eq 1 ]; then
36	IPPOOLCONF=$ETC_IPF_DIR/ippool.conf
37fi
38VAR_IPF_DIR=/var/run/ipf
39IPFILCONF=$VAR_IPF_DIR/ipf.conf
40IP6FILCONF=$VAR_IPF_DIR/ipf6.conf
41IPFILOVRCONF=$VAR_IPF_DIR/ipf_ovr.conf
42IP6FILOVRCONF=$VAR_IPF_DIR/ipf6_ovr.conf
43IPF_LOCK=/var/run/ipflock
44CONF_FILES=""
45CONF6_FILES=""
46NAT_FILES=""
47IPF_SUFFIX=".ipf"
48IPF6_SUFFIX=".ipf6"
49NAT_SUFFIX=".nat"
50
51# version for configuration upgrades
52CURRENT_VERSION=1
53
54IPF_FMRI="svc:/network/ipfilter:default"
55INETDFMRI="svc:/network/inetd:default"
56RPCBINDFMRI="svc:/network/rpc/bind:default"
57
58SMF_ONLINE="online"
59SMF_MAINT="maintenance"
60SMF_NONE="none"
61
62FW_CONTEXT_PG="firewall_context"
63METHOD_PROP="ipf_method"
64
65FW_CONFIG_PG="firewall_config"
66POLICY_PROP="policy"
67APPLY2_PROP="apply_to"
68APPLY2_6_PROP="apply_to_6"
69EXCEPTIONS_PROP="exceptions"
70EXCEPTIONS_6_PROP="exceptions_6"
71TARGET_PROP="target"
72TARGET_6_PROP="target_6"
73BLOCKPOL_PROP="block_policy"
74
75FW_CONFIG_DEF_PG="firewall_config_default"
76FW_CONFIG_OVR_PG="firewall_config_override"
77CUSTOM_FILE_PROP="custom_policy_file"
78CUSTOM_FILE_6_PROP="custom_policy_file_6"
79OPEN_PORTS_PROP="open_ports"
80
81PREFIX_HOST="host:"
82PREFIX_NET="network:"
83PREFIX_POOL="pool:"
84PREFIX_IF="if:"
85
86GLOBAL_CONFIG=""
87GLOBAL_POLICY=""
88GLOBAL_BLOCK_POLICY=""
89
90SERVINFO=/usr/lib/servinfo
91
92#
93# Get value(s) for given property from either firewall_config_default or
94# firewall_config_override property groups.
95#
96# global_get_prop_value pg_name propname
97#   pg_name - FW_CONFIG_DEF_PG or FW_CONFIG_OVR_PG
98#   propname - property name
99#
100global_get_prop_value()
101{
102	target_pg=$1
103	prop=$2
104
105	[ "$1" != $FW_CONFIG_OVR_PG -a "$1" != $FW_CONFIG_DEF_PG ] && return
106
107	[ "$1" == $FW_CONFIG_DEF_PG ] && extra_pg=$FW_CONFIG_OVR_PG  || \
108		extra_pg=$FW_CONFIG_DEF_PG
109
110	value=`echo $GLOBAL_CONFIG | awk '{
111		found=0
112		for (i=1; i<=NF; i++) {
113			if (found == 1) {
114				if (index($i, target_pg) == 1 || index($i, extra_pg) == 1)
115					break;
116
117				print $i;
118			}
119
120			if (split($i, values, "/") < 2)
121				continue;
122
123			if (values[1] == target_pg && values[2] == prop)
124				found=1;
125		}
126	}' target_pg=$target_pg prop=$prop extra_pg=$extra_pg`
127
128	# Return
129	echo "$value"
130}
131
132#
133# Initialize and cache network/ipfilter configuration, global configuration.
134#
135# Since an SMF service configuration may get updated during the execution of the
136# service method, it's best to read all relevant configuration via one svcprop
137# invocation and cache it for later use.
138#
139# This function reads and stores relevant configuration into GLOBAL_CONFIG and
140# initializes the GLOBAL_POLICY and GLOBAL_BLOCK_POLICY variables. GLOBAL_CONFIG
141# is a string containing pg/prop and their corresponding values (i.e. svcprop -p
142# pg fmri output). To get values for a certain pg/prop, use
143# global_get_prop_value().
144#
145global_init()
146{
147	GLOBAL_CONFIG=`svcprop -p ${FW_CONFIG_OVR_PG} -p ${FW_CONFIG_DEF_PG} \
148        $IPF_FMRI 2>/dev/null | awk '{$2=" "; print $0}'`
149
150	GLOBAL_POLICY=`global_get_prop_value $FW_CONFIG_DEF_PG $POLICY_PROP`
151        GLOBAL_BLOCK_POLICY=`global_get_prop_value $FW_CONFIG_DEF_PG \
152	   $BLOCKPOL_PROP`
153}
154
155#
156# Given a service, gets its config pg name
157#
158get_config_pg()
159{
160	if [ "$1" = "$IPF_FMRI" ]; then
161		echo "$FW_CONFIG_DEF_PG"
162	else
163		echo "$FW_CONFIG_PG"
164	fi
165	return 0
166}
167
168#
169# Given a service, gets its firewall policy
170#
171get_policy()
172{
173	config_pg=`get_config_pg $1`
174	svcprop -p $config_pg/${POLICY_PROP} $1 2>/dev/null
175}
176
177#
178# block policy can be set to "return", which will expand into
179# separate block rules for tcp (block return-rst ...) and all other
180# protocols (block return-icmp-as-dest ...)
181#
182get_block_policy()
183{
184	config_pg=`get_config_pg $1`
185	svcprop -p $config_pg/${BLOCKPOL_PROP} $1 2>/dev/null
186}
187
188#
189# Given a service, gets its source address exceptions for IPv4
190#
191get_exceptions()
192{
193	config_pg=`get_config_pg $1`
194	exceptions=`svcprop -p $config_pg/${EXCEPTIONS_PROP} $1 2>/dev/null`
195        echo $exceptions | sed -e 's/\\//g'
196}
197
198#
199# Given a service, gets its source address exceptions for IPv6
200#
201get_exceptions_6()
202{
203	config_pg=`get_config_pg $1`
204	exceptions6=`svcprop -p $config_pg/${EXCEPTIONS_6_PROP} $1 2>/dev/null`
205        echo $exceptions6 | sed -e 's/\\//g'
206}
207
208#
209# Given a service, gets its firewalled source addresses for IPv4
210#
211get_apply2_list()
212{
213	config_pg=`get_config_pg $1`
214	apply2=`svcprop -p $config_pg/${APPLY2_PROP} $1 2>/dev/null`
215        echo $apply2 | sed -e 's/\\//g'
216}
217
218#
219# Given a service, gets its firewalled source addresses for IPv6
220#
221get_apply2_6_list()
222{
223	config_pg=`get_config_pg $1`
224	apply2_6=`svcprop -p $config_pg/${APPLY2_6_PROP} $1 2>/dev/null`
225        echo $apply2_6 | sed -e 's/\\//g'
226}
227
228#
229# Given a service, gets its firewalled target addresses for IPv4
230#
231get_target_list()
232{
233	config_pg=`get_config_pg $1`
234	target=`svcprop -p $config_pg/${TARGET_PROP} $1 2>/dev/null`
235	[ -z "$target" -o "$target" = '""' ] && target=any
236	echo $target | sed -e 's/\\//g'
237}
238
239#
240# Given a service, gets its firewalled target addresses for IPv6
241#
242get_target_6_list()
243{
244	config_pg=`get_config_pg $1`
245	target6=`svcprop -p $config_pg/${TARGET_6_PROP} $1 2>/dev/null`
246	[ -z "$target6" -o "$target6" = '""' ] && target6=any
247	echo $target6 | sed -e 's/\\//g'
248}
249
250check_ipf_dir()
251{
252	[ -d $VAR_IPF_DIR ] && return 0
253	mkdir $VAR_IPF_DIR >/dev/null 2>&1 || return 1
254}
255
256#
257# fmri_to_file fmri suffix
258#
259fmri_to_file()
260{
261	check_ipf_dir || return 1
262	fprefix="${VAR_IPF_DIR}/`echo $1 | tr -s '/:' '__'`"
263	echo "${fprefix}${2}"
264}
265
266#
267# Return service's enabled property
268#
269service_is_enabled()
270{
271	#
272	# Temporary enabled state overrides the persistent state
273	# so check it first.
274	#
275	enabled_ovr=`svcprop -c -p general_ovr/enabled $1 2>/dev/null`
276	if [ -n "$enabled_ovr" ]; then
277		[ "$enabled_ovr" = "true" ] && return 0 || return 1
278	fi
279
280	enabled=`svcprop -c -p general/enabled $1 2>/dev/null`
281	[ -n "$enabled" -a "$enabled" = "true" ] && return 0 || return 1
282}
283
284#
285# Return whether service is desired state
286#
287# Args: fmri state
288# Return:
289#  0 - desired state is service's current state
290#  1 - desired state is not service's current state
291#
292service_check_state()
293{
294	#
295	# Make sure we're done with ongoing state transition
296	#
297	while [ "`svcprop -p restarter/next_state $1`" != "$SMF_NONE" ]; do
298		sleep 1
299	done
300
301	[ "`svcprop -p restarter/state $1`" = "$2" ] && return 0 || return 1
302}
303
304#
305# Deny/Allow list stores values in the form "host:addr", "network:addr/netmask",
306# "pool:number", and "if:interface". This function returns the
307# IP(addr or addr/netmask) value or a pool number.
308#
309get_IP()
310{
311	value_is_interface $1 && return 1
312	echo "$1" | sed -n -e "s,^${PREFIX_POOL}\(.*\),pool/\1,p" \
313	    -e "s,^${PREFIX_HOST}\(.*\),\1,p" \
314	    -e "s,^${PREFIX_NET}\(.*\),\1,p" \
315	    -e "s,^any,any,p"
316}
317
318get_interface()
319{
320	value_is_interface $1 || return 1
321	scratch=`echo "$1" | sed -e "s/^${PREFIX_IF}//"`
322
323	ifconfig $scratch >/dev/null 2>&1 || return 1
324	echo $scratch | sed -e 's/:.*//'
325}
326
327#
328#
329#
330value_is_interface()
331{
332	[ -z "$1" ] && return 1
333	echo $1 | grep "^${PREFIX_IF}" >/dev/null 2>&1
334}
335
336#
337# Remove rules in given file from active list without restarting ipfilter
338#
339remove_rules()
340{
341	[ -f "$1" ] && ipf $2 -r -f $1 >/dev/null 2>&1
342}
343
344remove_nat_rules()
345{
346	[ -f "$1" ] && ipnat -r -f $1 >/dev/null 2>&1
347}
348
349check_ipf_syntax()
350{
351	ipf $2 -n -f $1 >/dev/null 2>&1
352}
353
354check_nat_syntax()
355{
356	ipnat -n -f $1 >/dev/null 2>&1
357}
358
359unique_ports()
360{
361	echo $* | xargs -n 1 echo | sort -u
362}
363
364file_get_ports()
365{
366	ipf $2 -n -v -f $1 2>/dev/null | sed -n -e \
367	    's/.*to.* port = \([a-z0-9]*\).*/\1/p' | uniq | \
368	    awk '{if (length($0) > 1) {printf("%s ", $1)}}'
369}
370
371get_active_ports()
372{
373	ipfstat $1 -io 2>/dev/null | sed -n -e \
374	    's/.*to.* port = \([a-z0-9]*\).*/\1/p' | uniq | \
375	    awk '{if (length($0) > 1) {printf("%s ",$1)}}'
376}
377
378#
379# Given two list of ports, return failure if there's a duplicate.
380#
381sets_check_duplicate()
382{
383	#
384	# If either list is empty, there isn't any conflict.
385	#
386	[ -z "$1" -o -z "$2" ] && return 0
387
388	for p in $1; do
389		for ap in $2; do
390			[ "$p" = "$ap" ] && return 1
391		done
392	done
393
394	return 0
395}
396
397#
398# Given a file containing ipf rules, check the syntax and verify
399# the rules don't conflict, use same port number, with active
400# rules (ipfstat -io output).
401#
402update_check_ipf_rules()
403{
404	check_ipf_syntax $1 $2 || return 1
405
406	lports=`file_get_ports $1 $2`
407	lactive_ports=`get_active_ports $2`
408
409	sets_check_duplicate "$lports" "$lactive_ports" || return 1
410}
411
412server_port_list=""
413server_port_list_6=""
414
415#
416# Given a file containing ipf rules, check the syntax and verify
417# the rules don't conflict with already processed services.
418#
419# The list of processed services' ports are maintained in the global
420# variables 'server_port_list' and 'server_port_list_6'.
421#
422check_ipf_rules()
423{
424
425	check_ipf_syntax $1 $2 || return 1
426
427	lports=`file_get_ports $1 $2`
428
429	if [ "$2" = "-6" ]; then
430		sets_check_duplicate "$lports" "$server_port_list_6" || return 1
431	        server_port_list_6="$server_port_list_6 $lports"
432	else
433		sets_check_duplicate "$lports" "$server_port_list" || return 1
434	        server_port_list="$server_port_list $lports"
435	fi
436
437	return 0
438}
439
440prepend_new_rules()
441{
442	check_ipf_syntax $1 $2 && tail -r $1 | sed -e 's/^[a-z]/@0 &/' | \
443	    ipf $2 -f - >/dev/null 2>&1
444}
445
446append_new_rules()
447{
448	check_ipf_syntax $1 $2 && ipf $2 -f $1 >/dev/null 2>&1
449}
450
451append_new_nat_rules()
452{
453	check_nat_syntax $1 && ipnat -f $1 >/dev/null 2>&1
454}
455
456#
457# get port information from string of the form "proto:{port | port-port}"
458#
459tuple_get_port()
460{
461	port_str=`echo "$1" | sed -e 's/ //g; s/.*://' 2>/dev/null`
462	[ -z "$port_str" ] && return 1
463
464	echo $port_str | grep "-" >/dev/null
465	if  [ $? -eq  0 ]; then
466		echo $port_str | grep '^[0-9]\{1,5\}-[0-9]\{1,5\}$' >/dev/null || \
467		    return 1
468		ports=`echo $port_str | ( IFS=- read a b ; \
469		    [ $a \-le $b ] && echo $a $b || echo $b $a )`
470
471		for p in $ports; do
472			[ $p -gt 65535 ] && return 1
473		done
474		echo "$ports"
475	else
476		#
477		# port_str is a single port, verify and return it.
478		#
479		echo "$port_str" | grep '^[0-9]\{1,5\}$' >/dev/null || return 1
480		[ $port_str -gt 65535 ] && return 1
481		echo "$port_str"
482	fi
483}
484
485#
486# get proto info from string of the form "{tcp | udp}:port"
487#
488tuple_get_proto()
489{
490	proto=`echo "$1" | sed -e 's/ //g; s/:.*//' 2>/dev/null`
491	[ -z "$proto" ] && return 0
492
493	[ "$proto" = "tcp" -o "$proto" = "udp" ] && echo $proto || return 1
494	return 0
495}
496
497ipf_get_lock()
498{
499	newpid=$$
500
501	if [ -f "$IPF_LOCK/pid" ]; then
502		curpid=`cat $IPF_LOCK/pid 2>/dev/null`
503		[ "$curpid" = "$newpid" ] && return 0
504
505		#
506		# Clear lock if the owning process is no longer around.
507		#
508		ps -p $curpid >/dev/null 2>&1 || rm -r $IPF_LOCK >/dev/null 2>&1
509	fi
510
511	#
512	# Grab the lock
513	#
514	while :; do
515		mkdir $IPF_LOCK 2>/dev/null && break;
516		sleep 1
517	done
518	echo $newpid > $IPF_LOCK/pid
519}
520
521#
522# Remove lock if it's ours
523#
524ipf_remove_lock()
525{
526	if [ -f "$IPF_LOCK/pid" ]; then
527		[ "`cat $IPF_LOCK/pid`" = "$$" ] && rm -r $IPF_LOCK
528	fi
529	return 0
530}
531
532#
533# Make IPFILCONF, /var/tmp/ipf/ipf.conf, a symlink to the input file argument.
534#
535custom_set_symlink()
536{
537	#
538	# Nothing to do if the input file doesn't exist.
539	#
540	[ ! -f "$1" ] && return 0
541
542	check_ipf_dir || return 1
543
544	rm $IPFILCONF >/dev/null 2>&1
545	ln -s $1 $IPFILCONF >/dev/null 2>&1
546}
547
548#
549# New file replaces original file if they have different content
550#
551replace_file()
552{
553	orig=$1
554	new=$2
555
556	#
557	# IPFILCONF may be a symlink, remove it if that's the case
558	#
559	if [ -L "$orig" ]; then
560		rm $orig
561		touch $orig
562	fi
563
564	check_ipf_dir || return 1
565	mv $new $orig && return 0 || return 1
566}
567
568#
569# Given a service, gets the following details for ipf rule:
570# - policy
571# - protocol
572# - port(IANA port obtained by running servinfo)
573#
574process_server_svc()
575{
576	service=$1
577        policy=`get_policy ${service}`
578
579	#
580	# Empties service's rules file so callers won't use existing rule if
581	# we fail here.
582	#
583	file=`fmri_to_file $service $IPF_SUFFIX`
584	file6=`fmri_to_file $service $IPF6_SUFFIX`
585	[ -z "$file" ] && return 1
586	echo "# $service" >${file}
587	echo "# $service" >${file6}
588
589	#
590	# Nothing to do if policy is "use_global"
591	#
592	[ "$policy" = "use_global" ] && return 0
593
594	restarter=`svcprop -p general/restarter $service 2>/dev/null`
595	if [ "$restarter" = "$INETDFMRI" ]; then
596		iana_name=`svcprop -p inetd/name $service 2>/dev/null`
597		isrpc=`svcprop -p inetd/isrpc $service 2>/dev/null`
598	else
599		iana_name=`svcprop -p $FW_CONTEXT_PG/name $service 2>/dev/null`
600		isrpc=`svcprop -p $FW_CONTEXT_PG/isrpc $service 2>/dev/null`
601	fi
602
603	#
604	# Bail if iana_name isn't defined. Services with static rules
605	# like nis/client don't need to generate rules using
606	# iana name and protocol information.
607	#
608	[ -z "$iana_name" ] && return 1
609
610	#
611	# RPC services
612	#
613	if [ "$isrpc" = "true" ]; then
614		# The ports used for IPv6 are usually also reachable
615		# through IPv4, so generate IPv4 rules for them, too.
616		tports=`$SERVINFO -R -p -t -s $iana_name 2>/dev/null`
617		tports6=`$SERVINFO -R -p -t6 -s $iana_name 2>/dev/null`
618		if [ -n "$tports" -o -n "$tports6" ]; then
619			tports=`unique_ports $tports $tports6`
620			for tport in $tports; do
621				generate_rules $service $policy "tcp" \
622				    $tport $file
623			done
624		fi
625
626		if [ -n "$tports6" ]; then
627			for tport6 in $tports6; do
628				generate_rules $service $policy "tcp" \
629				    $tport6 $file6 _6
630			done
631		fi
632
633		uports=`$SERVINFO -R -p -u -s $iana_name 2>/dev/null`
634		uports6=`$SERVINFO -R -p -u6 -s $iana_name 2>/dev/null`
635		if [ -n "$uports" ]; then
636			uports=`unique_ports $uports $uports6`
637			for uport in $uports; do
638				generate_rules $service $policy "udp" \
639				    $uport $file
640			done
641		fi
642
643		if [ -n "$uports6" ]; then
644			for uport6 in $uports6; do
645				generate_rules $service $policy "udp" \
646				    $uport6 $file6 _6
647			done
648		fi
649
650		return 0
651	fi
652
653	#
654	# Get the IANA port and supported protocols(tcp and udp)
655	#
656	tport=`$SERVINFO -p -t -s $iana_name 2>&1`
657	if [ $? -eq 0 -a -n "$tport" ]; then
658		generate_rules $service $policy "tcp" $tport $file
659	fi
660
661	tport6=`$SERVINFO -p -t6 -s $iana_name 2>&1`
662	if [ $? -eq 0 -a -n "$tport6" ]; then
663		generate_rules $service $policy "tcp" $tport6 $file6 _6
664	fi
665
666	uport=`$SERVINFO -p -u -s $iana_name 2>&1`
667	if [ $? -eq 0 -a -n "$uport" ]; then
668		generate_rules $service $policy "udp" $uport $file
669	fi
670
671	uport6=`$SERVINFO -p -u6 -s $iana_name 2>&1`
672	if [ $? -eq 0 -a -n "$uport6" ]; then
673		generate_rules $service $policy "udp" $uport6 $file6 _6
674	fi
675
676	return 0
677}
678
679#
680# Given a service's name, policy, protocol and port, generate ipf rules
681# - list of host/network/interface to apply policy
682#
683# A 'use_global' policy inherits the system-wided Global Default policy
684# from network/ipfilter. For {deny | allow} policies, the rules are
685# ordered as:
686#
687# - make exceptions to policy for those in "exceptions" list
688# - apply policy to those specified in "apply_to" list
689# - policy rule
690#
691generate_rules()
692{
693	service=$1
694	mypolicy=$2
695	proto=$3
696	port=$4
697	out=$5
698	_6=$6
699
700	#
701	# Default mode is to inherit from global's policy
702	#
703	[ "$mypolicy" = "use_global" ] && return 0
704
705	tcp_opts=""
706	[ "$proto" = "tcp" ] && tcp_opts="flags S keep state keep frags"
707
708	block_policy=`get_block_policy $1`
709        if [ "$block_policy" = "use_global" ]; then
710		block_policy=${GLOBAL_BLOCK_POLICY}
711        fi
712
713	if [ "$block_policy" = "return" ]; then
714		[ "$proto" = "tcp" ] && block_policy="return-rst"
715		[ "$proto" != "tcp" ] && block_policy="return-icmp-as-dest"
716	else
717		block_policy=""
718        fi
719
720	iplist=`get_target${_6}_list $service`
721
722	#
723	# Allow all if policy is 'none'
724	#
725	if [ "$mypolicy" = "none" ]; then
726		for ip in $iplist; do
727			daddr=`get_IP ${ip}`
728			[ -z "$daddr" -o "$daddr" = '""' ] && continue
729			echo "pass in log quick proto ${proto} from any to ${daddr}" \
730			    "port = ${port} ${tcp_opts}" >>${out}
731		done
732		return 0
733	fi
734
735	#
736	# For now, let's concern ourselves only with incoming traffic.
737	#
738	[ "$mypolicy" = "deny" ] && { ecmd="pass"; acmd="block ${block_policy}"; }
739	[ "$mypolicy" = "allow" ] && { ecmd="block ${block_policy}"; acmd="pass"; }
740
741	for name in `get_exceptions${_6} $service`; do
742		[ -z "$name" -o "$name" = '""' ] && continue
743
744		ifc=`get_interface $name`
745		if [ $? -eq 0 -a -n "$ifc" ]; then
746			for ip in $iplist; do
747				daddr=`get_IP ${ip}`
748				[ -z "$daddr" -o "$daddr" = '""' ] && continue
749				echo "${ecmd} in log quick on ${ifc} from any to" \
750				    "${daddr} port = ${port}" >>${out}
751			done
752			continue
753		fi
754
755		saddr=`get_IP ${name}`
756		if [ $? -eq 0 -a -n "$saddr" ]; then
757			for ip in $iplist; do
758				daddr=`get_IP ${ip}`
759				[ -z "$daddr" -o "$daddr" = '""' ] && continue
760				echo "${ecmd} in log quick proto ${proto} from ${saddr}" \
761				    "to ${daddr} port = ${port} ${tcp_opts}" >>${out}
762			done
763		fi
764	done
765
766	for name in `get_apply2${_6}_list $service`; do
767		[ -z "$name" -o "$name" = '""' ] && continue
768
769		ifc=`get_interface $name`
770		if [ $? -eq 0 -a -n "$ifc" ]; then
771			for ip in $iplist; do
772				daddr=`get_IP ${ip}`
773				[ -z "$daddr" -o "$daddr" = '""' ] && continue
774				echo "${acmd} in log quick on ${ifc} from any to" \
775				    "${daddr} port = ${port}" >>${out}
776			done
777			continue
778		fi
779
780		saddr=`get_IP ${name}`
781		if [ $? -eq 0 -a -n "$saddr" ]; then
782			for ip in $iplist; do
783				daddr=`get_IP ${ip}`
784				[ -z "$daddr" -o "$daddr" = '""' ] && continue
785				echo "${acmd} in log quick proto ${proto} from ${saddr}" \
786				    "to ${daddr} port = ${port} ${tcp_opts}" >>${out}
787			done
788		fi
789	done
790
791	for ip in $iplist; do
792		daddr=`get_IP ${ip}`
793		[ -z "$daddr" -o "$daddr" = '""' ] && continue
794		echo "${ecmd} in log quick proto ${proto} from any to ${daddr}" \
795		    "port = ${port} ${tcp_opts}" >>${out}
796	done
797
798	return 0
799}
800
801#
802# Service has either IANA ports and proto or its own firewall method to
803# generate the rules.
804#
805# - if service has a custom method, use it to populate its rules
806# - if service has a firewall_config pg, use process_server_svc
807#
808# Argument - fmri
809#
810process_service()
811{
812	#
813	# Don't process network/ipfilter
814	#
815	[ "$1" = "$IPF_FMRI" ] && return 0
816
817	service_check_state $1 $SMF_MAINT && return 1
818
819	method=`svcprop -p $FW_CONTEXT_PG/$METHOD_PROP $1 2>/dev/null | \
820	    sed 's/\\\//g'`
821	if [ -n "$method" -a "$method" != '""' ]; then
822		( exec $method $1 >/dev/null )
823	else
824		svcprop -p $FW_CONFIG_PG $1 >/dev/null 2>&1 || return 1
825		process_server_svc $1 || return 1
826	fi
827	return 0
828}
829
830#
831# Generate rules for protocol/port defined in firewall_config_default/open_ports
832# property. These are non-service programs whose network resource info are
833# defined as "{tcp | upd}:{PORT | PORT-PORT}". Essentially, these programs need
834# some specific local ports to be opened. For example, BitTorrent clients need to
835# have 6881-6889 opened.
836#
837process_nonsvc_progs()
838{
839	out=$1
840	echo "# Non-service programs rules" >>${out}
841	progs=`global_get_prop_value $FW_CONFIG_DEF_PG $OPEN_PORTS_PROP`
842
843	for prog in $progs; do
844		[ -z "$prog" -o "$prog" = '""' ] && continue
845
846		port=`tuple_get_port $prog`
847		[ $? -eq 1 -o -z "$port" ] && continue
848
849		proto=`tuple_get_proto $prog`
850		[ $? -eq 1 ] && continue
851
852		set -- $port
853		if  [ $# -gt 1 ]; then
854			if [ -z "$proto" ]; then
855				echo "pass in log quick from any to any" \
856				    "port ${1} >< ${2}" >>${out}
857			else
858				echo "pass in log quick proto ${proto} from any" \
859				    "to any port ${1} >< ${2}" >>${out}
860			fi
861		else
862			if [ -z "$proto" ]; then
863				echo "pass in log quick from any to any" \
864				    "port = ${1}" >>${out}
865			else
866				echo "pass in log quick proto ${proto} from any" \
867				    "to any port = ${1}" >>${out}
868			fi
869		fi
870	done
871
872	return 0
873}
874
875#
876# Generate a new /etc/ipf/ipf.conf. If firewall policy is 'none',
877# ipf.conf is empty .
878#
879create_global_rules()
880{
881	if [ "$GLOBAL_POLICY" = "custom" ]; then
882		file=`global_get_prop_value $FW_CONFIG_DEF_PG $CUSTOM_FILE_PROP`
883		file6=`global_get_prop_value $FW_CONFIG_DEF_PG $CUSTOM_FILE_6_PROP`
884
885		[ -n "$file" ] && custom_set_symlink $file
886		[ -n "$file6" ] && custom_set_symlink $file6
887
888		return 0
889	fi
890
891	TEMP=`mktemp /var/run/ipf.conf.pid$$.XXXXXX`
892	TEMP6=`mktemp /var/run/ipf6.conf.pid$$.XXXXXX`
893	process_nonsvc_progs $TEMP
894	process_nonsvc_progs $TEMP6
895
896	echo "# Global Default rules" >>${TEMP}
897	echo "# Global Default rules" >>${TEMP6}
898	if [ "$GLOBAL_POLICY" != "none" ]; then
899		echo "pass out log quick all keep state" >>${TEMP}
900		echo "pass out log quick all keep state" >>${TEMP6}
901	fi
902
903	case "$GLOBAL_POLICY" in
904	'none')
905		# No rules
906		replace_file ${IPFILCONF} ${TEMP}
907		replace_file ${IP6FILCONF} ${TEMP6}
908		return $?
909		;;
910
911	'deny')
912		ecmd="pass"
913		acmd="block"
914		;;
915
916	'allow')
917		ecmd="block"
918		acmd="pass"
919		;;
920	*)
921		return 1;
922		;;
923	esac
924
925	for name in `global_get_prop_value $FW_CONFIG_DEF_PG $EXCEPTIONS_PROP`; do
926		[ -z "$name" -o "$name" = '""' ] && continue
927
928		ifc=`get_interface $name`
929		if [ $? -eq 0 -a -n "$ifc" ]; then
930			echo "${ecmd} in log quick on ${ifc} all" >>${TEMP}
931			continue
932		fi
933
934		addr=`get_IP ${name}`
935		if [ $? -eq 0 -a -n "$addr" ]; then
936			echo "${ecmd} in log quick from ${addr} to any" >>${TEMP}
937		fi
938
939	done
940
941	for name in `global_get_prop_value $FW_CONFIG_DEF_PG $EXCEPTIONS_6_PROP`; do
942		[ -z "$name" -o "$name" = '""' ] && continue
943
944		ifc=`get_interface $name`
945		if [ $? -eq 0 -a -n "$ifc" ]; then
946			echo "${ecmd} in log quick on ${ifc} all" >>${TEMP6}
947			continue
948		fi
949
950		addr=`get_IP ${name}`
951		if [ $? -eq 0 -a -n "$addr" ]; then
952			echo "${ecmd} in log quick from ${addr} to any" >>${TEMP6}
953		fi
954
955	done
956
957	for name in `global_get_prop_value $FW_CONFIG_DEF_PG $APPLY2_PROP`; do
958		[ -z "$name" -o "$name" = '""' ] && continue
959
960		ifc=`get_interface $name`
961		if [ $? -eq 0 -a -n "$ifc" ]; then
962			echo "${acmd} in log quick on ${ifc} all" >>${TEMP}
963			continue
964		fi
965
966		addr=`get_IP ${name}`
967		if [ $? -eq 0 -a -n "$addr" ]; then
968			echo "${acmd} in log quick from ${addr} to any" >>${TEMP}
969		fi
970	done
971
972	for name in `global_get_prop_value $FW_CONFIG_DEF_PG $APPLY2_6_PROP`; do
973		[ -z "$name" -o "$name" = '""' ] && continue
974
975		ifc=`get_interface $name`
976		if [ $? -eq 0 -a -n "$ifc" ]; then
977			echo "${acmd} in log quick on ${ifc} all" >>${TEMP6}
978			continue
979		fi
980
981		addr=`get_IP ${name}`
982		if [ $? -eq 0 -a -n "$addr" ]; then
983			echo "${acmd} in log quick from ${addr} to any" >>${TEMP6}
984		fi
985	done
986
987	if [ "$GLOBAL_POLICY" = "allow" ]; then
988		#
989		# Allow DHCP(v6) traffic if running as a DHCP client
990		#
991		/sbin/netstrategy | grep dhcp >/dev/null 2>&1
992		if [ $? -eq 0 ]; then
993			echo "pass out log quick from any port = 68" \
994			    "keep state" >>${TEMP}
995			echo "pass in log quick from any to any port = 68" >>${TEMP}
996
997			echo "pass out log quick from any port = 546" \
998			    "keep state" >>${TEMP6}
999			echo "pass in log quick from any to any port = 546" >>${TEMP6}
1000		fi
1001		echo "block in log all" >>${TEMP}
1002		echo "block in log all" >>${TEMP6}
1003	fi
1004
1005	replace_file ${IPFILCONF} ${TEMP}
1006	replace_file ${IP6FILCONF} ${TEMP6}
1007	return $?
1008}
1009
1010#
1011# Generate a new /etc/ipf/ipf_ovr.conf, the override system-wide policy. It's
1012# a simplified policy that doesn't support 'exceptions' entities.
1013#
1014# If firewall policy is "none", no rules are generated.
1015#
1016# Note that "pass" rules don't have "quick" as we don't want
1017# them to override services' block rules.
1018#
1019create_global_ovr_rules()
1020{
1021	#
1022	# Simply empty override file if global policy is 'custom'
1023	#
1024	if [ "$GLOBAL_POLICY" = "custom" ]; then
1025		echo "# 'custom' global policy" >$IPFILOVRCONF
1026		echo "# 'custom' global policy" >$IP6FILOVRCONF
1027		return 0
1028	fi
1029
1030	#
1031	# Get and process override policy
1032	#
1033	ovr_policy=`global_get_prop_value $FW_CONFIG_OVR_PG $POLICY_PROP`
1034	if [ "$ovr_policy" = "none" ]; then
1035		echo "# global override policy is 'none'" >$IPFILOVRCONF
1036		echo "# global override policy is 'none'" >$IP6FILOVRCONF
1037		return 0
1038	fi
1039
1040	TEMP=`mktemp /var/run/ipf_ovr.conf.pid$$.XXXXXX`
1041	[ "$ovr_policy" = "deny" ] && acmd="block in log quick"
1042	[ "$ovr_policy" = "allow" ] && acmd="pass in log"
1043
1044	apply2_list=`global_get_prop_value $FW_CONFIG_OVR_PG $APPLY2_PROP`
1045	for name in $apply2_list; do
1046		[ -z "$name" -o "$name" = '""' ] && continue
1047
1048		ifc=`get_interface $name`
1049		if [ $? -eq 0 -a -n "$ifc" ]; then
1050			echo "${acmd} on ${ifc} all" >>${TEMP}
1051			continue
1052		fi
1053
1054		addr=`get_IP ${name}`
1055		if [ $? -eq 0 -a -n "$addr" ]; then
1056			echo "${acmd} from ${addr} to any" >>${TEMP}
1057		fi
1058	done
1059
1060	apply2_6_list=`global_get_prop_value $FW_CONFIG_OVR_PG $APPLY2_6_PROP`
1061	for name in $apply2_6_list; do
1062		[ -z "$name" -o "$name" = '""' ] && continue
1063
1064		ifc=`get_interface $name`
1065		if [ $? -eq 0 -a -n "$ifc" ]; then
1066			echo "${acmd} on ${ifc} all" >>${TEMP6}
1067			continue
1068		fi
1069
1070		addr=`get_IP ${name}`
1071		if [ $? -eq 0 -a -n "$addr" ]; then
1072			echo "${acmd} from ${addr} to any" >>${TEMP6}
1073		fi
1074	done
1075
1076	replace_file ${IPFILOVRCONF} ${TEMP}
1077	replace_file ${IP6FILOVRCONF} ${TEMP6}
1078	return $?
1079}
1080
1081#
1082# Service is put into maintenance state due to its invalid firewall
1083# definition and/or policy.
1084#
1085svc_mark_maintenance()
1086{
1087	svcadm mark maintenance $1 >/dev/null 2>&1
1088
1089	date=`date`
1090	echo "[ $date ${0}: $1 has invalid ipf configuration. ]"
1091	echo "[ $date ${0}: placing $1 in maintenance. ]"
1092
1093	#
1094	# Move service's rule files to another location since
1095	# they're most likely invalid.
1096	#
1097	ipfile=`fmri_to_file $1 $IPF_SUFFIX`
1098	[ -f "$ipfile" ] && mv $ipfile "$ipfile.bak"
1099	ip6file=`fmri_to_file $1 $IPF6_SUFFIX`
1100	[ -f "$ip6file" ] && mv $ip6file "$ip6file.bak"
1101
1102	natfile=`fmri_to_file $1 $NAT_SUFFIX`
1103	[ -f "$natfile" ] && mv $natfile "$natfile.bak"
1104
1105	return 0
1106}
1107
1108svc_is_server()
1109{
1110	svcprop -p $FW_CONFIG_PG $1 >/dev/null 2>&1
1111}
1112
1113#
1114# Create rules for enabled firewalling and client services.
1115# - obtain the list of enabled services and process them
1116# - save the list of rules file for later use
1117#
1118create_services_rules()
1119{
1120	#
1121	# Do nothing if global policy is 'custom'
1122	#
1123	[ "$GLOBAL_POLICY" = "custom" ] && return 0
1124
1125	ipf_get_lock
1126
1127	#
1128	# Get all enabled services
1129	#
1130	allsvcs=`svcprop -cf -p general/enabled -p general_ovr/enabled '*' \
1131	    2>/dev/null | sed -n 's,^\(svc:.*\)/:properties/.* true$,\1,p' | sort -u`
1132
1133	#
1134	# Process enabled services
1135	#
1136	for s in $allsvcs; do
1137		service_is_enabled $s || continue
1138		process_service $s || continue
1139
1140		ipfile=`fmri_to_file $s $IPF_SUFFIX`
1141		if [ -n "$ipfile" -a -r "$ipfile" ]; then
1142			check_ipf_syntax $ipfile
1143			if [ $? -ne 0 ]; then
1144				svc_mark_maintenance $s
1145				continue
1146			fi
1147
1148			svc_is_server $s
1149			if [ $? -eq 0 ]; then
1150				check_ipf_rules $ipfile
1151				if [ $? -ne 0 ]; then
1152					svc_mark_maintenance $s
1153					continue
1154				fi
1155			fi
1156			CONF_FILES="$CONF_FILES $ipfile"
1157		fi
1158
1159		ip6file=`fmri_to_file $s $IPF6_SUFFIX`
1160		if [ -n "$ip6file" -a -r "$ip6file" ]; then
1161			check_ipf_syntax $ip6file -6
1162			if [ $? -ne 0 ]; then
1163				svc_mark_maintenance $s
1164				continue
1165			fi
1166
1167			svc_is_server $s
1168			if [ $? -eq 0 ]; then
1169				check_ipf_rules $ip6file -6
1170				if [ $? -ne 0 ]; then
1171					svc_mark_maintenance $s
1172					continue
1173				fi
1174			fi
1175			CONF6_FILES="$CONF6_FILES $ip6file"
1176		fi
1177
1178		natfile=`fmri_to_file $s $NAT_SUFFIX`
1179		if [ -n "$natfile" -a -r "$natfile" ]; then
1180			check_nat_syntax $natfile
1181			if [ $? -ne 0 ]; then
1182				svc_mark_maintenance $s
1183				continue
1184			fi
1185
1186			NAT_FILES="$NAT_FILES $natfile"
1187		fi
1188	done
1189
1190	ipf_remove_lock
1191	return 0
1192}
1193
1194#
1195# We update a services ipf ruleset in the following manners:
1196# - service is disabled, tear down its rules.
1197# - service is disable or refreshed(online), setup or update its rules.
1198#
1199service_update_rules()
1200{
1201	svc=$1
1202
1203	ipfile=`fmri_to_file $svc $IPF_SUFFIX`
1204	ip6file=`fmri_to_file $svc $IPF6_SUFFIX`
1205	[ -n "$ipfile" ] && remove_rules $ipfile
1206	[ -n "$ip6file" ] && remove_rules $ip6file -6
1207
1208	[ -z "$ipfile" -a -z "$ip6file" ] && return 0
1209
1210	natfile=`fmri_to_file $svc $NAT_SUFFIX`
1211	[ -n "$natfile" ] && remove_nat_rules $natfile
1212
1213	#
1214	# Don't go further if service is disabled or in maintenance.
1215	#
1216	service_is_enabled $svc || return 0
1217	service_check_state $1 $SMF_MAINT && return 0
1218
1219	process_service $svc || return 1
1220	if [ -f "$ipfile" ]; then
1221		check_ipf_syntax $ipfile
1222		if [ $? -ne 0 ]; then
1223			svc_mark_maintenance $svc
1224			return 1
1225		fi
1226	fi
1227
1228	if [ -f "$ip6file" ]; then
1229		check_ipf_syntax $ip6file -6
1230		if [ $? -ne 0 ]; then
1231			svc_mark_maintenance $svc
1232			return 1
1233		fi
1234	fi
1235
1236	if [ -f "$natfile" ]; then
1237		check_nat_syntax $natfile
1238		if [ $? -ne 0 ]; then
1239			svc_mark_maintenance $svc
1240			return 1
1241		fi
1242	fi
1243
1244	if [ -f "$ipfile" ]; then
1245		svc_is_server $svc
1246		if [ $? -eq 0 ]; then
1247			update_check_ipf_rules $ipfile
1248			if [ $? -ne 0 ]; then
1249				svc_mark_maintenance $svc
1250				return 1
1251			fi
1252		fi
1253
1254		prepend_new_rules $ipfile
1255
1256		#
1257		# reload Global Override rules to
1258		# maintain correct ordering.
1259		#
1260		remove_rules $IPFILOVRCONF
1261		prepend_new_rules $IPFILOVRCONF
1262	fi
1263
1264	if [ -f "$ip6file" ]; then
1265		svc_is_server $svc
1266		if [ $? -eq 0 ]; then
1267			update_check_ipf_rules $ip6file -6
1268			if [ $? -ne 0 ]; then
1269				svc_mark_maintenance $svc
1270				return 1
1271			fi
1272		fi
1273
1274		prepend_new_rules $ip6file -6
1275
1276		#
1277		# reload Global Override rules to
1278		# maintain correct ordering.
1279		#
1280		remove_rules $IP6FILOVRCONF -6
1281		prepend_new_rules $IP6FILOVRCONF -6
1282	fi
1283
1284	[ -f "$natfile" ] && append_new_nat_rules $natfile
1285
1286	return 0
1287}
1288
1289#
1290# Call the service_update_rules with appropriate svc fmri.
1291#
1292# This is called from '/lib/svc/method/ipfilter fw_update' whenever
1293# a service is disabled/enabled/refreshed.
1294#
1295service_update()
1296{
1297	svc=$1
1298	ret=0
1299
1300	#
1301	# If ipfilter isn't online or global policy is 'custom',
1302	# nothing should be done.
1303	#
1304	[ "$GLOBAL_POLICY" = "custom" ] && return 0
1305	service_check_state $SMF_FMRI $SMF_ONLINE || return 0
1306
1307	ipf_get_lock
1308	service_update_rules $svc || ret=1
1309
1310	ipf_remove_lock
1311	return $ret
1312}
1313
1314#
1315# Initialize global configuration
1316#
1317global_init
1318
1319