#!/bin/sh # # $FreeBSD$ # # PROVIDE: jail # REQUIRE: LOGIN FILESYSTEMS # BEFORE: securelevel # KEYWORD: nojail shutdown . /etc/rc.subr name="jail" rcvar="jail_enable" start_cmd="jail_start" start_postcmd="jail_warn" stop_cmd="jail_stop" config_cmd="jail_config" console_cmd="jail_console" status_cmd="jail_status" extra_commands="config console status" : ${jail_conf:=/etc/jail.conf} : ${jail_program:=/usr/sbin/jail} : ${jail_consolecmd:=/usr/bin/login -f root} : ${jail_jexec:=/usr/sbin/jexec} : ${jail_jls:=/usr/sbin/jls} need_dad_wait= # extract_var jail name param num defval # Extract value from ${jail_$jail_$name} or ${jail_$name} and # set it to $param. If not defined, $defval is used. # When $num is [0-9]*, ${jail_$jail_$name$num} are looked up and # $param is set by using +=. # When $num is YN or NY, the value is interpret as boolean. extract_var() { local i _j _name _param _num _def _name1 _name2 _j=$1 _name=$2 _param=$3 _num=$4 _def=$5 case $_num in YN) _name1=jail_${_j}_${_name} _name2=jail_${_name} eval $_name1=\"\${$_name1:-\${$_name2:-$_def}}\" if checkyesno $_name1; then echo " $_param = 1;" else echo " $_param = 0;" fi ;; NY) _name1=jail_${_j}_${_name} _name2=jail_${_name} eval $_name1=\"\${$_name1:-\${$_name2:-$_def}}\" if checkyesno $_name1; then echo " $_param = 0;" else echo " $_param = 1;" fi ;; [0-9]*) i=$_num while : ; do _name1=jail_${_j}_${_name}${i} _name2=jail_${_name}${i} eval _tmpargs=\"\${$_name1:-\${$_name2:-$_def}}\" if [ -n "$_tmpargs" ]; then echo " $_param += \"$_tmpargs\";" else break; fi i=$(($i + 1)) done ;; *) _name1=jail_${_j}_${_name} _name2=jail_${_name} eval _tmpargs=\"\${$_name1:-\${$_name2:-$_def}}\" if [ -n "$_tmpargs" ]; then echo " $_param = \"$_tmpargs\";" fi ;; esac } # parse_options _j # Parse options and create a temporary configuration file if necessary. # parse_options() { local _j _p _j=$1 _confwarn=0 if [ -z "$_j" ]; then warn "parse_options: you must specify a jail" return fi eval _jconf=\"\${jail_${_j}_conf:-/etc/jail.${_j}.conf}\" eval _rootdir=\"\$jail_${_j}_rootdir\" eval _hostname=\"\$jail_${_j}_hostname\" if [ -z "$_rootdir" -o \ -z "$_hostname" ]; then if [ -r "$_jconf" ]; then _conf="$_jconf" return 0 elif [ -r "$jail_conf" ]; then _conf="$jail_conf" return 0 else warn "Invalid configuration for $_j " \ "(no jail.conf, no hostname, or no path). " \ "Jail $_j was ignored." fi return 1 fi eval _ip=\"\$jail_${_j}_ip\" if [ -z "$_ip" ] && ! check_kern_features vimage; then warn "no ipaddress specified and no vimage support. " \ "Jail $_j was ignored." return 1 fi _conf=/var/run/jail.${_j}.conf # # To relieve confusion, show a warning message. # _confwarn=1 if [ -r "$jail_conf" -o -r "$_jconf" ]; then if ! checkyesno jail_parallel_start; then warn "$_conf is created and used for jail $_j." fi fi /usr/bin/install -m 0644 -o root -g wheel /dev/null $_conf || return 1 eval : \${jail_${_j}_flags:=${jail_flags}} eval _exec=\"\$jail_${_j}_exec\" eval _exec_start=\"\$jail_${_j}_exec_start\" eval _exec_stop=\"\$jail_${_j}_exec_stop\" if [ -n "${_exec}" ]; then # simple/backward-compatible execution _exec_start="${_exec}" _exec_stop="" else # flexible execution if [ -z "${_exec_start}" ]; then _exec_start="/bin/sh /etc/rc" if [ -z "${_exec_stop}" ]; then _exec_stop="/bin/sh /etc/rc.shutdown" fi fi fi eval _interface=\"\${jail_${_j}_interface:-${jail_interface}}\" eval _parameters=\"\${jail_${_j}_parameters:-${jail_parameters}}\" eval _fstab=\"\${jail_${_j}_fstab:-${jail_fstab:-/etc/fstab.$_j}}\" ( date +"# Generated by rc.d/jail at %Y-%m-%d %H:%M:%S" echo "$_j {" extract_var $_j hostname host.hostname - "" extract_var $_j rootdir path - "" if [ -n "$_ip" ]; then extract_var $_j interface interface - "" jail_handle_ips_option $_ip $_interface alias=0 while : ; do eval _x=\"\$jail_${_j}_ip_multi${alias}\" [ -z "$_x" ] && break jail_handle_ips_option $_x $_interface alias=$(($alias + 1)) done case $need_dad_wait in 1) # Sleep to let DAD complete before # starting services. echo " exec.start += \"sleep " \ $(($(${SYSCTL_N} net.inet6.ip6.dad_count) + 1)) \ "\";" ;; esac # These are applicable only to non-vimage jails. extract_var $_j fib exec.fib - "" extract_var $_j socket_unixiproute_only \ allow.raw_sockets NY YES else echo " vnet;" extract_var $_j vnet_interface vnet.interface - "" fi echo " exec.clean;" echo " exec.system_user = \"root\";" echo " exec.jail_user = \"root\";" extract_var $_j exec_prestart exec.prestart 0 "" extract_var $_j exec_poststart exec.poststart 0 "" extract_var $_j exec_prestop exec.prestop 0 "" extract_var $_j exec_poststop exec.poststop 0 "" echo " exec.start += \"$_exec_start\";" extract_var $_j exec_afterstart exec.start 1 "" echo " exec.stop = \"$_exec_stop\";" extract_var $_j consolelog exec.consolelog - \ /var/log/jail_${_j}_console.log if [ -r $_fstab ]; then echo " mount.fstab = \"$_fstab\";" fi eval : \${jail_${_j}_devfs_enable:=${jail_devfs_enable:-NO}} if checkyesno jail_${_j}_devfs_enable; then echo " mount.devfs;" eval _ruleset=\${jail_${_j}_devfs_ruleset:-${jail_devfs_ruleset}} case $_ruleset in "") ;; [0-9]*) echo " devfs_ruleset = \"$_ruleset\";" ;; devfsrules_jail) # XXX: This is the default value, # Let jail(8) to use the default because # mount(8) only accepts an integer. # This should accept a ruleset name. ;; *) warn "devfs_ruleset must be an integer." ;; esac fi eval : \${jail_${_j}_fdescfs_enable:=${jail_fdescfs_enable:-NO}} if checkyesno jail_${_j}_fdescfs_enable; then echo " mount.fdescfs;" fi eval : \${jail_${_j}_procfs_enable:=${jail_procfs_enable:-NO}} if checkyesno jail_${_j}_procfs_enable; then echo " mount.procfs;" fi eval : \${jail_${_j}_mount_enable:=${jail_mount_enable:-NO}} if checkyesno jail_${_j}_mount_enable; then echo " allow.mount;" >> $_conf fi extract_var $_j set_hostname_allow allow.set_hostname YN NO extract_var $_j sysvipc_allow allow.sysvipc YN NO for _p in $_parameters; do echo " ${_p%\;};" done echo "}" ) >> $_conf return 0 } # jail_extract_address argument iface # The second argument is the string from one of the _ip # or the _multi variables. In case of a comma separated list # only one argument must be passed in at a time. # The function alters the _type, _iface, _addr and _mask variables. # jail_extract_address() { local _i _interface _i=$1 _interface=$2 if [ -z "${_i}" ]; then warn "jail_extract_address: called without input" return fi # Check if we have an interface prefix given and split into # iFace and rest. case "${_i}" in *\|*) # ifN|.. prefix there _iface=${_i%%|*} _r=${_i##*|} ;; *) _iface="" _r=${_i} ;; esac # In case the IP has no interface given, check if we have a global one. _iface=${_iface:-${_interface}} # Set address, cut off any prefix/netmask/prefixlen. _addr=${_r} _addr=${_addr%%[/ ]*} # Theoretically we can return here if interface is not set, # as we only care about the _mask if we call ifconfig. # This is not done because we may want to santize IP addresses # based on _type later, and optionally change the type as well. # Extract the prefix/netmask/prefixlen part by cutting off the address. _mask=${_r} _mask=`expr "${_mask}" : "${_addr}\(.*\)"` # Identify type {inet,inet6}. case "${_addr}" in *\.*\.*\.*) _type="inet" ;; *:*) _type="inet6" ;; *) warn "jail_extract_address: type not identified" ;; esac # Handle the special /netmask instead of /prefix or # "netmask xxx" case for legacy IP. # We do NOT support shortend class-full netmasks. if [ "${_type}" = "inet" ]; then case "${_mask}" in /*\.*\.*\.*) _mask=" netmask ${_mask#/}" ;; *) ;; esac # In case _mask is still not set use /32. _mask=${_mask:-/32} elif [ "${_type}" = "inet6" ]; then # In case _mask is not set for IPv6, use /128. _mask=${_mask:-/128} fi } # jail_handle_ips_option input iface # Handle a single argument imput which can be a comma separated # list of addresses (theoretically with an option interface and # prefix/netmask/prefixlen). # jail_handle_ips_option() { local _x _type _i _defif _x=$1 _defif=$2 if [ -z "${_x}" ]; then # No IP given. This can happen for the primary address # of each address family. return fi # Loop, in case we find a comma separated list, we need to handle # each argument on its own. while [ ${#_x} -gt 0 ]; do case "${_x}" in *,*) # Extract the first argument and strip it off the list. _i=`expr "${_x}" : '^\([^,]*\)'` _x=`expr "${_x}" : "^[^,]*,\(.*\)"` ;; *) _i=${_x} _x="" ;; esac _type="" _addr="" _mask="" _iface="" jail_extract_address $_i $_defif # make sure we got an address. case $_addr in "") continue ;; *) ;; esac # Append address to list of addresses for the jail command. case $_type in inet) echo " ip4.addr += \"${_iface:+${_iface}|}${_addr}${_mask}\";" ;; inet6) echo " ip6.addr += \"${_iface:+${_iface}|}${_addr}${_mask}\";" need_dad_wait=1 ;; esac done } jail_config() { local _j case $1 in _ALL) return ;; esac for _j in $@; do _j=$(echo $_j | tr /. _) if parse_options $_j; then echo "$_j: parameters are in $_conf." fi done } jail_console() { local _j _cmd # One argument that is not _ALL. case $#:$1 in 0:*|1:_ALL) err 3 "Specify a jail name." ;; 1:*) ;; esac _j=$(echo $1 | tr /. _) shift case $# in 0) eval _cmd=\${jail_${_j}_consolecmd:-$jail_consolecmd} ;; *) _cmd=$@ ;; esac $jail_jexec $_j $_cmd } jail_status() { $jail_jls -N } jail_start() { local _j _jid _jl if [ $# = 0 ]; then return fi echo -n 'Starting jails:' case $1 in _ALL) command=$jail_program rc_flags=$jail_flags command_args="-f $jail_conf -c" _tmp=`mktemp -t jail` || exit 3 if $command $rc_flags $command_args >> $_tmp 2>&1; then $jail_jls jid name | while read IN; do set -- $IN echo -n " $2" echo $1 > /var/run/jail_$2.id done else tail -1 $_tmp fi rm -f $_tmp echo '.' return ;; esac if checkyesno jail_parallel_start; then # # Start jails in parallel and then check jail id when # jail_parallel_start is YES. # _jl= for _j in $@; do _j=$(echo $_j | tr /. _) parse_options $_j || continue _jl="$_jl $_j" eval rc_flags=\${jail_${_j}_flags:-$jail_flags} eval command=\${jail_${_j}_program:-$jail_program} command_args="-i -f $_conf -c $_j" $command $rc_flags $command_args \ >/dev/null 2>&1 /var/run/jail_${_j}.id else rm -f /var/run/jail_${_j}.id echo " cannot start jail " \ "\"${_hostname:-${_j}}\": " fi done else # # Start jails one-by-one when jail_parallel_start is NO. # for _j in $@; do _j=$(echo $_j | tr /. _) parse_options $_j || continue eval rc_flags=\${jail_${_j}_flags:-$jail_flags} eval command=\${jail_${_j}_program:-$jail_program} command_args="-i -f $_conf -c $_j" _tmp=`mktemp -t jail` || exit 3 if $command $rc_flags $command_args \ >> $_tmp 2>&1 /var/run/jail_${_j}.id else rm -f /var/run/jail_${_j}.id echo " cannot start jail " \ "\"${_hostname:-${_j}}\": " cat $_tmp fi rm -f $_tmp done fi echo '.' } jail_stop() { local _j if [ $# = 0 ]; then return fi echo -n 'Stopping jails:' case $1 in _ALL) command=$jail_program rc_flags=$jail_flags command_args="-f $jail_conf -r" $jail_jls name | while read _j; do echo -n " $_j" _tmp=`mktemp -t jail` || exit 3 $command $rc_flags $command_args $_j >> $_tmp 2>&1 if $jail_jls -j $_j > /dev/null 2>&1; then tail -1 $_tmp else rm -f /var/run/jail_${_j}.id fi rm -f $_tmp done echo '.' return ;; esac for _j in $@; do _j=$(echo $_j | tr /. _) parse_options $_j || continue if ! $jail_jls -j $_j > /dev/null 2>&1; then continue fi eval command=\${jail_${_j}_program:-$jail_program} echo -n " ${_hostname:-${_j}}" _tmp=`mktemp -t jail` || exit 3 $command -q -f $_conf -r $_j >> $_tmp 2>&1 if $jail_jls -j $_j > /dev/null 2>&1; then tail -1 $_tmp else rm -f /var/run/jail_${_j}.id fi rm -f $_tmp done echo '.' } jail_warn() { # To relieve confusion, show a warning message. case $_confwarn in 1) warn "Per-jail configuration via jail_* variables " \ "is obsolete. Please consider to migrate to $jail_conf." ;; esac } load_rc_config $name case $# in 1) run_rc_command $@ ${jail_list:-_ALL} ;; *) run_rc_command $@ ;; esac