1#!/bin/sh
2#
3#
4
5# PROVIDE: jail
6# REQUIRE: LOGIN FILESYSTEMS
7# BEFORE: securelevel
8# KEYWORD: shutdown
9
10. /etc/rc.subr
11
12name="jail"
13desc="Manage system jails"
14rcvar="jail_enable"
15
16start_cmd="jail_start"
17start_postcmd="jail_warn"
18stop_cmd="jail_stop"
19config_cmd="jail_config"
20console_cmd="jail_console"
21status_cmd="jail_status"
22extra_commands="config console status"
23: ${jail_program:=/usr/sbin/jail}
24: ${jail_consolecmd:=/usr/bin/login -f root}
25: ${jail_jexec:=/usr/sbin/jexec}
26: ${jail_jls:=/usr/sbin/jls}
27
28need_dad_wait=
29
30# extract_var jv name param num defval
31#	Extract value from ${jail_$jv_$name} or ${jail_$name} and
32#	set it to $param.  If not defined, $defval is used.
33#	When $num is [0-9]*, ${jail_$jv_$name$num} are looked up and
34#	$param is set by using +=.  $num=0 is optional (params may start at 1).
35#	When $num is YN or NY, the value is interpreted as boolean.
36#	When $num is @, the value is interpreted as an array separted by IFS.
37extract_var()
38{
39	local i _jv _name _param _num _def _name1 _name2
40	_jv=$1
41	_name=$2
42	_param=$3
43	_num=$4
44	_def=$5
45
46	case $_num in
47	YN)
48		_name1=jail_${_jv}_${_name}
49		_name2=jail_${_name}
50		eval $_name1=\"\${$_name1:-\${$_name2:-$_def}}\"
51		if checkyesno $_name1; then
52			echo "	$_param = 1;"
53		else
54			echo "	$_param = 0;"
55		fi
56	;;
57	NY)
58		_name1=jail_${_jv}_${_name}
59		_name2=jail_${_name}
60		eval $_name1=\"\${$_name1:-\${$_name2:-$_def}}\"
61		if checkyesno $_name1; then
62			echo "	$_param = 0;"
63		else
64			echo "	$_param = 1;"
65		fi
66	;;
67	[0-9]*)
68		i=$_num
69		while : ; do
70			_name1=jail_${_jv}_${_name}${i}
71			_name2=jail_${_name}${i}
72			eval _tmpargs=\"\${$_name1:-\${$_name2:-$_def}}\"
73			if [ -n "$_tmpargs" ]; then 
74				echo "	$_param += \"$_tmpargs\";"
75			elif [ $i != 0 ]; then
76				break;
77			fi
78			i=$(($i + 1))
79		done
80	;;
81	@)
82		_name1=jail_${_jv}_${_name}
83		_name2=jail_${_name}
84		eval _tmpargs=\"\${$_name1:-\${$_name2:-$_def}}\"
85		set -- $_tmpargs
86		if [ $# -gt 0 ]; then
87			echo -n "	$_param = "
88			while [ $# -gt 1 ]; do
89				echo -n "\"$1\", "
90				shift
91			done
92			echo "\"$1\";"
93		fi
94	;;
95	*)
96		_name1=jail_${_jv}_${_name}
97		_name2=jail_${_name}
98		eval _tmpargs=\"\${$_name1:-\${$_name2:-$_def}}\"
99		if [ -n "$_tmpargs" ]; then
100			echo "	$_param = \"$_tmpargs\";"
101		fi
102	;;
103	esac
104}
105
106# parse_options _j _jv
107#	Parse options and create a temporary configuration file if necessary.
108#
109parse_options()
110{
111	local _j _jv _p
112	_j=$1
113	_jv=$2
114
115	_confwarn=0
116	if [ -z "$_j" ]; then
117		warn "parse_options: you must specify a jail"
118		return
119	fi
120	eval _jconf=\"\${jail_${_jv}_conf:-/etc/jail.${_j}.conf}\"
121	eval _rootdir=\"\$jail_${_jv}_rootdir\"
122	eval _jconfdir=\"/etc/jail.conf.d/${_j}.conf\"
123	eval _hostname=\"\$jail_${_jv}_hostname\"
124	if [ -z "$_rootdir" -o \
125	     -z "$_hostname" ]; then
126		if [ -r "$_jconf" ]; then
127			_conf="$_jconf"
128			return 0
129		elif [ -r "$_jconfdir" ] && ! egrep -q \
130		    '^\s*\.include\s*["'\'']?/etc/jail.conf.d/' "$jail_conf" \
131		    2>/dev/null; then
132			_conf="$_jconfdir"
133			return 0
134		elif [ -r "$jail_conf" ]; then
135			_conf="$jail_conf"
136			return 0
137		else
138			warn "Invalid configuration for $_j " \
139			    "(no jail.conf, no hostname, or no path).  " \
140			    "Jail $_j was ignored."
141		fi
142		return 1
143	fi
144	eval _ip=\"\$jail_${_jv}_ip\"
145	if [ -z "$_ip" ] && ! check_kern_features vimage; then
146		warn "no ipaddress specified and no vimage support.  " \
147		    "Jail $_j was ignored."
148		return 1
149	fi
150	_conf=/var/run/jail.${_j}.conf
151	#
152	# To relieve confusion, show a warning message.
153	#
154	: ${jail_confwarn:=YES}
155	checkyesno jail_confwarn && _confwarn=1
156	if [ -r "$jail_conf" -o -r "$_jconf" ]; then
157		if ! checkyesno jail_parallel_start; then
158			warn "$_conf is created and used for jail $_j."
159		fi
160	fi
161	/usr/bin/install -m 0644 -o root -g wheel /dev/null $_conf || return 1
162
163	eval : \${jail_${_jv}_flags:=${jail_flags}}
164	eval _exec=\"\$jail_${_jv}_exec\"
165	eval _exec_start=\"\$jail_${_jv}_exec_start\"
166	eval _exec_stop=\"\$jail_${_jv}_exec_stop\"
167	if [ -n "${_exec}" ]; then
168		#   simple/backward-compatible execution
169		_exec_start="${_exec}"
170		_exec_stop=""
171	else
172		#   flexible execution
173		if [ -z "${_exec_start}" ]; then
174			_exec_start="/bin/sh /etc/rc"
175			if [ -z "${_exec_stop}" ]; then
176				_exec_stop="/bin/sh /etc/rc.shutdown jail"
177			fi
178		fi
179	fi
180	eval _interface=\"\${jail_${_jv}_interface:-${jail_interface}}\"
181	eval _parameters=\"\${jail_${_jv}_parameters:-${jail_parameters}}\"
182	eval _fstab=\"\${jail_${_jv}_fstab:-${jail_fstab:-/etc/fstab.$_j}}\"
183	(
184		date +"# Generated by rc.d/jail at %Y-%m-%d %H:%M:%S"
185		echo "$_j {"
186		extract_var $_jv hostname host.hostname - ""
187		extract_var $_jv rootdir path - ""
188		if [ -n "$_ip" ]; then
189			extract_var $_jv interface interface - ""
190			jail_handle_ips_option $_ip $_interface
191			alias=0
192			while : ; do
193				eval _x=\"\$jail_${_jv}_ip_multi${alias}\"
194				[ -z "$_x" ] && break
195
196				jail_handle_ips_option $_x $_interface
197				alias=$(($alias + 1))
198			done
199			case $need_dad_wait in
200			1)
201				# Sleep to let DAD complete before
202				# starting services.
203				echo "	exec.start += \"sleep " \
204				$(($(${SYSCTL_N} net.inet6.ip6.dad_count) + 1)) \
205				"\";"
206			;;
207			esac
208			# These are applicable only to non-vimage jails. 
209			extract_var $_jv fib exec.fib - ""
210			extract_var $_jv socket_unixiproute_only \
211			    allow.raw_sockets NY YES
212		else
213			echo "	vnet;"
214			extract_var $_jv vnet_interface vnet.interface @ ""
215		fi
216
217		echo "	exec.clean;"
218		echo "	exec.system_user = \"root\";"
219		echo "	exec.jail_user = \"root\";"
220		extract_var $_jv exec_prestart exec.prestart 0 ""
221		extract_var $_jv exec_poststart exec.poststart 0 ""
222		extract_var $_jv exec_prestop exec.prestop 0 ""
223		extract_var $_jv exec_poststop exec.poststop 0 ""
224
225		echo "	exec.start += \"$_exec_start\";"
226		extract_var $_jv exec_afterstart exec.start 0 ""
227		echo "	exec.stop = \"$_exec_stop\";"
228
229		extract_var $_jv consolelog exec.consolelog - \
230		    /var/log/jail_${_j}_console.log
231
232		if [ -r $_fstab ]; then
233			echo "	mount.fstab = \"$_fstab\";"
234		fi
235
236		eval : \${jail_${_jv}_devfs_enable:=${jail_devfs_enable:-NO}}
237		if checkyesno jail_${_jv}_devfs_enable; then
238			echo "	mount.devfs;"
239			eval _ruleset=\${jail_${_jv}_devfs_ruleset:-${jail_devfs_ruleset}}
240			case $_ruleset in
241			"")	;;
242			[0-9]*) echo "	devfs_ruleset = \"$_ruleset\";" ;;
243			devfsrules_jail)
244				# XXX: This is the default value,
245				# Let jail(8) to use the default because
246				# mount(8) only accepts an integer. 
247				# This should accept a ruleset name.
248			;;
249			*)	warn "devfs_ruleset must be an integer." ;;
250			esac
251		fi
252		eval : \${jail_${_jv}_fdescfs_enable:=${jail_fdescfs_enable:-NO}}
253		if checkyesno jail_${_jv}_fdescfs_enable; then
254			echo "	mount.fdescfs;"
255		fi
256		eval : \${jail_${_jv}_procfs_enable:=${jail_procfs_enable:-NO}}
257		if checkyesno jail_${_jv}_procfs_enable; then
258			echo "	mount.procfs;"
259		fi
260
261		eval : \${jail_${_jv}_mount_enable:=${jail_mount_enable:-NO}}
262		if checkyesno jail_${_jv}_mount_enable; then
263			echo "	allow.mount;"
264		fi
265
266		extract_var $_jv set_hostname_allow allow.set_hostname YN NO
267		extract_var $_jv sysvipc_allow allow.sysvipc YN NO
268		extract_var $_jv enforce_statfs enforce_statfs - 2
269		extract_var $_jv osreldate osreldate
270		extract_var $_jv osrelease osrelease
271
272		_zfs_dataset="$(eval echo \$jail_${_jv}_zfs_dataset)"
273		if [ -n "$_zfs_dataset" ]; then
274			for ds in $_zfs_dataset; do
275				echo "	zfs.dataset += ${ds};"
276			done
277		fi
278		for _p in $_parameters; do
279			echo "	${_p%\;};"
280		done
281		echo "}"
282	) >> $_conf
283
284	return 0
285}
286
287# jail_extract_address argument iface
288#	The second argument is the string from one of the _ip
289#	or the _multi variables. In case of a comma separated list
290#	only one argument must be passed in at a time.
291#	The function alters the _type, _iface, _addr and _mask variables.
292#
293jail_extract_address()
294{
295	local _i _interface
296	_i=$1
297	_interface=$2
298
299	if [ -z "${_i}" ]; then
300		warn "jail_extract_address: called without input"
301		return
302	fi
303
304	# Check if we have an interface prefix given and split into
305	# iFace and rest.
306	case "${_i}" in
307	*\|*)	# ifN|.. prefix there
308		_iface=${_i%%|*}
309		_r=${_i##*|}
310		;;
311	*)	_iface=""
312		_r=${_i}
313		;;
314	esac
315
316	# In case the IP has no interface given, check if we have a global one.
317	_iface=${_iface:-${_interface}}
318
319	# Set address, cut off any prefix/netmask/prefixlen.
320	_addr=${_r}
321	_addr=${_addr%%[/ ]*}
322
323	# Theoretically we can return here if interface is not set,
324	# as we only care about the _mask if we call ifconfig.
325	# This is not done because we may want to santize IP addresses
326	# based on _type later, and optionally change the type as well.
327
328	# Extract the prefix/netmask/prefixlen part by cutting off the address.
329	_mask=${_r}
330	_mask=`expr -- "${_mask}" : "${_addr}\(.*\)"`
331
332	# Identify type {inet,inet6}.
333	case "${_addr}" in
334	*\.*\.*\.*)	_type="inet" ;;
335	*:*)		_type="inet6" ;;
336	*)		warn "jail_extract_address: type not identified"
337			;;
338	esac
339
340	# Handle the special /netmask instead of /prefix or
341	# "netmask xxx" case for legacy IP.
342	# We do NOT support shortend class-full netmasks.
343	if [ "${_type}" = "inet" ]; then
344		case "${_mask}" in
345		/*\.*\.*\.*)	_mask=" netmask ${_mask#/}" ;;
346		*)		;;
347		esac
348
349		# In case _mask is still not set use /32.
350		_mask=${_mask:-/32}
351
352	elif [ "${_type}" = "inet6" ]; then
353		# In case _mask is not set for IPv6, use /128.
354		_mask=${_mask:-/128}
355	fi
356}
357
358# jail_handle_ips_option input iface
359#	Handle a single argument imput which can be a comma separated
360#	list of addresses (theoretically with an option interface and
361#	prefix/netmask/prefixlen).
362#
363jail_handle_ips_option()
364{
365	local _x _type _i _defif
366	_x=$1
367	_defif=$2
368
369	if [ -z "${_x}" ]; then
370		# No IP given. This can happen for the primary address
371		# of each address family.
372		return
373	fi
374
375	# Loop, in case we find a comma separated list, we need to handle
376	# each argument on its own.
377	while [ ${#_x} -gt 0 ]; do
378		case "${_x}" in
379		*,*)	# Extract the first argument and strip it off the list.
380			_i=`expr -- "${_x}" : '^\([^,]*\)'`
381			_x=`expr -- "${_x}" : "^[^,]*,\(.*\)"`
382		;;
383		*)	_i=${_x}
384			_x=""
385		;;
386		esac
387
388		_type=""
389		_addr=""
390		_mask=""
391		_iface=""
392		jail_extract_address $_i $_defif
393
394		# make sure we got an address.
395		case $_addr in
396		"")	continue ;;
397		*)	;;
398		esac
399
400		# Append address to list of addresses for the jail command.
401		case $_type in
402		inet)
403			echo "	ip4.addr += \"${_iface:+${_iface}|}${_addr}${_mask}\";"
404		;;
405		inet6)
406			echo "	ip6.addr += \"${_iface:+${_iface}|}${_addr}${_mask}\";"
407			need_dad_wait=1
408		;;
409		esac
410	done
411}
412
413jail_config()
414{
415	local _j _jv
416
417	case $1 in
418	_ALL)	return ;;
419	esac
420	for _j in $@; do
421		_j=$(echo $_j | tr /. _)
422		_jv=$(echo -n $_j | tr -c '[:alnum:]' _)
423		if parse_options $_j $_jv; then 
424			echo "$_j: parameters are in $_conf."
425		fi
426	done
427}
428
429jail_console()
430{
431	local _j _jv _cmd
432
433	# One argument that is not _ALL.
434	case $#:$1 in
435	0:*|1:_ALL)	err 3 "Specify a jail name." ;;
436	1:*)		;;
437	esac
438	_j=$(echo $1 | tr /. _)
439	_jv=$(echo -n $1 | tr -c '[:alnum:]' _)
440	shift
441	case $# in
442	0)	eval _cmd=\${jail_${_jv}_consolecmd:-$jail_consolecmd} ;;
443	*)	_cmd=$@ ;;
444	esac
445	$jail_jexec $_j $_cmd
446}
447
448jail_status()
449{
450
451	$jail_jls -N
452}
453
454jail_start()
455{
456	local _j _jv _jid _id _name
457
458	if [ $# = 0 ]; then
459		return
460	fi
461	startmsg -n 'Starting jails:'
462	case $1 in
463	_ALL)
464		command=$jail_program
465		rc_flags=$jail_flags
466		command_args="-f $jail_conf -c"
467		if ! checkyesno jail_parallel_start; then
468			command_args="$command_args -p1"
469		fi
470		_tmp=`mktemp -t jail` || exit 3
471		if $command $rc_flags $command_args >> $_tmp 2>&1; then
472			$jail_jls jid name | while read _id _name; do
473				startmsg -n " $_name"
474				echo $_id > /var/run/jail_${_name}.id
475			done
476		else
477			cat $_tmp
478		fi
479		rm -f $_tmp
480		startmsg '.'
481		return
482	;;
483	esac
484	if checkyesno jail_parallel_start; then
485		#
486		# Start jails in parallel and then check jail id when
487		# jail_parallel_start is YES.
488		#
489		for _j in $@; do
490			_j=$(echo $_j | tr /. _)
491			_jv=$(echo -n $_j | tr -c '[:alnum:]' _)
492			parse_options $_j $_jv || continue
493
494			eval rc_flags=\${jail_${_jv}_flags:-$jail_flags}
495			eval command=\${jail_${_jv}_program:-$jail_program}
496			command_args="-i -f $_conf -c $_j"
497			(
498				_tmp=`mktemp -t jail_${_j}` || exit 3
499				if $command $rc_flags $command_args \
500				    >> $_tmp 2>&1 </dev/null; then
501					startmsg -n " ${_hostname:-${_j}}"
502					_jid=$($jail_jls -j $_j jid)
503					echo $_jid > /var/run/jail_${_j}.id
504				else
505					startmsg " cannot start jail " \
506					    "\"${_hostname:-${_j}}\": "
507					cat $_tmp
508				fi
509				rm -f $_tmp
510			) &
511		done
512		wait
513	else
514		#
515		# Start jails one-by-one when jail_parallel_start is NO.
516		#
517		for _j in $@; do
518			_j=$(echo $_j | tr /. _)
519			_jv=$(echo -n $_j | tr -c '[:alnum:]' _)
520			parse_options $_j $_jv || continue
521
522			eval rc_flags=\${jail_${_jv}_flags:-$jail_flags}
523			eval command=\${jail_${_jv}_program:-$jail_program}
524			command_args="-i -f $_conf -c $_j"
525			_tmp=`mktemp -t jail` || exit 3
526			if $command $rc_flags $command_args \
527			    >> $_tmp 2>&1 </dev/null; then
528				startmsg -n " ${_hostname:-${_j}}"
529				_jid=$($jail_jls -j $_j jid)
530				echo $_jid > /var/run/jail_${_j}.id
531			else
532				startmsg " cannot start jail " \
533				    "\"${_hostname:-${_j}}\": "
534				cat $_tmp
535			fi
536			rm -f $_tmp
537		done
538	fi
539	startmsg '.'
540}
541
542jail_stop()
543{
544	local _j _jv
545
546	if [ $# = 0 ]; then
547		return
548	fi
549	echo -n 'Stopping jails:'
550	case $1 in
551	_ALL)
552		command=$jail_program
553		rc_flags=$jail_flags
554		command_args="-f $jail_conf -r"
555		if checkyesno jail_reverse_stop; then
556			$jail_jls name | tail -r
557		else
558			$jail_jls name
559		fi | while read _j; do
560			echo -n " $_j"
561			_tmp=`mktemp -t jail` || exit 3
562			$command $rc_flags $command_args $_j >> $_tmp 2>&1
563			if $jail_jls -j $_j > /dev/null 2>&1; then
564				cat $_tmp
565			else
566				rm -f /var/run/jail_${_j}.id
567			fi
568			rm -f $_tmp
569		done
570		echo '.'
571		return
572	;;
573	esac
574	checkyesno jail_reverse_stop && set -- $(reverse_list $@)
575	for _j in $@; do
576		_j=$(echo $_j | tr /. _)
577		_jv=$(echo -n $_j | tr -c '[:alnum:]' _)
578		parse_options $_j $_jv || continue
579		if ! $jail_jls -j $_j > /dev/null 2>&1; then
580			continue
581		fi
582		eval command=\${jail_${_jv}_program:-$jail_program}
583		echo -n " ${_hostname:-${_j}}"
584		_tmp=`mktemp -t jail` || exit 3
585		$command -q -f $_conf -r $_j >> $_tmp 2>&1
586		if $jail_jls -j $_j > /dev/null 2>&1; then
587			cat $_tmp
588		else
589			rm -f /var/run/jail_${_j}.id
590		fi
591		rm -f $_tmp
592	done
593	echo '.'
594}
595
596jail_warn()
597{
598
599	# To relieve confusion, show a warning message.
600	case $_confwarn in
601	1)	warn "Per-jail configuration via jail_* variables " \
602		    "is obsolete.  Please consider migrating to $jail_conf."
603	;;
604	esac
605}
606
607load_rc_config $name
608
609# doesn't make sense to run in a svcj
610jail_svcj="NO"
611
612case $# in
6131)	run_rc_command $@ ${jail_list:-_ALL} ;;
614*)	jail_reverse_stop="no"
615	run_rc_command $@ ;;
616esac
617