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