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/\\\//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# Make IP6FILCONF, /var/tmp/ipf/ipf6.conf, a symlink to the input file argument.
550#
551custom_set_symlink_6()
552{
553	#
554	# Nothing to do if the input file doesn't exist.
555	#
556	[ ! -f "$1" ] && return 0
557
558	check_ipf_dir || return 1
559
560	rm $IP6FILCONF >/dev/null 2>&1
561	ln -s $1 $IP6FILCONF >/dev/null 2>&1
562}
563
564#
565# New file replaces original file if they have different content
566#
567replace_file()
568{
569	orig=$1
570	new=$2
571
572	#
573	# IPFILCONF may be a symlink, remove it if that's the case
574	#
575	if [ -L "$orig" ]; then
576		rm $orig
577		touch $orig
578	fi
579
580	check_ipf_dir || return 1
581	mv $new $orig && return 0 || return 1
582}
583
584#
585# Given a service, gets the following details for ipf rule:
586# - policy
587# - protocol
588# - port(IANA port obtained by running servinfo)
589#
590process_server_svc()
591{
592	service=$1
593        policy=`get_policy ${service}`
594
595	#
596	# Empties service's rules file so callers won't use existing rule if
597	# we fail here.
598	#
599	file=`fmri_to_file $service $IPF_SUFFIX`
600	file6=`fmri_to_file $service $IPF6_SUFFIX`
601	[ -z "$file" ] && return 1
602	echo "# $service" >${file}
603	echo "# $service" >${file6}
604
605	#
606	# Nothing to do if policy is "use_global"
607	#
608	[ "$policy" = "use_global" ] && return 0
609
610	restarter=`svcprop -p general/restarter $service 2>/dev/null`
611	if [ "$restarter" = "$INETDFMRI" ]; then
612		iana_name=`svcprop -p inetd/name $service 2>/dev/null`
613		isrpc=`svcprop -p inetd/isrpc $service 2>/dev/null`
614	else
615		iana_name=`svcprop -p $FW_CONTEXT_PG/name $service 2>/dev/null`
616		isrpc=`svcprop -p $FW_CONTEXT_PG/isrpc $service 2>/dev/null`
617	fi
618
619	#
620	# Bail if iana_name isn't defined. Services with static rules
621	# like nis/client don't need to generate rules using
622	# iana name and protocol information.
623	#
624	[ -z "$iana_name" ] && return 1
625
626	#
627	# RPC services
628	#
629	if [ "$isrpc" = "true" ]; then
630		# The ports used for IPv6 are usually also reachable
631		# through IPv4, so generate IPv4 rules for them, too.
632		tports=`$SERVINFO -R -p -t -s $iana_name 2>/dev/null`
633		tports6=`$SERVINFO -R -p -t6 -s $iana_name 2>/dev/null`
634		if [ -n "$tports" -o -n "$tports6" ]; then
635			tports=`unique_ports $tports $tports6`
636			for tport in $tports; do
637				generate_rules $service $policy "tcp" \
638				    $tport $file
639			done
640		fi
641
642		if [ -n "$tports6" ]; then
643			for tport6 in $tports6; do
644				generate_rules $service $policy "tcp" \
645				    $tport6 $file6 _6
646			done
647		fi
648
649		uports=`$SERVINFO -R -p -u -s $iana_name 2>/dev/null`
650		uports6=`$SERVINFO -R -p -u6 -s $iana_name 2>/dev/null`
651		if [ -n "$uports" ]; then
652			uports=`unique_ports $uports $uports6`
653			for uport in $uports; do
654				generate_rules $service $policy "udp" \
655				    $uport $file
656			done
657		fi
658
659		if [ -n "$uports6" ]; then
660			for uport6 in $uports6; do
661				generate_rules $service $policy "udp" \
662				    $uport6 $file6 _6
663			done
664		fi
665
666		return 0
667	fi
668
669	#
670	# Get the IANA port and supported protocols(tcp and udp)
671	#
672	tport=`$SERVINFO -p -t -s $iana_name 2>&1`
673	if [ $? -eq 0 -a -n "$tport" ]; then
674		generate_rules $service $policy "tcp" $tport $file
675	fi
676
677	tport6=`$SERVINFO -p -t6 -s $iana_name 2>&1`
678	if [ $? -eq 0 -a -n "$tport6" ]; then
679		generate_rules $service $policy "tcp" $tport6 $file6 _6
680	fi
681
682	uport=`$SERVINFO -p -u -s $iana_name 2>&1`
683	if [ $? -eq 0 -a -n "$uport" ]; then
684		generate_rules $service $policy "udp" $uport $file
685	fi
686
687	uport6=`$SERVINFO -p -u6 -s $iana_name 2>&1`
688	if [ $? -eq 0 -a -n "$uport6" ]; then
689		generate_rules $service $policy "udp" $uport6 $file6 _6
690	fi
691
692	return 0
693}
694
695#
696# Given a service's name, policy, protocol and port, generate ipf rules
697# - list of host/network/interface to apply policy
698#
699# A 'use_global' policy inherits the system-wided Global Default policy
700# from network/ipfilter. For {deny | allow} policies, the rules are
701# ordered as:
702#
703# - make exceptions to policy for those in "exceptions" list
704# - apply policy to those specified in "apply_to" list
705# - policy rule
706#
707generate_rules()
708{
709	service=$1
710	mypolicy=$2
711	proto=$3
712	port=$4
713	out=$5
714	_6=$6
715
716	#
717	# Default mode is to inherit from global's policy
718	#
719	[ "$mypolicy" = "use_global" ] && return 0
720
721	tcp_opts=""
722	[ "$proto" = "tcp" ] && tcp_opts="flags S keep state keep frags"
723
724	block_policy=`get_block_policy $1`
725        if [ "$block_policy" = "use_global" ]; then
726		block_policy=${GLOBAL_BLOCK_POLICY}
727        fi
728
729	if [ "$block_policy" = "return" ]; then
730		[ "$proto" = "tcp" ] && block_policy="return-rst"
731		[ "$proto" != "tcp" ] && block_policy="return-icmp-as-dest"
732	else
733		block_policy=""
734        fi
735
736	iplist=`get_target${_6}_list $service`
737
738	#
739	# Allow all if policy is 'none'
740	#
741	if [ "$mypolicy" = "none" ]; then
742		for ip in $iplist; do
743			daddr=`get_IP ${ip}`
744			[ -z "$daddr" -o "$daddr" = '""' ] && continue
745			echo "pass in log quick proto ${proto} from any to ${daddr}" \
746			    "port = ${port} ${tcp_opts}" >>${out}
747		done
748		return 0
749	fi
750
751	#
752	# For now, let's concern ourselves only with incoming traffic.
753	#
754	[ "$mypolicy" = "deny" ] && { ecmd="pass"; acmd="block ${block_policy}"; }
755	[ "$mypolicy" = "allow" ] && { ecmd="block ${block_policy}"; acmd="pass"; }
756
757	for name in `get_exceptions${_6} $service`; do
758		[ -z "$name" -o "$name" = '""' ] && continue
759
760		ifc=`get_interface $name`
761		if [ $? -eq 0 -a -n "$ifc" ]; then
762			for ip in $iplist; do
763				daddr=`get_IP ${ip}`
764				[ -z "$daddr" -o "$daddr" = '""' ] && continue
765				echo "${ecmd} in log quick on ${ifc} from any to" \
766				    "${daddr} port = ${port}" >>${out}
767			done
768			continue
769		fi
770
771		saddr=`get_IP ${name}`
772		if [ $? -eq 0 -a -n "$saddr" ]; then
773			for ip in $iplist; do
774				daddr=`get_IP ${ip}`
775				[ -z "$daddr" -o "$daddr" = '""' ] && continue
776				echo "${ecmd} in log quick proto ${proto} from ${saddr}" \
777				    "to ${daddr} port = ${port} ${tcp_opts}" >>${out}
778			done
779		fi
780	done
781
782	for name in `get_apply2${_6}_list $service`; do
783		[ -z "$name" -o "$name" = '""' ] && continue
784
785		ifc=`get_interface $name`
786		if [ $? -eq 0 -a -n "$ifc" ]; then
787			for ip in $iplist; do
788				daddr=`get_IP ${ip}`
789				[ -z "$daddr" -o "$daddr" = '""' ] && continue
790				echo "${acmd} in log quick on ${ifc} from any to" \
791				    "${daddr} port = ${port}" >>${out}
792			done
793			continue
794		fi
795
796		saddr=`get_IP ${name}`
797		if [ $? -eq 0 -a -n "$saddr" ]; then
798			for ip in $iplist; do
799				daddr=`get_IP ${ip}`
800				[ -z "$daddr" -o "$daddr" = '""' ] && continue
801				echo "${acmd} in log quick proto ${proto} from ${saddr}" \
802				    "to ${daddr} port = ${port} ${tcp_opts}" >>${out}
803			done
804		fi
805	done
806
807	for ip in $iplist; do
808		daddr=`get_IP ${ip}`
809		[ -z "$daddr" -o "$daddr" = '""' ] && continue
810		echo "${ecmd} in log quick proto ${proto} from any to ${daddr}" \
811		    "port = ${port} ${tcp_opts}" >>${out}
812	done
813
814	return 0
815}
816
817#
818# Service has either IANA ports and proto or its own firewall method to
819# generate the rules.
820#
821# - if service has a custom method, use it to populate its rules
822# - if service has a firewall_config pg, use process_server_svc
823#
824# Argument - fmri
825#
826process_service()
827{
828	#
829	# Don't process network/ipfilter
830	#
831	[ "$1" = "$IPF_FMRI" ] && return 0
832
833	service_check_state $1 $SMF_MAINT && return 1
834
835	method=`svcprop -p $FW_CONTEXT_PG/$METHOD_PROP $1 2>/dev/null | \
836	    sed 's/\\\//g'`
837	if [ -n "$method" -a "$method" != '""' ]; then
838		( exec $method $1 >/dev/null )
839	else
840		svcprop -p $FW_CONFIG_PG $1 >/dev/null 2>&1 || return 1
841		process_server_svc $1 || return 1
842	fi
843	return 0
844}
845
846#
847# Generate rules for protocol/port defined in firewall_config_default/open_ports
848# property. These are non-service programs whose network resource info are
849# defined as "{tcp | upd}:{PORT | PORT-PORT}". Essentially, these programs need
850# some specific local ports to be opened. For example, BitTorrent clients need to
851# have 6881-6889 opened.
852#
853process_nonsvc_progs()
854{
855	out=$1
856	echo "# Non-service programs rules" >>${out}
857	progs=`global_get_prop_value $FW_CONFIG_DEF_PG $OPEN_PORTS_PROP`
858
859	for prog in $progs; do
860		[ -z "$prog" -o "$prog" = '""' ] && continue
861
862		port=`tuple_get_port $prog`
863		[ $? -eq 1 -o -z "$port" ] && continue
864
865		proto=`tuple_get_proto $prog`
866		[ $? -eq 1 ] && continue
867
868		set -- $port
869		if  [ $# -gt 1 ]; then
870			if [ -z "$proto" ]; then
871				echo "pass in log quick from any to any" \
872				    "port ${1} >< ${2}" >>${out}
873			else
874				echo "pass in log quick proto ${proto} from any" \
875				    "to any port ${1} >< ${2}" >>${out}
876			fi
877		else
878			if [ -z "$proto" ]; then
879				echo "pass in log quick from any to any" \
880				    "port = ${1}" >>${out}
881			else
882				echo "pass in log quick proto ${proto} from any" \
883				    "to any port = ${1}" >>${out}
884			fi
885		fi
886	done
887
888	return 0
889}
890
891#
892# Generate a new /etc/ipf/ipf.conf. If firewall policy is 'none',
893# ipf.conf is empty .
894#
895create_global_rules()
896{
897	if [ "$GLOBAL_POLICY" = "custom" ]; then
898		file=`global_get_prop_value $FW_CONFIG_DEF_PG $CUSTOM_FILE_PROP`
899		file6=`global_get_prop_value $FW_CONFIG_DEF_PG $CUSTOM_FILE_6_PROP`
900
901		[ -n "$file" ] && custom_set_symlink $file
902		[ -n "$file6" ] && custom_set_symlink_6 $file6
903
904		return 0
905	fi
906
907	TEMP=`mktemp /var/run/ipf.conf.pid$$.XXXXXX`
908	TEMP6=`mktemp /var/run/ipf6.conf.pid$$.XXXXXX`
909	process_nonsvc_progs $TEMP
910	process_nonsvc_progs $TEMP6
911
912	echo "# Global Default rules" >>${TEMP}
913	echo "# Global Default rules" >>${TEMP6}
914	if [ "$GLOBAL_POLICY" != "none" ]; then
915		echo "pass out log quick all keep state" >>${TEMP}
916		echo "pass out log quick all keep state" >>${TEMP6}
917	fi
918
919	case "$GLOBAL_POLICY" in
920	'none')
921		# No rules
922		replace_file ${IPFILCONF} ${TEMP}
923		replace_file ${IP6FILCONF} ${TEMP6}
924		return $?
925		;;
926
927	'deny')
928		ecmd="pass"
929		acmd="block"
930		;;
931
932	'allow')
933		ecmd="block"
934		acmd="pass"
935		;;
936	*)
937		return 1;
938		;;
939	esac
940
941	for name in `global_get_prop_value $FW_CONFIG_DEF_PG $EXCEPTIONS_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" >>${TEMP}
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" >>${TEMP}
953		fi
954
955	done
956
957	for name in `global_get_prop_value $FW_CONFIG_DEF_PG $EXCEPTIONS_6_PROP`; do
958		[ -z "$name" -o "$name" = '""' ] && continue
959
960		ifc=`get_interface $name`
961		if [ $? -eq 0 -a -n "$ifc" ]; then
962			echo "${ecmd} in log quick on ${ifc} all" >>${TEMP6}
963			continue
964		fi
965
966		addr=`get_IP ${name}`
967		if [ $? -eq 0 -a -n "$addr" ]; then
968			echo "${ecmd} in log quick from ${addr} to any" >>${TEMP6}
969		fi
970
971	done
972
973	for name in `global_get_prop_value $FW_CONFIG_DEF_PG $APPLY2_PROP`; do
974		[ -z "$name" -o "$name" = '""' ] && continue
975
976		ifc=`get_interface $name`
977		if [ $? -eq 0 -a -n "$ifc" ]; then
978			echo "${acmd} in log quick on ${ifc} all" >>${TEMP}
979			continue
980		fi
981
982		addr=`get_IP ${name}`
983		if [ $? -eq 0 -a -n "$addr" ]; then
984			echo "${acmd} in log quick from ${addr} to any" >>${TEMP}
985		fi
986	done
987
988	for name in `global_get_prop_value $FW_CONFIG_DEF_PG $APPLY2_6_PROP`; do
989		[ -z "$name" -o "$name" = '""' ] && continue
990
991		ifc=`get_interface $name`
992		if [ $? -eq 0 -a -n "$ifc" ]; then
993			echo "${acmd} in log quick on ${ifc} all" >>${TEMP6}
994			continue
995		fi
996
997		addr=`get_IP ${name}`
998		if [ $? -eq 0 -a -n "$addr" ]; then
999			echo "${acmd} in log quick from ${addr} to any" >>${TEMP6}
1000		fi
1001	done
1002
1003	if [ "$GLOBAL_POLICY" = "allow" ]; then
1004		#
1005		# Allow DHCP(v6) traffic if running as a DHCP client
1006		#
1007		/sbin/netstrategy | grep dhcp >/dev/null 2>&1
1008		if [ $? -eq 0 ]; then
1009			echo "pass out log quick from any port = 68" \
1010			    "keep state" >>${TEMP}
1011			echo "pass in log quick from any to any port = 68" >>${TEMP}
1012
1013			echo "pass out log quick from any port = 546" \
1014			    "keep state" >>${TEMP6}
1015			echo "pass in log quick from any to any port = 546" >>${TEMP6}
1016		fi
1017		echo "block in log all" >>${TEMP}
1018		echo "block in log all" >>${TEMP6}
1019	fi
1020
1021	replace_file ${IPFILCONF} ${TEMP}
1022	replace_file ${IP6FILCONF} ${TEMP6}
1023	return $?
1024}
1025
1026#
1027# Generate a new /etc/ipf/ipf_ovr.conf, the override system-wide policy. It's
1028# a simplified policy that doesn't support 'exceptions' entities.
1029#
1030# If firewall policy is "none", no rules are generated.
1031#
1032# Note that "pass" rules don't have "quick" as we don't want
1033# them to override services' block rules.
1034#
1035create_global_ovr_rules()
1036{
1037	#
1038	# Simply empty override file if global policy is 'custom'
1039	#
1040	if [ "$GLOBAL_POLICY" = "custom" ]; then
1041		echo "# 'custom' global policy" >$IPFILOVRCONF
1042		echo "# 'custom' global policy" >$IP6FILOVRCONF
1043		return 0
1044	fi
1045
1046	#
1047	# Get and process override policy
1048	#
1049	ovr_policy=`global_get_prop_value $FW_CONFIG_OVR_PG $POLICY_PROP`
1050	if [ "$ovr_policy" = "none" ]; then
1051		echo "# global override policy is 'none'" >$IPFILOVRCONF
1052		echo "# global override policy is 'none'" >$IP6FILOVRCONF
1053		return 0
1054	fi
1055
1056	TEMP=`mktemp /var/run/ipf_ovr.conf.pid$$.XXXXXX`
1057	[ "$ovr_policy" = "deny" ] && acmd="block in log quick"
1058	[ "$ovr_policy" = "allow" ] && acmd="pass in log"
1059
1060	apply2_list=`global_get_prop_value $FW_CONFIG_OVR_PG $APPLY2_PROP`
1061	for name in $apply2_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" >>${TEMP}
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" >>${TEMP}
1073		fi
1074	done
1075
1076	apply2_6_list=`global_get_prop_value $FW_CONFIG_OVR_PG $APPLY2_6_PROP`
1077	for name in $apply2_6_list; do
1078		[ -z "$name" -o "$name" = '""' ] && continue
1079
1080		ifc=`get_interface $name`
1081		if [ $? -eq 0 -a -n "$ifc" ]; then
1082			echo "${acmd} on ${ifc} all" >>${TEMP6}
1083			continue
1084		fi
1085
1086		addr=`get_IP ${name}`
1087		if [ $? -eq 0 -a -n "$addr" ]; then
1088			echo "${acmd} from ${addr} to any" >>${TEMP6}
1089		fi
1090	done
1091
1092	replace_file ${IPFILOVRCONF} ${TEMP}
1093	replace_file ${IP6FILOVRCONF} ${TEMP6}
1094	return $?
1095}
1096
1097#
1098# Service is put into maintenance state due to its invalid firewall
1099# definition and/or policy.
1100#
1101svc_mark_maintenance()
1102{
1103	svcadm mark maintenance $1 >/dev/null 2>&1
1104
1105	date=`date`
1106	echo "[ $date ${0}: $1 has invalid ipf configuration. ]"
1107	echo "[ $date ${0}: placing $1 in maintenance. ]"
1108
1109	#
1110	# Move service's rule files to another location since
1111	# they're most likely invalid.
1112	#
1113	ipfile=`fmri_to_file $1 $IPF_SUFFIX`
1114	[ -f "$ipfile" ] && mv $ipfile "$ipfile.bak"
1115	ip6file=`fmri_to_file $1 $IPF6_SUFFIX`
1116	[ -f "$ip6file" ] && mv $ip6file "$ip6file.bak"
1117
1118	natfile=`fmri_to_file $1 $NAT_SUFFIX`
1119	[ -f "$natfile" ] && mv $natfile "$natfile.bak"
1120
1121	return 0
1122}
1123
1124svc_is_server()
1125{
1126	svcprop -p $FW_CONFIG_PG $1 >/dev/null 2>&1
1127}
1128
1129#
1130# Create rules for enabled firewalling and client services.
1131# - obtain the list of enabled services and process them
1132# - save the list of rules file for later use
1133#
1134create_services_rules()
1135{
1136	#
1137	# Do nothing if global policy is 'custom'
1138	#
1139	[ "$GLOBAL_POLICY" = "custom" ] && return 0
1140
1141	ipf_get_lock
1142
1143	#
1144	# Get all enabled services
1145	#
1146	allsvcs=`svcprop -cf -p general/enabled -p general_ovr/enabled '*' \
1147	    2>/dev/null | sed -n 's,^\(svc:.*\)/:properties/.* true$,\1,p' | sort -u`
1148
1149	#
1150	# Process enabled services
1151	#
1152	for s in $allsvcs; do
1153		service_is_enabled $s || continue
1154		process_service $s || continue
1155
1156		ipfile=`fmri_to_file $s $IPF_SUFFIX`
1157		if [ -n "$ipfile" -a -r "$ipfile" ]; then
1158			check_ipf_syntax $ipfile
1159			if [ $? -ne 0 ]; then
1160				svc_mark_maintenance $s
1161				continue
1162			fi
1163
1164			svc_is_server $s
1165			if [ $? -eq 0 ]; then
1166				check_ipf_rules $ipfile
1167				if [ $? -ne 0 ]; then
1168					svc_mark_maintenance $s
1169					continue
1170				fi
1171			fi
1172			CONF_FILES="$CONF_FILES $ipfile"
1173		fi
1174
1175		ip6file=`fmri_to_file $s $IPF6_SUFFIX`
1176		if [ -n "$ip6file" -a -r "$ip6file" ]; then
1177			check_ipf_syntax $ip6file -6
1178			if [ $? -ne 0 ]; then
1179				svc_mark_maintenance $s
1180				continue
1181			fi
1182
1183			svc_is_server $s
1184			if [ $? -eq 0 ]; then
1185				check_ipf_rules $ip6file -6
1186				if [ $? -ne 0 ]; then
1187					svc_mark_maintenance $s
1188					continue
1189				fi
1190			fi
1191			CONF6_FILES="$CONF6_FILES $ip6file"
1192		fi
1193
1194		natfile=`fmri_to_file $s $NAT_SUFFIX`
1195		if [ -n "$natfile" -a -r "$natfile" ]; then
1196			check_nat_syntax $natfile
1197			if [ $? -ne 0 ]; then
1198				svc_mark_maintenance $s
1199				continue
1200			fi
1201
1202			NAT_FILES="$NAT_FILES $natfile"
1203		fi
1204	done
1205
1206	ipf_remove_lock
1207	return 0
1208}
1209
1210#
1211# We update a services ipf ruleset in the following manners:
1212# - service is disabled, tear down its rules.
1213# - service is disable or refreshed(online), setup or update its rules.
1214#
1215service_update_rules()
1216{
1217	svc=$1
1218
1219	ipfile=`fmri_to_file $svc $IPF_SUFFIX`
1220	ip6file=`fmri_to_file $svc $IPF6_SUFFIX`
1221	[ -n "$ipfile" ] && remove_rules $ipfile
1222	[ -n "$ip6file" ] && remove_rules $ip6file -6
1223
1224	[ -z "$ipfile" -a -z "$ip6file" ] && return 0
1225
1226	natfile=`fmri_to_file $svc $NAT_SUFFIX`
1227	[ -n "$natfile" ] && remove_nat_rules $natfile
1228
1229	#
1230	# Don't go further if service is disabled or in maintenance.
1231	#
1232	service_is_enabled $svc || return 0
1233	service_check_state $1 $SMF_MAINT && return 0
1234
1235	process_service $svc || return 1
1236	if [ -f "$ipfile" ]; then
1237		check_ipf_syntax $ipfile
1238		if [ $? -ne 0 ]; then
1239			svc_mark_maintenance $svc
1240			return 1
1241		fi
1242	fi
1243
1244	if [ -f "$ip6file" ]; then
1245		check_ipf_syntax $ip6file -6
1246		if [ $? -ne 0 ]; then
1247			svc_mark_maintenance $svc
1248			return 1
1249		fi
1250	fi
1251
1252	if [ -f "$natfile" ]; then
1253		check_nat_syntax $natfile
1254		if [ $? -ne 0 ]; then
1255			svc_mark_maintenance $svc
1256			return 1
1257		fi
1258	fi
1259
1260	if [ -f "$ipfile" ]; then
1261		svc_is_server $svc
1262		if [ $? -eq 0 ]; then
1263			update_check_ipf_rules $ipfile
1264			if [ $? -ne 0 ]; then
1265				svc_mark_maintenance $svc
1266				return 1
1267			fi
1268		fi
1269
1270		prepend_new_rules $ipfile
1271
1272		#
1273		# reload Global Override rules to
1274		# maintain correct ordering.
1275		#
1276		remove_rules $IPFILOVRCONF
1277		prepend_new_rules $IPFILOVRCONF
1278	fi
1279
1280	if [ -f "$ip6file" ]; then
1281		svc_is_server $svc
1282		if [ $? -eq 0 ]; then
1283			update_check_ipf_rules $ip6file -6
1284			if [ $? -ne 0 ]; then
1285				svc_mark_maintenance $svc
1286				return 1
1287			fi
1288		fi
1289
1290		prepend_new_rules $ip6file -6
1291
1292		#
1293		# reload Global Override rules to
1294		# maintain correct ordering.
1295		#
1296		remove_rules $IP6FILOVRCONF -6
1297		prepend_new_rules $IP6FILOVRCONF -6
1298	fi
1299
1300	[ -f "$natfile" ] && append_new_nat_rules $natfile
1301
1302	return 0
1303}
1304
1305#
1306# Call the service_update_rules with appropriate svc fmri.
1307#
1308# This is called from '/lib/svc/method/ipfilter fw_update' whenever
1309# a service is disabled/enabled/refreshed.
1310#
1311service_update()
1312{
1313	svc=$1
1314	ret=0
1315
1316	#
1317	# If ipfilter isn't online or global policy is 'custom',
1318	# nothing should be done.
1319	#
1320	[ "$GLOBAL_POLICY" = "custom" ] && return 0
1321	service_check_state $SMF_FMRI $SMF_ONLINE || return 0
1322
1323	ipf_get_lock
1324	service_update_rules $svc || ret=1
1325
1326	ipf_remove_lock
1327	return $ret
1328}
1329
1330#
1331# Initialize global configuration
1332#
1333global_init
1334
1335