#!/usr/bin/bash

#
# Copyright (c) 2015-2018 Red Hat, Inc.
#
# All rights reserved.
#
# Author: Jan Friesse (jfriesse@redhat.com)
#
# This software licensed under BSD license, the text of which follows:
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# - Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
# - Neither the name of the Red Hat, Inc. nor the names of its
#   contributors may be used to endorse or promote products derived from this
#   software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#

CONFIG_DIR="/etc/corosync/qnetd"
DB_DIR="$CONFIG_DIR/nssdb"
# Validity of certificate (months)
CRT_VALIDITY=1200
CA_NICKNAME="QNet CA"
SERVER_NICKNAME="QNetd Cert"
CA_SUBJECT="CN=QNet CA"
SERVER_SUBJECT="CN=Qnetd Server"
PWD_FILE="$DB_DIR/pwdfile.txt"
NOISE_FILE="$DB_DIR/noise.txt"
SERIAL_NO_FILE="$DB_DIR/serial.txt"
CA_EXPORT_FILE="$DB_DIR/qnetd-cacert.crt"
CERTDB_FILES=("cert9.db key4.db pkcs11.txt"
              "cert8.db key3.db secmod.db")

usage() {
    echo "$0: [-i|-s] [-c certificate] [-G] [-n cluster_name]"
    echo
    echo " -i                  Initialize QNetd CA and generate server certificate"
    echo " -s                  Sign cluster certificate (needs cluster certificate)"
    echo " -c certificate      CRQ certificate file name"
    echo " -G                  Do not set group write bit for new files"
    echo " -n cluster_name     Name of cluster (for -s operation)"

    exit 0
}

chown_ref_cfgdir() {
    if [ "$UID" == "0" ];then
        chown --reference="$CONFIG_DIR" "$@" 2>/dev/null || chown "$(stat -f "%u:%g" "$CONFIG_DIR")" "$@" 2>/dev/null || return $?
    fi
}

# get_perm [directory]
# Return permission based on -G and directory flag
get_perm() {
    if [ "$1" == true ];then
        [ "$SET_GROUP_WRITE_BIT" == true ] && echo "0770" || echo "0750"
    else
        [ "$SET_GROUP_WRITE_BIT" == true ] && echo "0660" || echo "0640"
    fi
}

create_new_noise_file() {
    local noise_file="$1"

    if [ ! -e "$noise_file" ];then
        echo "Creating new noise file $noise_file"

        (ps -elf; date; w) | sha1sum | (read sha_sum rest; echo $sha_sum) > "$noise_file"

        chown_ref_cfgdir "$noise_file"
        chmod "$(get_perm)" "$noise_file"
    else
        echo "Using existing noise file $noise_file"
    fi
}

get_serial_no() {
    local serial_no

    if ! [ -f "$SERIAL_NO_FILE" ];then
        echo "100" > $SERIAL_NO_FILE
        chown_ref_cfgdir "$SERIAL_NO_FILE"
        chmod "$(get_perm)" "$SERIAL_NO_FILE"
    fi
    serial_no=`cat $SERIAL_NO_FILE`
    serial_no=$((serial_no+1))
    echo "$serial_no" > $SERIAL_NO_FILE
    echo "$serial_no"
}

find_certdb_files() {
    for cert_files_index in "${!CERTDB_FILES[@]}";do
        cert_files=${CERTDB_FILES[$cert_files_index]}
        test_file=${cert_files%% *}
        if [ -f "$DB_DIR/$test_file" ];then
            echo "$cert_files"

            return 0
        fi
    done

    return 1
}

init_qnetd_ca() {
    cert_files=`find_certdb_files`
    if [ "$cert_files" != "" ];then
        echo "Certificate database ($DB_DIR) already exists. Delete it to initialize new db" >&2

        exit 1
    fi

    if ! [ -d "$DB_DIR" ];then
        echo "Creating $DB_DIR"
        mkdir -p "$DB_DIR"
        chown_ref_cfgdir "$DB_DIR"
        chmod "$(get_perm true)" "$DB_DIR"
    fi

    echo "Creating new key and cert db"
    echo -n "" > "$PWD_FILE"
    chown_ref_cfgdir "$PWD_FILE"
    chmod "$(get_perm)" "$PWD_FILE"

    certutil -N -d "$DB_DIR" -f "$PWD_FILE"
    cert_files=`find_certdb_files`
    if [ "$cert_files" == "" ];then
        echo "Can't find certificate database files. Certificate database ($DB_DIR) cannot be created" >&2

        exit 1
    fi

    for fname in $cert_files;do
        chown_ref_cfgdir "$DB_DIR/$fname"
        chmod "$(get_perm)" "$DB_DIR/$fname"
    done

    create_new_noise_file "$NOISE_FILE"

    echo "Creating new CA"
    # Create self-signed certificate (CA). Asks 3 questions (is this CA, lifetime and critical extension
    echo -e "y\n0\ny\n" | certutil -S -n "$CA_NICKNAME" -s "$CA_SUBJECT" -x \
        -t "CT,," -m "$(get_serial_no)" -v $CRT_VALIDITY -d "$DB_DIR" \
        -z "$NOISE_FILE" -f "$PWD_FILE" -2
    # Export CA certificate in ascii
    certutil -L -d "$DB_DIR" -n "$CA_NICKNAME" > "$CA_EXPORT_FILE"
    certutil -L -d "$DB_DIR" -n "$CA_NICKNAME" -a >> "$CA_EXPORT_FILE"
    chown_ref_cfgdir "$CA_EXPORT_FILE"

    certutil -S -n "$SERVER_NICKNAME" -s "$SERVER_SUBJECT" -c "$CA_NICKNAME" -t "u,u,u" -m "$(get_serial_no)" \
        -v $CRT_VALIDITY -d "$DB_DIR" -z "$NOISE_FILE" -f "$PWD_FILE"

    echo "QNetd CA certificate is exported as $CA_EXPORT_FILE"
}


sign_cluster_cert() {
    cert_files=`find_certdb_files`
    if [ "$cert_files" == "" ];then
        echo "Certificate database doesn't exists. Use $0 -i to create it" >&2

        exit 1
    fi

    echo "Signing cluster certificate"
    certutil -C -v "$CRT_VALIDITY" -m "$(get_serial_no)" -i "$CERTIFICATE_FILE" -o "$CRT_FILE" -c "$CA_NICKNAME" -d "$DB_DIR"
    chown_ref_cfgdir "$CRT_FILE"

    echo "Certificate stored in $CRT_FILE"
}


OPERATION=""
CERTIFICATE_FILE=""
CLUSTER_NAME=""
SET_GROUP_WRITE_BIT=true

while getopts ":Ghisc:n:" opt; do
    case $opt in
        i)
            OPERATION=init_qnetd_ca
            ;;
        s)
            OPERATION=sign_cluster_cert
            ;;
        h)
            usage
            ;;
        c)
            CERTIFICATE_FILE="$OPTARG"
            ;;
        G)
            SET_GROUP_WRITE_BIT=false
            ;;
        n)
            CLUSTER_NAME="$OPTARG"
            ;;
        \?)
            echo "Invalid option: -$OPTARG" >&2

            exit 1
            ;;
        :)
            echo "Option -$OPTARG requires an argument." >&2

            exit 1
            ;;
   esac
done

[ "$OPERATION" == "" ] && usage

CRT_FILE="$DB_DIR/cluster-$CLUSTER_NAME.crt"

case "$OPERATION" in
    "init_qnetd_ca")
        init_qnetd_ca
    ;;
    "sign_cluster_cert")
        if ! [ -e "$CERTIFICATE_FILE" ];then
            echo "Can't open certificate file $CERTIFICATE_FILE" >&2

            exit 2
        fi

        if [ "$CLUSTER_NAME" == "" ];then
            echo "You have to specify cluster name" >&2

            exit 2
        fi

        sign_cluster_cert
    ;;
    *)
        usage
    ;;
esac
