da95763b3b
(Due to some misconfiguration) I ended up with _mask set to "-v<something>", and /etc/rc.d/jail then failed with "expr: illegal option -- v". Use "expr --" so that variable content is never interpreted as an option. Reviewed by: jamie Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D14535
602 lines
14 KiB
Bash
Executable File
602 lines
14 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
# $FreeBSD$
|
|
#
|
|
|
|
# PROVIDE: jail
|
|
# REQUIRE: LOGIN FILESYSTEMS
|
|
# BEFORE: securelevel
|
|
# KEYWORD: nojail shutdown
|
|
|
|
. /etc/rc.subr
|
|
|
|
name="jail"
|
|
desc="Manage system jails"
|
|
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 jv name param num defval
|
|
# Extract value from ${jail_$jv_$name} or ${jail_$name} and
|
|
# set it to $param. If not defined, $defval is used.
|
|
# When $num is [0-9]*, ${jail_$jv_$name$num} are looked up and
|
|
# $param is set by using +=. $num=0 is optional (params may start at 1).
|
|
# When $num is YN or NY, the value is interpreted as boolean.
|
|
# When $num is @, the value is interpreted as an array separted by IFS.
|
|
extract_var()
|
|
{
|
|
local i _jv _name _param _num _def _name1 _name2
|
|
_jv=$1
|
|
_name=$2
|
|
_param=$3
|
|
_num=$4
|
|
_def=$5
|
|
|
|
case $_num in
|
|
YN)
|
|
_name1=jail_${_jv}_${_name}
|
|
_name2=jail_${_name}
|
|
eval $_name1=\"\${$_name1:-\${$_name2:-$_def}}\"
|
|
if checkyesno $_name1; then
|
|
echo " $_param = 1;"
|
|
else
|
|
echo " $_param = 0;"
|
|
fi
|
|
;;
|
|
NY)
|
|
_name1=jail_${_jv}_${_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_${_jv}_${_name}${i}
|
|
_name2=jail_${_name}${i}
|
|
eval _tmpargs=\"\${$_name1:-\${$_name2:-$_def}}\"
|
|
if [ -n "$_tmpargs" ]; then
|
|
echo " $_param += \"$_tmpargs\";"
|
|
elif [ $i != 0 ]; then
|
|
break;
|
|
fi
|
|
i=$(($i + 1))
|
|
done
|
|
;;
|
|
@)
|
|
_name1=jail_${_jv}_${_name}
|
|
_name2=jail_${_name}
|
|
eval _tmpargs=\"\${$_name1:-\${$_name2:-$_def}}\"
|
|
set -- $_tmpargs
|
|
if [ $# -gt 0 ]; then
|
|
echo -n " $_param = "
|
|
while [ $# -gt 1 ]; do
|
|
echo -n "\"$1\", "
|
|
shift
|
|
done
|
|
echo "\"$1\";"
|
|
fi
|
|
;;
|
|
*)
|
|
_name1=jail_${_jv}_${_name}
|
|
_name2=jail_${_name}
|
|
eval _tmpargs=\"\${$_name1:-\${$_name2:-$_def}}\"
|
|
if [ -n "$_tmpargs" ]; then
|
|
echo " $_param = \"$_tmpargs\";"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# parse_options _j _jv
|
|
# Parse options and create a temporary configuration file if necessary.
|
|
#
|
|
parse_options()
|
|
{
|
|
local _j _jv _p
|
|
_j=$1
|
|
_jv=$2
|
|
|
|
_confwarn=0
|
|
if [ -z "$_j" ]; then
|
|
warn "parse_options: you must specify a jail"
|
|
return
|
|
fi
|
|
eval _jconf=\"\${jail_${_jv}_conf:-/etc/jail.${_j}.conf}\"
|
|
eval _rootdir=\"\$jail_${_jv}_rootdir\"
|
|
eval _hostname=\"\$jail_${_jv}_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_${_jv}_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.
|
|
#
|
|
: ${jail_confwarn:=YES}
|
|
checkyesno jail_confwarn && _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_${_jv}_flags:=${jail_flags}}
|
|
eval _exec=\"\$jail_${_jv}_exec\"
|
|
eval _exec_start=\"\$jail_${_jv}_exec_start\"
|
|
eval _exec_stop=\"\$jail_${_jv}_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_${_jv}_interface:-${jail_interface}}\"
|
|
eval _parameters=\"\${jail_${_jv}_parameters:-${jail_parameters}}\"
|
|
eval _fstab=\"\${jail_${_jv}_fstab:-${jail_fstab:-/etc/fstab.$_j}}\"
|
|
(
|
|
date +"# Generated by rc.d/jail at %Y-%m-%d %H:%M:%S"
|
|
echo "$_j {"
|
|
extract_var $_jv hostname host.hostname - ""
|
|
extract_var $_jv rootdir path - ""
|
|
if [ -n "$_ip" ]; then
|
|
extract_var $_jv interface interface - ""
|
|
jail_handle_ips_option $_ip $_interface
|
|
alias=0
|
|
while : ; do
|
|
eval _x=\"\$jail_${_jv}_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 $_jv fib exec.fib - ""
|
|
extract_var $_jv socket_unixiproute_only \
|
|
allow.raw_sockets NY YES
|
|
else
|
|
echo " vnet;"
|
|
extract_var $_jv vnet_interface vnet.interface @ ""
|
|
fi
|
|
|
|
echo " exec.clean;"
|
|
echo " exec.system_user = \"root\";"
|
|
echo " exec.jail_user = \"root\";"
|
|
extract_var $_jv exec_prestart exec.prestart 0 ""
|
|
extract_var $_jv exec_poststart exec.poststart 0 ""
|
|
extract_var $_jv exec_prestop exec.prestop 0 ""
|
|
extract_var $_jv exec_poststop exec.poststop 0 ""
|
|
|
|
echo " exec.start += \"$_exec_start\";"
|
|
extract_var $_jv exec_afterstart exec.start 0 ""
|
|
echo " exec.stop = \"$_exec_stop\";"
|
|
|
|
extract_var $_jv consolelog exec.consolelog - \
|
|
/var/log/jail_${_j}_console.log
|
|
|
|
if [ -r $_fstab ]; then
|
|
echo " mount.fstab = \"$_fstab\";"
|
|
fi
|
|
|
|
eval : \${jail_${_jv}_devfs_enable:=${jail_devfs_enable:-NO}}
|
|
if checkyesno jail_${_jv}_devfs_enable; then
|
|
echo " mount.devfs;"
|
|
eval _ruleset=\${jail_${_jv}_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_${_jv}_fdescfs_enable:=${jail_fdescfs_enable:-NO}}
|
|
if checkyesno jail_${_jv}_fdescfs_enable; then
|
|
echo " mount.fdescfs;"
|
|
fi
|
|
eval : \${jail_${_jv}_procfs_enable:=${jail_procfs_enable:-NO}}
|
|
if checkyesno jail_${_jv}_procfs_enable; then
|
|
echo " mount.procfs;"
|
|
fi
|
|
|
|
eval : \${jail_${_jv}_mount_enable:=${jail_mount_enable:-NO}}
|
|
if checkyesno jail_${_jv}_mount_enable; then
|
|
echo " allow.mount;"
|
|
fi
|
|
|
|
extract_var $_jv set_hostname_allow allow.set_hostname YN NO
|
|
extract_var $_jv sysvipc_allow allow.sysvipc YN NO
|
|
extract_var $_jv enforce_statfs enforce_statfs - 2
|
|
extract_var $_jv osreldate osreldate
|
|
extract_var $_jv osrelease osrelease
|
|
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 _jv
|
|
|
|
case $1 in
|
|
_ALL) return ;;
|
|
esac
|
|
for _j in $@; do
|
|
_j=$(echo $_j | tr /. _)
|
|
_jv=$(echo -n $_j | tr -c '[:alnum:]' _)
|
|
if parse_options $_j $_jv; then
|
|
echo "$_j: parameters are in $_conf."
|
|
fi
|
|
done
|
|
}
|
|
|
|
jail_console()
|
|
{
|
|
local _j _jv _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 /. _)
|
|
_jv=$(echo -n $1 | tr -c '[:alnum:]' _)
|
|
shift
|
|
case $# in
|
|
0) eval _cmd=\${jail_${_jv}_consolecmd:-$jail_consolecmd} ;;
|
|
*) _cmd=$@ ;;
|
|
esac
|
|
$jail_jexec $_j $_cmd
|
|
}
|
|
|
|
jail_status()
|
|
{
|
|
|
|
$jail_jls -N
|
|
}
|
|
|
|
jail_start()
|
|
{
|
|
local _j _jv _jid _id _name
|
|
|
|
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"
|
|
if ! checkyesno jail_parallel_start; then
|
|
command_args="$command_args -p1"
|
|
fi
|
|
_tmp=`mktemp -t jail` || exit 3
|
|
if $command $rc_flags $command_args >> $_tmp 2>&1; then
|
|
$jail_jls jid name | while read _id _name; do
|
|
echo -n " $_name"
|
|
echo $_id > /var/run/jail_${_name}.id
|
|
done
|
|
else
|
|
cat $_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.
|
|
#
|
|
for _j in $@; do
|
|
_j=$(echo $_j | tr /. _)
|
|
_jv=$(echo -n $_j | tr -c '[:alnum:]' _)
|
|
parse_options $_j $_jv || continue
|
|
|
|
eval rc_flags=\${jail_${_jv}_flags:-$jail_flags}
|
|
eval command=\${jail_${_jv}_program:-$jail_program}
|
|
command_args="-i -f $_conf -c $_j"
|
|
(
|
|
_tmp=`mktemp -t jail_${_j}` || exit 3
|
|
if $command $rc_flags $command_args \
|
|
>> $_tmp 2>&1 </dev/null; then
|
|
echo -n " ${_hostname:-${_j}}"
|
|
_jid=$($jail_jls -j $_j jid)
|
|
echo $_jid > /var/run/jail_${_j}.id
|
|
else
|
|
echo " cannot start jail " \
|
|
"\"${_hostname:-${_j}}\": "
|
|
cat $_tmp
|
|
fi
|
|
rm -f $_tmp
|
|
) &
|
|
done
|
|
wait
|
|
else
|
|
#
|
|
# Start jails one-by-one when jail_parallel_start is NO.
|
|
#
|
|
for _j in $@; do
|
|
_j=$(echo $_j | tr /. _)
|
|
_jv=$(echo -n $_j | tr -c '[:alnum:]' _)
|
|
parse_options $_j $_jv || continue
|
|
|
|
eval rc_flags=\${jail_${_jv}_flags:-$jail_flags}
|
|
eval command=\${jail_${_jv}_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 </dev/null; then
|
|
echo -n " ${_hostname:-${_j}}"
|
|
_jid=$($jail_jls -j $_j jid)
|
|
echo $_jid > /var/run/jail_${_j}.id
|
|
else
|
|
echo " cannot start jail " \
|
|
"\"${_hostname:-${_j}}\": "
|
|
cat $_tmp
|
|
fi
|
|
rm -f $_tmp
|
|
done
|
|
fi
|
|
echo '.'
|
|
}
|
|
|
|
jail_stop()
|
|
{
|
|
local _j _jv
|
|
|
|
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"
|
|
if checkyesno jail_reverse_stop; then
|
|
$jail_jls name | tail -r
|
|
else
|
|
$jail_jls name
|
|
fi | 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
|
|
cat $_tmp
|
|
else
|
|
rm -f /var/run/jail_${_j}.id
|
|
fi
|
|
rm -f $_tmp
|
|
done
|
|
echo '.'
|
|
return
|
|
;;
|
|
esac
|
|
checkyesno jail_reverse_stop && set -- $(reverse_list $@)
|
|
for _j in $@; do
|
|
_j=$(echo $_j | tr /. _)
|
|
_jv=$(echo -n $_j | tr -c '[:alnum:]' _)
|
|
parse_options $_j $_jv || continue
|
|
if ! $jail_jls -j $_j > /dev/null 2>&1; then
|
|
continue
|
|
fi
|
|
eval command=\${jail_${_jv}_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
|
|
cat $_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 migrating to $jail_conf."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
load_rc_config $name
|
|
case $# in
|
|
1) run_rc_command $@ ${jail_list:-_ALL} ;;
|
|
*) jail_reverse_stop="no"
|
|
run_rc_command $@ ;;
|
|
esac
|