#!/bin/sh

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

. alterator-sh-functions
. shell-config
. shell-ip-address
. 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}"
IFACEDIR="/etc/net/ifaces"
OVPNCONFIG="ovpnoptions"
DEFAULT_OWN_CA="$CERTSDIR/openvpn-client-CA.crt"
DEFAULT_CA="$SSL_CERTDIR/ca-root.pem"
OPENSSL="${OPENSSL:-openssl}"

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

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

### lists
_list_keys()
{
	local keysdir="$1"
	local certsdir="$2"
	[ -n "$keysdir" -a -n "$certsdir" ] || return
	for i in `ls $keysdir | grep \.key$`; do
		local name="$(echo "$i" | sed 's/\.key$//')"
		[ "$name" != 'ca' ] &&
		[ -r "$certsdir/$name.cert" ] &&
		echo "$name" 2>/dev/null
	done
}

list_keys()
{
	_list_keys "$KEYSDIR" "$CERTSDIR"
	_list_keys "$CONFDIR/keys" "$CONFDIR/keys"
}

list_reqs()
{
	find "$CSRDIR" -type f \( -name '*.csr' -and -not -name 'openvpn-client.csr' \) | sort | sed 's;^.*/\(.\+\)\..*$;\1;'
}

list_states()
{
	write_enum_item "ignore" "`_ "don't change"`"
	write_enum_item "start" "`_ "start"`"
	write_enum_item "stop" "`_ "stop"`"
}

list_vdev()
{
	for i in `find $IFACEDIR -type d \( -name 'tap*' -or -name 'tun*' \) | sort`
	do
		[ -f $i/options ] &&
		grep -qs '^TYPE=ovpn' $i/options &&
		grep -qs '^client$' $i/$OVPNCONFIG &&
		echo "${i##*/}" 2>/dev/null
	done
}

### network configuration helpers
get_ip()
{
	if valid_ipv4 "$1";then
		echo "$1"
	else
		local ip="$(dig +short "$1" 2>/dev/null)"
		valid_ipv4 "$ip" && echo "$ip"
	fi
}

### read 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_client_config()
{
	local dev="$1";shift
	local dev_conf=
	local remote=
	local tmp=
	local ovpn_pidfile="/var/run/openvpn-iface-$dev.pid"

	if [ -n "$NEW_DEV" ];then
		dev="$NEW_DEV"
		unset NEW_DEV
	fi
	[ -n "$dev" -a -d "$IFACEDIR/$dev" ] || dev="$(list_vdev | head -n1)"

	if [ -n "$dev" -a -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_string_param connections "$dev"
		if [ -s "$ovpn_pidfile" ] && kill -0 "$(cat "$ovpn_pidfile")" 2>/dev/null;then
			write_string_param info "`_ "enabled"`"
		else
			write_string_param info "`_ "disabled"`"
		fi
		remote="$(get_config_val "$dev" remote)"
		write_string_param server "$(echo "$remote" | cut -s -f1 -d' ')"
		tmp="$(echo "$remote" | cut -f2 -d' ')"
		write_string_param port "${tmp:-1194}"
		write_string_param keys "$(get_config_val "$dev" key | sed -r -n 's/^.*\/(.*)\.key$/\1/p')"
		write_bool_param onboot "$(shell_config_get "$IFACEDIR/$dev/options" 'ONBOOT')"
		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 grep -qs '^redirect-gateway' "$IFACEDIR/$dev/$OVPNCONFIG";then
			write_bool_param def_via_vpn true
		else
			write_bool_param def_via_vpn false
		fi
	fi
}

### write config
make_up_down_scripts()
{
	local dev="$1";shift
	local ifup_post="$IFACEDIR/$dev/ifup-post"
	local ifdown_post="$IFACEDIR/$dev/ifdown-post"
	local server_ip=
	[ -n "$dev" ] || return

	if [ ! -f "$ifup_post" ];then
		cat > "$ifup_post" <<IFUP_POST
#!/bin/sh
dev="\$1"

[ -n "\$dev" ] &&
set | sed -n "s/^foreign_option_[[:digit:]]\+='.*DNS[[:space:]]\+\([[:alnum:].]\+\)'/nameserver \1/; T next; p;
:next; s/^foreign_option_[[:digit:]]\+='.*DOMAIN[[:space:]]\+\([[:alnum:].[:space:]]\+\)'/search \1/; T; p;" |
/sbin/resolvconf -a "\$dev"
IFUP_POST
		chmod +x "$ifup_post"
	fi

	[ -n "$in_def_via_vpn" ] && test_bool "$in_def_via_vpn" && server_ip="$(get_ip "$in_server")"

	if [ ! -f "$ifdown_post" ];then
		cat > "$ifdown_post" <<IFDOWN_POST
#!/bin/sh
dev="\$1"

/sbin/resolvconf -fd "\$dev"
IFDOWN_POST

		chmod +x "$ifdown_post"
	fi

	if [ -n "$server_ip" ];then
		sed -i '/ip route del/d' "$ifdown_post"
		echo "ip route del $server_ip/32" >>"$ifdown_post"
	fi
}

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

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

make_client_conf()
{
	local dev="$1";shift
	local ca="$DEFAULT_CA"
	local cert=
	local key=
	local proto='udp'
	if [ -z "$dev" ];then
		dev="$(next_iface "$in_dev_type")"
		NEW_DEV="$dev"
	fi
	[ -d "$IFACEDIR/$dev" ] || { mkdir "$IFACEDIR/$dev" || return 1; }
	[ ! -f "$IFACEDIR/$dev/options" ] && { make_iface_options "$IFACEDIR/$dev" || return 1; }

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

	if [ -n "$in_keys" ];then
		cert="$CERTSDIR/$in_keys.cert"
		key="$KEYSDIR/$in_keys.key"

		if [ ! -f "$key" -o ! -f "$cert" ];then
			key="$CONFDIR/keys/$in_keys.key"
			cert="$CONFDIR/keys/$in_keys.cert"
		fi
	fi

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

        cat > "$IFACEDIR/$dev/$OVPNCONFIG" <<CLIENT_CONF_TEMPLATE
client
dev $dev
proto $proto
remote $in_server $in_port
resolv-retry infinite
nobind
user openvpn
group openvpn
persist-key
persist-tun
ca   $ca
cert $cert
key  $key
script-security 2
verb 3
CLIENT_CONF_TEMPLATE

	if [ -n "$NEW_DEV" ] || test_bool "$in_lzo";then
		echo 'comp-lzo' >>"$IFACEDIR/$dev/$OVPNCONFIG"
	fi

	if [ -n "$in_def_via_vpn" ] && test_bool "$in_def_via_vpn";then
		echo 'redirect-gateway def1' >>"$IFACEDIR/$dev/$OVPNCONFIG"
	fi

	make_up_down_scripts "$dev"
}

check_cert()
{
    local dev="$1";shift
    local ca="$(get_config_val "$dev" ca)"
    local cert="$(get_config_val "$dev" cert)"

    if [ ! -r "$ca" ]; then
        write_error "`_ "Unable to read"` $ca"
        return 1
    fi
    if [ ! -r "$cert" ]; then
        write_error "`_ "Unable to read"` $cert"
        return 1
    fi

    local issuer="$($OPENSSL x509 -issuer -noout -in "$cert" | sed 's;^issuer= ;;')"
    local subject="$($OPENSSL x509 -subject -noout -in "$cert" | sed 's;^subject= ;;')"
    if [ "$issuer" = "$subject" ]; then
        write_error "`_ "Self signed certificate"`"
        return 1
    fi

    if ! $OPENSSL verify -CAfile "$ca" "$cert" 2>/dev/null | grep -qs "^$cert: OK"; then
        write_error "`_ "Wrong client certificate or CA certificate"`"
        return 1
    fi

    return 0
}

stop_openvpn()
{
	local dev="$1";shift
	local ovpn_pidfile="/var/run/openvpn-iface-$dev.pid"

	[ -n "$dev" ] || return
	if ip link show "$dev" >/dev/null 2>&1;then
		ifdown "$dev"
	else
		[ -s "$ovpn_pidfile" ] && kill "$(cat "$ovpn_pidfile")"
	fi
}

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

	stop_openvpn "$dev"

	make_client_conf "$dev"
    check_cert "$dev" || return

	ifup "$dev" || write_error "`_ "openvpn start failed"`"
}

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

	if [ -n "$dev" ];then
		stop_openvpn "$dev"
		rm -rf "$IFACEDIR/$dev"
	fi
}

on_message()
{
	case "$in_action" in
		type)
		write_type_item server hostname
		;;
		read)
		case "$in__objects" in
			/)
			read_client_config "$in_dev"
			;;
			*)
			;;
		esac
		;;
		write)
		case "$in__objects" in
			/)
			if [ -n "$in_new" -a -n "$in_dev_type" ];then
				make_client_conf
			elif [ -n "$in_delete" -a -n "$in_dev" ];then
				delete_connection "$in_dev"
			elif [ -n "$in_commit" -a -n "$in_dev" ];then
				if test_bool "$in_onboot";then
					shell_config_set "$IFACEDIR/$in_dev/options" 'ONBOOT' 'yes'
				else
					shell_config_set "$IFACEDIR/$in_dev/options" 'ONBOOT' 'no'
				fi
				case "$in_status" in
					start) start_openvpn "$in_dev"
					;;
					stop) stop_openvpn "$in_dev"
					;;
					ignore) make_client_conf "$in_dev"
					;;
				esac
			fi
			;;
			import-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 "`_ "Import CA failed"`"
				fi
			fi
			;;
		esac
		;;
		list)
		case "${in__objects##*/}" in
			avail_connections) list_vdev | write_enum
			;;
			avail_keys) list_keys | write_enum
			;;
			avail_reqs) list_reqs | write_enum
			;;
			avail_state) list_states
			;;
		esac
		;;
	esac
}

message_loop

