#!/bin/bash
#
# License: Copyright 2015 SpinetiX. This file is licensed
#          under the terms of the GNU General Public License version 2.
#          This program is licensed "as is" without any warranty of any
#          kind, whether express or implied.
#
# Copyright 1999-2003 MontaVista Software, Inc.
# Copyright 2002, 2003, 2004 Sony Corporation
# Copyright 2002, 2003, 2004 Matsushita Electric Industrial Co., Ltd.
#
### BEGIN INIT INFO
# Required-Start:
# Required-Stop:
# Should-Start:
# Should-Stop:
# Default-Start: 2 3 5
# Default-Stop:
# Short-Description: Starting/stopping SpinetiX's player administrative interface
# Description: Starting/stopping SpinetiX's player administrative interface
### END INIT INFO
# NOTE: this should be started before apache

# Init script information
NAME=spxmanage
DESC="SPX management interface"

# Load init script configuration
[ -f /etc/default/$NAME ] && . /etc/default/$NAME

# Source the init script functions
. /etc/init.d/functions

# Set up the path variables
. /etc/spxmanage/init-functions
init_dirs

# Source main resources
[ -f /usr/share/resources/default/init/main ] && . /usr/share/resources/default/init/main

# Load apache2 configuration to detect SSL
[ -f /etc/default/apache2 ] && . /etc/default/apache2

PKIDIR=/etc/pki/spxmanage
HTCONFDIR=/etc/apache2
SAFEINDICATOR=/var/run/raperca/safe-mode
SAFEREBOOTDELAY=3600
RUNFILE=/var/run/spxmanage.started
HTTPDRUNDEFS=/var/run/spxmanage/httpd-defs.conf
ARYA_SETTINGS_FILE=/etc/spxmanage/arya-settings
SRPSECRETSDIR=/etc/spxmanage/srpsecrets
# The location where the persistent data store is located
PERSISTENT_DATA_DIR=/var/lib/spinetix/persistent-data
RESTORE_CONFIG_FILE=/var/cache/spxmanage/restore.cfg

setup_http_defs() {
    local arya_enabled

    echo -e "# spxmanage runtime defines\n" > "$HTTPDRUNDEFS"
    if [ -f $ARYA_SETTINGS_FILE ]; then
        arya_enabled="$(sed -n -e '/^\s*ENABLED=/{s/.*=//;s/\s*//g;p}' $ARYA_SETTINGS_FILE)"
    else
        arya_enabled="no"
    fi
    if [ "$arya_enabled" = "yes" ]; then
        echo -e "# ARYA enabled, disallow user publish\nDefine OnlyUploaderPublish\n" >> "$HTTPDRUNDEFS"
    fi
}

# creates a self-signed certificate
create_self_signed_cert() {
    local hostname days endtime nowtime

    hostname="$(hostname)"
    if [ -z "$hostname" ]; then
        echo "Hostname is not set, cannot create self signed certificate" >&2
        return 1
    fi
    cat > $PKIDIR/server/httpd-auto-ss.cnf <<EOF
[req]
default_bits		= 2048
default_md		= sha256
encrypt_key		= no
distinguished_name	= req_distinguished_name
x509_extensions		= v3_ext
prompt			= no

[ req_distinguished_name ]
CN			= $hostname

[ v3_ext ]

# extensions for CA bits (from default openssl file)
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always,issuer

# extensions for name
subjectAltName		= @alt_names

[ alt_names ]
# the CN of the distinguished name above is to appear here as well
DNS.1			= $hostname
DNS.2			= $hostname.local

EOF
    # TODO: append any other hostnames (with domain) or IP addresses
    # to configuration file, maybe add IPv6 local-link address

    # RSA 2048 is deemed secure until 2030, the unix time 1924992000
    # below is 2031-01-01
    endtime=1924992000
    nowtime="$(date -u +%s)"
    days=$(( ( endtime - nowtime ) / 86400 ))
    [ "$days" -gt 30 ] || days=30

    # create the self-signed certificate
    #
    # NOTE: specifying just "rsa" instead of "rsa:2048" generates a
    # 1024 bit key despite the default_bits = 2048 setting in the
    # config file and the output message that says 2048!
    openssl req -x509 -newkey rsa:2048 -batch \
        -config $PKIDIR/server/httpd-auto-ss.cnf \
        -days "$days" \
        -out $PKIDIR/server/httpd-auto-ss.crt \
        -keyout $PKIDIR/private/httpd-auto-ss.key > /dev/null 2>&1 && \
        chmod 0600 $PKIDIR/private/httpd-auto-ss.key
}

verify_cert() {
    local cert="$1"
    local key="$2"

    # check that the key is not encrypted (i.e. it can be read using a
    # NULL password)
    openssl pkey -in "$key" -noout -passin pass: 2> /dev/null || return

    # check the certificate and key are readable and that they both
    # have the same public key
    cmp -s \
        <(openssl x509 -in "$cert" -pubkey -noout 2>/dev/null || echo BAD CERT) \
        <(openssl pkey -pubout -in "$key" 2>/dev/null || echo BAD KEY ) || return

    return 0
}

setup_https_cert() {
    local ss_cert_ret

    verify_cert $HTCONFDIR/server.crt $HTCONFDIR/server.key
    if [ $? -ne 0 ]; then

        # cleanup invalid or missing certificate
        [ -e $HTCONFDIR/server.crt -o \
            -e $HTCONFDIR/server.key -o \
            -h $HTCONFDIR/server.crt -o \
            -h $HTCONFDIR/server.key ] && echo "warning: removing invalid https cert"
        rm -f $HTCONFDIR/server.crt $HTCONFDIR/server.key
        sync # make sure links in $HTCONFDIR are really gone

        # check if self-signed certificate available and create if not
        verify_cert $PKIDIR/server/httpd-auto-ss.crt $PKIDIR/private/httpd-auto-ss.key
        ss_cert_ret=$?
        if [ $ss_cert_ret -ne 0 ]; then
            # automatic self-signed cert not present or not valid, attempt to recover from persistent storage
            verify_cert "$PERSISTENT_DATA_DIR"/httpd-auto-ss.crt "$PERSISTENT_DATA_DIR"/httpd-auto-ss.key && \
                rm -f $PKIDIR/server/httpd-auto-ss.* $PKIDIR/private/httpd-auto-ss.* && \
                cp "$PERSISTENT_DATA_DIR"/httpd-auto-ss.crt $PKIDIR/server/ && \
                cp "$PERSISTENT_DATA_DIR"/httpd-auto-ss.key $PKIDIR/private/ && \
                chmod 0600 $PKIDIR/private/httpd-auto-ss.key
            ss_cert_ret=$?
        fi
        if [ $ss_cert_ret -ne 0 ]; then
            create_self_signed_cert
            if [ $? -ne 0 ]; then	    
                echo -n "error: failed creating self-signed https cert"
                return 1
            fi
            # save self-signed cert to persistent storage (it should only be saved
            # to persistent storage when it is created to avoid conflicts or never
            # ending cycles when doing configuration resets and persistent storage resets)
            cp -p $PKIDIR/server/httpd-auto-ss.crt "$PERSISTENT_DATA_DIR"/ && \
                cp -p $PKIDIR/private/httpd-auto-ss.key "$PERSISTENT_DATA_DIR"/
        fi

        sync # make sure new certificates are saved before failsafe-data

        # add self-signed cert to trusted set
        ln -sf ../server/httpd-auto-ss.crt $PKIDIR/certs/httpd-auto-ss.crt
        update-ca-certificates

        # save certificate for recovery console
        failsafe-data \
            copy httpd-cert.crt $PKIDIR/server/httpd-auto-ss.crt \
            copy httpd-cert.key $PKIDIR/private/httpd-auto-ss.key \
            copy cert.pem /etc/ssl/certs/ca-certificates.crt
        [ $? -eq 0 ] || echo "error: failed copying self-signed https cert to failsafe-data"

        # activate it for httpd by creating the links
        ln -snf $PKIDIR/server/httpd-auto-ss.crt $HTCONFDIR/server.crt && \
            ln -snf $PKIDIR/private/httpd-auto-ss.key $HTCONFDIR/server.key
        [ $? -eq 0 ] || echo "error: failed activating self-signed https cert"

        sync
    fi

    # if a power failure occured at just the wrong moment when running
    # update-ca-certificates in a previous boot then
    # /etc/ssl/certs/ca-certificates.crt may be missing or zero sized,
    # this would prevent the http server from starting and also
    # prevent verification of SSL/TLS certificates.
    if [ ! -s /etc/ssl/certs/ca-certificates.crt ]; then
        update-ca-certificates
        sync
    fi
}

setup_https_srp() {
    local seed
    
    # This is the g,N group which is to be used for unknown users and is set to match
    # the one used to create users in the SRP verfifier database. If this is not
    # present in the SRP verifier file then the default one will be used, which in
    # OpenSSL is the 8192-bit group. Using a different group for known and unknown users
    # would allow for probing for valid user names by inspecting the SSL ServerHello message.
    # This group is the standard 3072-bit group from the TLS-SRP RFC 5054, encoded with the
    # base 64 method used in SRP (not the same as standard Base 64).
    local us_GN="///////////93zgY8MZ2DCJ6Oek0t1pHAG9E28fdp7G22xwcEnER8b5A27cED0JTxvKPiyqwGnimAmfjybyKDq/XDMrjKS95v8MrTc9UViRqJ4BffZVjQml/NBRq1hVjxZXh.rg9dwMkdoGHV4iVvaaePb7iv5izmW1ykA5ZlmMOsaWs75NJccaMFwZz9CzVWsLT8zoZhPOSOlDM88LIkvxLAGTmbfPjPmmrJagyc0JnT6m8oXWXV3AGNaOkDiuxuvvtB1WEXWER9uEYx0UYZxN5NV1lJ5B9tYlBzfLO5nWvbKbywfLgvHNI9XYO.WKG5NAEMeggn2sjCnSD151wCwXL8QlV7BfaxFk515ZRxmgAwd5NNGOCVREN3uMcuUJ7g/MkZDi9CzSUZ9JWIYLXdSxZqYOQqkvhyI/w1jcA26JOTW9pFiXgP58VAnWNUo0Ck.4NLtfXNMnt2OZ0kjb6uWZYJw1qvQinGzjR/E3z48vBWj4WgJhIol//////////";
    local us_Gg="05"
    local us_Gname="default"
    local us_Gid="3072"
    local us_Gcomm="default gN for unknown users"

    if ! [ -s "$SRPSECRETSDIR"/passwd.srpv ]; then
        # A non-empty and valid SRP verifier DB file is necessary for the web
         # server to start. The DB file is 6 tab separated values. We use the
        # default g,N group entry, but if removed it needs to be replaced with
        # a dummy record type (it will be preserved by the openssl srp tool). 
        echo -e "I\t${us_GN}\t${us_Gg}\t${us_Gname}\t${us_Gid}\t${us_Gcomm}" > "$SRPSECRETSDIR"/passwd.srpv.tmp
        fsync "$SRPSECRETSDIR"/passwd.srpv.tmp 2>/dev/null || sync
        mv "$SRPSECRETSDIR"/passwd.srpv.tmp "$SRPSECRETSDIR"/passwd.srpv
        if ! [ -s "$SRPSECRETSDIR"/passwd.srpv ]; then
            echo "error: failed initializing SRP verifier file"
            return 1
        fi
    fi
    if ! [ -s "$SRPSECRETSDIR"/httpd-srp-random-seed.conf ]; then
        seed="$(< /proc/sys/kernel/random/uuid)"
        if [ -z "$seed" ]; then
            echo "error: could not obtain SRP random seed"
            return 1
        fi
        echo 'Define SRP_UNKNOWN_USER_SEED "'"$seed"'"' > "$SRPSECRETSDIR"/httpd-srp-random-seed.conf.tmp
        fsync "$SRPSECRETSDIR"/httpd-srp-random-seed.conf.tmp 2>/dev/null || sync
        mv "$SRPSECRETSDIR"/httpd-srp-random-seed.conf.tmp "$SRPSECRETSDIR"/httpd-srp-random-seed.conf
        if ! [ -s "$SRPSECRETSDIR"/httpd-srp-random-seed.conf ]; then
            echo "error: failed initializing SRP random seed config file"
            return 1
        fi
    fi
}

update_font_css() {
    local f
    local tmp
    local dest=/var/cache/spxmanage/www/docs/font-faces.css
    local ret=0

    tmp="$(mktemp)" || return

    for f in /etc/spxmanage/fonts/conf.d/*.css; do
        [ -f "$f" ] || continue
        sed -e 's:@@FONT_URL_PREFIX@@:/api/fonts/system:' "$f" >> "$tmp"
    done
    if ! [ -f "$dest" ] || ! cmp -s "$tmp" "$dest"; then
        cat "$tmp" > "$dest" || ret=1
    else
        ret=0
    fi
    rm "$tmp"
    return $ret
}

check_bootcount() {
    local bootcount
    local bcntlim=3

    [ -n "$rc_bootcount_lim_offset" ] && \
        [ "$rc_bootcount_lim_offset" -gt 0 ] && \
        bcntlim=$(( bcntlim + rc_bootcount_lim_offset ))

    bootcount="$(bootcount read)"
    if [ "${bootcount:-10}" -ge $bcntlim ]; then
        echo -n " enabling safe mode (bootcount >= $bcntlim)"
        touch "$SAFEINDICATOR"
        # NOTE: we use sleep instead of shutdown's own delay capabilities
        # because the latter precludes any other shutdown calls to succeed
        (sleep $SAFEREBOOTDELAY; \
            logger -p user.notice -t spxmanage-safe-mode -- \
            "safe mode rebooting automatically (${SAFEREBOOTDELAY}s)" ; \
            shutdown -r now rebooting due to safe mode) &
    fi
}

start() {
    local RET
    local RUN_UPDATE_JOBS

    echo -n "Starting $DESC: "

    if [ -e "$RUNFILE" ]; then
        echo "already started "
        failure; echo
        return 1
    fi

    echo -n "shadow dir"
    mount_shadow_dir
    RET=$?
    if [ $RET -eq 0 ]; then
        echo -n ", "
    else
        echo -n " failed, "
    fi

    echo -n "http defs"
    setup_http_defs
    if [ $? -ne 0 ]; then
        RET=1
    fi
    echo -n ", "

    if [ "$ENABLESSL" = "yes" ]; then
        echo -n "https"
        setup_https_cert && setup_https_srp 
        if [ $? -ne 0 ]; then
            RET=1
        fi
    else
        echo -n "https disabled"
    fi
    echo -n ", "

    echo -n "content dir"
    if [ ! -d "$SHADOWCONTENTDIR" ]; then
        copy_default_content "$SHADOWCONTENTDIR"
    fi
    echo -n ", "

    if [ -e "$SPOOLDIR"/reset-interface-data ]; then
        echo -n "resetting interface data"
        clean "$PUBLICINTERFACEDIR" "$PROTECTEDINTERFACEDIR" "$TMPINTERFACEDIR"
        rm "$SPOOLDIR"/reset-interface-data
        echo -n ", "
    fi

    echo -n "bootcount check"
    check_bootcount
    echo -n ", "

    echo -n "clean dav tmp"
    clean_dav_tmp "$CONTENTDIR"
    echo -n ", "

    echo -n "empty boot-id"
    echo -n > /var/cache/spxmanage/www/docs/boot-id
    echo -n ", "

    echo -n "font css"
    update_font_css
    echo -n ", "

    echo -n "clean dav locks"
    clean_davlockdb
    echo -n ", "

    echo -n "clean yii runtime"
    clean "$YIIRUNTIME"
    echo -n " "

    if [ $RET -eq 0 ]; then
        success; echo
    else
        failure; echo
    fi

    # The startup jobs must run before any modification may occur
    php -f /usr/share/spxmanage/www/protected/utils/startupJobs.php

    # NOTE: zero size DB files are the same as missing DB files
    RUN_UPDATE_JOBS=
    RESTORE_CONFIG=
    [ -e "$SPOOLDIR"/run-update-jobs ] && RUN_UPDATE_JOBS=1
    if [ -d "$ASSETSDIR" ]; then
        [ -s "$ASSETSDB" ] || RUN_UPDATE_JOBS=1
    fi
    [ -s "$UIDB" ] || RUN_UPDATE_JOBS=1
    if [ -f "$RESTORE_CONFIG_FILE" ]; then
        RUN_UPDATE_JOBS=1
        RESTORE_CONFIG=1
        echo "INFO: will restore configuration file"
        # we will force reboot and this is part of a kind of firmware update so bring up the firmware update splash screen
        /etc/init.d/splash updating
        sleep 5 # add a small delay for user to notice splash screen before we start
        # be sure we cannot end in a boot loop should we encounter catastrophic errors
        /etc/init.d/bootcount noreset
    fi
    if [ -n "$RUN_UPDATE_JOBS" ]; then
        # make sure that if job is interrupted halfway due to reboot it is run again
        [ ! -e "$SPOOLDIR"/run-update-jobs ] && \
            touch "$SPOOLDIR"/run-update-jobs && \
            sync
        SPX_RESTORE_CONFIG="${RESTORE_CONFIG:+$RESTORE_CONFIG_FILE}" \
            php -f /usr/share/spxmanage/www/protected/utils/UpdateJobs.php
        # make sure all created data is on stable storage before removing the run-update-jobs flag
        sync
        [ -e "$SPOOLDIR"/run-update-jobs ] && rm -f "$SPOOLDIR"/run-update-jobs "$RESTORE_CONFIG_FILE" && sync
    fi

    echo -n > "$RUNFILE"

    if [ -n "$RESTORE_CONFIG" ]; then
        echo "INFO: rebooting immediately to finish applying configuration"
        shutdown -c 2>/dev/null # cancel any potentially waiting delayed shutdown
        shutdown -r now Rebooting now to apply restored configuration
        while [ -z '' ]; do sleep 600; done # hang startup process until reboot kills us
    fi

    return $RET
}

stop () {

    [ -e "$RUNFILE" ] || return 0

    # Make sure all data has reached stable storage before running the shutdown jobs
    sync
    php -f /usr/share/spxmanage/www/protected/utils/shutdownJobs.php

    rm "$RUNFILE"

    return 0
}

restart() {
    local RET

    echo "Restarting $DESC..."
    stop
    start
    RET=$?

    return $RET
}

parse() {
    case "$1" in
        start)
            start
            return $?
            ;;
        restart)
            restart
            return $?
            ;;
        stop)
            stop
            return $?
            ;;
        reload|force-reload)
            return 0
            ;;
        *)
            echo "Usage: $NAME {start|stop|restart|reload|force-reload}" >&2
            ;;
    esac
        
    return 1
}

parse $@



