#!/bin/sh
# This is a script to control a virtual server
USR_SBIN=/usr/sbin
USR_LIB_VSERVER=/usr/lib/vserver
VSERVER_CMD=$USR_SBIN/vserver
CHBIND_CMD=$USR_SBIN/chbind
CHCONTEXT_CMD=$USR_SBIN/chcontext
SAVE_S_CONTEXT_CMD=$USR_LIB_VSERVER/save_s_context
CAPCHROOT_CMD=$USR_LIB_VSERVER/capchroot
VSERVERKILLALL_CMD=$USR_LIB_VSERVER/vserverkillall
DEFAULTPATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin

source /etc/vservers.conf
source $USR_LIB_VSERVER/dhcp-functions


# Read the vserver configuration file and perform some sanity checks
readconf(){
	READSILENT=$2
	if [ ! -f /etc/vservers/$1.conf ] ; then
		echo No configuration for this vserver: /etc/vservers/$1.conf
		exit 1
	else
		VSERVERDIR=
		IPROOT=
		IPROOTMASK=
		IPROOTBCAST=
		IPROOTDEV=
		S_NICE=
		S_FLAGS=
		eval `$USR_LIB_VSERVER/printconf.sh --quote $1`
		if [ "$READSILENT" != "1" -a ! -d $VSERVERDIR/. ] ; then
			echo No directory for this vserver: $VSERVERS_ROOT/$1
			return 1
		fi
 	fi
	#testperm $1
	return 0
}

# Make sure some entries in /proc are visible to vservers
vproc_visible(){
	$USR_LIB_VSERVER/vproc -E \
		/proc/loadavg \
		/proc/meminfo \
		/proc/stat \
		/proc/uptime \
		/proc/version \
		/proc/cpuinfo \
		/proc/net \
		/proc/net/dev \
		/proc/net/tcp \
		/proc/net/udp \
		/proc/net/unix
}

mountproc()
{
	mkdir -p $1/proc $1/dev/pts
	if [ ! -d $1/proc/1 ] ; then
		mount -t proc none $1/proc
		mount -t devpts none $1/dev/pts
	fi
}

umountproc()
{
	umount $1/proc 2>/dev/null
	umount $1/dev/pts 2>/dev/null
}

# Check that the vservers parent directory has permission 000
# This is the key to avoid chroot escape
testperm()
{
	return
	PERM=`$USR_LIB_VSERVER/showperm $VSERVERDIR/..`
	if [ "$PERM" != 000 ] ; then
		echo
		echo "**********************************************************"
		echo $VSERVERS_ROOT/$1/.. has insecure permissions.
		echo A vserver administrator may be able to visit the root server.
		echo To fix this, do
		echo "	" chmod 000 $VSERVERS_ROOT/$1/..
		echo do it anytime you want, even if vservers are running.
		echo "**********************************************************"
		echo
	fi
}

# Set the IP alias needed by a vserver
ifconfig_iproot()
{
	if [ "$NODEV" = "" -a "$IPROOT" != "" -a "$IPROOT" != "0.0.0.0" -a "$IPROOT" != "ALL" ] ;then
		# A vserver may have more than one IP
		# The first alias is dev:vserver
		# and the other are dev:vserver1,2,3 and so on
		# An IP may hold the device. The following is valid
		#	IPROOT="1.2.4.5 eth1:1.2.3.5"
		#	IPROOTDEV=eth0
		# The first IP 1.2.3.4 will go on eth0 and the other on eth1
		# VLAN devices are also supported (eth0.231 for vlan 231)
		SUFFIX=
		for oneip in $IPROOT
		do
			IPDEV=$IPROOTDEV
			MASK=$IPROOTMASK
			BCAST=$IPROOTBCAST
			# Split the device and IP if available
			case $oneip in
			*:*)
				eval `echo $oneip | tr : ' ' | (read dev ip; echo oneip=$ip; echo IPDEV=$dev)`
				;;
			esac
			# Split the IP and the netmask if available
			case $oneip in
			*/*)
				eval `echo $oneip | tr / ' ' | (read ip msk; echo oneip=$ip; echo MASK=$msk)`
				eval `$USR_LIB_VSERVER/ifspec "" "$oneip" "$MASK" ""`
				;;
			esac
			if [ "$IPDEV" != "" ] ; then
				case $IPDEV in
				*.*)
					if [ ! -f /proc/net/vlan/$IPDEV ] ; then
						/sbin/vconfig add `echo $IPDEV | tr . ' '`
						# Put a dummy IP
						/sbin/ifconfig $IPDEV 127.0.0.1
					fi
					;;
				esac
				if [ "$oneip" == "dhcp" ] ; then
					dhcp_acquire_ip $IPDEV $1 $S_HOSTNAME $SUFFIX
					oneip=$DHCP_IP
					MASK=$DHCP_MASK
					BCAST=$DHCP_BCAST
				fi
				# Compute the default netmask, if missing
				eval `$USR_LIB_VSERVER/ifspec $IPDEV "$oneip" "$MASK" "$BCAST"`
				IPROOTMASK=$NETMASK
				IPROOTBCAST=$BCAST

				if [ "$VERBOSE" != "" ]; then
					echo /sbin/ifconfig $IPDEV:$1$SUFFIX $oneip netmask $IPROOTMASK broadcast $IPROOTBCAST
				fi
				/sbin/ifconfig $IPDEV:$1$SUFFIX $oneip netmask $IPROOTMASK broadcast $IPROOTBCAST
			fi
			if [ "$SUFFIX" = "" ] ; then
				SUFFIX=1
			else
				SUFFIX=`expr $SUFFIX + 1`
			fi
		done
	fi
	if [ "$IPROOTBCAST" = "" ] ; then
		IPROOTBCAST=255.255.255.255
	fi
}

ifconfig_iproot_off()
{
	if [ "$NODEV" = "" -a "$IPROOT" != "" -a "$IPROOT" != "0.0.0.0" -a "$IPROOT" != "ALL" ] ;then
		SUFFIX=
		for oneip in $IPROOT
		do
			IPDEV=$IPROOTDEV
			# Split the device and IP if available
			case $oneip in
			*:*)
				eval `echo $oneip | tr : ' ' | (read dev ip; echo IPDEV=$dev; echo oneip=$ip)`
				;;
			esac
			if [ "$IPDEV" != "" ] ; then
				/sbin/ifconfig $IPDEV:$1$SUFFIX down 2>/dev/null
			fi
			if [ "$oneip" == "dhcp" ] ; then
				dhcp_relinquish_ip $IPDEV $1 $SUFFIX
			fi
			if [ "$SUFFIX" = "" ] ; then
				SUFFIX=1
			else
				SUFFIX=`expr $SUFFIX + 1`
			fi
		done
	fi
}

# Split an IPROOT definition, trash the devices and
# compose a set of --ip option for chbind
setipopt(){
	RET=
	IPS="$*"
	if [ "$IPS" = "" ] ; then
		IPS=0.0.0.0
	fi
	if [ "$1" = "ALL" ] ; then
		IPS=`$USR_LIB_VSERVER/listdevip`
	fi
	SUFFIX=
	for oneip in $IPS
	do
		# Split the device and IP if available
		case $oneip in
		*:*)
			eval `echo $oneip | tr : ' ' | (read dev ip; echo oneip=$ip)`
			;;
		esac
		if [ "$oneip" == "dhcp" ] ; then
			dhcp_acquire_ip $IPDEV $VSERVER $S_HOSTNAME $SUFFIX
			oneip=$DHCP_IP
		fi
		if [ "$SUFFIX" = "" ] ; then
			SUFFIX=1
		else
			SUFFIX=`expr $SUFFIX + 1`
		fi
		#case $oneip in
		#*/*)
		#	eval `echo $oneip | tr / ' ' | (read ip msk; echo oneip=$ip)`
		#	;;
		#esac
		echo --ip $oneip
	done
}

# Generate an /etc/mtab file for the vserver (if requested)
generatemtab(){
	if [ "$GENERATEMTAB" = "yes" ] ; then
		MTABFILE=$VSERVERDIR/etc/mtab
		rm -f $MTABFILE
		echo /dev/hdv1 / ext3 defaults 1 1 >$MTABFILE
		NUMDEV=2
		cat /proc/mounts | while read src dir fs flags stuff
		do
			if [ "$src" != "none" ] ; then
				case $dir in
				$VSERVERDIR/*)
					case $src in
					/dev/*)
						echo /dev/hdv$NUMDEV ${dir/$VSERVERDIR/} $fs $flags $stuff >>$MTABFILE
						rm -f $VSERVERDIR/dev/hdv$NUMDEV
						touch $VSERVERDIR/dev/hdv$NUMDEV
						NUMDEV=`expr $NUMDEV + 1`
						;;
					*)
						echo $src ${dir/$VSERVERDIR/} $fs $flags $stuff >>$MTABFILE
						;;
					esac
					;;
				esac
			fi
		done		
	fi
}

# Extract the initial runlevel from the vserver inittab
get_initdefault()
{
	INITDEFAULT=`grep :initdefault $VSERVERS_ROOT/$1/etc/inittab | sed 's/:/ /g' | ( read a level b; echo $level)`
}

# Read the vserver configuration file, reusing the PROFILE value
# found in /var/run/vservers
readlastconf()
{
	if [ -f /var/run/vservers/$1.ctx ] ; then
		. /var/run/vservers/$1.ctx
		if [ "$S_PROFILE" != "" ] ; then
			export PROFILE=$S_PROFILE
		fi
	fi
	export PROFILE
	readconf $1
}

# Execute the companion script if present
# $1 is the command (pre-start,...)
# $2 is the vserver name
execvscript(){
	test -x /etc/vservers/$2.sh && /etc/vservers/$2.sh $1 $2 $VSERVERDIR
}

check_build_config(){
	if [ ! -x "$BUILDUSING" ] ; then
		echo "BUILDUSING is not an executable program" >&2
		return 1
	elif [ "$BUILDFROM" = "" ] ; then
		echo "BUILDFROM is not defined" >&2
		return 1
	fi

	return 0
}

usage()
{
	echo vserver [ options ] server-name command ...
	echo
	echo server-name is a directory in $VSERVERS_ROOT
	echo
	echo The commands are:
	echo " build   : Create a virtual server by copying the packages"
	echo "           of the root server"
	echo " enter   : Enter in the virtual server context and starts a shell"
	echo "           Same as \"vserver name exec /bin/sh\""
	echo " exec    : Exec a command in the virtual server context"
	echo " suexec  : Exec a command in the virtual server context uid"
	echo " service : Control a service inside a vserver"
	echo "           vserver name service service-name start/stop/restart/status"
	echo " start   : Starts the various services in the vserver, runlevel 3"
	echo " stop    : Ends all services and kills the remaining processes"
	echo " running : Tells if a virtual server is running"
	echo "           It returns proper exit code, so you can use it as a test"
	echo " status  : Tells some information about a vserver"
	echo " chkconfig : It turns a server on or off in a vserver"
	echo " assemble: Puts a vserver together based on the BUILDFROM,"
    echo "           BUILDUSING and BUILDARGS attributes"
	echo " remove  : Removes a vserver based on the BUILDFROM and BUILDUSING"
	echo "           attributes."
	echo
	echo "--nodev  : Do not configure the IP aliases of the vserver"
	echo "           Useful to enter a vserver without enabling its network"
	echo "           and avoiding conflicts with another copy of this vserver"
	echo "           running elsewhere"
	echo "--silent : No informative messages about vserver context and IP numbers"
	echo "           Useful when you want to redirect the output"
}

SILENT=
NODEV=
VERBOSE=
while true
do
	if [ "$1" = "--silent" ] ; then
		SILENT=--silent
		shift
	elif [ "$1" = "--nodev" ] ; then
		NODEV=--nodev
		shift
	elif [ "$1" = "--verbose" ] ; then
		VERBOSE=--verbose
		shift
	else
		break
	fi
done

# Setup the default ulimit for a vserver
setdefulimit(){
	# File handle are limited to half of the current system limit
	# Virtual memory is limited to the ram size
	NFILE=`cat /proc/sys/fs/file-max`
	NFILE=`expr $NFILE / 2`
	VMEM=`cat /proc/meminfo  | grep MemTotal | (read a b c; echo $b)`
	# Disabled for now, we need a different to set the security
	# context limit than fiddling with ulimit
	#ulimit -H -n $NFILE -v $VMEM
}
if [ $# -lt 2 ] ; then
	usage
elif [ "$2" = "build" ] ; then
	$USR_LIB_VSERVER/build_from_root.sh $1
elif [ "$2" = "assemble" ] ; then
	readconf $1 1
	if check_build_config ; then
		if ! $0 $1 running ; then
			if [ ! -d $VSERVERDIR ] ; then
				$BUILDUSING --assemble $1 $BUILDFROM $BUILDARGS
			else
				echo "Directory $VSERVERDIR already exists" 2>&1
			fi
		fi
	else
		echo "Assemble only implemented on BUILDUSING/BUILDFROM vservers"
	fi
elif ! readconf $1 
then
	exit 1
elif [ "$2" = "start" ] ; then
	echo Starting the virtual server $1
	if ! $VSERVER_CMD $1 running
	then
		execvscript pre-start $1
		export PROFILE
		ifconfig_iproot $1
		cd $VSERVERDIR || exit 1

		if [ "$PROFILE" != "" ] ; then
			echo export PROFILE=$PROFILE >etc/PROFILE
		fi

		rm -f `find var/run -type f`
		touch var/run/utmp
		rm -f  var/lock/subsys/*
		mountproc $VSERVERDIR
		CTXOPT=
		HOSTOPT=
		DOMAINOPT=
		NICECMD=
		FLAGS=
		CAPS=
		get_initdefault $1
		STARTCMD="/etc/rc.d/rc $INITDEFAULT"
		if [ -x $VSERVERDIR/etc/init.d/rc ] ; then
			STARTCMD="/etc/init.d/rc $INITDEFAULT"
		fi

		DISCONNECT=
		FAKEINIT=
		for f in $S_FLAGS dummy
		do
			case $f in
			dummy)
				;;
			fakeinit)
				FAKEINIT=true
				FLAGS="$FLAGS --flag $f"
				STARTCMD=/sbin/init
				DISCONNECT=--disconnect
				;;
			*)
				FLAGS="$FLAGS --flag $f"
				;;
			esac
		done
		if [ "$FAKEINIT" = "" ] ; then
			$USR_LIB_VSERVER/fakerunlevel $INITDEFAULT var/run/utmp
		fi
		for f in $S_CAPS dummy
		do
			case $f in
			dummy)
				;;
			!CAP_SYS_CHROOT)
				CHROOTOPT=--nochroot
				;;
			*)
				CAPS="$CAPS --cap $f"
				;;
			esac
		done
		if [ "$REQ_S_CONTEXT" != "" ] ; then
			CTXOPT="--ctx $REQ_S_CONTEXT"
		fi
		if [ "$S_HOSTNAME" != "" ] ; then
			HOSTOPT="--hostname $S_HOSTNAME"
			export HOSTNAME=$S_HOSTNAME
		fi
		if [ "$S_DOMAINNAME" != "" ] ; then
			DOMAINOPT="--domainname $S_DOMAINNAME"
		fi
		if [ "$S_NICE" != "" ] ; then
			NICECMD="nice -$S_NICE"
		fi
		mkdir -p /var/run/vservers
		chmod 700 /var/run/vservers
		setdefulimit
		if [ "$ULIMIT" != "" ] ; then
			ulimit $ULIMIT
		fi
		generatemtab
		#echo FLAGS=$FLAGS
		#echo CAPS=$CAPS
		# We switch to $VSERVERDIR now, because after the
		# security context switch $VSERVERDIR directory becomes a dead zone.
		cd $VSERVERDIR
		IPOPT=`setipopt $IPROOT`
		export PATH=$DEFAULTPATH
		$NICECMD $CHBIND_CMD $SILENT $IPOPT --bcast $IPROOTBCAST \
			$CHCONTEXT_CMD $SILENT $DISCONNECT $CAPS $FLAGS $CTXOPT $HOSTOPT $DOMAINOPT --secure \
			$SAVE_S_CONTEXT_CMD /var/run/vservers/$1.ctx \
			$CAPCHROOT_CMD $CHROOTOPT . $STARTCMD
		sleep 2
		execvscript post-start $1
	fi
elif [ "$2" = "running" ] ; then
	vproc_visible
	if [ ! -f /var/run/vservers/$1.ctx ] ; then
		echo Server $1 is not running
		exit 1
	else
		. /var/run/vservers/$1.ctx
		NB=$($USR_SBIN/vps ax | awk '{print $2}' | grep \^$S_CONTEXT\$ | wc -l)
		#NB=`$CHCONTEXT_CMD --silent --ctx $S_CONTEXT ps ax | wc -l`
		#NB=`eval expr $NB + 0`
		if [ "$NB" -gt 0 ] ; then
			echo Server $1 is running
			exit 0
		else
			echo Server $1 is not running
			exit 1
		fi
	fi
elif [ "$2" = "status" ] ; then
	if $0 $1 running
	then
		. /var/run/vservers/$1.ctx
		NB=$($USR_SBIN/vps ax | awk '{print $2}' | grep \^$S_CONTEXT\$ | wc -l)
		echo $NB processes running
		echo Vserver uptime: `$USR_LIB_VSERVER/filetime /var/run/vservers/$1.ctx`
	fi
elif [ "$2" = "stop" ] ; then
	echo Stopping the virtual server $1
	readlastconf $1
	if $VSERVER_CMD $1 running
	then
		execvscript pre-stop $1
		ifconfig_iproot $1
		cd $VSERVERDIR
		mountproc $VSERVERDIR
		FLAGS=
		CAPS=
		# The fakeinit flag tell us how to turn off the server
		get_initdefault $1
		export PREVLEVEL=$INITDEFAULT
		STOPCMD="/etc/rc.d/rc 6"
		if [ -x $VSERVERDIR/etc/init.d/rc ] ; then
			STOPCMD="/etc/init.d/rc 6"
		fi
		for f in $S_FLAGS dummy
		do
			case $f in
			fakeinit)
				FLAGS="$FLAGS --flag $f"
				STOPCMD="/sbin/init 6"
				;;
			*)
				;;
			esac
		done
		for f in $S_CAPS dummy
		do
			case $f in
			dummy)
				;;
			!CAP_SYS_CHROOT)
				CHROOTOPT=--nochroot
				;;
			*)
				CAPS="$CAPS --cap $f"
				;;
			esac
		done

		cd $VSERVERDIR
		IPOPT=`setipopt $IPROOT`
		export PATH=$DEFAULTPATH
		$CHBIND_CMD $SILENT $IPOPT --bcast $IPROOTBCAST \
			$CHCONTEXT_CMD $SILENT $CAPS $FLAGS --secure --ctx $S_CONTEXT \
			$CAPCHROOT_CMD $CHROOTOPT . $STOPCMD
		echo sleeping 5 seconds
		sleep 5
		echo Killing all processes
		$CHBIND_CMD --silent $IPOPT --bcast $IPROOTBCAST \
			$CHCONTEXT_CMD --secure --silent --ctx $S_CONTEXT \
			$VSERVERKILLALL_CMD
	fi
	# We umount anyway, because "enter" establish the mount
	# but when you exit, the server is considered not running
	umountproc $VSERVERDIR
	cd /
	execvscript post-stop $1
	ifconfig_iproot_off $1
elif [ "$2" = "restart" ] ; then
	if $0 $1 running
	then
		$0 $1 stop
		$0 $1 start
	fi
elif [ "$2" = "suexec" ] ; then
	if [ -z "$3" ] ; then
		echo "Missing user!" >&2
		echo "vserver vserver-name suexec user command [ args ... ]" >&2
		exit 1
	elif [ -z "$4" ] ; then
		echo "Missing command and arguments!" >&2
		echo "vserver vserver-name suexec user command [ args ... ]" >&2
		exit 1
	else
		readlastconf $1
		cd $VSERVERDIR
		ifconfig_iproot $1
		mountproc $VSERVERDIR
		PS1="[\u@vserver:$1 \W]"
		export PS1
		VSERVER=$1
		USERID=$3
		shift; shift; shift
		CAPS=
		for f in $S_CAPS dummy
		do
			case $f in
			dummy)
				;;
			!CAP_SYS_CHROOT)
				CHROOTOPT=--nochroot
				;;
			*)
				CAPS="$CAPS --cap $f"
				;;
			esac
		done
		FLAGS=
		for f in $S_FLAGS dummy
		do
			case $f in
			dummy)
				;;
			*)
				FLAGS="$FLAGS --flag $f"
				;;
			esac
		done
		setdefulimit
		if [ "$ULIMIT" != "" ] ; then
			ulimit $ULIMIT
		fi
		if $0 $VSERVER running >/dev/null
		then
			. /var/run/vservers/$VSERVER.ctx
			generatemtab
			cd $VSERVERDIR
			IPOPT=`setipopt $IPROOT`
			export PATH=$DEFAULTPATH
			$CHBIND_CMD $SILENT $IPOPT --bcast $IPROOTBCAST \
				$CHCONTEXT_CMD $SILENT $FLAGS $CAPS --secure --ctx $S_CONTEXT \
				$CAPCHROOT_CMD --suid $USERID . "$@"
		else
			unset S_CONTEXT
			execvscript pre-start $1
			generatemtab
			CTXOPT=
			HOSTOPT=
			DOMAINOPT=
			if [ "$REQ_S_CONTEXT" != "" ] ; then
				CTXOPT="--ctx $REQ_S_CONTEXT"
			fi
			if [ "$S_HOSTNAME" != "" ] ; then
				HOSTOPT="--hostname $S_HOSTNAME"
				export HOSTNAME=$S_HOSTNAME
			fi
			if [ "$S_DOMAINNAME" != "" ] ; then
				DOMAINOPT="--domainname $S_DOMAINNAME"
			fi
			mkdir -p /var/run/vservers
			cd $VSERVERDIR
			IPOPT=`setipopt $IPROOT`
			export PATH=$DEFAULTPATH
			$CHBIND_CMD $SILENT $IPOPT --bcast $IPROOTBCAST \
				$CHCONTEXT_CMD $SILENT $FLAGS $CAPS --secure $CTXOPT $HOSTOPT $DOMAINOPT \
				$SAVE_S_CONTEXT_CMD /var/run/vservers/$VSERVER.ctx \
				$CAPCHROOT_CMD --suid $USERID $CHROOTOPT . "$@"
		fi
	fi
elif [ "$2" = "exec" ] ; then
	VSERV=$1
	shift; shift
	exec $0 $VERBOSE $NODEV $SILENT $VSERV suexec root "$@"
elif [ "$2" = "enter" ] ; then
	exec $0 $VERBOSE $NODEV $SILENT $1 exec /bin/bash -login
elif [ "$2" = "service" ] ; then
	VSERVER=$1
	shift
	shift
	exec $0 $VERBOSE $NODEV $SILENT $VSERVER exec /sbin/service "$@"
elif [ "$2" = "chkconfig" ] ; then
	VSERVER=$1
	shift
	shift
	if [ "$1" = "--level" ] ; then
		shift
		LEVELS="--level $1"
		shift
	fi
	if [ $# != 2 -a ! -x $VSERVERDIR/sbin/chkconfig ] ; then
		echo Invalid argument, expected vserver name chkconfig [ --level nnn ] service on\|off
	elif [ -x $VSERVERDIR/sbin/chkconfig ] ; then
		exec $0 --silent $VSERVER exec /sbin/chkconfig "$@"
	elif [ -x $VSERVERDIR/usr/sbin/update-rc.d ] ; then
		if [ "$2" = "on" -o "$2" = "start" ] ; then
			$0 --silent $VSERVER exec /usr/sbin/update-rc.d -f $1 remove >/dev/null
			exec $0 --silent $VSERVER exec /usr/sbin/update-rc.d $1 start 80 2 3 4 5 . stop 20 0 1 6 . >/dev/null
		elif [ "$2" = "off" -o "$2" = "stop" ] ; then
			$0 --silent $VSERVER exec /usr/sbin/update-rc.d -f $1 remove >/dev/null
			exec $0 --silent $VSERVER exec /usr/sbin/update-rc.d $1 stop 20 0 1 2 3 4 5 6 . >/dev/null
		else
			echo vserver chkconfig: Expecting on or off
		fi
	else
		echo chkconfig functionality is not available on this
		echo vserver distribution.
		echo Looked for /sbin/chkconfig and /usr/sbin/update-rc.d
	fi
elif [ "$2" = "remove" ] ; then
	if check_build_config ; then
		if ! $0 $1 running ; then
			$BUILDUSING --remove $1 $BUILDFROM $BUILDARGS
		fi
	else
		echo "Remove only implemented on BUILDUSING/BUILDFROM vservers"
	fi
else
	echo Command unknown $2
	echo
	usage
fi

