#!/bin/sh
#
# prefix command to run stuff from our programs directory
#
# Copyright (C) 1998-2002  Henry Spencer.
# Copyright (C) 2013-2017  Tuomo Soini <tis@foobar.fi>
# Copyright (C) 2013-2016  Paul Wouters <pwouters@redhat.com>
#
# 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.  See <http://www.fsf.org/copyleft/gpl.txt>.
#
# 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.
#

test "${IPSEC_INIT_SCRIPT_DEBUG}" && set -v -x

# where the private directory and the config files are
IPSEC_CONF="${IPSEC_CONF:-/etc/ipsec.conf}"
IPSEC_EXECDIR="${IPSEC_EXECDIR:-/usr/lib/ipsec}"
IPSEC_SBINDIR="${IPSEC_SBINDIR:-/usr/sbin}"
IPSEC_CONFDDIR="${IPSEC_CONFDDIR:-/etc/ipsec.d}"
IPSEC_RUNDIR="${IPSEC_RUNDIR:-/run/pluto}"
IPSEC_NSSDIR="${IPSEC_NSSDIR:-/var/lib/ipsec/nss}"
IPSEC_NSSDIR_SQL="sql:${IPSEC_NSSDIR}"
IPSEC_NSSPW="${IPSEC_CONFDDIR}/nsspassword"
DBPW=""
CACERTDIR="${IPSEC_CONFDDIR}/cacerts"
CRLDIR="${IPSEC_CONFDDIR}/crls"
CTLSOCKET="${IPSEC_RUNDIR}/pluto.ctl"
ADDSOCKET=""

IPSEC_DIR="${IPSEC_EXECDIR}"
export IPSEC_DIR IPSEC_EXECDIR IPSEC_CONF IPSEC_RUNDIR CTLSOCKET

# standardize PATH, and export it for everything else's benefit
PATH="${IPSEC_SBINDIR}":/sbin:/usr/sbin:/usr/local/bin:/bin:/usr/bin
export PATH

# suppress ElectricFence banner changing our reference testing output
export EF_DISABLE_BANNER=1

# things not to be listed in --help command list
DONTMENTION='^(ipsec|_.*|.*\.old|.*\.orig|.*~)$'

# version numbering (details filled in by build)
# Possibly should call a C program to invoke the version_code() function
# instead, but for performance's sake, we inline it here (and only here).
version="3.23"

# export the version information
IPSEC_VERSION="${version}"
export IPSEC_VERSION

# function for the funky user/kernel version stuff
fixversion() {
    if [ -f /proc/net/ipsec_version ]; then
	stack=" (klips)"
	kv="$(awk '{print $NF}' /proc/net/ipsec_version)"
    else
	if [ -f /proc/net/pfkey ]; then
	    stack=" (netkey)"
	    kv="${version}"
	else
	    if [ -f /mach_kernel ]; then
		stack=" (OSX)"
		kv="$(uname -r)"
	    else
		kv="(no kernel code presently loaded)"
	    fi
	fi
    fi
    if [ "${kv}" != "${version}" ]; then
	version="U${version}/K${kv}"
    fi
    version="${version}${stack} on $(uname -r)"
}

set_db_trusts() {
    # has to handle a NSS nick with spaces
    certutil -L -d "${IPSEC_NSSDIR_SQL}" | \
	grep -E -v '(Trust Attributes|SSL,S/MIME,JAR/XPI|^)$' | \
	awk '{$NF=""; print $0}' | \
	while read -r cert
    do
	if certutil -L -d "${IPSEC_NSSDIR_SQL}" -n "${cert}" | \
	    grep -q 'Is a CA' && \
	    [ $(certutil -L -d "${IPSEC_NSSDIR_SQL}" -n "${cert}" | grep -i -A3 'ssl flags' | grep -i 'trusted' | wc -l) -ne 2 ]
	then
	    echo "correcting trust bits for ${cert}"
	    certutil -M -d "${IPSEC_NSSDIR_SQL}" -n "${cert}" -t 'CT,,'
	fi
    done
}

case "${1}" in
    rundir|--rundir)
	RUNDIR="${2}"
	CTLSOCKET="${RUNDIR}/pluto.ctl"
	shift;
	shift;
	;;
esac
case "${1}" in
    ctlsocket|--ctlsocket)
	CTLSOCKET="${2}"
	shift;
	shift;
	;;
esac

case "${1}" in
    '')
	echo "Usage: ipsec <command> <argument ...>"
	echo "Use --help for a list of commands, or see the ipsec(8) manual page"
	echo "Most commands have their own manual pages, e.g. ipsec_auto(8)."
	echo "See <http://www.libreswan.org> for more general info."
	fixversion
	echo "Linux Libreswan ${version}"
	exit 0
	;;
    status|--status)
	exec ${IPSEC_EXECDIR}/whack --ctlsocket ${CTLSOCKET} --status
	;;
    start|--start)
	exec ipsec setup start
	;;
    stop|--stop)
	exec ipsec setup stop
	;;
    restart|--restart)
	# restart does not work when stopped in systemd - it's dumb
	ipsec setup stop
	exec ipsec setup start
	;;
    help|--help)
	echo "Usage: ipsec <command> <argument ...>"
	echo "where <command> is one of:"
	echo ""
	GOTTWO=""
	for f in start stop restart status import initnss checknss checknflog \
	    $(ls "${IPSEC_EXECDIR}" | egrep -v -i "${DONTMENTION}"); do
	    if [ -z "${GOTTWO}" ]; then
		# first of two entries
		GOTTWO="${f}"
	    else
		# second of two entries, we can print
		printf "\t%s" "${GOTTWO}"
		if [ "${#GOTTWO}" -ge 16 ]; then
		    printf  "\t"
		elif [ "${#GOTTWO}" -ge 8 ]; then
		    printf "\t\t"
		else
		    printf "\t\t\t"
		fi
		printf "%s\n" "${f}"
		GOTTWO=""
	    fi
	done
	if [ -n "${GOTTWO}" ]; then
	    # leftover entry
	    printf "\t%s" "${GOTTWO}"
	fi
	echo
	echo "See also: man ipsec <command> or ipsec <command> --help"
	echo "See <https://libreswan.org/> for more general info."
	fixversion
	echo "Linux Libreswan ${version}"
	exit 0
	;;
    # some ubuntu/debian scripts use --versioncode, so let's keep the alias
    version|--version|--versioncode)
	fixversion
	echo "Linux Libreswan ${version}"
	exit 0
	;;
    --directory)
	printf "%s\n" "${IPSEC_DIR}"
	exit 0
	;;

    --stopnflog|stopnflog)
	NFGROUP=$(ASAN_OPTIONS=detect_leaks=0 ipsec addconn --ctlsocket ${CTLSOCKET} --configsetup | grep -v "#" | grep nflog | sed -e "s/^.*=//" -e "s/'//g");
	if [ -z "${NFGROUP}" ]; then
	    exit 0
	fi
	iptables -D INPUT  -m policy --dir in  --pol ipsec -j NFLOG --nflog-group ${NFGROUP} --nflog-prefix all-ipsec
	iptables -D OUTPUT -m policy --dir out --pol ipsec -j NFLOG --nflog-group ${NFGROUP} --nflog-prefix all-ipsec
	exit 0
	;;

    --checknflog|checknflog|nflog)
	NFGROUP=$(ASAN_OPTIONS=detect_leaks=0 ipsec addconn --ctlsocket ${CTLSOCKET} --configsetup | grep -v "#" | grep nflog| sed -e "s/^.*=//" -e "s/'//g");
	if [ -z "${NFGROUP}" ]; then
	    OLDNFGROUP=$(iptables -L -n |grep "all-ipsec nflog-group" | sed "s/^.* //" | tail -1);
	    if [ -n "${OLDNFGROUP}" ]; then
		echo "deleting rules with old nflog group ${OLDNFGROUP}"
		iptables -D INPUT  -m policy --dir in  --pol ipsec -j NFLOG --nflog-group ${OLDNFGROUP} --nflog-prefix all-ipsec
		iptables -D OUTPUT -m policy --dir out --pol ipsec -j NFLOG --nflog-group ${OLDNFGROUP} --nflog-prefix all-ipsec
	    fi
	    echo "nflog ipsec capture disabled"
	    exit 0
	else
	    OLDNFGROUP=$(iptables -L -n |grep "all-ipsec nflog-group" | sed "s/^.* //" | tail -1);
	    if [ -n "${OLDNFGROUP}" ]; then
		if [ "${NFGROUP}" = "${OLDNFGROUP}" ]; then
		    # nothing to do
		    echo "nflog ipsec capture enabled on nflog:${NFGROUP}"
		    exit 0
		else
		    # delete rules with old group number
		    echo "deleting rules with old nflog group ${OLDNFGROUP}"
		    iptables -D INPUT  -m policy --dir in  --pol ipsec -j NFLOG --nflog-group ${OLDNFGROUP} --nflog-prefix all-ipsec
		    iptables -D OUTPUT -m policy --dir out --pol ipsec -j NFLOG --nflog-group ${OLDNFGROUP} --nflog-prefix all-ipsec
		fi
	    fi
	    # insert rules with current group number
	    iptables -I INPUT  -m policy --dir in  --pol ipsec -j NFLOG --nflog-group ${NFGROUP} --nflog-prefix all-ipsec
	    iptables -I OUTPUT -m policy --dir out --pol ipsec -j NFLOG --nflog-group ${NFGROUP} --nflog-prefix all-ipsec
	    echo "nflog ipsec capture enabled on nflog:${NFGROUP}"
	fi
	exit 0
	;;

    sniff|--sniff)
	if [ -z "${2}" ]; then
	    NFGROUP=$(ASAN_OPTIONS=detect_leaks=0 ipsec addconn --ctlsocket ${CTLSOCKET} --configsetup | grep -v "#" | grep nflog | sed -e "s/^.*=//" -e "s/'//g");
	    tcpdump -n -i nflog:${NFGROUP}
	else
	    echo "conn specific coming soon"
	fi
	exit 0
	;;

    import|--import)
	if [ -n "${2}" ]; then
	    # A lot of nss commands use -d to specify NSS db location.
	    # We use --nssdir.
	    if [ "${2}" = "-d" -o \
		"${2}" = "--configdir" -o \
		"${2}" = "--nssdir" ]
	    then
		if [ -d "${3}" ]; then
		    IPSEC_NSSDIR="${3}"
		    if [ "${2}" = "--configdir" ]; then
			echo "ipsec import warning: --configdir is obsoleted, use --nssdir" >&2
		    fi
		else
		    echo "usage: ipsec import [--nssdir ${IPSEC_NSSDIR}] /path/to/pkcs.12" >&2
		    exit 1
		fi
		shift
		shift
	    fi
	    if [ -f "${IPSEC_NSSDIR}/key4.db" -a \
		-f "${IPSEC_NSSDIR}/cert9.db" ]
	    then
		IPSEC_NSSDIR_SQL="sql:${IPSEC_NSSDIR}"
	    else
		echo "ERROR: NSS database files are missing, import aborted." >&2
		echo "Initialize database with command \"ipsec initnss\"." >&2
		exit 1
	    fi
	fi
	if [ -z "${2}" -o ! -f "${2}" ]; then
	    echo "usage: ipsec import [--nssdir ${IPSEC_NSSDIR}] /path/to/pkcs.12" >&2
	    exit 1
	fi
	pk12util -i "${2}" -d "${IPSEC_NSSDIR_SQL}"
	# check and correct trust bits
	set_db_trusts
	exit 0
	;;
    initnss|--initnss|checknss|--checknss)
	if [ -n "${2}" ]; then
	    # A lot of nss commands use -d to specify NSS db location.
	    # We use --nssdir.
	    if [ "${2}" = "-d" -o \
		"${2}" = "--configdir" -o \
		"${2}" = "--nssdir" ]
	    then
		IPSEC_NSSDIR="${3}"
		IPSEC_NSSDIR_SQL="sql:${IPSEC_NSSDIR}"
		if [ "${2}" = "--configdir" ]; then
		    echo "ipsec initnss warning: --configdir is obsoleted, use --nssdir" >&2
		fi
	    else
		echo "usage: ipsec initnss [--nssdir ${IPSEC_NSSDIR}]" >&2
		exit 1
	    fi
	fi
	if [ ! -d "${IPSEC_NSSDIR}" ]; then
	    echo "ERROR: destination directory \"${IPSEC_NSSDIR}\" is missing" >&2
	    exit 1
	fi
	# if we have old database
	if [ -f "${IPSEC_NSSDIR}/cert8.db" -o \
	    -f "${IPSEC_NSSDIR}/key3.db" -o \
	    -f "${IPSEC_NSSDIR}/secmod.db" ]; then
	    if [ ! -f "${IPSEC_NSSDIR}/cert9.db" -o \
		! -f "${IPSEC_NSSDIR}/key4.db" ]; then
		IMPORTDBPW=""
		NSSTMP=$(mktemp -d /tmp/ipsec_nss_tmp.XXXXXXXXXX)
		if [ $? -gt 0 ]; then
		    echo "Failed to create temporary directory for NSS db migration" >&2
		    exit 4
		fi
		# save current umask
		umask=$(umask)
		# set safe umask
		umask 077
		echo "Migrating NSS db to ${IPSEC_NSSDIR_SQL}"

		# this section works around a few certutil quirks
		# to maintain the current password and merge keys
		certutil -N -d sql:"${NSSTMP}" --empty-password
		if [ $? -gt 0 ]; then
		    echo "Failed to initialize nss database sql:${NSSTMP}" >&2
		    exit 4
		fi
		if [ -f "${IPSEC_NSSPW}" ]; then
		    # Look for FIPS format of token:pw, or just the pw
		    grep -q ':' "${IPSEC_NSSPW}"
		    if [ $? -eq 0 ]; then
			cut -d':' -f2 "${IPSEC_NSSPW}" \
			    > "${NSSTMP}/nsspassword.txt"
			cut -d':' -f2 "${IPSEC_NSSPW}" \
			    >> "${NSSTMP}/nsspassword.txt"
		    else
			cat "${IPSEC_NSSPW}" > "${NSSTMP}/nsspassword.txt"
			cat "${IPSEC_NSSPW}" >> "${NSSTMP}/nsspassword.txt"
		    fi
		    # For the empty password prompt:
		    printf "\n\n" > "${NSSTMP}/nsspassword2.txt"
		    # Change blank pw to the current, and use
		    # for certutil --upgrade-merge
		    certutil -W -d sql:"${NSSTMP}" \
			-f "${NSSTMP}/nsspassword2.txt" \
			-@ "${NSSTMP}/nsspassword.txt"
		    DBPW="-f ${NSSTMP}/nsspassword.txt -@ ${NSSTMP}/nsspassword.txt"
		    IMPORTDBPW="-f ${NSSTMP}/nsspassword.txt"
		fi
		# restore umask
		umask ${umask}

		certutil --upgrade-merge --source-dir "${IPSEC_NSSDIR}" \
		    -d sql:"${NSSTMP}" --upgrade-id pluto ${DBPW}
		rc=$?
		if [ ${rc} -ne 0 ]; then
		    echo "NSS upgrade failed. You should run certutil --upgrade-merge manually against ${IPSEC_NSSDIR_SQL}"
		    exit ${rc}
		fi
		# import cacerts and crls
		if [ -d "${CACERTDIR}" ]; then
		    for file in "${CACERTDIR}"/*; do
			if [ -f "${file}" ]; then
			    filename=$(basename "${file}")
			    name=${filename%%.*}
			    certutil -A -i "${file}" -d sql:"${NSSTMP}" -n "${name}" -t 'CT,,' ${IMPORTDBPW}
			    [ $? -eq 0 ] || printf "%s\n" "${file}"
			fi
		    done
		fi
		if [ -d "${CRLDIR}" ]; then
		    for file in "${CRLDIR}"/*; do
			if [ -f "${file}" ]; then
			    crlutil -I -i "${file}" -d sql:"${NSSTMP}" -B ${IMPORTDBPW}
			    [ $? -eq 0 ] || printf "%s\n" "${file}"
			fi
		    done
		fi
		cp "${NSSTMP}"/*.db "${NSSTMP}"/*.txt "${IPSEC_NSSDIR}"
		rm -f "${NSSTMP}"/*.txt "${NSSTMP}"/*.db
		rmdir "${NSSTMP}"
		echo "NSS upgrade complete"
	    fi
	    exit 0
	fi	# no old database

	if [ -f "${IPSEC_NSSDIR}/cert9.db" -o -f "${IPSEC_NSSDIR}/key4.db" ]; then
	    if [ "${1}" = "checknss" -o "${1}" = "--checknss" ]; then
		exit 0
	    fi
	    echo "NSS database already initialised - aborted"
	    echo "To wipe the old NSS database, issue: rm ${IPSEC_NSSDIR}/*.db"
	    exit 42
	fi

	echo "Initializing NSS database"
	echo ""

	certutil -N -d "${IPSEC_NSSDIR_SQL}" --empty-password
	if [ $? -gt 0 ]; then
	    echo "Failed to initialize nss database ${IPSEC_NSSDIR_SQL}" >&2
	    exit 4
	fi
	if [ -x "$(which restorecon)" ]; then
	    restorecon -Rv "${IPSEC_NSSDIR}"
	fi
	exit 0
	;;
    secrets)
	"${IPSEC_EXECDIR}/whack" --ctlsocket ${CTLSOCKET} --rereadsecrets
	exit $?
	;;
    rereadcrls|--rereadcrls)
	echo "ipsec auto: --rereadcrls and /etc/ipsec.d/crls/ are obsoleted. did you mean: ipsec crls"
	exit 1
	;;
    crls|fetchcrls)
	"${IPSEC_EXECDIR}/whack" --ctlsocket ${CTLSOCKET} --fetchcrls
	exit $?
	;;
    unbound|--unbound)
	# activate the unbound ipsec module
	unbound-control set_option ipsecmod-enabled: yes
	exit $?
	;;
    --*)
	printf "%s: unknown option \"%s\" (perhaps command name was omitted?)\n" "${0}" "${1}" >&2
	exit 1
	;;
esac

cmd="${1}"
if [ "${cmd}" = "whack" ]; then
     ADDSOCKET="--ctlsocket ${CTLSOCKET}"
fi
shift

path="${IPSEC_EXECDIR}/${cmd}"

if [ ! -x "${path}" ]; then
    path="${IPSEC_EXECDIR}/${cmd}"
    if [ ! -x "${path}" ]; then
	printf "%s: unknown IPsec command \"%s\" (\"ipsec --help\" for list)\n" "${0}" "${cmd}" >&2
	exit 1
    fi
fi

exec "${path}" ${ADDSOCKET} "$@"
