#!/bin/sh

# (c) 2011 Rafał Frühling <rafamiga@gmail.com>

# last revision: $Date: 2011-08-30 12:07:53 +0200 (wto) $

# This script will check bring up, bring down and check existence of an
# stunnel (SSL tunnel).  It's meant to be executed at startup/shutdown by an
# init.d script passing along it's argument ("start", "stop", "reload" etc).

# It gets IP address to bind to and PEM file name from it's name, for ex. 
# running this script as "stunnel-192.168.55.121-technogence" will bind
# stunnel to 192.168.55.121:443 and start encrypting data from
# 192.168.55.121:80 using the key and certificates found in file
# /etc/pki/technogence.pem

# NOTE: in this version, perl is used to determine if stunnel can bind to
# specific address:port; netstat was ridiculously slow on a busy server

# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0

. /etc/init.d/functions

ipsrc=$0

https_port=443
http_port=80
asuser=haproxy
asgroup=haproxy
max_wait_bind=5 # seconds to wait for IP to become unbound in case of restart

netstat_exe="/bin/netstat"
grep_exe="/bin/grep"
stunnel_exe="/usr/sbin/stunnel"
perl_exe="/usr/bin/perl"

scriptname=${ipsrc%%-*}
ipcert=${ipsrc#*-}
ipalias=${ipcert%%-*}
cert=${ipcert#*-}
pemfile=/etc/pki/${cert}.pem
piddir=/var/chroot/stunnel
pidfile=${piddir}/stunnel-${ipalias}-${cert}.pid
confdir=/etc/haproxy/conf.d

if [ ! -r ${pemfile} ]; then
    echo "Cannot open '${pemfile}' for reading..."; failure; echo
    exit 3
fi

if [ ! -d "${piddir}" ]; then
    echo "Directory '${piddir}' does not exist (owned by ${asuser}:${asgroup})..."; failure; echo
    exit 3
fi

check_bind() {
    # on a busy server perl is faster than netstat; this one just checks if
    # a process can bind to specific ip:port
    ${perl_exe} << PERL_TEST_BIND
        use Socket;
        \$port = $2;
        \$ip='$1';
        socket(S,PF_INET,SOCK_STREAM,getprotobyname('tcp')) or exit 1;
        setsockopt(S,SOL_SOCKET,SO_REUSEADDR,1) or exit 3;
        bind(S,sockaddr_in(\$port,inet_aton(\$ip))) or exit 2;
        exit 0
PERL_TEST_BIND
    return $?
    }

start() {
    echo -n "Starting '${cert}' stunnel on ${ipalias}:${https_port}: "

# the OLD way
#    if [ `${netstat_exe} -ltn | grep -q ${ipalias}:${https_port} ; echo $?` -eq 0 ]; then
#    # TODO: check if it's stunnel using the proper cert
#        echo "bound already"
#        return 1
#    fi

    if ! check_bind ${ipalias} ${https_port}; then
        echo -n "bound already"; failure; echo
	return 1
    fi
        
    stu_conf="${confdir}/config-stunnel-${ipalias}-${cert}.conf"

    ulimit -n 10240

    if [ -r "${stu_conf}" ]; then
    	stunnel "${stu_conf}"
    else
( cat << STUNNEL_CONF
; config file was generated by ${ipsrc} script on `date "+%Y-%m-%d %H:%M:%S"`
cert = ${pemfile}
key = ${pemfile}
debug = local3.5
socket = r:TCP_NODELAY=1
socket = l:TCP_NODELAY=1
pid = ${pidfile}
setuid = ${asuser}
setgid = ${asgroup}
[${cert}]
accept = ${ipalias}:${https_port}
connect = ${ipalias}:${http_port}
STUNNEL_CONF
) | stunnel -fd 0
    fi

    if [ $? -eq 0 ]; then
        success; echo # "OK"
        exit 0
    fi

    echo -n "failed, code=$?"; success; echo
    exit 1
    }

reload() {
    # shoul work kill -HUP but it doesn't, the processes just die; we need to restart
    restart
    }

restart() {
    stop
    breakfree=${max_wait_bind}
    
    while ! check_bind ${ipalias} ${https_port}; do
	if [ ${breakfree} -eq ${max_wait_bind} ]; then
		echo -n "bound? "
	else
		echo -n ". "
	fi

    	sleep 1

	breakfree=$((--breakfree))

	if [ ${breakfree} -eq 0 ]; then
	    echo -n "Stunnel on ${ipalias}:${https_port}: can't bind"
	    failure; echo
	    return 1
	fi
    done

    start
    }

stop() {
    echo -n "Terminating '${cert}' stunnel on ${ipalias}:${https_port}: "

    if [ ! -r ${pidfile} ]; then
        echo -n "no PID known ('${pidfile}' is missing)"; failure; echo
        return 1
    fi

    kill `cat ${pidfile}`

    success; echo #"OK"
    }

status()
    {
    if check_bind ${ipalias} ${https_port}; then
        return 0
    fi
    
    # not found
    return 1
    }

if [ $UID != 0 ]; then
    echo "This script can be run only by the root user."
    exit 4
fi

if [ -z "${ipalias}" ] || [ -z "${cert}" ]; then
   echo "IP/cert missing. This script's name should contain an IP address and pem file name, for ex.: stunnel-192.168.19.74-site"
   exit 2
fi

case "$1" in
    start|stop|status|reload|restart)
        $1
        ;;
    *)
        echo "Usage: $0 {start|stop|reload|restart|status}"
        exit 2
esac
