#!/bin/bash

# Copyright (C) 2020 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
#
# 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 script expects a host.domain name as first cmdline arguemnt.
#
# Usage: <this-script> <server-name>.<server-domain>
#
# Then, this script searches LDAP for the given host.domain name. It expects
# this host.domain name to have a DNS A record in LDAP (i.e. it is expected to
# to be the IP's FQDN.
#
# If a host is given matching the above criterion, this script extracts
# all CNAME records pointing at this host.domain FQDN from LDAP.
#
# The FQDN and all CNAME aliases are then put into a temporary openssl.conf
# file and the script tries to create an SSL server certificate for the
# given host.
#
# The created files will be stored in /etc/ssl/certs/<host>_<domain>.crt and
# /etc/ssl/private/<host>_domain.key.
#
# Limitations / ToDos:
#   - the script expects A records and CNAME records to share the same
#     domain
#

set -e

SSLCERT_GROUP="ssl-cert"

server_fqdn="$1"
server_name="$(echo "$server_fqdn" | cut -d"." -f1)"
server_domain="$(echo "$server_fqdn" | cut -d"." -f2-)"

# Some basic input validation...
if [ -z "$server_fqdn" ] || [ -z "$server_name" ] || [ -z "$server_domain" ]; then
	echo "USAGE: $(basename $0) <server-name>.<server-domain>"
	exit -1
fi

declare -a SANs

# Does the host exist and is the given host.domain its FQDN (i.e. is it defined as a A record in LDAP's DNS data
num_arecords=0
while read KEY VALUE ; do
	case "$KEY" in
		dn:) let "num_arecords+=1" ;;
		"")
			if test $num_arecords -gt 1; then
				echo "ERROR: more than two hosts of this name found in LDAP."
				echo "Something is entirely wrong with your setup!!!"
				exit 10
			fi
			SANs[$(($num_arecords-1))]="DNS.$num_arecords=$server_name.$server_domain"
		;;
	esac
done < <(ldapsearch -xLLL "(&(objectClass=dNSZone)(relativeDomainName=$server_name)(aRecord=*)(zoneName=$server_domain)(dNSClass=IN))" dn 2>/dev/null | perl -p0e 's/\n //g')

if test $num_arecords -lt 1; then
	echo "ERROR: host '$server_name.$server_domain' not found in LDAP."
	exit 20
fi

# Collect all CNAME records from LDAP associated to this host
num_cnames=$num_arecords
while read KEY VALUE ; do
	case "$KEY" in
		dn:) let "num_cnames+=1" ;;
		relativeDomainName:) SANs[$(($num_cnames-1))]="DNS.$num_cnames=$VALUE.$server_domain" ;;
		"")
			:
		;;

	esac
done < <(ldapsearch -xLLL "(&(objectClass=dNSZone)(relativeDomainName=*)(cNAMERecord=$server_name)(zoneName=$server_domain)(dNSClass=IN))" dn relativeDomainName 2>/dev/null | perl -p0e 's/\n //g')


if [ "$(id -u)" = "0" ]; then
	:
else
	# check, if the system has a group for accessing SSL private keys 
	sslcert_gidnum=$(getent group "$SSLCERT_GROUP" | cut -d":" -f3)
	if [ -z "$sslcert_gidnum" ]; then
		echo "ERROR: No POSIX group '$SSLCERT_GROUP' found on this system."
		exit 1
	fi

	# check if user is in SSLCERT_GROUP
	if echo " $(id -G "$USER") " | grep -q $sslcert_gidnum; then
		:
	else
		echo "ERROR: User '$USER' is not a member of POSIX group '$SSLCERT_GROUP'."
		exit 2
	fi

fi

# check, if we have the Debian-Edu_rootCA's private key...
# this implicitly checks that /etc/ssl/private/ exists as a directory
if [ ! -e "/etc/ssl/private/Debian-Edu_rootCA.key" ]; then
	echo "ERROR: Debian-Edu_rootCA's private key not found in /etc/ssl/private/."
	exit 3
fi


# check that /etc/ssl/certs/ exists and is a directory
if [ ! -d "/etc/ssl/certs" ]; then
	echo "ERROR: /etc/ssl/certs does not exist or is not a directory."
	exit 4
fi

certname="$(echo ${server_fqdn} | sed 's/\./_/g')"

certfile="${certname}.crt"
certdir=/etc/ssl/certs/
keyfile="${certname}.key"
keydir=/etc/ssl/private/
csrfile="${certname}.csr"

tempdir=$(mktemp -d)

cat > "${tempdir}/v3.conf"  <<EOF
# v3.ext
authorityKeyIdentifier=keyid,issuer
subjectAltName = @alt_names

[alt_names]
$(for item in ${SANs[*]}; do echo $item; done)
EOF

# same as in /usr/share/debian-edu-config/tools/create-debian-edu-certs
SSL_CA_CONF="/usr/share/debian-edu-config/sslCA.cnf"

# tweak the common name to match our FQDN
cp "${SSL_CA_CONF}" "${tempdir}/ssl.cnf"
sed -r -i "${tempdir}/ssl.cnf" -e "s/(commonName\s+=\s+)(.*)/\1${server_name}.${server_domain}/"

if [ -f "${keydir}/${keyfile}" ]; then
	echo "Re-using key file: ${keydir}/${keyfile}"
	echo
	openssl  req  -config "${tempdir}/ssl.cnf" \
	              -nodes  -new \
	              -key "${keydir}/${keyfile}" \
	              -out "${tempdir}/${csrfile}"
else
	echo "Creating new key file: ${keydir}/${keyfile}"
	echo
	openssl  req  -config "${tempdir}/ssl.cnf" \
	              -nodes  -new -newkey rsa:2048 \
	              -keyout "${keydir}/${keyfile}" \
	              -out "${tempdir}/${csrfile}"
fi

openssl  x509 -req \
            -in "${tempdir}/${csrfile}" \
            -CA "/etc/ssl/certs//Debian-Edu_rootCA.pem" \
            -CAkey "/etc/ssl/private/Debian-Edu_rootCA.key" \
            -CAcreateserial \
            -out "${certdir}/${certfile}" \
            -days 3560 \
            -extfile "${tempdir}/v3.conf"

echo
echo "New server certificate: ${certdir}/${certfile}"
echo "Server key file: ${keydir}/${keyfile}"
echo
echo "[subjectAltNames]"
for item in ${SANs[*]}; do echo $item; done
echo
