#!/bin/sh
# James Budiono 2011, 2013, 2015, 2017, 2018, 2019, 2020, 2024
# fatdog interactive secure sandbox
# forked from rw-sandbox.sh 15 and rw-sandbox-lxc.sh 11
# now acts as front-end to sfs-sandbox.sh

### configuration variables
. $BOOTSTATE_PATH # AUFS_ROOT_ID, BASELINE_MOUNT
[ "$NOT_USING_AUFS" ] && exit 1
APPTITLE="rw-image sandbox"
XTERM="defaultterm"
SANDBOX_ROOT=/mnt/sb

### run-time variables
TMPFILE=$(mktemp -p /tmp)
#OS_IMAGE=1   # image file is a OS image
#USE_OVERLAY= # use overlayfs instead of aufs
#USE_LXC=                      # blank (no), yes
#USE_UML=                      # blank (no), yes
USE_DESKTOP=${USE_DESKTOP-ask} # blank (no), ask, yes

# standard sandbox options
#NO_NS=1      # don't use user namespace, just do chroot only
#NO_TMP=1     # don't share host /tmp

# LXC sandbox options
USE_NETWORK=${USE_NETWORK-ask} # blank (none), ask, proxyarp or nat
SHARE_SOUND=${SHARE_SOUND-ask} # blank (no), ask, yes
SHARE_IPC=${SHARE_IPC-no}      # blank (no), ask, yes
#WINDOW_SIZE=${WINDOW_SIZE-1024x600}
#HOST_ADAPTER= # which adapter to use for auto-calculate
#HOST_IP=      # auto-calculate if blank
#GUEST_IP=     # auto-calculate if blank
#NAME_SERVER=  # name sever used for guest network

# user namespaces settings
#IDMAP=			# enable IDMAP=yes on env to use user namespaces
LXC_UID=${LXC_UID:-100000}          # UID and GID when running under user namespaces
LXC_GID=${LXC_GID:-100000}
LXC_IDRANGE=${LXC_IDRANGE:-65536}   # no of ids to map under user name spaces
#LXC_EXTRA= # whatever you want to append

# UML sandbox options
UML_SRC_DIR=/usr/lib64/uml
VMLINUX=$UML_SRC_DIR/vmlinux
RAM_SIZE=${RAM_SIZE:-256M}
#NICE_LEVEL= # nice level for UML kernel

############## helpers ##############

die() {
	echo "$1"
	exit 1
}

help() {
	cat << EOF
Usage: ${0##*/} [/path/to/persistent.disk-image] [cmd [args]]
Starts a persistent sandbox. 
In LXC mode type 'poweroff' to leave, otherwise just type 'exit'.
Path is optional, you will be asked to select or create one if not specified.
EOF
	exit 
}

############## main ##############

# 0.1 must be root
[ $(id -u) -ne 0 ] && die "You must be root to use sandbox."

# 0.2 if not running from terminal but in Xorg, then launch via terminal
! [ -t 0 ] && [ -n "$DISPLAY" ] && exec $XTERM -e "$0" "$@"
! [ -t 0 ] && exit

# 0.3 process cmdline
case "$1" in
	--help|-h) help ;; 
esac
rwbranch="$1"
[ $# -gt 0 ] && shift
[ "$rwbranch" = tmpfs ] && APPTITLE="tmpfs sandbox"

# 0.4 cannot launch sandbox within sandbox
if [ $USE_LXC ]; then
	APPTITLE="LXC-mode $APPTITLE" 
	# check for lxc
	if ! type lxc-start > /dev/null 2>&1; then
		die "LXC not found, cannot activate LXC mode."
	fi
	if grep -q LXC_ID $BOOTSTATE_PATH ||
	   [ "$AUFS_ROOT_ID" = overlay ]; then
	   die "Cannot launch sandbox within sandbox."
	fi
	
elif [ $USE_UML ]; then # USE_LXC
	APPTITLE="UML-mode $APPTITLE"
	if ! [ -x $VMLINUX ]; then
		die "UML kernel not found, cannot activate UML mode."
	fi
	
else # USE_LXC
	if grep -q $SANDBOX_ROOT /sys/fs/aufs/$AUFS_ROOT_ID/br0 ||
	   [ "$AUFS_ROOT_ID" = overlay ]; then
	   die "Cannot launch sandbox within sandbox."
	fi
fi # USE_LXC

# 1. get aufs system-id for the root filesystem
[ -z "$AUFS_ROOT_ID" ] && AUFS_ROOT_ID=$(
awk '{ if ($2 == "/" && $3 == "aufs") { match($4,/si=[0-9a-f]*/); print "si_" substr($4,RSTART+3,RLENGTH-3) } }' /proc/mounts
)

# 2. get branches, then map branches to mount types or loop devices
if [ -z "$OS_IMAGE" ]; then
items=$(
{ echo ==mount==; cat /proc/mounts; 
  echo ==losetup==; losetup-FULL -a; 
  echo ==branches==; ls -v /sys/fs/aufs/$AUFS_ROOT_ID/br[0-9]* | xargs sed 's/=.*//'; } | awk '
  /==mount==/ { mode=1 }
  /==losetup==/ { mode=2 }
  /==branches==/ { mode=3 }
  {
	if (mode == 1) {
		# get list of mount points, types, and devices - index is $3 (mount points)
		mountdev[$2]=$1
		mounttypes[$2]=$3
	} else if (mode == 2) {
		# get list of loop devices and files - index is $1 (loop devs)
		sub(/:/,"",$1)
		sub(/.*\//,"",$3); sub(/)/,"",$3)
		loopdev[$1]=$3
	} else if (mode == 3) {
		# map mount types to loop files if mount devices is a loop
		for (m in mountdev) {
			if ( loopdev[mountdev[m]] != "" ) mounttypes[m]=loopdev[mountdev[m]]
		}
		# for (m in mountdev) print m " on " mountdev[m] " type " mounttypes[m]
		mode=4
	} else if (mode == 4) {
		# print the branches and its mappings
		if (mounttypes[$0])
			print $0, mounttypes[$0], "on"
		else
			print $0, "unknown", "on"
	}
  }  
'
)
# '
# 3. Ask user to choose the SFS
[ -z "$items" ] && die "No layered filesystem found."
dialog --separate-output --backtitle "$APPTITLE" --title "sandbox config" \
	--checklist "Choose which SFS you want to use" 0 60 10 $items 2> $TMPFILE ||
	{ clear && die "Cancelled, exiting."; }

chosen="$(cat $TMPFILE)"
clear
[ -z "$chosen" ] && OS_IMAGE=1 # if no SFS is chosen, we're running an OS IMAGE

else ### if OS_IMAGE
chosen=""
fi   ### if OS_IMAGE
rm $TMPFILE

# 5. get location of rw image
if [ -z "$rwbranch" ]; then
	# location not specified - then ask
	dialog --backtitle "$APPTITLE" --title "choose rw image" \
	--extra-button --extra-label "Create" --ok-label "Locate" \
	--yesno "You didn't specify the location of the rw image file. Do you want to locate existing file, or do you want to create a new one?" 0 0
	case $? in
		0) # ok - locate
			dialog --backtitle "$APPTITLE" --title "Specify location of existing rw image" --fselect $(pwd) 8 60 2> $TMPFILE
			rwbranch=$(cat $TMPFILE)
			rm $TMPFILE
			if [ -n "$rwbranch" ]; then
				[ ! -f "$rwbranch" ] && die "$rwbranch doesn't exist, exiting."
			else
				die "You didn't specify any file or you pressed cancel, exiting."
			fi
			;;
		3) # create
			#echo "create"
			dialog --backtitle "$APPTITLE" --title "Specify name and path of new the file" --fselect $(pwd) 8 60 2> $TMPFILE
			rwbranch=$(cat $TMPFILE)
			rm $TMPFILE
			if [ -n "$rwbranch" ]; then
				if [ -f "$rwbranch" ]; then
					die "$rwbranch already exist, exiting."
				else
					# get the size
					dialog --title "Create new rw image" --inputbox "Specify size (in megabytes)" 0 40 100 2> $TMPFILE
					size=$(cat $TMPFILE)
					rm $TMPFILE
					
					if [ -n "$size" ]; then
						if dd if=/dev/zero of="$rwbranch" bs=1 count=0 seek="$size"M; then
							if ! mke2fs -F "$rwbranch"; then
								die "I fail to make an ext2 filesystem at $rwbranch, exiting."
							fi
						else
							die "I fail to create a ${size}M file at $rwbranch, exiting."
						fi
					else
						die "You didn't specify the size or your press cancel, exiting."
					fi					
				fi
			else
				die "You didn't specify any file or you pressed cancel, exiting."
			fi
			;;
		1 | 255) # 1 is cancel, 255 is Escape
			;&
		*) # invalid input - treat as cancel
			die "Cancelled, exiting."
			;;
	esac
fi

### 6. process options
OPTIONS=

if [ $USE_LXC ] || [ $USE_UML ]; then
	[ $USE_LXC ] && OPTIONS="--lxc"
	[ $USE_UML ] && OPTIONS="--uml" && USE_LXC=""

	### 6.1 LXC gui options
	# 6.1.1 lxc - enable network?
	[ "$USE_NETWORK" = ask ] && dialog --backtitle "$APPTITLE" \
		--title "Network Configuration" --no-cancel --no-tags \
		--radiolist "Choose how to configure network (required for desktop emulation)" 0 60 0 \
		"" "None" on proxyarp "Proxy ARP" off nat "NAT" off 2> $TMPFILE-net
	read USE_NETWORK < $TMPFILE-net
	rm -f $TMPFILE-net
	clear
	[ "$USE_NETWORK" ] && OPTIONS="$OPTIONS --net $USE_NETWORK"

	# 6.1.2 lxc - enable desktop emulation? (only if we use network)
	if [ "$USE_NETWORK" ]; then
		case "$USE_DESKTOP" in
			ask) USE_DESKTOP=
				 dialog --backtitle "$APPTITLE" --title "use xorg" \
					--defaultno --yesno "Enable desktop emulation?" 0 0 && USE_DESKTOP=yes
				 ;;
			yes) ;; # keep the yes
			*) USE_DESKTOP= # invalid option, ignore
			;;
		esac
	else
		USE_DESKTOP=
	fi
	[ "$USE_DESKTOP" ] && OPTIONS="$OPTIONS --desktop"
	clear

	# 6.1.3 lxc - share sounds?
	[ "$SHARE_SOUND" = "ask" ] && dialog --backtitle "$APPTITLE" --title "share sound" \
		   --defaultno --yesno "Enable sound output?" 0 0 && SHARE_SOUND=yes
	[ "$SHARE_SOUND" = "yes" ] && OPTIONS="$OPTIONS --sound"
	clear

	# 6.1.4 lxc - share IPC?
	[ "$USE_LXC" ] && [ "$SHARE_IPC" = ask ] && dialog --backtitle "$APPTITLE" --title "share ipc" \
		   --defaultno --yesno "Enable IPC sharing?" 0 0 && SHARE_IPC=yes
	[ "$SHARE_IPC" = "yes" ] && OPTIONS="$OPTIONS --ipc"
	clear

fi # USE_LXC

### 6.2 process env options
[ "$USE_OVERLAY" ] && OPTIONS="$OPTIONS --overlay"
[ "$NO_NS" ] && OPTIONS="$OPTIONS --no-ns"
[ "$NO_TMP" ] && OPTIONS="$OPTIONS --no-tmp"
[ "$WINDOW_SIZE" ] && OPTIONS="$OPTIONS --win $WINDOW_SIZE"
[ "$HOST_ADAPTER" ] && OPTIONS="$OPTIONS --host-adapter $HOST_ADAPTER"
[ "$HOST_IP" ] && OPTIONS="$OPTIONS --host-ip $HOST_IP"
[ "$GUEST_IP" ] && OPTIONS="$OPTIONS --guest-ip $GUEST_IP"
[ "$NAME_SERVER" ] && OPTIONS="$OPTIONS --dns $NAME_SERVER"
[ "$OS_IMAGE" ] && OPTIONS="$OPTIONS --os-image"
[ "$IDMAP" ] && OPTIONS="$OPTIONS --idmap"
[ "$LXC_UID" ] && OPTIONS="$OPTIONS --uid $LXC_UID"
[ "$LXC_GID" ] && OPTIONS="$OPTIONS --gid $LXC_GID"
[ "$LXC_IDRANGE" ] && OPTIONS="$OPTIONS --idrange $LXC_IDRANGE"
#LXC_EXTRA is already exported if we can see it here

[ "$RAM_SIZE" ] && OPTIONS="$OPTIONS --ram-size $RAM_SIZE"
[ "$NICE_LEVEL" ] && OPTIONS="$OPTIONS --nice $NICE_LEVEL"
#UML_EXTRA is already exported if we can see it here

# 6. let sfs-sandbox-lxc do the heavy lifting
QUIET=1 sfs-sandbox.sh $OPTIONS $rwbranch $chosen -- "$@"
