#!/bin/bash

po_domain="alterator-openvpn-server"
alterator_api_version=1

. alterator-sh-functions
. shell-config
. cert-sh-functions
. alterator-net-functions

CHROOTDIR="/var/lib/openvpn"
CONFDIR="/etc/openvpn"
KEYSDIR="${SSL_KEYDIR:-$CONFDIR/keys}"
CERTSDIR="${SSL_CERTDIR:-$CONFDIR/keys}"
CSRDIR="${SSL_CSRDIR:-$CONFDIR/keys}"
ETCNET_IFACEDIR="/etc/net/ifaces"
OVPNCONFIG="ovpnoptions"
DEFAULT_SERVERNAME="openvpn-server"
DEFAULT_OWN_CA="$CERTSDIR/$DEFAULT_SERVERNAME-CA.crt"
DEFAULT_CA="$SSL_CERTDIR/ca-root.pem"
DHPARAM_NUMBITS="1024"
CACHEDIR="/var/cache/alterator/openvpn-server"
CCDDIR="$CHROOTDIR/$CONFDIR/ccd"
IFACEDIR="$ETCNET_IFACEDIR"
SERVER_NETWORKS_TMP="$CACHEDIR/server_networks.tmp"
OPENSSL="${OPENSSL:-openssl}"

GENERATEDBY='# Generated by alterator-openvpn-server, do not edit manually'
SERVER_NETWORKS_START='# Server networks start'
SERVER_NETWORKS_END='# Server networks end'

### cache
init_cache()
{
	local dev="$1";shift

	[ -n "$dev" ] || return 1
	[ -d "$CACHEDIR" ] || mkdir "$CACHEDIR"
	IFACEDIR="$CACHEDIR"
	CCDDIR="$CACHEDIR/ccd"
	if [ -d "$ETCNET_IFACEDIR/$dev" -a ! -d "$CACHEDIR/$dev" ];then
		cp -a "$ETCNET_IFACEDIR/$dev" "$CACHEDIR/$dev"
		[ ! -f "$SERVER_NETWORKS_TMP" ] &&
		sed -r -n "/^$SERVER_NETWORKS_START/,/$SERVER_NETWORKS_END/ \
			s/^[[:blank:]]*(push[[:blank:]]+\"route[[:blank:]]+[[:digit:].]+[[:blank:]]+[[:digit:].]+\")/\1/p" \
			"$IFACEDIR/$dev/$OVPNCONFIG" >"$SERVER_NETWORKS_TMP"
	fi

	if [ -d "$CHROOTDIR/$CONFDIR/ccd" -a ! -d "$CACHEDIR/ccd" ];then
		cp -a "$CHROOTDIR/$CONFDIR/ccd" "$CACHEDIR/ccd"
	fi

}

clear_cache()
{
	IFACEDIR="$ETCNET_IFACEDIR"
	CCDDIR="$CHROOTDIR/$CONFDIR/ccd"
	rm -rf "$CACHEDIR"
}

commit_cache()
{
	local dev="$1";shift

	[ -n "$dev" ] || return 1

	if [ -d "$CACHEDIR/$dev" ];then
		rm -rf "$ETCNET_IFACEDIR/$dev"
		mv "$CACHEDIR/$dev" "$ETCNET_IFACEDIR/$dev"
	fi
	if [ -d "$CACHEDIR/ccd" ];then
		rm -rf "$CHROOTDIR/$CONFDIR/ccd"
		mv "$CACHEDIR/ccd" "$CHROOTDIR/$CONFDIR/ccd"
	fi
#	case "$dev" in
#		tap*)
#		local br="$(find_bridge_config)"
#		if [ -n "$br" ];then
#			local iface="$(shell_config_get "$CACHEDIR/$br/options" "HOST" | \
#			 	sed -n "s;[[:blank:]\'\"]*\([[:alnum:]_]\+\).*$;\1;p")"
#			if [ -n "$iface" -a -d "$CACHEDIR/$iface" ];then
#				ifdown "$iface"
#				rm -rf "$ETCNET_IFACEDIR/$iface"
#				rm -rf "$ETCNET_IFACEDIR/$br"
#				mv "$CACHEDIR/$iface" "$ETCNET_IFACEDIR/$iface"
#				mv "$CACHEDIR/$br" "$ETCNET_IFACEDIR/$br"
#				ifup "$br"
#			fi
#		fi
#		;;
#	esac

	clear_cache
}

init_cached_server_networks()
{
	local dev="$(find_server_vdev)"
	if [ -z "$dev" -o -z "$(list_server_networks "$dev")" ];then
		[ -d "$CACHEDIR" ] || mkdir "$CACHEDIR"
		for i in $(iptables_helper show -i);do
			netdev_is_eth "$i" || continue
			local iface_net="$(netname "$(ip addr show "$i" 2>/dev/null \
			| sed -n 's;[[:space:]]\+inet[[:space:]]\+\([[:digit:]./]\+\).*;\1;p')" | cut -f1)"
			if [ -n "$iface_net" ];then
				local prefix="$(echo "$iface_net" | cut -f2 -d'/')"
				local mask="$(ipv4addr_prefix_to_mask "$prefix")"
				echo "$iface_net" \
				| sed -r "s;([[:digit:].]+)/[[:digit:]]+;push \"route \1 $mask\";" >>"$SERVER_NETWORKS_TMP"
			fi
		done
	fi
}

get_cached_vdev()
{
	local tmp="$(find "$CACHEDIR" -type d \( -name tun\* -o -name tap\* \) 2>/dev/null | head -n1)"

	[ -n "$tmp" ] && echo "${tmp##*/}"
}

write_cached_server_network()
{
	local net="$1";shift
	local mask="$1";shift

	echo "push \"route $net $mask\"" >>"$SERVER_NETWORKS_TMP"
}

### ssl
is_cert()
{
    $OPENSSL x509 -noout -in "$1" 2>/dev/null
}

make_ssl_files()
{
	[ -n "$1" ] || return
	ssl_make_key "$1"
	ssl_check_cert "$1" || ssl_make_req "$1"
	ssl_check_dhparam "$1" || ssl_make_dhparam "$1" "$DHPARAM_NUMBITS"
}

### ip helpers
vpn_start_addr()
{
	local end_addr="$1";shift
	local prefix="$1";shift
	local end="${end_addr##*.}"
	local start=

	[ -n "$end" -a -n "$prefix" ] || return

	if [ "$prefix" -le 24 ];then
		start=190
	else
		start=$(( $end - (1 << (32 - $prefix)) / 4 ))
	fi

	echo "$end_addr" | sed -n "s;\([[:digit:].]\+\.\).\+;\1$start;p"
}

check_networks()
{
	local net1="${1%%/*}"
	local prefix1="$(ipv4addr_mask_to_prefix "${1##*/}")";shift
	local net2="${1%%/*}"
	local prefix2="$(ipv4addr_mask_to_prefix "${1##*/}")";shift

	[ -n "$net1" -a -n "$prefix1" -a -n "$net2" -a -n "$prefix2" ] || return 0

	if [ "$prefix1" -lt "$prefix2" ];then
		! ipv4addr_is_in_subnet "$net2" "$net1/$prefix1"
	else
		! ipv4addr_is_in_subnet "$net1" "$net2/$prefix2"
	fi
}

### network configuration helpers
get_iface_ip4addr()
{
	local iface="$1";shift

	[ -n "$iface" ] || return
	ip addr show "$iface" | sed -n 's;[[:space:]]\+inet[[:space:]]\+\([[:digit:]./]\+\).*;\1;p'
}

get_ip4addr_for_subnet()
{
	local server_net="$1";shift
	local server_prefix="$1";shift

	for i in $(iptables_helper show -i);do
		local iface_net="$(ip addr show "$i" | sed -n 's;[[:space:]]\+inet[[:space:]]\+\([[:digit:]./]\+\).*;\1;p')"
		if [ -n "$iface_net" ];then
			local addr="$(echo "$iface_net" | cut -f1 -d'/')"
			local prefix="$(echo "$iface_net" | cut -f2 -d'/')"
			[ -n "$addr" -a -n "$prefix" -a -n "$server_net" -a -n "$server_prefix" -a \
					"$server_prefix" -eq "$prefix" ] &&
			ipv4addr_is_in_subnet "$addr" "$server_net/$prefix" && { echo "$addr"; break; }
		fi
	done
}

get_ns()
{
	local server_net=
	local server_prefix=

	for net in $(list_server_networks)
	do
		server_net="$(echo "$net" | cut -f1 -d '/')"
		server_prefix="$(ipv4addr_mask_to_prefix "$(echo "$net" | cut -f2 -d '/')")"

		for addr in $(shell_config_get '/etc/resolv.conf' 'nameserver' ' ');do
			if [ "$addr" = '127.0.0.1' ];then
				local ns="$(get_ip4addr_for_subnet "$server_net" "$server_prefix")"
				[ -n "$ns" ] && { echo "$ns"; return; }
			elif ipv4addr_is_in_subnet "$addr" "$server_net/$server_prefix";then
				echo "$addr"
				return
			fi
		done
	done
}

next_vdev()
{
	local ovpn_type="$1";shift
	local dev_type="$(vdev_type "$ovpn_type")"

	[ -n "$dev_type" ] && next_iface "$dev_type"
}

find_server_vdev()
{
	local dev_type="$1";shift

	for i in `find $ETCNET_IFACEDIR -type d -name $dev_type\* 2>/dev/null`
	do
		[ -f $i/options ] &&
		grep -qs "$GENERATEDBY" $i/$OVPNCONFIG &&
		grep -qs "^TYPE=ovpn" $i/options &&
		grep -qs "^server" $i/$OVPNCONFIG &&
		echo "${i##*/}" && break
	done
}

#find_bridge_config()
#{
#	for i in `find $CACHEDIR -type d -name br\* 2>/dev/null`
#	do
#		[ -f $i/options ] &&
#		grep -qs "^TYPE=bri" $i/options &&
#		echo "${i##*/}" && break
#	done
#}
#
#create_bridge_config()
#{
#	local iface="$1"
#	local br=
#
#	[ -n "$iface" ] || return
#
#	br="$(next_iface 'br')"
#	mkdir "$CACHEDIR/$br"
#	[ -d "$ETCNET_IFACEDIR/$iface" ] && cp -a $ETCNET_IFACEDIR/$iface/* $CACHEDIR/$br/
#	if [ -s "$CACHEDIR/$br/options" ];then
#		shell_config_set "$CACHEDIR/$br/options" TYPE bri
#		shell_config_set "$CACHEDIR/$br/options" HOST "'$iface'"
#		mkdir "$CACHEDIR/$iface"
#		cp "$ETCNET_IFACEDIR/$iface/options" "$CACHEDIR/$iface"
#		shell_config_set "$CACHEDIR/$iface/options" BOOTPROTO static
#	else
#		cat > "$CACHEDIR/$br/options" <<BR_OPTIONS_TEMPLATE
#DISABLED=no
#ONBOOT=yes
#TYPE=bri
#HOST='$iface'
#BR_OPTIONS_TEMPLATE
#	fi
#}

### common helpers
vdev_type()
{
	local type="$1"

	case "$type" in
		routed) echo 'tun'
		;;
		bridged) echo 'tap'
		;;
	esac
}

get_dev_type()
{
	case "$1" in
		tun*) echo 'tun'
		;;
		tap*) echo 'tap'
		;;
	esac
}

is_dev_type_changed()
{
	local dev="$1";shift
	local dev_type="$1";shift

	[ -n "$dev_type" ] && [ "$dev_type" != "$(get_dev_type "$dev")" ]
}

### lists
list_mask()
{
	for i in `seq 32 -1 0`; do
		local mask="$(ipv4addr_prefix_to_mask "$i")"
		write_enum_item "$mask" "/$i ($mask)"
	done
}

list_types()
{
	write_enum_item "routed" "`_ "Routed (TUN)"`"
	write_enum_item "bridged" "`_ "Bridged (TAP)"`"
}

list_clients()
{
	ls "$CCDDIR" 2>/dev/null
}

list_client_networks()
{
	local client_name="$1";shift
	local client_file="$CCDDIR/$client_name"

	[ -n "$client_name" -a -f "$client_file" ] &&
	shell_config_get "$client_file" iroute ' ' | tr -s ' ' '/'
}

list_server_networks()
{
	local dev="$1";shift

	if [ -f "$SERVER_NETWORKS_TMP" ];then
		sed -r -n 's;^[[:blank:]]*push[[:blank:]]+\"route[[:blank:]]+([[:digit:].]+)[[:blank:]]+([[:digit:].]+)\";\1/\2;p' \
		"$SERVER_NETWORKS_TMP"
	else
		[ -n "$dev" ] && sed -r -n "/^$SERVER_NETWORKS_START/,/$SERVER_NETWORKS_END/ \
				s;^[[:blank:]]*push[[:blank:]]+\"route[[:blank:]]+([[:digit:].]+)[[:blank:]]+([[:digit:].]+)\";\1/\2;p" \
				"$IFACEDIR/$dev/$OVPNCONFIG"
	fi
}

list_ethdev()
{
	for i in $(iptables_helper show -i);do
		netdev_is_eth "$i" && netdev_is_real "$i" && echo "$i"
	done
}

list_bridges()
{
	for i in $(iptables_helper show -i);do
		netdev_is_bridge "$i" && echo "$i"
	done
}

### read client config
read_client_ns_info()
{
	local dev="$1";shift
	local name="$1";shift
	local ns= domain=

	if [ -n "$dev" -a -f "$IFACEDIR/$dev/ifup-post" ];then
		domain="$(sed -n "s/^.*domain[[:space:]]\+\([[:alnum:].]\+\).*ovpn-$name$/\1/p" "$IFACEDIR/$dev/ifup-post")"
		ns="$(sed -n "s/^.*nameserver[[:space:]]\+\([[:alnum:].]\+\).*ovpn-$name$/\1/p" "$IFACEDIR/$dev/ifup-post")"
	fi
	write_string_param client_ns "$ns"
	write_string_param client_domain "$domain"
}

### read server config
get_config_val()
{
	local dev="$1";shift
	local name="$1";shift
	shell_config_get "$IFACEDIR/$dev/$OVPNCONFIG" "$name" ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*#.*$//'
}

read_bridged_vpn_params()
{
	local dev="$1";shift

	get_config_val "$dev" "server-bridge"
}

read_vpn_net()
{
	local dev="$1";shift

	get_config_val "$dev" "server"
}

read_vpn_type()
{
	local dev="$1";shift

	if grep -qs '^server-bridge[[:space:]]' "$IFACEDIR/$dev/$OVPNCONFIG";then
		echo 'bridged'
	elif grep -qs '^server[[:space:]]' "$IFACEDIR/$dev/$OVPNCONFIG";then
		echo 'routed'
	fi
}

get_bridge()
{
	local vdev="$1";shift

	[ -n "$vdev" ] || return

	[ -f "$IFACEDIR/$vdev/ifup-post" ] &&
	sed -n 's;^[[:blank:]]*br="$(netdev_find_bridge "\([[:alnum:]_]\+\)")".*$;\1;p' "$IFACEDIR/$vdev/ifup-post"
}

read_server_config()
{
	local dev="$1";shift
	local dev_conf=
	local dev_type="$(vdev_type "$in_type")"
	local vpn_network= bridged_vpn_params=
	local type="$in_type"

	if [ -n "$dev" ] &&	[ -f "$IFACEDIR/$dev/$OVPNCONFIG" ];then
		dev_conf="$(get_config_val "$dev" dev)"
		if [ "$dev" != "$dev_conf" ];then
			write_error "`_ "Invalid config value 'dev'"` $dev_conf"
			return
		fi
		write_bool_param enabled "$(shell_config_get "$IFACEDIR/$dev/options" 'ONBOOT')"
		[ -n "$type" ] || type="$(read_vpn_type "$dev")"
		write_string_param type "$type"
		write_string_param port "$(get_config_val "$dev" port)"
		write_string_param server_netmask '255.255.255.0'
		if grep -qs '^comp-lzo' "$IFACEDIR/$dev/$OVPNCONFIG";then
			write_bool_param lzo true
		else
			write_bool_param lzo false
		fi
		if get_config_val "$dev" proto | grep -qs '^tcp';then
			write_bool_param use_tcp true
		else
			write_bool_param use_tcp false
		fi
		if [ "$type" = 'routed' ];then
			vpn_network="$(read_vpn_net "$dev")"
			write_string_param vpnnet "${vpn_network%% *}"
			write_string_param vpnnetmask "${vpn_network##* }"
		else
			bridged_vpn_params="$(read_bridged_vpn_params "$dev")"
			write_string_param gateway_vpnaddr "${bridged_vpn_params%% *}"
			write_string_param vpnnetmask "$(echo "$bridged_vpn_params" | cut -f2 -d' ')"
			write_string_param vpnpool_start "$(echo "$bridged_vpn_params" | cut -f3 -d' ')"
			write_string_param vpnpool_end "${bridged_vpn_params##* }"
			write_string_param bridge "$(get_bridge "$dev")"
		fi
	else
		#default parameters
		write_bool_param enabled '#f'
		[ -z "$type" ] && type='routed'
		write_string_param type "$type"
		write_string_param port '1194'
		write_bool_param lzo true
		write_bool_param use_tcp false
		if [ "$type" = 'routed' ];then
			write_string_param server_netmask '255.255.255.0'
			write_string_param vpnnetmask '255.255.255.0'
			write_string_param vpnnet '10.8.0.0'
		else
			local br="$(list_bridges | head -n1)"
			local iface_addr="$(get_iface_ip4addr "$br")"
			if [ -n "$iface_addr" ];then
				local addr="${iface_addr%%/*}"
				local prefix="${iface_addr##*/}"
				write_string_param gateway_vpnaddr "$addr"
				write_string_param vpnnetmask "$(ipv4addr_prefix_to_mask "$prefix")"
				local end_addr="$(netname "$iface_addr" | cut -f3)"
				write_string_param vpnpool_start "$(vpn_start_addr "$end_addr" "$prefix")"
				write_string_param vpnpool_end "$end_addr"

			else
				write_string_param vpnnetmask '255.255.255.0'
			fi
		fi
	fi
}

### networks checks
check_clients_net()
{
	local ip="$1";shift
	local mask="$1";shift

	for client in $(list_clients);do
		for net in $(list_client_networks "$client");do
			check_networks "$ip/$mask" "$net" || return 1
		done
	done
}

check_server_net()
{
	local ip="$1";shift
	local mask="$1";shift

	for net in $(list_server_networks);do
		check_networks "$ip/$mask" "$net" || return 1
	done
}

check_vpn_net()
{
	local ip="$1";shift
	local mask="$1";shift
	local dev="$1";shift
	local vpn_type="${in_type:-$(read_vpn_type "$dev")}"
	local vpn_net= vpn_netmask=

	if [ "$vpn_type" = 'routed' ];then
		if [ -n "$in_vpnnet" -a -n "$in_vpnnetmask" ];then
			vpn_net="$in_vpnnet"
			vpn_netmask="$in_vpnnetmask"
		elif [ -n "$dev" -a -f "$IFACEDIR/$dev/$OVPNCONFIG" ];then
			local vpn_network="$(read_vpn_net "$dev")"
			if [ -n "$vpn_network" ];then
				vpn_net="${vpn_network%% *}"
				vpn_netmask="${vpn_network##* }"
			fi
		fi
	else
		if [ -n "$in_gateway_vpnaddr" -a -n "$in_vpnnetmask" ];then
			vpn_net="$in_gateway_vpnaddr"
			vpn_netmask="$in_vpnnetmask"
		elif [ -n "$dev" -a -f "$IFACEDIR/$dev/$OVPNCONFIG" ];then
			local bridged_vpn_params="$(read_bridged_vpn_params "$dev")"
			if [ -n "$bridged_vpn_params" ];then
				vpn_net="${bridged_vpn_params%% *}"
				vpn_netmask="$(echo "$bridged_vpn_params" | cut -f2 -d' ')"
			fi
		fi
	fi

	check_networks "$ip/$mask" "$vpn_net/$vpn_netmask"
}

check_client_ns()
{
	local dev="$1";shift
	local client="$1";shift
	local ns="$1";shift

	if [ -z "$ns" ];then
		ns="$(sed -n "s;^.*nameserver[[:space:]]\+\([[:alnum:].-_]\+\).*ovpn-$client.*$;\1;p" \
			"$CACHEDIR/$dev/ifup-post" 2>/dev/null)"
		[ -n "$ns" ] || return 0
	fi

	for net in $(list_client_networks "$client");do
		local addr="${net%%/*}"
		local prefix="$(ipv4addr_mask_to_prefix "${net##*/}")"
		ipv4addr_is_in_subnet "$ns" "$addr/$prefix" && return 0
	done
	return 1
}

### write clients configuration
add_client_iroute()
{
	local ccd_dir="$CACHEDIR/ccd"
	local client_name="$1"; shift
	local client_net="$1"; shift
	local client_netmask="$1"; shift

	[ -d "$ccd_dir" ] || mkdir "$ccd_dir"
	[ -n "$client_name" -a -n "$client_net" -a -n "$client_netmask" ] &&
		echo "iroute $client_net $client_netmask" >>$ccd_dir/$client_name
}

remove_client_iroute()
{
	local client_name="$1";shift
	local client_net="$1";shift
	local client_netmask="$1";shift
	local client_file="$CACHEDIR/ccd/$client_name"
	local tmp="$(mktemp -t "$client_name".XXXXXX)"

	[ -n "$client_name" -a -n "$client_net" -a -n "$client_netmask" -a -f "$client_file" ] &&
	sed -r /"$client_net"[[:space:]]+"$client_netmask"/d "$client_file" >"$tmp" &&
	mv -f "$tmp" "$client_file"
	[ -s "$client_file" ] || rm -f "$client_file"
}

write_client_ns_info()
{
	local dev="$1";shift
	local name="$1";shift
	local domain="$1";shift
	local ns="$1";shift

	if [ -z "$dev" ];then
		echo "Device not specified" 1>&2
		return 1
	fi
	[ -n "$name" ] || return 1

	if [ -f "$CACHEDIR/$dev/ifup-post" ];then
		sed -i "/ovpn-$name/d" "$CACHEDIR/$dev/ifup-post"
	else
		echo '#!/bin/sh' >"$CACHEDIR/$dev/ifup-post"
		chmod +x "$CACHEDIR/$dev/ifup-post"
	fi
	if [ ! -f "$CACHEDIR/$dev/ifdown-post" ];then
		echo '#!/bin/sh' >"$CACHEDIR/$dev/ifdown-post"
		chmod +x "$CACHEDIR/$dev/ifdown-post"
	fi

	if [ -n "$domain" -a -n "$ns" ];then
		echo "echo -e 'domain $domain\\nnameserver $ns' | resolvconf -p -a ovpn-$name" >>"$CACHEDIR/$dev/ifup-post"
		grep -qs "ovpn-$name" "$CACHEDIR/$dev/ifdown-post" ||
		echo "resolvconf -d ovpn-$name" >>"$CACHEDIR/$dev/ifdown-post"
	else
		[ "$(wc -l "$CACHEDIR/$dev/ifup-post" | cut -f1 -d' ')" -gt 1 ] || rm -f "$CACHEDIR/$dev/ifup-post"
		sed -i "/ovpn-$name/d" "$CACHEDIR/$dev/ifdown-post"
		[ "$(wc -l "$CACHEDIR/$dev/ifdown-post" | cut -f1 -d' ')" -gt 1 ] || rm -f "$CACHEDIR/$dev/ifdown-post"
	fi
}

# write server configuration
push_route()
{
	local dev="$1";shift

	echo "push \"route $1 $2\"" >>$CACHEDIR/$dev/$OVPNCONFIG
}

make_iface_options()
{
	local options="$1/options";shift

	cat > "$options" <<OPTIONS_TEMPLATE
ONBOOT=yes
TYPE=ovpn
BOOTPROTO=static
OPTIONS_TEMPLATE
}

add_route()
{
	local dev="$1"; shift
	local client_net="$1"; shift
	local client_netmask="$1"; shift

	echo "route $client_net $client_netmask" >>$CACHEDIR/$dev/$OVPNCONFIG
	push_route "$dev" "$client_net" "$client_netmask"
}

add_all_clients_routes()
{
	local ccd_dir="$CACHEDIR/ccd"
	local dev="$1";shift
	local network=
	for i in $(list_clients)
	do
		#Spaces substituted on '|' for for easier splitting of strings
		networks="$(shell_config_get "$ccd_dir/$i" iroute ' ' | sed 's/[[:space:]]\+/|/')"
		for i in $networks;do
			add_route "$dev" "${i%|*}" "${i#*|}"
		done
	done
}

push_ns()
{
	echo "push \"dhcp-option DNS $1\"" >>$CACHEDIR/$dev/$OVPNCONFIG
}

write_bridged_updown()
{
	local br="$1";shift
	local vdev="$1";shift
	[ -n "$vdev" -a -n "$br" ] || return
	if [ -f "$CACHEDIR/$vdev/ifup-post" ];then
		sed	-i "s;^\([[:blank:]]*brctl addif\);\1 '$br' '$vdev';" "$CACHEDIR/$vdev/ifup-post"
	else
		cat > "$CACHEDIR/$vdev/ifup-post" <<IFUP_POST_TEMPLATE
#!/bin/sh

brctl addif '$br' '$vdev'
IFUP_POST_TEMPLATE
		chmod +x "$CACHEDIR/$vdev/ifup-post"
	fi

	if [ -f "$CACHEDIR/$vdev/ifdown-pre" ];then
		sed	-i "s;^\([[:blank:]]*brctl delif\);\1 '$br' '$vdev';" "$CACHEDIR/$vdev/ifdown-pre"
	else
		cat > "$CACHEDIR/$vdev/ifdown-pre" <<IFDOWN_PRE_TEMPLATE
#!/bin/sh

brctl delif '$br' '$vdev'
IFDOWN_PRE_TEMPLATE
		chmod +x "$CACHEDIR/$vdev/ifdown-pre"
	fi
}

make_server_conf()
{
	local dev="$1";shift
	local server_param=
	local servername="$DEFAULT_SERVERNAME"
	local ca="$DEFAULT_CA"
    local ns=
	local proto='udp'

	[ -d "$CACHEDIR" ] || { mkdir "$CACHEDIR" || return 1; }

	if [ "$in_type" = 'routed' ];then
		server_param="server $in_vpnnet $in_vpnnetmask"
		if ! check_server_net "$in_vpnnet" "$in_vpnnetmask" "$dev";then
			write_error "`_ "VPN network conflicts with server network"`"
			return 1
		fi
		if ! check_clients_net "$in_vpnnet" "$in_vpnnetmask";then
			write_error "`_ "VPN network conflicts with client network"`"
			return 1
		fi
	elif [ "$in_type" = 'bridged' ];then
		server_param="server-bridge $in_gateway_vpnaddr $in_vpnnetmask $in_vpnpool_start $in_vpnpool_end"
		if [ -n "$in_gateway_vpnaddr" -a -n "$in_vpnnetmask" \
				-a -n "$in_vpnpool_start" -a -n "$in_vpnpool_end" ];then
			local vpn_prefix="$(ipv4addr_mask_to_prefix "$in_vpnnetmask")"
			if ! ipv4addr_is_in_subnet "$in_vpnpool_start" "$in_gateway_vpnaddr/$vpn_prefix";then
				write_error "`_ "VPN start address not in VPN network"`"
				return 1
			fi
			if ! ipv4addr_is_in_subnet "$in_vpnpool_end" "$in_gateway_vpnaddr/$vpn_prefix";then
				write_error "`_ "VPN end address not in VPN network"`"
				return 1
			fi
			local vpn_start_hex="$(ipv4addr_to_hex "$in_vpnpool_start")"
			local vpn_end_hex="$(ipv4addr_to_hex "$in_vpnpool_end")"
            # '(( .. ))' - it's bashism, but usefull here
			if (("$vpn_end_hex" < "$vpn_start_hex"));then
				write_error "`_ "VPN start address is greater than VPN end address"`"
				return 1
			fi
			local gateway_vpnaddr_hex="$(ipv4addr_to_hex "$in_gateway_vpnaddr")"
			if (("$gateway_vpnaddr_hex" >= "$vpn_start_hex")) && (("$gateway_vpnaddr_hex" <= "$vpn_end_hex"));then
				write_error "`_ "Gateway address in VPN addresses pool"`"
				return 1
			fi

		fi
	fi

	[ -d "$CACHEDIR/$dev" ] || { mkdir "$CACHEDIR/$dev" || return 1; }
	[ ! -f "$CACHEDIR/$dev/options" ] && { make_iface_options "$CACHEDIR/$dev" || return 1; }

	[ -s "$DEFAULT_OWN_CA" ] && ca="$DEFAULT_OWN_CA"

	test_bool "$in_use_tcp" && proto='tcp-server'

	cat > "$CACHEDIR/$dev/$OVPNCONFIG" <<SERVER_CONF_TEMPLATE
$GENERATEDBY
port $in_port
proto $proto
dev  $dev
ca   $ca
cert $CERTSDIR/$servername.cert
key  $KEYSDIR/$servername.key
dh   $KEYSDIR/$servername.dh
$server_param
user openvpn
group openvpn
ifconfig-pool-persist ipp.txt
keepalive 10 120
client-config-dir $CONFDIR/ccd
persist-key
persist-tun
client-to-client
script-security 2
status openvpn-status.log
verb 3
SERVER_CONF_TEMPLATE

	test_bool "$in_lzo" && echo 'comp-lzo' >>"$CACHEDIR/$dev/$OVPNCONFIG"

	if [ "$in_type" = 'routed' ];then
		echo "$SERVER_NETWORKS_START" >>"$CACHEDIR/$dev/$OVPNCONFIG"
		[ -s "$SERVER_NETWORKS_TMP" ] && cat "$SERVER_NETWORKS_TMP" >>"$CACHEDIR/$dev/$OVPNCONFIG"
		echo "$SERVER_NETWORKS_END" >>"$CACHEDIR/$dev/$OVPNCONFIG"
		add_all_clients_routes "$dev"
		ns="$(get_ns)"
		[ -n "$ns" ] && push_ns "$ns"
	elif [ "$in_type" = 'bridged' ];then
		if [ -n "$in_bridge" ];then
			write_bridged_updown "$in_bridge" "$dev"
		fi
	fi
	return 0
}

###

stop_server()
{
	local new_dev="$1"; shift
	local dev_type="$(vdev_type "$in_type")"
	local old_dev="$(find_server_vdev 'tun')"

	[ -n "$old_dev" ] || old_dev="$(find_server_vdev 'tap')"

	if [ -n "$old_dev" ];then
		ifdown "$old_dev"
		is_dev_type_changed "$old_dev" "$dev_type" && rm -rf "$ETCNET_IFACEDIR/$old_dev"
	fi
}

stop_start_server()
{
	local dev="$1"; shift
	local servername="$DEFAULT_SERVERNAME"
	local dev_type="$(vdev_type "$in_type")"

	stop_server "$dev"
	commit_cache "$dev"
	make_ssl_files "$servername"

	[ -d $ETCNET_IFACEDIR/$dev ] || return
	if test_bool "$in_enabled";then
		if ! ssl_check_cert "$servername";then
			write_error "`_ "Certificate not found!"`"
		elif ! ssl_check_key "$servername";then
			write_error "`_ "Key not found!"`"
		elif [ ! -f "$DEFAULT_OWN_CA" -a ! -f "$DEFAULT_CA" ];then
			write_error "`_ "CA not found!"`"
		else
			shell_config_set "$ETCNET_IFACEDIR/$dev/options" 'ONBOOT' 'yes'
			if ! ifup "$dev";then
				write_error "`_ "openvpn server start failed"`"
				shell_config_set "$ETCNET_IFACEDIR/$dev/options" 'ONBOOT' 'no'
			fi
		fi
	else
		shell_config_set "$ETCNET_IFACEDIR/$dev/options" 'ONBOOT' 'no'
	fi
}

clear_cache
make_ssl_files "$DEFAULT_SERVERNAME"
init_cached_server_networks

on_message()
{
  local dev_type="$(vdev_type "$in_type")"
  local vdev="$(find_server_vdev "$dev_type")"
  [ -n "$vdev" ] || vdev="$(get_cached_vdev)"

  case "$in_action" in
	  type)
	  write_type_item server_net ipv4-address
	  write_type_item vpnnet ipv4-address
	  write_type_item client_net ipv4-address
	  write_type_item client_ns ipv4-address
	  ;;
	  read)
	  case "$in__objects" in
		  /)
		  read_server_config "$vdev"
		  ;;
		  clients)
		  if [ -n "$in_client_name" ];then
			  read_client_ns_info "$vdev" "$in_client_name"
		  else
			  write_string_param client_name "$(list_clients | head -n1)"
		  fi
		  ;;
		  sslkey-name)
		  write_string_param name "$DEFAULT_SERVERNAME"
		  ;;
		  sslkey-default)
		  write_string_param CN "$(hostname)"
		  write_string_param O "$DEFAULT_SERVERNAME"
		  ;;
		  *)
		  ;;
	  esac
	  ;;
	write)
    case "$in__objects" in
        /)
    	init_cache "$vdev"
        case "$in_operation" in
            config)
            [ -n "$vdev" ] || vdev="$(next_vdev "$in_type")"
            make_server_conf "$vdev"
            ;;
            apply)
            [ -n "$vdev" ] || vdev="$(next_vdev "$in_type")"
            make_server_conf "$vdev"
            stop_start_server "$vdev"
            ;;
            reset)
            clear_cache
            init_cached_server_networks
            ;;
            add-server-network)
            if [ -n "$in_server_net" -a -n "$in_server_netmask" ];then
                local server_net="$(netname "$in_server_net/$(ipv4addr_mask_to_prefix "$in_server_netmask")" | cut -f1 -d '/')"
                if ! check_server_net "$server_net" "$in_server_netmask";then
                    write_error "`_ "Server network conflicts with other server network"`"
                elif ! check_vpn_net "$server_net" "$in_server_netmask" "$dev";then
                    write_error "`_ "Server network conflicts with VPN network"`"
                elif ! check_clients_net "$server_net" "$in_server_netmask";then
                    write_error "`_ "Server network conflicts with client network"`"
                else
                    write_cached_server_network "$server_net" "$in_server_netmask"
                fi
            fi
            ;;
            remove-server-network)
            [ -n "$in_server_net" -a -n "$in_server_netmask" ] &&
            sed -i "/$in_server_net[[:blank:]]\+$in_server_netmask/d" "$SERVER_NETWORKS_TMP"
            ;;
            client-ns-domain)
            vdev="$(get_cached_vdev)"
            if ! check_client_ns "$vdev" "$in_client_name" "$in_client_ns";then
                write_error "`_ "Client nameserver not in client network(s)"`"
            else
                write_client_ns_info "$vdev" "$in_client_name" "$in_client_domain" "$in_client_ns"
            fi
            ;;
            add-client-network)
            vdev="$(get_cached_vdev)"
            local client_net="$(netname "$in_client_net/$(ipv4addr_mask_to_prefix "$in_client_netmask")" | cut -f1 -d '/')"
            if ! check_server_net "$client_net" "$in_client_netmask" "$vdev";then
                write_error "`_ "Server network conflicts with client network"`"
            elif ! check_vpn_net "$client_net" "$in_client_netmask" "$vdev";then
                write_error "`_ "VPN network conflicts with client network"`"
            elif ! check_clients_net "$client_net" "$in_client_netmask";then
                write_error "`_ "Network conflicts with other client network"`"
            else
                add_client_iroute "$in_client_name" "$client_net" "$in_client_netmask"
            fi
            ;;
            remove-client-network)
            remove_client_iroute "$in_client_name" "$in_client_net" "$in_client_netmask"
            vdev="$(get_cached_vdev)"
            check_client_ns "$vdev" "$in_client_name" || write_client_ns_info "$vdev" "$in_client_name"
            ;;
        esac
        ;;
        upload-ca)
        if [ -n "$in_path" ];then
            if [ ! -f "$in_path" ];then
                write_error "`_ "File does not exist"`"
            elif ! is_cert "$in_path"; then
                write_error "`_ "Invalid certificate"`"
            else
                cp -T "$in_path" "$DEFAULT_OWN_CA" || write_error "`_ "Upload CA failed"`"
            fi
        fi
        ;;
    esac
    ;;
	list)
	case "${in__objects##*/}" in
		avail_masks) list_mask
		;;
		avail_types) list_types
		;;
		avail_clients) list_clients | write_enum
		;;
		avail_clients_networks)
		[ -n "$in_client_name" ] && list_client_networks "$in_client_name"  | write_enum
		;;
		avail_server_networks) list_server_networks "$vdev" | write_enum
		;;
		avail_ethdev) list_ethdev | write_enum
		;;
		avail_bridges) list_bridges | write_enum
		;;
	esac
	;;
  esac
}

message_loop

