#!/bin/sh -ef

CONFIG_DIR='/etc/vz'
TEMPLATE_DIR='/var/lib/vz/template/cache'
CAP_LIST='/usr/share/alterator-ovz/caps'
IPTABLES_NAT='iptables -t nat'

_()
{
	LANG="${in_language%%;*}.utf8" gettext 'alterator-ovz' "$1"
}

. /usr/share/alterator/build/backend3.sh

# base functions

# taken from /usr/bin/hsh-sh-functions
check_number()
{
	[ -n "${1##0*}" -a -n "${1##*[!0-9]*}" ] &&
		[ "$1" -gt 0 ] 2>/dev/null ||
		return 1
}

# translate veid or vename to veid
get_veid()
{
	local veid
	if check_number "$1"; then
		veid="$(vzlist -a -H -oveid "$1" 2>/dev/null |
			tr -d '[:space:]')"
	else
		veid="$(vzlist -a -H -oveid -N "$1" 2>/dev/null |
			tr -d '[:space:]')"
	fi
	if check_number "$veid"; then
		printf %s "$veid"
	fi
}

# takes veid, outputs veid bc
show_bc()
{
	cat /proc/bc/"$1"/resources
}

# takes veid/vename, outputs configured bc names for the given ve
show_local_bc()
{
	local p="^($(awk '{print $1}' /proc/bc/0/resources |tr '\n' '|' |sed 's,|$,,'))=[^[:space:]].*"
	sed -nr "s/$p/\\1/ip" "$CONFIG_DIR"/conf/"$(get_veid "$1")".conf |
		tr '[:upper:]' '[:lower:]'
}

# outputs first unused veid >= 101
first_available()
{
	local avail_ve="$(vzlist -a -H -o veid)"
	local i=101
	while :; do
		if ! echo "$avail_ve"|grep -qs "^[[:space:]]*$i[[:space:]]*\$"; then
			echo "$i"
			break
		fi
		i=$(($i + 1))
	done
}

# takes file name and option name, outputs option value
read_ve_config_option()
{
	sed -n "s/^$2=\"\?\([^\"]\+\)\"\?[[:space:]]*$/\\1/ip" "$1"
}
# takes veid/vename and option name, outputs option value
read_option()
{
	local veid
	veid="$(get_veid "$1")"; shift
	read_ve_config_option "$CONFIG_DIR/conf/$veid.conf" "$1"
}

# takes veid/vename, option name, and format name,
# outputs option name and value in alterator format
alt_format_option()
{
	local value
	value="$(read_option "$1" "$2")"
	[ -z "$value" ] ||
		printf ' %s "%s" ' "$3" "$value"
}

# ouputs all valid template names in alterator format
alt_list_templates()
{
	local i
	for i in $(find "$TEMPLATE_DIR/" -maxdepth 1 -type f -printf '%f\n'); do
		i="${i#altlinux-}"
		i="${i%.gz}"
		i="${i%.tar}"
		[ -z "$i" ] ||
			printf '("%s")' "$i"
	done
}

# takes veid/vename, output formatted netlist
list_services()
{
	local vename=$1
	local user pid command fd type localip remoteip state
	vzctl exec "$vename" netlist |sed '1d' |
	while read user pid command fd type localip remoteip state; do
		[ "${localip%:*}" != '127.0.0.1' ] || continue
		case "$type" in
			tcp)
				[ "$state" = 'LISTEN' ] ||
					continue
				;;
			udp)
				[ "$state" = 'LISTEN' -o "$state" = 'CLOSED' ] ||
					continue
				;;
			*)
				continue
				;;
		esac
 		echo "$command" "${localip##*:}" "$fd" "$type"
	done | sort -u
}


# network functions

do_netsave()
{
	service iptables save >&2
}

# outputs default gateway device
get_snat_dev()
{
	ip r l |sed -n 's/^default[[:space:]]\+.*[[:space:]]\+dev[[:space:]]\+\([^[:space:]]\+\).*/\1/p'
}

# outputs default gateway ip address
get_snat_ip()
{
	local dev="$(get_snat_dev)"
	ip a l $dev |sed -ne 's,.*\<inet \([^ /]\+\)[ /].*,\1,p'
}

# takes ip address and action name, applies requested SNAT changes
do_simple_snat()
{
	local ip="$1"
	local action="$2"
	local dev="$(get_snat_dev)"

	$IPTABLES_NAT -nL POSTROUTING |
		grep -q "^SNAT.*\<to:$ip\>" && return
	$IPTABLES_NAT -nL POSTROUTING |
		grep -q "^MASQUERADE.*\<$dev\>" && return
	$IPTABLES_NAT "$action" POSTROUTING -s "$ip"  -o "$dev" -j SNAT --to "$(get_snat_ip)"
	do_netsave
}

# takes veid and DNAT parameters, applies requested DNAT changes
do_dnat()
{
	local veid="$1"; shift
	local ip="$(read_option "$veid" IP_ADDRESS)"
	local action="$1"; shift
	local proto="$1"; shift
	local dport="$1"; shift
	local sport="$1"; shift
	$IPTABLES_NAT "$action" PREROUTING -p "$proto" -d $(get_snat_ip) --dport "$dport" \
		-i "$(get_snat_dev)" -j DNAT --to-destination "$ip":"$sport"
	do_simple_snat "$ip" "$action"
	do_netsave
}

# takes veid, removes all DNATs for all ip addresses for the given veid
drop_dnat()
{
	local veid="$1"; shift
	local ip ips="$(read_option "$veid" IP_ADDRESS)"
	local found=

	for ip in $ips; do
		local rules="$(iptables-save -t nat |
			grep "^-A PREROUTING .* -j DNAT --to-destination $ip:")"

		[ -n "$rules" ] || continue
		found=1
		printf '%s\n' "$rules" |
			while read action params; do
				$IPTABLES_NAT -D $params >&2
			done
	done
	[ -z "$found" ] || do_netsave
}

# takes veid, outputs DNAT port for the ip address of the given veid
get_dnat_port()
{
	local veid="$1"; shift
	local ip="$(read_option "$veid" IP_ADDRESS)"
	local sport="$1"; shift
	local proto="$1"; shift
	local snat_ip="$(get_snat_ip)"
	local dport="$($IPTABLES_NAT -nL PREROUTING |
		sed -n "/ $snat_ip .* $proto .*to:$ip:$sport / s/.*dpt:\([0-9]\+\) to:.*/\1/p")"
	check_number "$dport" && echo "$dport"
}

# outputs alterator constrains
alt_constrains()
{
	echo '('

	if [ "$in__objects" = '/' ]; then
		local required
		[ "$in_orig_action" = 'new' ] && required='#t' || required='#f'

		printf ' name (label "%s" required %s match ("^[a-z0-9][a-z0-9_-]*$" "%s"))' \
			"`_ "VE name"`" \
			"$required" \
			"`_ "should be only small latin letters and digits"`"
	else
		printf ' name (label "%s")' \
			"`_ "Service"`"
	fi

	printf ' template (label "%s")' \
		"`_ "Template Name"`"
	printf ' status (label "%s")' \
		"`_ "Status"`"
	printf ' numproc (label "%s")' \
		"`_ "Number of processes"`"

	printf 'ipaddress (ipv4-address #t label "%s")' \
		"`_ "IP address"`"
	#XXX printf 'hostname (hostname #t label "%s" default "localhost.localdomain")\n' \
	printf 'hostname (hostname #t label "%s")\n' \
		"`_ "Hostname"`"
	printf 'search (hostname #t label "%s")\n' \
		"`_ "Search Domains"`"
	printf 'dns (ipv4-address #t label "%s")' \
		"`_ "Domain Name Servers (DNS)"`" #"
	printf ' systempass (label "%s" equal systempass2)' \
		"`_ "Administrator password"`"
	printf ' ltype (label "%s")' \
		"`_ "Service type"`"
	printf ' lport (label "%s")' \
		"`_ "Internal port"`"
	printf ' rport (label "%s")' \
		"`_ "External port"`"
	printf ' forward (label "%s" default #f)' \
		"`_ "Forward"`"

	printf ' onboot (label "%s" default #f)' \
		"`_ "Start on boot"`"

	printf ' resource (label "%s")' \
		"`_ "resource"`"
	printf ' held (label "%s")' \
		"`_ "held"`"
	printf ' maxheld (label "%s")' \
		"`_ "maxheld"`"
	printf ' barrier (label "%s")' \
		"`_ "barrier"`"
	printf ' limit (label "%s")' \
		"`_ "limit"`"
	printf ' failcnt (label "%s")' \
		"`_ "failcnt"`"
	printf ' capability (label "%s")' \
		"`_ "capability"`"
	printf ' capability_state (label "%s")' \
		"`_ "capability state"`"
	printf ' veid (label "%s")' \
		"`_ "Numeric Id"`"
	printf ' veip (label "%s")' \
		"`_ "IP address"`"
	echo ')'
}

alt_check_ovz()
{
	local err
	if ! err="$(service vz status 2>&1)" ; then
		err="$(printf %s "$err" |simple_quote)"
		printf '(error "%s")' "$err"
		return 1
	fi
}

alt_list()
{
	echo '('
	case "$in__objects" in
		templates)
			alt_list_templates
			;;
		ve)
			vzlist -a -H -oveid,numproc,name,status,ip |
			    while read veid numproc name status ip; do
				[ $name != '-' ] || name="$veid"
				printf '( "%s" status "%s" onboot %s numproc "%s")' \
					"$name" \
					"$status" \
					"$(read_option "$name" ONBOOT)" \
					"$numproc"
			done
			;;
		*/ports*)
			local name="${in__objects%/ports*}"
			local lport
			name="${name#ve/}"
			list_services "$name" |
			    while read command lport fd ltype; do
				local rport="$(get_dnat_port $name $lport $ltype)"
				local checked forwarded

				if [ -n "$rport" ]; then
					checked="#t"
					forwarded=1
				else
					checked="#f"
					forwarded=
				fi

				printf '( "%s_%s" command "%s" lport "%s" ltype "%s" rport "%s" forward %s forwarded "%s")' \
					"$command" "$fd" \
					"$command" \
					"$lport" \
					"$ltype" \
					"$rport" \
					"$checked" \
					"$forwarded"
			done
			;;
		*/caps)
			local name="${in__objects%/caps}"
			name="${name#ve/}"
			local n caps="$(read_option "$name" CAPABILITY)"

			for n in $caps; do
				local cap_state=disabled
				[ -n "${n##*:on}" ] ||
					cap_state=enabled
				cap="$(echo "${n%:*}" |tr '[:upper:]' '[:lower:]')"
				printf '( "%s" capability_state %s )\n' "$cap" "$cap_state"
			done
			;;
		*/avail_caps)
			local name="${in__objects%/caps*}"
			name="${name#ve/}"
			local caps="$(read_option "$name" CAPABILITY)"

			if [ -n "$caps" ]; then
				echo "$caps" |
				tr '[:upper:] ' '[:lower:]\n' |
				sed -e 's,:.*,,' |
				sort |
				comm -23 "$CAP_LIST" - |
				sed 's,.*,("&"),'
			else
				sed -e 's,.*,("&"),' "$CAP_LIST"
			fi
			;;
		*actions)
			printf '("start" label "%s")' "`_ "start"`"
			printf '("stop" label "%s")' "`_ "stop"`"
			printf '("restart" label "%s")' "`_ "restart"`"
			printf '("delete" label "%s")' "`_ "delete"`"
			;;
		*/actions_cap)
			printf '("enable_cap" label "%s")' "`_ "enable capability"`"
			printf '("disable_cap" label "%s")' "`_ "disable capability"`"
			;;
	esac
	echo ')'
}

# takes veid and in_*_{barrier,limit}, applies ubc changes
write_ubc()
{
	local veid="$1"; shift
	local tmpcfg="$(mktemp -t $veid.cfg.XXXXXXXXX)"
	cat "$CONFIG_DIR/conf/$veid.conf" > "$tmpcfg"

	local name value barrier limit changed=
	for name in $(show_local_bc "$veid"); do
		value="$(read_option "$veid" $name)"
		barrier="$(eval echo \$in_${name}_barrier)"
		limit="$(eval echo \$in_${name}_limit)"
		if [ -n "$barrier" -a -n "$limit" -a "$value" != "$barrier:$limit" ]; then
			changed="$changed $name "
			sed -i "s/^\($name\)=.*/\1=$barrier:$limit/i" "$tmpcfg"
		fi
	done

	if [ -z "$changed" ]; then
		echo '()'
	elif err="$(vzcfgvalidate "$tmpcfg" 2>&1)"; then
		local bc
		for bc in $changed; do
			barrier="$(eval echo \$in_${bc}_barrier)"
			limit="$(eval echo \$in_${bc}_limit)"
			vzctl set "$veid" "--$bc" "$barrier:$limit" --save >&2
		done
		echo '()'
	else
		err="$(printf %s "$err" |simple_quote)"
		printf '(error "%s")' "$err"
	fi

	rm -f "$tmpcfg" >&2
}

alt_write()
{
	if [ -n "$in_reset_rules" ]; then
		drop_dnat "$(get_veid ${in__objects##*/})"
	fi
	case "$in__objects" in
		*/ports*)
			local name="${in__objects%/ports*}"
			name="${name#ve/}"
			# isn't forward yet? forward it!
			[ "$in_forward" = "#t" -a -z "$in_forwarded" ] &&
				do_dnat "$name" "-A" "$in_ltype" "$in_rport" "$in_lport" >&2
			# already forwarded? delete it!
			[ "$in_forward" = "#f" -a -n "$in_forwarded" ] &&
				do_dnat "$name" "-D" "$in_ltype" "$in_rport" "$in_lport" >&2
			echo '()'
			;;
		*/devices)
			local name="${in__objects%/devices}"
			name="${name#ve/}"
			name="$(get_veid "$name")"
			[ -z "$in_devnodes" ] ||
				vzctl set "$name" --devnodes "$in_devnodes" --save >&2
			echo '()'
			;;
		*/quota)
			local name="${in__objects%/quota}"
			name="${name#ve/}"
			name="$(get_veid "$name")"
			[ -z "$in_diskspace" ] ||
				vzctl set "$name" --diskspace "$in_diskspace" --save >&2
			[ -z "$in_diskinodes" ] ||
				vzctl set "$name" --diskinodes "$in_diskinodes" --save >&2
			echo '()'
			;;
		*/ubc)
			local name="${in__objects%/ubc}"
			name="${name#ve/}"
			name="$(get_veid "$name")"
			write_ubc "$name"
			;;
		ve/*)
			local name=${in__objects##ve/}
			[ -z "$in_hostname" ] ||
				vzctl set "$name" --hostname "$in_hostname" --save >&2
			if [ -n "$in_ipaddress" ]; then
				vzctl set "$name" --ipdel all --save >&2
				vzctl set "$name" --ipadd "$in_ipaddress" --save >&2
			fi
			[ -z "$in_search" ] ||
				vzctl set "$name" --searchdomain "$in_search" --save >&2
			[ -z "$in_dns" ] ||
				vzctl set "$name" --nameserver "$in_dns" --save >&2
			[ -z "$in_systempass" ] ||
				vzctl set "$name" --userpasswd "root:$in_systempass" --save >&2
			[ -z "$in_capability" ] ||
				vzctl set "$name" --capability "$in_capability" --save >&2
			if [ "$in_onboot" = "#t" ] ;then
				vzctl set "$name" --onboot yes --save >&2
			else
				vzctl set "$name" --onboot no --save >&2
			fi
			echo '()'
			;;
	esac
}

alt_read()
{
	echo '('
	case "$in__objects" in
		*/ubc)
			local name="${in__objects%/ubc*}"
			name="${name#ve/}"
			name="$(get_veid "$name")"
			if [ -d /proc/bc/"$name" ]; then
				show_bc "$name" |
				while read bc held maxheld barrier limit failcnt; do
					printf '%s_name "%s" %s_held "%d" %s_maxheld "%d" %s_barrier "%d" %s_limit "%d" %s_failcnt "%d"\n' \
						$bc $bc $bc "$held" $bc "$maxheld" $bc "$barrier" $bc "$limit" $bc "$failcnt"
				done
			else
				local bc value
				for bc in $(show_local_bc "$name"); do
					value="$(read_option "$name" $bc)"
					local barrier="${value%%:*}"
					local limit="${value##*:}"
					printf '%s_name "%s" %s_held "N/A" %s_maxheld "N/A" %s_barrier "%d" %s_limit "%d" %s_failcnt "N/A"\n' \
						 $bc "$bc" $bc $bc $bc "$barrier" $bc "$limit" $bc
				done
			fi
			;;
		*/devices)
			local name="${in__objects%/devices*}"
			name="${name#ve/}"
			name="$(get_veid "$name")"
			alt_format_option "$name" DEVNODES devnodes
			;;
		*/quota)
			local name="${in__objects%/quota*}"
			name="${name#ve/}"
			name="$(get_veid "$name")"
			alt_format_option "$name" DISKSPACE diskspace
			alt_format_option "$name" DISKINODES diskinodes
			;;
		/|*/caps)
			echo '()'
			;;
		*)
			local name=${in__objects##*/}
			alt_format_option "$name" HOSTNAME hostname
			alt_format_option "$name" IP_ADDRESS ipaddress
			alt_format_option "$name" SEARCHDOMAIN search
			alt_format_option "$name" NAMESERVER dns
			local onboot="$(read_option "$name" ONBOOT)"
			if [ "$onboot" = 'yes' ];then
				printf ' onboot #t'
			else
				printf ' onboot #f'
			fi
			;;
	esac
	echo ')'
}

alt_new()
{
	if [ -n "$(vzlist -a -H -o veid)" ]; then
		if [ -n "$(vzlist -a -H -o veid -N "$in_name")" ]; then
			printf '(error"%s")' "`_ "Same VE name already in use"`"
			return
		fi

		if [ -n "$in_veid" ] && [ -n "$(vzlist -a -H -o veid "$in_veid")" ]; then
			printf '(error"%s")' "`_ "Same VE ID already in use"`"
			return
		fi

		if [ -n "$in_veip" ] && vzlist -a -H -o ip |tr -s ' ' '\n' |fgrep -xqs "$in_veip"; then
			printf '(error"%s")' "`_ "Same VE IP already in use"`"
			return
		fi
	fi

	local veid
	[ -n "$in_veid" ] &&
		veid="$in_veid" ||
		veid="$(first_available)"

	local ipaddress="192.0.2.$veid"
	if [ -n "$in_veip" ]; then
		ipaddress="$in_veip"
	elif [ "$veid" -gt 254 ] 2>/dev/null; then
		# we don't have ip's > xxx.xxx.xxx.255
		printf '(error "%s")' "`_ "VE ID must be less than 255"`"
		return
	fi

	local ostemplate="$in_template"
	local veconfig="/etc/spt/profiles/ovz/$ostemplate/config"
	local config='resolver' # XXX: not yet configurable
	local capability="$(read_ve_config_option "$veconfig" 'CAPABILITY')"
	local devnodes="$(read_ve_config_option "$veconfig" 'DEVNODES')"

	if [ -f "$TEMPLATE_DIR/altlinux-$ostemplate.tar" -o \
	     -f "$TEMPLATE_DIR/altlinux-$ostemplate.tar.gz" ]; then
		ostemplate="altlinux-$in_template"
	fi

	local hostname="$in_hostname"
	[ -n "$hostname" ] ||
		hostname='localhost.localdomain'

	if ! err="$(vzctl create "$veid" --ostemplate "$ostemplate" --config "$config" 2>&1)" ; then
		err="$(printf %s "$err" |simple_quote)"
		printf '(error "%s")' "$err"
		return
	fi
	vzctl set "$veid" --name "$in_name" --save >&2
	vzctl set "$veid" --hostname "$hostname" --save >&2
	vzctl set "$veid" --ipadd "$ipaddress" --save >&2
	[ -z "$capability" ] ||
		vzctl set "$veid" --capability "$capability" --save >&2
	[ -z "$devnodes" ] ||
		vzctl set "$veid" --devnodes "$devnodes" --save >&2 ||:
	vzctl set "$veid" --onboot yes --save >&2
	do_simple_snat "$ipaddress" '-A'
	echo '()'
}

on_message()
{
	case "$in_action" in
		constraints)
			alt_constrains
			;;
		list)
			alt_check_ovz || return 0
			alt_list
			;;
		write)  #write VE params
			alt_check_ovz || return 0
			alt_write
			;;
		read)   #read VE params
			alt_check_ovz || return 0
			alt_read
			;;
		start)  #start VE
			alt_check_ovz || return 0
			vzctl start "${in__objects##*/}" >&2
			echo '()'
			;;
		stop)	#stop VE
			alt_check_ovz || return 0
			vzctl stop "${in__objects##*/}" >&2
			echo '()'
			;;
		restart)#restart VE
			alt_check_ovz || return 0
			vzctl restart "${in__objects##*/}" >&2
			echo '()'
			;;
		new)    #create VE
			alt_check_ovz || return 0
			alt_new
			;;
		new_cap) #add capability
			alt_check_ovz || return 0
			local ve="${in__objects%/caps*}"
			ve="${ve#ve/}"
			vzctl set "$ve" --capability "$in_capability":on --save >&2
			echo '()'
			;;
		disable_cap|enable_cap) #disable capability
			alt_check_ovz || return 0
			local ve="${in__objects%/caps*}"
			ve="${ve#ve/}"
			local cap="${in__objects##*/}"
			local cap_state=on
			[ "$in_action" = 'disable_cap' ] && cap_state=off
			vzctl set "$ve" --capability "$cap":"$cap_state" --save >&2
			echo '()'
			;;
		delete) #destroy VE
			alt_check_ovz || return 0
			local ve="${in__objects##*/}"
			vzctl stop "$ve" >&2
			drop_dnat "$ve"
			do_simple_snat "$(read_option "$ve" IP_ADDRESS)" '-D' >&2
			vzctl destroy "$ve" >&2
			echo '()'
			;;
		*)
			echo '#f'
			;;
	esac
}

message_loop
