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