#!/bin/bash
#
# luci    high availability management application
#
# chkconfig: - 99 01
# description: Starts and stops luci
#
#
### BEGIN INIT INFO
# Provides:        luci
# Required-Start:  $named $network $time
# Required-Stop:   $network $time
# Default-Start:
# Default-Stop:
# Short-Description:  Starts and stops luci
# Description:  Starts and stops the luci high availability management application
### END INIT INFO

USEFUNCS=1
# Source function library.
if [ -e /etc/rc.d/init.d/functions ]; then
    . /etc/rc.d/init.d/functions
else
    PATH="/sbin:/usr/sbin:/bin:/usr/bin"
    export PATH
    success() { return; }
    failure() { return; }
    USEFUNCS=0
fi


prog="luci"
config="/var/lib/luci/etc/luci.ini"
sysconfig="/etc/sysconfig/luci"
exec="/usr/bin/paster"


# Defaults that can be overridden by the content of $sysconfig
LOG_FILE="/var/log/luci/luci.log"
KEEP_RUNTIME_DATA=0

# Override defaults with values from initscript configuration file
[ -e "$sysconfig" ] && . "$sysconfig"


lockfile="/var/lock/subsys/luci"

# Following values are not intended to be overridden by $sysconfig

PKG_NAME="luci"
DAEMON_USER="luci"
DAEMON_GROUP="luci"
# These are only used to inform user and only when the port a/o host is not
# redefined in initscript configuration file ($sysconfig), otherwise these
# values are obtained from this file
HOST=0.0.0.0
PORT=8084

STATE_DIR="/var/lib/luci"
DB_FILE="/var/lib/luci/data/luci.db"

RUNTIMEDATA_DIR="/var/run/luci"
CACHE_DIR="/var/run/luci/cache"
SESSIONS_DIR="/var/run/luci/sessions"
PID_FILE="/var/run/luci/luci.pid"

CERT_KEY_BITS=2048
CERT_KEY_LIFE_DAYS=1825
CERT_CONFIG="/var/lib/luci/etc/cacert.config"
CERT_PEM="/var/lib/luci/certs/host.pem"


# Check some conditions and return respective return code
entry_check() {
    this_identity=$(whoami)
    if [ "$this_identity" != "root" -a "$this_identity" != "$DAEMON_USER" ]; then
        # Must be either root or the luci user to run this
        echo "Insufficient permissions" >&2
        return 4
    elif [ ! -x "$exec" ]; then
        echo "Missing program or component installed: paster (paste.script)" >&2
        return 5
    fi
}


# Automatically adds subjectAltName values for hostname domain names and/or IP
# addresses to the configuration of self-managed self-signed certificate
certconfig_complete() {
    if [ "$(tail -n 1 "$CERT_CONFIG")" == "###" ]; then
        echo "Adding following auto-detected host IDs (IP addresses/domain" \
             "names), corresponding to \`$HOST' address, to the"            \
             "configuration of self-managed certificate \`$CERT_CONFIG'"    \
             "(you can change them by editing \`$CERT_CONFIG', removing"    \
             "the generated certificate \`$CERT_PEM' and restarting"        \
             "$prog):" >&2
        CNT_DOM_NAME=16
        CNT_IP_ADDR=16
        IP_ADDRS=$(ip addr show scope global | sed '/^[ \t]*inet/!d; s/[ \t]*inet[^ \t]*[ \t]\+\([^ \t/]\+\).*$/\1/')
        CUSTOM_HOST=$(echo "$INIT_CONFIG" | sed 's/\$/\n/g' | sed '/^[ \t]*host[ \t]*=/!d; s/[^=]*=[ \t]*\([^$]\+\)$/\1/')
        [ -n "$CUSTOM_HOST" ] && HOST="$CUSTOM_HOST"
        if [ "$HOST" == "0.0.0.0" ]; then
            # Use only resolved names of all global host IP addresses for
            # identification within the certificate, no information about
            # these IPs added (for confidentality reasons)
            for IP_ADDR in $IP_ADDRS; do
                DOM_NAME=$(python -c "from socket import getfqdn; print(getfqdn('$IP_ADDR'))" 2>/dev/null)
                if [ -n "$DOM_NAME" -a "$DOM_NAME" != "$IP_ADDR" ]; then
                    echo "DNS.$CNT_DOM_NAME = $DOM_NAME" >>"$CERT_CONFIG"
                    echo -e "\tDNS: $DOM_NAME" >&2
                    let CNT_DOM_NAME++
                fi
            done
        else
            # Use only resolved names of particular global host IP address
            # corresponding to the host IP address luci binds at, together
            # with this IP
            for IP_ADDR in $IP_ADDRS; do
                if [ "$IP_ADDR" == "$HOST" ]; then
                    echo "IP.$CNT_IP_ADDR = $IP_ADDR" >>"$CERT_CONFIG"
                    echo -e "\tIP: $IP_ADDR" >&2
                    let CNT_IP_ADDR++
                    DOM_NAME=$(python -c "from socket import getfqdn; print(getfqdn('$IP_ADDR'))" 2>/dev/null)
                    if [ -n "$DOM_NAME" -a "$DOM_NAME" != "$IP_ADDR" ]; then
                        echo "DNS.$CNT_DOM_NAME = $DOM_NAME" >>"$CERT_CONFIG"
                        echo -e "\tDNS: $DOM_NAME" >&2
                        let CNT_DOM_NAME++
                    fi
                fi
            done
        fi
        if [ $CNT_DOM_NAME -eq 16 -a $CNT_IP_ADDR -eq 16 ]; then
            echo -e "\t(none suitable found, you can still do it manually" \
                    "as mentioned above)" >&2
        fi
        echo -ne "\n" >&2
    fi
}

prepare_config() {
    # Touching $config first and then using ``.. make-config .. --overwrite''
    # does not work now (see http://trac.pythonpaste.org/pythonpaste/ticket/450)
    "$exec" make-config $PKG_NAME "$config" --no-default-sysconfig --no-install &>/dev/null
    if [ $? -ne 0 ]; then
        rm -f -- "$config" &>/dev/null
        echo "Unable to create the $PKG_NAME base configuration file (\`$config')." >&2
        return 6
    fi
    chown $DAEMON_USER:$DAEMON_GROUP "$config" \
    && chmod 0640 "$config"
    if [ $? -ne 0 ]; then
        rm -f -- "$config" &>/dev/null
        echo "Unable to change ownership/attributes of the $PKG_NAME base configuration file (\`$config')." >&2
        return 1
    fi
}

prepare_db() {
    touch "$DB_FILE"
    chown $DAEMON_USER:$DAEMON_GROUP "$DB_FILE" \
    && chmod 0640 "$DB_FILE"
    if [ $? -ne 0 ]; then
        rm -f -- "$DB_FILE" &>/dev/null
        echo "Unable to change ownership/attributes of the $PKG_NAME database file (\`$DB_FILE')." >&2
        return 1
    fi
    "$exec" setup-app "$config" --no-default-sysconfig &>/dev/null
    if [ $? -ne 0 ]; then
        rm -f -- "$DB_FILE" &>/dev/null
        echo "Unable to create the $PKG_NAME database file (\`$DB_FILE')." >&2
        return 6
    fi
}

# Prepare self-managed self-signed certificate (/var/lib/luci/certs/host.pem)
prepare_cert() {
    touch "$CERT_PEM"
    chown $DAEMON_USER:$DAEMON_GROUP "$CERT_PEM" \
    && chmod 0600 "$CERT_PEM"
    if [ $? -ne 0 ]; then
        rm -f -- "$CERT_PEM" &>/dev/null
        echo "Unable to change ownership/attributes of the $PKG_NAME host certificate file (\`$CERT_PEM')." >&2
        return 1
    fi
    certconfig_complete
    [ -x /usr/bin/openssl ] || exit 6
    # Generate the SSL certificate (PEM file containing also private key)
    # Note: Explicit specification of file containing random data is needed
    #       to suppress complaints (and we also control where this file is)
    export RANDFILE="$(mktemp -q "cert_rnd.XXXXXX")"
    out="$(/usr/bin/openssl req -new -x509 -nodes -sha1        \
                                -newkey rsa:"$CERT_KEY_BITS"   \
                                -config "$CERT_CONFIG"         \
                                -days "$CERT_KEY_LIFE_DAYS"    \
                                -set_serial "$(/bin/date +%s)" \
                                -keyout "$CERT_PEM"            \
                                -out "$CERT_PEM" 2>&1)"
    ret=$?
    rm -f -- "$RANDFILE" &>/dev/null
    echo "$out" | sed "/^[.+-]/d" >&2
    if [ $ret -ne 0 ]; then
        rm -f -- "$CERT_PEM" &>/dev/null
        echo "Unable to generate the $PKG_NAME host certificate file (\`$CERT_PEM')." >&2
        return 6
    fi
}

initialize() {
    # Ensure the existence of base configuration file
    if [ ! -f "$config" ]; then
        prepare_config || return $?
    fi
    # Ensure the existence of database file
    if [ ! -f "$DB_FILE" ]; then
        prepare_db || return $?
    fi
    # Ensure existence of SSL certificate (also check the state of custom
    # certificate possibly defined in $sysconfig)
    # TODO: When luci is able to handle the change of certificate used for
    #       connections to ricci conveniently via its GUI, there will be no need
    #       to generate it if custom one provided (double "if" -> "if - elif")
    CUSTOM_CERT_PEM=$(echo "$INIT_CONFIG" | sed 's/\$/\n/g' | sed '/^[ \t]*ssl_pem[ \t]*=/!d; s/[^=]*=[ \t]*\([^$]\+\)$/\1/')
    if [ -n "$CUSTOM_CERT_PEM" ]; then
        if [ ! -r "$CUSTOM_CERT_PEM" ]; then
            echo "ERROR: Custom certificate \`$CUSTOM_CERT_PEM' (defined in \`$sysconfig'): cannot read" >&2
            return 6
        fi
    fi
    if [ ! -f "$CERT_PEM" ]; then
        prepare_cert || return $?
    fi
    # Ensure the existence of log file (and also the parent directory)
    if [ ! -f "$LOG_FILE" ]; then
        install -o $DAEMON_USER -g $DAEMON_GROUP -d "$(dirname "$LOG_FILE")"
        touch "$LOG_FILE"
        chown $DAEMON_USER:$DAEMON_GROUP "$LOG_FILE" \
        && chmod 0640 "$LOG_FILE"
        if [ $? -ne 0 ]; then
            echo "Unable to change ownership/attributes of the $PKG_NAME log file (\`$LOG_FILE')." >&2
            return 1
        fi
    fi
    # Ensure of directory for run-time data with nested directories for cache
    # and sessions data (these are restricted from unauthorized access)
    if [ ! -d "$RUNTIMEDATA_DIR" ]; then
        mkdir -p "$RUNTIMEDATA_DIR"
        chown $DAEMON_USER:$DAEMON_GROUP "$RUNTIMEDATA_DIR" \
        && chmod 0755 "$RUNTIMEDATA_DIR"
        if [ $? -ne 0 ]; then
            echo "Unable to change ownership/attributes of the $PKG_NAME directory for run-time data (\`$RUNTIMEDATA_DIR')." >&2
            return 1
        fi
    fi
    if [ ! -d "$CACHE_DIR" ]; then
        mkdir -p "$CACHE_DIR"
        chown $DAEMON_USER:$DAEMON_GROUP "$CACHE_DIR" \
        && chmod 0750 "$CACHE_DIR"
        if [ $? -ne 0 ]; then
            echo "Unable to change ownership/attributes of the $PKG_NAME directory for cache data (\`$CACHE_DIR')." >&2
            return 1
        fi
    fi
    if [ ! -d "$SESSIONS_DIR" ]; then
        mkdir -p "$SESSIONS_DIR"
        chown $DAEMON_USER:$DAEMON_GROUP "$SESSIONS_DIR" \
        && chmod 0750 "$SESSIONS_DIR"
        if [ $? -ne 0 ]; then
            echo "Unable to change ownership/attributes of the $PKG_NAME directory for sessions data (\`$SESSIONS_DIR')." >&2
            return 1
        fi
    fi
}

start_server() {
    # TODO: Can be the dependency on running saslauthd solved in a better way?
    #       LSB header doesn't seem to help there.
    cd /etc/init.d || return 1
    ./saslauthd start || return 1

    "$exec" serve --daemon --user "$DAEMON_USER" --group "$DAEMON_GROUP" \
                  --log-file="$LOG_FILE" --pid-file="$PID_FILE"          \
                  --server-name=init --app-name=init "$config"  >/dev/null
}

start() {
    step=$"Start $prog..."

    CUSTOM_HOST=$(echo "$INIT_CONFIG" | sed 's/\$/\n/g' | sed '/^[ \t]*host[ \t]*=/!d; s/[^=]*=[ \t]*\([^$]\+\)$/\1/')
    [ -n "$CUSTOM_HOST" ] && HOST="$CUSTOM_HOST"
    [ "$HOST" == "0.0.0.0" ] && HOST=$(python -c "from socket import getfqdn; print(getfqdn())" 2>/dev/null)
    [ -z "$HOST" ] && HOST="127.0.0.1"

    CUSTOM_PORT=$(echo "$INIT_CONFIG" | sed 's/\$/\n/g' | sed '/^[ \t]*port[ \t]*=/!d; s/[^=]*=[ \t]*\([^$]\+\)$/\1/')
    [ -n "$CUSTOM_PORT" ] && PORT="$CUSTOM_PORT"
    [ "$PORT" != "443" ] && PRINT_PORT=":$PORT"

    if [ "$KEEP_RUNTIME_DATA" -eq "0" ]; then
        rm -rf -- "$CACHE_DIR" "$SESSIONS_DIR" &>/dev/null
    fi

    if [ "$DAEMON_USER" != "root" -a "$PORT" -lt "1024" ]; then
        echo "Unable to use privileged port (<1024) because $PKG_NAME runs as a non-root user." >&2
        return 1
    fi

    ret=0
    initialize || ret=$?
    start_server || ret=$?

    [ $ret -eq 0 ] && touch "$lockfile"
    [ $ret -eq 0 ] && success || failure; echo -n "$step"
    [ $USEFUNCS -eq 0 ] && echo " $ret" || echo
    if [ $ret -eq 0 ]; then
        echo "Point your web browser to https://$HOST$PRINT_PORT (or equivalent) to access $PKG_NAME" >&2
    fi
    return $ret
}

stop() {
    step=$"Stop $prog..."

    # If PID file does not exists, paster returns 1 otherwise 0
    "$exec" serve --stop-daemon --pid-file="$PID_FILE" >/dev/null
    ret=$?
    if [ $ret -eq 0 ]; then
        if [ "$KEEP_RUNTIME_DATA" -eq "0" ]; then
            rm -rf -- "$CACHE_DIR" "$SESSIONS_DIR" &>/dev/null
        fi
        rm -f -- "$lockfile"
    fi
    [ $ret -eq 0 ] && success || failure; echo -n "$step"
    [ $USEFUNCS -eq 0 ] && echo " $ret" || echo
    return $ret
}

restart() {
    stop
    start
}

status() {
    # If PID file exists and contains valid PID, paster returns 0 otherwise 1
    out=$("$exec" serve --status --pid-file="$PID_FILE" "$config" 2>&1)
    ret=$?
    echo "$out" | tail -1
    if [ $ret -ne 0 ]; then
        # Check the existence of the PID file to choose the right return code
        [ -e "$PID_FILE" ] && ret=1 || ret=3
    fi
    return $ret
}


case "$1" in
    start)
        entry_check || exit $?
        status &>/dev/null && exit 0
        $1
        ;;
    stop)
        entry_check || exit $?
        status &>/dev/null || exit 0
        $1
        ;;
    restart)
        entry_check || exit $?
        $1
        ;;
    reload|force-reload)
        entry_check || exit $?
        status &>/dev/null || exit 7
        restart
        ;;
    condrestart|try-restart)
        entry_check || exit $?
        status &>/dev/null || exit 0
        restart
        ;;
    status)
        entry_check || exit 4
        $1
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|reload|restart|condrestart|try-restart}"
        exit 2
esac
exit $?
