#!/bin/sh
#
# © 2009 David Woodhouse <dwmw2@infradead.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
################
#
# This is a replacement for the standard vpnc-script for use with vpnc
# and openconnect. It sets up VPN routing which doesn't screw over the
# _normal_ routing of the box.

# It sets up a new network namespace for the VPN to use, and it runs
# pTRTd (http://www.litech.org/ptrtd/) in that namespace. Connections
# to the VPN are actually made as IPv6 connections, and pTRTd handles
# converting them to Legacy IP and forwarding them.

# The full range of Legacy IP addresses on the VPN is available to the
# host through a tiny corner of the IPv6 address space; for this purpose
# we use fec0:0:0:ffff:0:0:xxyy:zzww to represent the address xx.yy.zz.ww
# on the VPN.

# TODO: Either use totd (ftp://ftp.pasta.cs.uit.no/pub/Vermicelli/) or
# preferably extend dnsmasq to handle DNS for us. We want the following:
#  - A queries for non-existent VPN hosts return NXDOMAIN (no munging)
#  - A queries for _extant_ VPN hosts return NOERROR instead of the result.
#  - AAAA queries return the A result, converted to the above IPv6 space.
#  - PTR queries within the IPv6 space converted appropriately.

IP="`command -v ip | grep '^/'`"

connect_parent()
{
    # XXX: How do we work out what it _really_ is?
    export PARENT_NETNS=$$

    # XXX: Make sure it doesn't exist in this namespace already
    export RETURNDEV=x$TUNDEV

    ./netunshare $0 $@ &
    CHILDPID=$!

    # XXX: If we do this too soon (before the unshare), we're just
    # giving it to our _own_ netns. which achieves nothing.
    # So give it away until we _can't_ give it away any more.
    while $IP link set $TUNDEV netns $CHILDPID 2>/dev/null; do
	sleep 0.1
    done

    # Wait for the ptrtd tundev to appear in this namespace
    while ! $IP link show $RETURNDEV >/dev/null 2>&1 ; do
	sleep 1
    done
    $IP link set $RETURNDEV up
    $IP addr add fe80::1 dev $RETURNDEV
    $IP route add fec0:0:0:ffff::/64 dev $RETURNDEV

    # XXX: Local hack -- my company's VPN server returns
    # "foo.company.com" instead of just "company.com".
    CISCO_DEF_DOMAIN=`echo $CISCO_DEF_DOMAIN | cut -f2- -d.`

    # Work out the IPv6 address of the nameservers...
    IPV6NS=
    for NS in $INTERNAL_IP4_DNS; do
	A=`echo $NS | cut -f1 -d.`
	B=`echo $NS | cut -f2 -d.`
	C=`echo $NS | cut -f3 -d.`
	D=`echo $NS | cut -f4 -d.`
	THISNS=`printf fec0:0:0:ffff:0:0:%02x%02x:%02x%02x $A $B $C $D`
	IPV6NS="$THISNS $IPV6NS"
	DNSMASQ_CMDLINE="-S /$CISCO_DEF_DOMAIN/$IPV6NS $DNSMASQ_CMDLINE"
    done
    echo IPv6 DNS: $IPV6NS
    
    # XXX: Add totd-like capability to dnsmasq
    dnsmasq $DNSMASQ_CMDLINE
}

connect()
{
    if [ -z "$PARENT_NETNS" ]; then
	connect_parent
	exit 0
    fi

    # Wait for the tundev to appear in this namespace
    while ! $IP link show $TUNDEV >/dev/null 2>&1 ; do
	sleep 0.1
    done

    # Set up Legacy IP in the new namespace
    $IP link set lo up
    $IP link set $TUNDEV up
    $IP -4 addr add $INTERNAL_IP4_ADDRESS dev $TUNDEV
    $IP -4 route add default dev $TUNDEV
    if [ "$INTERNAL_IP4_MTU" != "" ]; then
	$IP link set $TUNDEV mtu $INTERNAL_IP4_MTU
    fi

#    ifconfig
#    route

    # For debugging, really. Lets you ssh into the netns with
    # ssh fec0:0:0:ffff:0:0:7f00:1
    /usr/sbin/sshd -D &
    SSHD_PID=$!

    # Start ptrtd
    ptrtd -i tun:$RETURNDEV -d >/dev/null 2>&1 &
    PTRTD_PID=$!

    # Wait for the ptrtd to make its device
    while ! $IP link show $TUNDEV >/dev/null 2>&1 ; do
	sleep 0.1
    done

    # Now give the ptrtd device back to the parent
    $IP link set $RETURNDEV down
    $IP link set $RETURNDEV netns $PARENT_NETNS

    #Hm, this doesn't work because the tundev doesn't go away when it should
    #while ip link show $TUNDEV 2> /dev/null ; do
    #  sleep 1
    #done

    # Wait for ptrtd to die (which it will when disconnect() kills its tun)
    wait $PTRTD_PID

    kill -TERM $SSHD_PID

    # Wait a while to avoid tun BUG() if we quit and the netns goes away
    # before vpnc/openconnect closes its tun fd.
    sleep 1
}

disconnect()
{
    RETURNDEV=x$TUNDEV

    # This will kill ptrtd inside the netns, leaving the script to clean up
    $IP link del $RETURNDEV

    # XXX: Undo the dnsmasq stuff.
    killall dnsmasq
    # XXX: properly.
}

case $reason in
    connect)
	connect
	;;

    disconnect)
	disconnect
	;;
esac

