#!/bin/sh

. /etc/control.d/functions

OPENSSL_CNF="${OPENSSL_CNF:-/etc/pki/tls/openssl.cnf}"
CONFIG="$OPENSSL_CNF"

# Adapt OpenSSL for GOST cryptography support

def_OPENSSL_CONF="default_modules"
def_ENGINES="engine_section"
def_GOST="gost_section"

DEFAULTS="
engine_id           gost
default_algorithms  ALL
CRYPT_PARAMS        id-Gost28147-89-CryptoPro-A-ParamSet
"

main_section_get() {
    sed -n -e "1,/^\\[/ {
        /^\\[/ d;
        /^[[:space:]]*$2[[:space:]]*=/ {
            s/^[[:space:]]*$2[[:space:]]*=[[:space:]]*//;
            s/[[:space:]]*\$//;
            p; q
        }
    }" "$1"
}

main_section_set() {
    sed -i \
        -e "/^$2[[:space:]]*=[[:space:]]*$3\\([[:space:]].*\\)\\?\$/ b exit" \
        -e "/^$2[[:space:]]*=/ { s/^$2[[:space:]]*=.*\$/$2 = $3/; b exit }"  \
        -e "/^[[:space:]]*\\[[[:space:]]*[^]]\\+[[:space:]]*\\]\\([[:space:]]*#.*\\)\\?\$/ {
                s/^.*\$/$2 = $3\n\n&/;
                b exit;
            }" \
        -e '$ q1; p; d' \
        -e ':exit $ q0; n; b exit' \
        "$1"
}

osslcnf_get() {
    sed -n -e "/^[[:space:]]*\\[[[:space:]]*$2[[:space:]]*\\]/! { $ q2; d }" \
           -e ':loop n' \
           -e "/^[[:space:]]*\\[/! {
                 /^[[:space:]]*$3[[:space:]]=/ {
                   s/^[[:space:]]*$3[[:space:]]=[[:space:]]*//;
                   s/#.*\$//;
                   p; q0
                 }
               }" \
          -e '$ q1; b loop' \
        "$1"
}

_osslcnf_set_nonempty() {
    sed -i -e "/^[[:space:]]*\\[[[:space:]]*$2[[:space:]]*\\]/! {
                 \$ {
                   s/^.*\$/&\n\n[ $2 ]\n$3 = $4/;
                   b exit;
                 };
                 p; d
               }" \
           -e "\$ {
                 s/^.*\$/&\n$3 = $4/;
                 q0
               }" \
           -e ':loop n' \
           -e "/^[[:space:]]*\\[/! {
                 /^[[:space:]]*$3[[:space:]]=/ {
                   s/^\\([[:space:]]*$3[[:space:]]=[[:space:]]*\\).*\$/\\1$4/;
                   b exit
                 };
                 \$ {
                   s/^.*\$/&\n$3 = $4/;
                   q0
                 };
                 /^[[:space:]]*\$/ {
                   s/^.*\$/$3 = $4\n&/;
                   b exit
                 };
                 b loop
               }" \
           -e "/^[[:space:]]*\\[/ {
                 s/^.*\$/$3 = $4\n\n&/;
                 b exit
               }" \
           -e 'b loop' \
           -e ':exit $ q0; n; b exit' \
           "$1"
}

osslcnf_del() {
    sed -n -i \
           -e ":sec /^[[:space:]]*\\[[[:space:]]*$2[[:space:]]*\\]/! {
                 \$ { p; q0; }; p; d
               }" \
           -e ':print p' \
           -e ':skip n' \
           -e "/^[[:space:]]*\\[/! {
                 /^[[:space:]]*$3[[:space:]]=/ b skip;
               }" \
           -e '/^[[:space:]]*\[/ b sec' \
           -e 'b print' \
           -e ':exit $ { p; q0 }; p; n; b exit' \
           "$1"
}

osslcnf_set() {
    if [ -n "${4:-}" ]; then
        _osslcnf_set_nonempty "$@"
    else
        osslcnf_del "$1" "$2" "$3"
    fi
}

_tmp_config=
_copy_mode() {
    _tmp_config="$(mktemp --tmpdir openssl-gost.XXXX)"
    chown --reference="$CONFIG" "$_tmp_config"
    chmod --reference="$CONFIG" "$_tmp_config"
}

_apply_mode() {
    chown --reference="$_tmp_config" "${1:-$CONFIG}"
    chmod --reference="$_tmp_config" "${1:-$CONFIG}"
    rm "$_tmp_config"
}

_backup() {
    rm -f "${2:-${1:-$CONFIG}~}"
    cp -a "${1:-$CONFIG}" "${2:-${1:-$CONFIG}~}"
}

OPENSSL_CONF=
ENGINES=
GOST=
read_sections() {
    OPENSSL_CONF=
    ENGINES=
    GOST=

    OPENSSL_CONF="$(main_section_get "$CONFIG" 'openssl_conf')"
    if [ -n "$OPENSSL_CONF" ]; then
        ENGINES="$(osslcnf_get "$CONFIG" "$OPENSSL_CONF" 'engines')"
        if [ -n "$ENGINES" ]; then
            GOST="$(osslcnf_get "$CONFIG" "$ENGINES" 'gost')"
        fi
    fi
}

fix_osslcnf() {
    # For some reason 'engines' should be set before 'HOME'.
    # Setting 'engines' leads to unexpected channel errors.

    read_sections

    if [ -n "$ENGINES" ]; then
        osslcnf_del "$CONFIG" "$OPENSSL_CONF" 'engines'
        sed -i -e "/^\\[[[:space:]]*$OPENSSL_CONF[[:space:]]*\\]/ {
                     s/^.*\$/&\nengines = $ENGINES/;
                   }" \
            "$CONFIG"
    fi
}

enable_gost() {
    _copy_mode
    _backup
    
    main_section_set "$CONFIG" 'openssl_conf' \
                     "${OPENSSL_CONF:-$def_OPENSSL_CONF}"
    osslcnf_set "$CONFIG" "${OPENSSL_CONF:-$def_OPENSSL_CONF}" \
                'engines' "${ENGINES:-$def_ENGINES}"
    osslcnf_set "$CONFIG" "${ENGINES:-$def_ENGINES}" 'gost' \
                "${GOST:-$def_GOST}"

    echo "$DEFAULTS" | while read key val; do
        [ -n "$key" ] || continue
        osslcnf_set "$CONFIG" "${GOST:-$def_GOST}" "$key" "$val"
    done

    fix_osslcnf
    
    _apply_mode
}

disable_gost() {
    [ -n "$ENGINES" ] || return 0

    _copy_mode
    _backup

    osslcnf_del "$CONFIG" "$ENGINES" 'gost'    

    fix_osslcnf

    _apply_mode
}

is_gost_enabled() {
    [ -n "$GOST" ] || return 1

    echo "$DEFAULTS" | while read key dval; do
        [ -n "$key" ] || continue
        # Currently, check only the 'engine_id' parameter.
        case "$key" in
            engine_id)
                ;;
            *)
                continue
                ;;
        esac
        val="$(osslcnf_get "$CONFIG" "$GOST" "$key")"
        if [ "$val" != "$dval" ]; then
            exit 1  # exit from the subshell
        fi
    done
}


## Main

read_sections

new_summary "Enable/disable GOST ciphers with OpenSSL"
new_help 'enabled' "Enable GOST ciphers"
new_help 'disabled' "Disable GOST ciphers"

REQUEST="$*"

case "$REQUEST" in
	help|'help '*)
		control_help "${REQUEST#help}"
		;;
	list)
		control_list
		;;
	summary)
		control_summary
		;;
	status)
        if is_gost_enabled; then
            echo 'enabled'
        else
            echo 'disabled'
        fi
		;;
	enabled)
        enable_gost
		;;
	disabled)
        disable_gost
		;;
    *)
		printf '%s: %s\n' "${0##*/}" "Invalid mode: $REQUEST" >&2
		exit 1
		;;
esac
