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