jail revision 187708
1#!/bin/sh
2#
3# $FreeBSD: head/etc/rc.d/jail 187708 2009-01-26 12:59:11Z bz $
4#
5
6# PROVIDE: jail
7# REQUIRE: LOGIN cleanvar
8# BEFORE: securelevel
9# KEYWORD: nojail shutdown
10
11# WARNING: This script deals with untrusted data (the data and
12# processes inside the jails) and care must be taken when changing the
13# code related to this!  If you have any doubt whether a change is
14# correct and have security impact, please get the patch reviewed by
15# the FreeBSD Security Team prior to commit.
16
17. /etc/rc.subr
18
19name="jail"
20rcvar=`set_rcvar`
21start_cmd="jail_start"
22stop_cmd="jail_stop"
23
24# init_variables _j
25#	Initialize the various jail variables for jail _j.
26#
27init_variables()
28{
29	_j="$1"
30
31	if [ -z "$_j" ]; then
32		warn "init_variables: you must specify a jail"
33		return
34	fi
35
36	eval _rootdir=\"\$jail_${_j}_rootdir\"
37	_devdir="${_rootdir}/dev"
38	_fdescdir="${_devdir}/fd"
39	_procdir="${_rootdir}/proc"
40	eval _hostname=\"\$jail_${_j}_hostname\"
41	eval _ip=\"\$jail_${_j}_ip\"
42	eval _interface=\"\${jail_${_j}_interface:-${jail_interface}}\"
43	eval _exec=\"\$jail_${_j}_exec\"
44	eval _exec_start=\"\${jail_${_j}_exec_start:-${jail_exec_start}}\"
45
46	i=1
47	while [ true ]; do
48		eval _exec_afterstart${i}=\"\${jail_${_j}_exec_afterstart${i}:-\${jail_exec_afterstart${i}}}\"
49		[ -z "$(eval echo \"\$_exec_afterstart${i}\")" ] &&  break
50		i=$((i + 1))
51	done
52	
53	eval _exec_stop=\"\${jail_${_j}_exec_stop:-${jail_exec_stop}}\"
54	if [ -n "${_exec}" ]; then
55		#   simple/backward-compatible execution
56		_exec_start="${_exec}"
57		_exec_stop=""
58	else
59		#   flexible execution
60		if [ -z "${_exec_start}" ]; then
61			_exec_start="/bin/sh /etc/rc"
62			if [ -z "${_exec_stop}" ]; then
63				_exec_stop="/bin/sh /etc/rc.shutdown"
64			fi
65		fi
66	fi
67
68	# The default jail ruleset will be used by rc.subr if none is specified.
69	eval _ruleset=\"\${jail_${_j}_devfs_ruleset:-${jail_devfs_ruleset}}\"
70	eval _devfs=\"\${jail_${_j}_devfs_enable:-${jail_devfs_enable}}\"
71	[ -z "${_devfs}" ] && _devfs="NO"
72	eval _fdescfs=\"\${jail_${_j}_fdescfs_enable:-${jail_fdescfs_enable}}\"
73	[ -z "${_fdescfs}" ] && _fdescfs="NO"
74	eval _procfs=\"\${jail_${_j}_procfs_enable:-${jail_procfs_enable}}\"
75	[ -z "${_procfs}" ] && _procfs="NO"
76
77	eval _mount=\"\${jail_${_j}_mount_enable:-${jail_mount_enable}}\"
78	[ -z "${_mount}" ] && _mount="NO"
79	# "/etc/fstab.${_j}" will be used for {,u}mount(8) if none is specified.
80	eval _fstab=\"\${jail_${_j}_fstab:-${jail_fstab}}\"
81	[ -z "${_fstab}" ] && _fstab="/etc/fstab.${_j}"
82	eval _flags=\"\${jail_${_j}_flags:-${jail_flags}}\"
83	[ -z "${_flags}" ] && _flags="-l -U root"
84	eval _consolelog=\"\${jail_${_j}_consolelog:-${jail_consolelog}}\"
85	[ -z "${_consolelog}" ] && _consolelog="/var/log/jail_${_j}_console.log"
86	eval _fib=\"\${jail_${_j}_fib:-${jail_fib}}\"
87
88	# Debugging aid
89	#
90	debug "$_j devfs enable: $_devfs"
91	debug "$_j fdescfs enable: $_fdescfs"
92	debug "$_j procfs enable: $_procfs"
93	debug "$_j mount enable: $_mount"
94	debug "$_j hostname: $_hostname"
95	debug "$_j ip: $_ip"
96	jail_show_addresses ${_j}
97	debug "$_j interface: $_interface"
98	debug "$_j fib: $_fib"
99	debug "$_j root: $_rootdir"
100	debug "$_j devdir: $_devdir"
101	debug "$_j fdescdir: $_fdescdir"
102	debug "$_j procdir: $_procdir"
103	debug "$_j ruleset: $_ruleset"
104	debug "$_j fstab: $_fstab"
105	debug "$_j exec start: $_exec_start"
106	debug "$_j consolelog: $_consolelog"
107
108	i=1
109	while [ true ]; do
110		eval out=\"\${_exec_afterstart${i}:-''}\"
111
112		if [ -z "$out" ]; then
113			break;
114		fi
115
116		debug "$_j exec after start #${i}: ${out}"
117		i=$((i + 1))
118	done
119
120	debug "$_j exec stop: $_exec_stop"
121	debug "$_j flags: $_flags"
122	debug "$_j consolelog: $_consolelog"
123
124	if [ -z "${_hostname}" ]; then
125		err 3 "$name: No hostname has been defined for ${_j}"
126	fi
127	if [ -z "${_rootdir}" ]; then
128		err 3 "$name: No root directory has been defined for ${_j}"
129	fi
130}
131
132# set_sysctl rc_knob mib msg
133#	If the mib sysctl is set according to what rc_knob
134#	specifies, this function does nothing. However if
135#	rc_knob is set differently than mib, then the mib
136#	is set accordingly and msg is displayed followed by
137#	an '=" sign and the word 'YES' or 'NO'.
138#
139set_sysctl()
140{
141	_knob="$1"
142	_mib="$2"
143	_msg="$3"
144
145	_current=`${SYSCTL} -n $_mib 2>/dev/null`
146	if checkyesno $_knob ; then
147		if [ "$_current" -ne 1 ]; then
148			echo -n " ${_msg}=YES"
149			${SYSCTL_W} 1>/dev/null ${_mib}=1
150		fi
151	else
152		if [ "$_current" -ne 0 ]; then
153			echo -n " ${_msg}=NO"
154			${SYSCTL_W} 1>/dev/null ${_mib}=0
155		fi
156	fi
157}
158
159# is_current_mountpoint()
160#	Is the directory mount point for a currently mounted file
161#	system?
162#
163is_current_mountpoint()
164{
165	local _dir _dir2
166
167	_dir=$1
168
169	_dir=`echo $_dir | sed -Ee 's#//+#/#g' -e 's#/$##'`
170	[ ! -d "${_dir}" ] && return 1
171	_dir2=`df ${_dir} | tail +2 | awk '{ print $6 }'`
172	[ "${_dir}" = "${_dir2}" ]
173	return $?
174}
175
176# is_symlinked_mountpoint()
177#	Is a mount point, or any of its parent directories, a symlink?
178#
179is_symlinked_mountpoint()
180{
181	local _dir
182
183	_dir=$1
184
185	[ -L "$_dir" ] && return 0
186	[ "$_dir" = "/" ] && return 1
187	is_symlinked_mountpoint `dirname $_dir`
188	return $?
189}
190
191# secure_umount
192#	Try to unmount a mount point without being vulnerable to
193#	symlink attacks.
194#
195secure_umount()
196{
197	local _dir
198
199	_dir=$1
200
201	if is_current_mountpoint ${_dir}; then
202		umount -f ${_dir} >/dev/null 2>&1
203	else
204		debug "Nothing mounted on ${_dir} - not unmounting"
205	fi
206}
207
208
209# jail_umount_fs
210#	This function unmounts certain special filesystems in the
211#	currently selected jail. The caller must call the init_variables()
212#	routine before calling this one.
213#
214jail_umount_fs()
215{
216	local _device _mountpt _rest
217
218	if checkyesno _fdescfs; then
219		if [ -d "${_fdescdir}" ] ; then
220			secure_umount ${_fdescdir}
221		fi
222	fi
223	if checkyesno _devfs; then
224		if [ -d "${_devdir}" ] ; then
225			secure_umount ${_devdir}
226		fi
227	fi
228	if checkyesno _procfs; then
229		if [ -d "${_procdir}" ] ; then
230			secure_umount ${_procdir}
231		fi
232	fi
233	if checkyesno _mount; then
234		[ -f "${_fstab}" ] || warn "${_fstab} does not exist"
235		tail -r ${_fstab} | while read _device _mountpt _rest; do
236			case ":${_device}" in
237			:#* | :)
238				continue
239				;;
240			esac
241			secure_umount ${_mountpt}
242		done
243	fi
244}
245
246# jail_mount_fstab()
247#	Mount file systems from a per jail fstab while trying to
248#	secure against symlink attacks at the mount points.
249#
250#	If we are certain we cannot secure against symlink attacks we
251#	do not mount all of the file systems (since we cannot just not
252#	mount the file system with the problematic mount point).
253#
254#	The caller must call the init_variables() routine before
255#	calling this one.
256#
257jail_mount_fstab()
258{
259	local _device _mountpt _rest
260
261	while read _device _mountpt _rest; do
262		case ":${_device}" in
263		:#* | :)
264			continue
265			;;
266		esac
267		if is_symlinked_mountpoint ${_mountpt}; then
268			warn "${_mountpt} has symlink as parent - not mounting from ${_fstab}"
269			return
270		fi
271	done <${_fstab}
272	mount -a -F "${_fstab}"
273}
274
275# jail_show_addresses jail
276#	Debug print the input for the given _multi aliases
277#	for a jail for init_variables().
278#
279jail_show_addresses()
280{
281	local _j _type alias
282	_j="$1"
283	alias=0
284
285	if [ -z "${_j}" ]; then
286		warn "jail_show_addresses: you must specify a jail"
287		return
288	fi
289
290	while : ; do
291		eval _addr=\"\$jail_${_j}_ip_multi${alias}\"
292		if [ -n "${_addr}" ]; then
293			debug "${_j} ip_multi${alias}: $_addr"
294			alias=$((${alias} + 1))
295		else
296			break
297		fi
298	done
299}
300
301# jail_extract_address argument
302#	The second argument is the string from one of the _ip
303#	or the _multi variables. In case of a comma separated list
304#	only one argument must be passed in at a time.
305#	The function alters the _type, _iface, _addr and _mask variables.
306#
307jail_extract_address()
308{
309	local _i
310	_i=$1
311
312	if [ -z "${_i}" ]; then
313		warn "jail_extract_address: called without input"
314		return
315	fi
316
317	# Check if we have an interface prefix given and split into
318	# iFace and rest.
319	case "${_i}" in
320	*\|*)	# ifN|.. prefix there
321		_iface=${_i%%|*}
322		_r=${_i##*|}
323		;;
324	*)	_iface=""
325		_r=${_i}
326		;;
327	esac
328
329	# In case the IP has no interface given, check if we have a global one.
330	_iface=${_iface:-${_interface}}
331
332	# Set address, cut off any prefix/netmask/prefixlen.
333	_addr=${_r}
334	_addr=${_addr%%[/ ]*}
335
336	# Theoretically we can return here if interface is not set,
337	# as we only care about the _mask if we call ifconfig.
338	# This is not done because we may want to santize IP addresses
339	# based on _type later, and optionally change the type as well.
340
341	# Extract the prefix/netmask/prefixlen part by cutting off the address.
342	_mask=${_r}
343	_mask=`expr "${_mask}" : "${_addr}\(.*\)"`
344
345	# Identify type {inet,inet6}.
346	case "${_addr}" in
347	*\.*\.*\.*)	_type="inet" ;;
348	*:*)		_type="inet6" ;;
349	*)		warn "jail_extract_address: type not identified"
350			;;
351	esac
352
353	# Handle the special /netmask instead of /prefix or
354	# "netmask xxx" case for legacy IP.
355	# We do NOT support shortend class-full netmasks.
356	if [ "${_type}" = "inet" ]; then
357		case "${_mask}" in
358		/*\.*\.*\.*)	_mask=" netmask ${_mask#/}" ;;
359		*)		;;
360		esac
361
362		# In case _mask is still not set use /32.
363		_mask=${_mask:-/32}
364
365	elif [ "${_type}" = "inet6" ]; then
366		# In case _maske is not set for IPv6, use /128.
367		_mask=${_mask:-/128}
368	fi
369}
370
371# jail_handle_ips_option {add,del} input
372#	Handle a single argument imput which can be a comma separated
373#	list of addresses (theoretically with an option interface and
374#	prefix/netmask/prefixlen).
375#
376jail_handle_ips_option()
377{
378	local _x _action _type _i
379	_action=$1
380	_x=$2
381
382	if [ -z "${_x}" ]; then
383		# No IP given. This can happen for the primary address
384		# of each address family.
385		return
386	fi
387
388	# Loop, in case we find a comma separated list, we need to handle
389	# each argument on its own.
390	while [ ${#_x} -gt 0 ]; do
391		case "${_x}" in
392		*,*)	# Extract the first argument and strip it off the list.
393			_i=`expr "${_x}" : '^\([^,]*\)'`
394			_x=`expr "${_x}" : "^[^,]*,\(.*\)"`
395			;;
396		*)	_i=${_x}
397			_x=""
398			;;
399		esac
400
401		_type=""
402		_iface=""
403		_addr=""
404		_mask=""
405		jail_extract_address "${_i}"
406
407		# make sure we got an address.
408		case "${_addr}" in
409		"")	continue ;;
410		*)	;;
411		esac
412
413		# Append address to list of addresses for the jail command.
414		case "${_addrl}" in
415		"")	_addrl="${_addr}" ;;
416		*)	_addrl="${_addrl},${_addr}" ;;
417		esac
418
419		# Configure interface alias if requested by a given interface
420		# and if we could correctly parse everything.
421		case "${_iface}" in
422		"")	continue ;;
423		esac
424		case "${_type}" in
425		inet)	;;
426		inet6)	;;
427		*)	warn "Could not determine address family.  Not going" \
428			    "to ${_action} address '${_addr}' for ${_jail}."
429			continue
430			;;
431		esac
432		case "${_action}" in
433		add)	ifconfig ${_iface} ${_type} ${_addr}${_mask} alias
434			;;
435		del)	# When removing the IP, ignore the _mask.
436			ifconfig ${_iface} ${_type} ${_addr} -alias
437			;;
438		esac
439	done
440}
441
442# jail_ips {add,del}
443#	Extract the comma separated list of addresses and return them
444#	for the jail command.
445#	Handle more than one address via the _multi option as well.
446#	If an interface is given also add/remove an alias for the
447#	address with an optional netmask.
448#
449jail_ips()
450{
451	local _action
452	_action=$1
453
454	case "${_action}" in
455	add)	;;
456	del)	;;
457	*)	warn "jail_ips: invalid action '${_action}'"
458		return
459		;;
460	esac
461
462	# Handle addresses.
463	jail_handle_ips_option ${_action} "${_ip}"
464	# Handle jail_xxx_ip_multi<N>
465	alias=0
466	while : ; do
467		eval _x=\"\$jail_${_jail}_ip_multi${alias}\"
468		case "${_x}" in
469		"")	break ;;
470		*)	jail_handle_ips_option ${_action} "${_x}"
471			alias=$((${alias} + 1))
472			;;
473		esac
474	done
475}
476
477jail_start()
478{
479	echo -n 'Configuring jails:'
480	set_sysctl jail_set_hostname_allow security.jail.set_hostname_allowed \
481	    set_hostname_allow
482	set_sysctl jail_socket_unixiproute_only \
483	    security.jail.socket_unixiproute_only unixiproute_only
484	set_sysctl jail_sysvipc_allow security.jail.sysvipc_allowed \
485	    sysvipc_allow
486	echo '.'
487
488	echo -n 'Starting jails:'
489	_tmp_dir=`mktemp -d /tmp/jail.XXXXXXXX` || \
490	    err 3 "$name: Can't create temp dir, exiting..."
491	for _jail in ${jail_list}
492	do
493		init_variables $_jail
494		if [ -f /var/run/jail_${_jail}.id ]; then
495			echo -n " [${_hostname} already running (/var/run/jail_${_jail}.id exists)]"
496			continue;
497		fi
498		_addrl=""
499		jail_ips "add"
500		if [ -n "${_fib}" ]; then
501			_setfib="setfib -F '${_fib}'"
502		else
503			_setfib=""
504		fi
505		if checkyesno _mount; then
506			info "Mounting fstab for jail ${_jail} (${_fstab})"
507			if [ ! -f "${_fstab}" ]; then
508				err 3 "$name: ${_fstab} does not exist"
509			fi
510			jail_mount_fstab
511		fi
512		if checkyesno _devfs; then
513			# If devfs is already mounted here, skip it.
514			df -t devfs "${_devdir}" >/dev/null
515			if [ $? -ne 0 ]; then
516				if is_symlinked_mountpoint ${_devdir}; then
517					warn "${_devdir} has symlink as parent - not starting jail ${_jail}"
518					continue
519				fi
520				info "Mounting devfs on ${_devdir}"
521				devfs_mount_jail "${_devdir}" ${_ruleset}
522				# Transitional symlink for old binaries
523				if [ ! -L "${_devdir}/log" ]; then
524					__pwd="`pwd`"
525					cd "${_devdir}"
526					ln -sf ../var/run/log log
527					cd "$__pwd"
528				fi
529			fi
530
531			# XXX - It seems symlinks don't work when there
532			#	is a devfs(5) device of the same name.
533			# Jail console output
534			#	__pwd="`pwd`"
535			#	cd "${_devdir}"
536			#	ln -sf ../var/log/console console
537			#	cd "$__pwd"
538		fi
539		if checkyesno _fdescfs; then
540			if is_symlinked_mountpoint ${_fdescdir}; then
541				warn "${_fdescdir} has symlink as parent, not mounting"
542			else
543				info "Mounting fdescfs on ${_fdescdir}"
544				mount -t fdescfs fdesc "${_fdescdir}"
545			fi
546		fi
547		if checkyesno _procfs; then
548			if is_symlinked_mountpoint ${_procdir}; then
549				warn "${_procdir} has symlink as parent, not mounting"
550			else
551				info "Mounting procfs onto ${_procdir}"
552				if [ -d "${_procdir}" ] ; then
553					mount -t procfs proc "${_procdir}"
554				fi
555			fi
556		fi
557		_tmp_jail=${_tmp_dir}/jail.$$
558		eval ${_setfib} jail ${_flags} -i ${_rootdir} ${_hostname} \
559			\"${_addrl}\" ${_exec_start} > ${_tmp_jail} 2>&1
560
561		if [ "$?" -eq 0 ] ; then
562			_jail_id=$(head -1 ${_tmp_jail})
563			i=1
564			while [ true ]; do
565				eval out=\"\${_exec_afterstart${i}:-''}\"
566
567				if [ -z "$out" ]; then
568					break;
569				fi
570
571				jexec "${_jail_id}" ${out}
572				i=$((i + 1))
573			done
574
575			echo -n " $_hostname"
576			tail +2 ${_tmp_jail} >${_consolelog}
577			echo ${_jail_id} > /var/run/jail_${_jail}.id
578		else
579			jail_umount_fs
580			jail_ips "del"
581			echo " cannot start jail \"${_jail}\": "
582			tail +2 ${_tmp_jail}
583		fi
584		rm -f ${_tmp_jail}
585	done
586	rmdir ${_tmp_dir}
587	echo '.'
588}
589
590jail_stop()
591{
592	echo -n 'Stopping jails:'
593	for _jail in ${jail_list}
594	do
595		if [ -f "/var/run/jail_${_jail}.id" ]; then
596			_jail_id=$(cat /var/run/jail_${_jail}.id)
597			if [ ! -z "${_jail_id}" ]; then
598				init_variables $_jail
599				if [ -n "${_exec_stop}" ]; then
600					eval env -i /usr/sbin/jexec ${_jail_id} ${_exec_stop} \
601						>> ${_consolelog} 2>&1
602				fi
603				killall -j ${_jail_id} -TERM > /dev/null 2>&1
604				sleep 1
605				killall -j ${_jail_id} -KILL > /dev/null 2>&1
606				jail_umount_fs
607				echo -n " $_hostname"
608			fi
609			jail_ips "del"
610			rm /var/run/jail_${_jail}.id
611		else
612			echo " cannot stop jail ${_jail}. No jail id in /var/run"
613		fi
614	done
615	echo '.'
616}
617
618load_rc_config $name
619cmd="$1"
620if [ $# -gt 0 ]; then
621	shift
622fi
623if [ -n "$*" ]; then
624	jail_list="$*"
625fi
626run_rc_command "${cmd}"
627