Mini Shell

Direktori : /usr/share/imunify360-webshield/
Upload File :
Current File : //usr/share/imunify360-webshield/webshieldctl

#!/usr/bin/bash

if [ "$IM360_DEBUG_SHELL" = "1" ]; then
    echo "IM360_ARGV: <$0 [$@]>"
    set -x
else
    :
fi

# For backwards compatibility with CentOS 6 only.
WEBSHIELD='imunify360-webshield'

JOBFILE=/etc/cron.d/imunify360-webshield-check

STATEFILE="/usr/share/imunify360-webshield/.webshieldctl.status"
VIRTSERVER_CONF="/etc/imunify360-webshield/virtserver.conf"
WEBSHIELD_ANTIBOT_CONF="/etc/imunify360-webshield/splashscreen-antibot.conf"

MODE='undefined'

SSL_UNIT='imunify360-webshield-ssl-cache'
WAFD_UNIT='imunify360-wafd'
MAIN_UNIT='undefined'

OUR_MODULES_DIR=/usr/share/imunify360-webshield/modules
NGINX_MODULE_CONF=/etc/nginx/modules-enabled/40-imunify360-access-checker.conf
NGINX_CHECKER_CONF=/etc/nginx/conf.d/imunify360-access-checker.conf

PREVIOUS_MODE_STATE_PATH=/usr/share/imunify360-webshield/.previous_mode
STATE_PATH=/usr/share/imunify360-webshield/modularity_mode

readonly COMMON_CONF=/etc/sysconfig/imunify360/imunify360-merged.config

# Returns code 0 if webshield is enabled and code 1 otherwise.
# On any errors we suppose that webshield is enabled, which is the default value.
# Refer to: defence360:config_schema/firewall.py.
# WARN: According to the YAML standard, all maps must contains unique keys only.
# But Python implementation silently overwrites the values with the same keys.
# This parser behaves exactly the same - it returns the last value as the result.
is_enabled_in_config() {
    [ -s "$COMMON_CONF" ] || return 0
    local value=$(
        awk -F: '
        /^WEBSHIELD:$/ {
            section_found=1;
            next;
        };
        section_found == 1 && /^[A-Z][A-Z0-9_-]*:$/ {
            section_over=1;
            next;
        };
        section_found == 1 && section_over != 1 && /^[[:space:]]+enable:/ {
            gsub(/[[:space:]]/, "", $2);
            print $2;
        }' "$COMMON_CONF"
    )
    [ "$value" = 'false' ] && return 1 || return 0
}

is_cloudways() {
    hostname -f 2>/dev/null | grep -qE '^.+\.(cloudwaysapps|cloudwaysstagingapps)\.com$' && return 0
    [ -x /usr/local/sbin/apm ] && /usr/local/sbin/apm info | grep -qi cloudways
}

detect_mode() {
    local mode='standalone'

    # TODO: At the moment, module-based nginx is available for CloudWays only.
    if is_cloudways; then
        mode='nginx'
    fi

    if [ -s "$STATE_PATH" ] && [ "$1" = 'skip-on-update' ]; then
        # Do not override config on packages update.
        mode=$(<"$STATE_PATH")
        echo "Module-based config left untouched with mode: '$mode'."
    else
        echo $mode >| $STATE_PATH
        echo "Module-based mode set to: '$mode'."
    fi
}

load_mode() {
    local mode='undefined'
    local unit='undefined'
    local value=''
    [ -s "$STATE_PATH" ] && value=$(<"$STATE_PATH")
    case "$value" in
        nginx)
            mode="$value"
            unit="$value"
            ;;
        apache)
            mode="$value"
            unit=$(get_unit)
            ;;
        *)
            mode='standalone'
            unit='imunify360-webshield'
            ;;
    esac
    export MODE="$mode"
    export MAIN_UNIT="$unit"
}

is_cpanel(){
    if [ -x /usr/local/cpanel/cpanel ];then
        return 0
    fi
    return 1
}

is_redhat(){
    if [ -e /etc/redhat-release ];then
        return 0
    fi
    return 1
}

has_apache(){
    if which apachectl &>/dev/null; then
        return 0
    fi
    return 1
}

is_supported(){
    if has_apache && is_cpanel && [ ! -d /usr/local/lsws ]; then
        [ "$1" = "out" ] && echo yes
        return 0
    else
        [ "$1" = "out" ] && echo no
        return 1
    fi
}

get_apache_root(){
    echo $(apachectl -V | awk -F= '/HTTPD_ROOT/{gsub("\042", "", $2);print $2}')
}

put_apache_conf(){
    if is_supported; then
        local root=$(get_apache_root)
        if is_redhat;then
            if [ -d $root/conf.d ];then
                cp /usr/share/imunify360-webshield/access_checker.conf $root/conf.d
            fi
        else
            # By default debian-based apache has modules configs in ROOT/mods-available folder
            # and enables the modules with placing a symlink to the config file into ROOT/mods-enabled.
            # Ubuntu-based cpanel keeps configs in ROOT/conf.d
            if [ -d $root/conf.d ];then
                cp /usr/share/imunify360-webshield/access_checker.conf $root/conf.d
            elif [ -d $root/mods-available ] && [ -d $root/mods-enabled ];then
                cp /usr/share/imunify360-webshield/access_checker.conf $root/mods-available
                ln -s $root/mods-available/access_checker.conf $root/mods-enabled
            # But we've seen different debian apache setups
            elif [ -d $root/mods-enabled ];then
                cp /usr/share/imunify360-webshield/access_checker.conf $root/mods-enabled
            fi
        fi
    fi
}

remove_apache_conf(){
    local root=$(get_apache_root)
    if is_redhat;then
        rm -f $root/conf.d/access_checker.conf
    else
        if [ -d $root/conf.d ];then
            rm -f $root/conf.d/access_checker.conf
        else
            rm -f $root/mods-enabled/access_checker.conf
        fi
    fi
}

check_apache_config(){
    apachectl configtest
}

get_unit(){
    if is_redhat;then
	    # Both ea-apache and httpd packages supply the same unit: httpd
        echo httpd
    else
        # Ubuntu-based cPanel runs httpd unit. However, it returns 0 even on missing unit file
        # So we check output '0/1 unit files listed'
        if [ $(systemctl list-unit-files httpd.service 2>&1 | awk '/unit files listed/{print $1}') = '1' ];then
            echo httpd
        else
            echo apache2
        fi
    fi
}

# the webshield is running as a separate entity
is_standalone() {
    [ "$MODE" = 'standalone' ]
}

# We supplied our module for a hoster's NGINX. No webshield is running
is_nginx(){
    [ "$MODE" = nginx ]
}

# We supplied our module for a hoster's Apache.
# Webshield is running as well and serves non-HTTP/HTTPS ports
is_apache(){
    [ "$MODE" = apache ]
}

cleanup_configs() {
    case "$MODE" in
        nginx)
            rm -vf "$NGINX_MODULE_CONF" "$NGINX_CHECKER_CONF"
            ;;
        apache)
            remove_apache_conf
            ;;
    esac
}

prepare_nginx() {
    local version=$(nginx -v 2>&1 | grep -oP '(\d+[.]){2}\d+')
    local mod_path="$OUR_MODULES_DIR"/"ngx_http_access_checker_module_${version}.so"

    if ! [ -s "$mod_path" ]; then
        echo "ERROR: Unable to find nginx module for version '$version'." >&2
        return 1
    fi

    # WARN: Explicit check that the path is exist and it is a directory
    # cause simple 'echo' to a file will raise error with an ambiguous message
    # "No such file or directory" like you're trying to *READ* from the file.
    # "dirname" does not check type of its argument and just cut everything
    # following the last slash.
    local path=''
    for path in "$NGINX_MODULE_CONF" "$NGINX_CHECKER_CONF"; do
        if ! [ -d $(dirname "$path") ]; then
            echo "ERROR: Unable to find directory for config '$path'." >&2
            return 1
        fi
    done

    if ! nginx -t ; then
        echo "ERROR: Invalid nginx config before installing module." >&2
        return 1
    fi

    echo "load_module ${mod_path};" >| "$NGINX_MODULE_CONF" || return 1
    echo 'access_checker unix:/var/run/imunify360/libiplists-daemon.sock;' >| "$NGINX_CHECKER_CONF" || return 1

    if ! nginx -t ; then
        echo "ERROR: Invalid nginx config after installing module." >&2
        return 1
    fi
}

prepare_configs() {
    is_enabled_in_config || return 0
    case "$MODE" in
        nginx)
            prepare_nginx
            ;;
        apache)
            put_apache_conf
            ;;
    esac
}

check_nginx() {
    [ -s "$NGINX_MODULE_CONF" ] || return 1
    [ -s "$NGINX_CHECKER_CONF" ] || return 1
    nginx -T 2> /dev/null | grep -qE '^access_checker\s+unix:/[^.]+[.]sock;$' || return 1
    local pid=''
    pid=$(pgrep -f 'nginx:\s+master') || return 1
    # pid=1 is acquired by init.
    [[ "$pid" =~ ^([2-9]|[1-9][0-9]+)$ ]] || return 1
    grep -qP '/ngx_http_access_checker_module_(\d+[.]){2}\d+[.]so$' /proc/"$pid"/maps
}

check_configs() {
    case "$MODE" in
        nginx)
            check_nginx
            ;;
        apache)
            check_apache_config
            ;;
    esac
}

has_hosting_panel(){
    local checks=(
        /usr/local/cpanel/cpanel
        /usr/sbin/plesk
        /usr/local/directadmin/custombuild/build
    )
    for i in ${checks[@]};do
        [ -e $i ] && return 0
    done
    return 1
}

count_processes(){
    local count=$(ps aux | grep -c '[i]m360:\|webshield-[s]sl-cache')
    echo $count
}

check_running(){
    # for hosts with hosting panels we expect 5 processes to be running.
    # otherwise 4 ones (ssl-cache is not expected to be run on no-panel hosts)
    local num=$(count_processes)
    local expected
    if has_hosting_panel; then expected=3; else expected=2; fi
    [ $num -ge $expected ] && return 0
    return 1
}

check_stopped(){
    local num=$(count_processes)
    [ $num -eq 0 ] && return 0
    return 1
}

enable_for_systemd(){
    systemctl enable $WAFD_UNIT || return $?
    systemctl enable $SSL_UNIT || return $?
    # Webshield should be enabled and running in standalone mode to serve all ports
    # and in Apache mode to serve non-standard HTTP/HTTPS ports like cPanel ports.
    is_nginx || systemctl enable $WEBSHIELD
}

safe_reload() {
    # WARN: In some cases 'reload-or-restart' action is not applicable
    # and need to be done directly.
    if systemctl --quiet is-active $1 ; then
        systemctl reload $1
    else
        systemctl restart $1
    fi
}

start_for_systemd(){
    systemctl start $WAFD_UNIT || return $?
    if ! systemctl start $SSL_UNIT; then
        # WARN: Ignore error cause ssl-cache will exit if there is no panel.
        has_hosting_panel && return 1 || :
    fi

    if is_nginx;then
        prepare_configs && systemctl reload $MAIN_UNIT && return 0
        cleanup_configs
        return 1
    elif is_apache;then
        # In apache mode we expect both apache and webshield are running: apache
        # for HTTP/HTTPS ports and webshield for the rest of them (like 2082/2083)
        # WARN: Sometimes Apache unit can not be reloaded and must to be restarted.
        prepare_configs && safe_reload $MAIN_UNIT && systemctl start $WEBSHIELD && return 0
        cleanup_configs
        return 1
    elif is_standalone;then
        systemctl start $MAIN_UNIT
    else
        echo "Configuration error: unknown mode: $MODE"
        return 1
    fi
}

activate_for_systemd(){
    local RV
    enable_for_systemd
    RV=$?
    [ $RV -ne 0 ] && return $RV
    start_for_systemd
}

disable_for_systemd(){
    local failed=0
    # we don't disable wafd because it's used
    # for ip-list functionality
    systemctl disable $SSL_UNIT || failed=1
    if is_standalone; then
        systemctl disable $MAIN_UNIT || failed=1
    else
        cleanup_configs || failed=1
        systemctl reload $MAIN_UNIT || failed=1
        if is_apache; then
            # Webshield is enabled in Apache mode to serve
            # non-standard HTTP/HTTPS ports like cPanel ports.
            systemctl disable $WEBSHIELD || failed=1
        fi
    fi
    return $failed
}

stop_for_systemd(){
    local failed=0
    local wafd_force=$1
    # we stop wafd only in 'restart' action, i.e stop with
    # subsequent start
    if [ "x$wafd_force" = xforce ];then
        systemctl stop $WAFD_UNIT || failed=1
    fi
    systemctl stop $SSL_UNIT || failed=1

    if is_nginx;then
        cleanup_configs || failed=1
        systemctl reload $MAIN_UNIT || failed=1
    elif is_apache;then
        # stopping in apache mode means that we unload access_checker
        # module from apache server and stop webshield
        cleanup_configs || failed=1
        systemctl reload $MAIN_UNIT || failed=1
        systemctl stop $WEBSHIELD || failed=1
    elif is_standalone;then
        systemctl stop $MAIN_UNIT || failed=1
    fi

    return $failed
}

deactivate_for_systemd(){
    local RV
    stop_for_systemd
    RV=$?
    [ $RV -ne 0 ] && return $RV
    disable_for_systemd
}

do_terminate(){
    stop_for_systemd force
    local RV0=$?
    disable_for_systemd
    local RV=$?
    # We run both command but return success only if both of them succeded
    if [ $RV -ne 0 ]; then
        return $RV
    fi
    return $RV0
}

do_enable(){
    enable_for_systemd

    local rv=$?
    if [ $rv == 0 ]; then
        echo "enabled" > $STATEFILE
    fi
    return $rv
}

do_disable(){
    disable_for_systemd

    local rv=$?
    if [ $rv == 0 ]; then
        echo "disabled" > $STATEFILE
    fi
    return $rv
}

do_start(){
    start_for_systemd

    local rv=$?
    if [ $rv == 0 ]; then
        echo "started" > $STATEFILE
    fi
    return $rv
}

do_stop(){
    local force=$1
    stop_for_systemd $force

    local rv=$?
    if [ $rv == 0 ]; then
        echo "stopped" > $STATEFILE
    fi
    return $rv
}

do_activate(){
    activate_for_systemd

    local rv=$?
    if [ $rv == 0 ]; then
        echo "activated" > $STATEFILE
    fi
    return $rv
}

do_deactivate(){
    deactivate_for_systemd

    local rv=$?
    if [ $rv == 0 ]; then
        echo "deactivated" > $STATEFILE
    fi
    return $rv
}

is_enabled(){
    # We don't care if ssl-cache unit is enabled. Webshield will start it
    # by 'Wants' dependency before its own start. If it's not required for
    # given environment (no known hosting panel) it'll exit then.
    local failed=0

    if systemctl -q is-enabled $WAFD_UNIT; then
        echo "Unit '$WAFD_UNIT' is enabled."
    else
        echo "ERROR: Unit '$WAFD_UNIT' is NOT enabled." >&2
        failed=1
    fi

    # $MAIN_UNIT is either imunify360-webshield (in standalone mode) or a host
    # webserver. It seems that there's no sense to check if host webserver is
    # enabled as we don't have control over it at any rate.
    if is_standalone; then
        if systemctl -q is-enabled $MAIN_UNIT; then
            echo "Unit '$MAIN_UNIT' is enabled."
        else
            echo "Unit '$MAIN_UNIT' is NOT enabled." >&2
            failed=1
        fi
    else
        # WARN: Webshield is always disabled for Cloudways, so its state must not be used as the result.
        is_cloudways && check_nginx || systemctl -q is-enabled $WEBSHIELD
        failed=$?
    fi

    return $failed
}

is_active(){
    if is_standalone; then
        if check_running; then
            echo "$MAIN_UNIT is running"
            return 0
        else
            echo "$MAIN_UNIT is not running"
            return 1
        fi
    fi
    local name='undefined'
    local state='undefined'
    local units=($MAIN_UNIT $WAFD_UNIT $SSL_UNIT)
    local failed=0
    for name in ${units[@]}; do
        state="$(systemctl is-active $name 2>&1)"
        if [ "$state" = "active" ]; then
            echo "Unit '$name' is active."
        else
            # WARN: ssl-cache must be processed in its very special way, see below.
            if ! [ "$name" = $SSL_UNIT ]; then
                echo "ERROR: Unit '$name' is NOT active, result='$state'." >&2
                failed=1
                continue
            fi
            if has_hosting_panel; then
                echo "ERROR: Unit '$SSL_UNIT' is NOT active, result '$state'." >&2
                failed=1
            else
                echo "WARNING: Unit '$SSL_UNIT' is NOT active, result '$state' (ignored)." >&2
            fi
        fi
    done
    if [ $failed -eq 0 ]; then
        if ! check_configs; then
            echo "ERROR: Web server is not configured properly." >&2
            failed=1
        fi
    fi
    return $failed
}

do_restart(){
    do_stop force
    do_start
}

do_enable_splashscreen(){
    is_standalone || return 0
    local ss_state=$(awk '$1 == "splashscreen_antibot" {gsub(";","",$2);print $2}' $WEBSHIELD_ANTIBOT_CONF)
    if [ "$ss_state" = on ];then
        echo "splashscreen is already enabled"
        return 0
    fi
    sed -i -e "/splashscreen_antibot/ {s/off/on/}" $WEBSHIELD_ANTIBOT_CONF
    do_restart
}

do_disable_splashscreen(){
    is_standalone || return 0
    local ss_state=$(awk '$1 == "splashscreen_antibot" {gsub(";","",$2);print $2}' $WEBSHIELD_ANTIBOT_CONF)
    if [ "$ss_state" = off ];then
        echo "splashscreen is already disabled"
        return 0
    fi
    sed -i -e "/splashscreen_antibot/ {s/on/off/}" $WEBSHIELD_ANTIBOT_CONF
    do_restart
}

do_enable_cpanelprotection(){
    is_standalone || return 0
    local cp_state=$(awk '$2 == "$cpanel_protection" {gsub(";","",$3);print $3}' $VIRTSERVER_CONF)
    if [ "$cp_state" = 1 ];then
        echo "cpanel_protection is already enabled"
        return 0
    fi
    sed -i -e '/$cpanel_protection/ {s/0/1/}' $VIRTSERVER_CONF
    do_restart
}

do_disable_cpanelprotection(){
    is_standalone || return 0
    local cp_state=$(awk '$2 == "$cpanel_protection" {gsub(";","",$3);print $3}' $VIRTSERVER_CONF)
    if [ "$cp_state" = 0 ];then
        echo "cpanel_protection is already disabled"
        return 0
    fi
    sed -i -e '/$cpanel_protection/ {s/1/0/}' $VIRTSERVER_CONF
    do_restart
}

do_reload(){
    # WARN: Ignoring errors here is for the compatibility with sysvinit script
    # which contains bug - on action "reload" it always exits with code 0.
    # try-reload-or-restart is supported only from version 229,
    # which is not the case for Centos7. The difference between try-reload-or-restart
    # and reload-or-restart is that former does nothing unless the service is running.
    if systemctl --quiet is-active $SSL_UNIT;then
        systemctl reload-or-restart $SSL_UNIT || :
    fi
    if systemctl --quiet is-active $WAFD_UNIT;then
        systemctl reload-or-restart $WAFD_UNIT || :
    fi
    if systemctl --quiet is-active $MAIN_UNIT;then
        systemctl reload-or-restart $MAIN_UNIT || :
    fi
}

# Enable standalone mode
set_standalone(){
    if ! is_standalone;then
        echo "$MODE" >| "$PREVIOUS_MODE_STATE_PATH"
        echo standalone >| "$STATE_PATH"
        do_stop force
        load_mode
        do_start
    else
        echo "Required mode is already applied. Nothing to do"
        return 1
    fi

}

# Returned to the previous state if we have state backup file
# otherwise switch to apache mode if supported
unset_standalone(){
    if is_standalone; then
        if [ -s "$PREVIOUS_MODE_STATE_PATH" ]; then
            local prev_mode=$(<"$PREVIOUS_MODE_STATE_PATH")
            if [ -n "$prev_mode" ] && [ "$prev_mode" != standalone ]; then
                echo -n "$prev_mode" >| "$STATE_PATH" || return 1
            fi
            rm -f "$PREVIOUS_MODE_STATE_PATH"
        elif is_supported; then
            echo apache >| $STATE_PATH
        fi
        do_stop force
        load_mode
        do_start
    else
        echo "Required mode is already applied. Nothing to do"
        return 1
    fi
}

configure() {
    deactivate_for_systemd || true
    systemctl unmask imunify360-webshield || true
    detect_mode 'skip-on-update'
    load_mode
    is_cloudways && systemctl mask imunify360-webshield || true
    activate_for_systemd
}

print_help(){
    echo "enable                    : enables webshield starting on boot (without actully starting it)"
    echo "is-enabled                : shows if the webshield is enabled to start on boot"
    echo "is-active                 : shows if the webshield is running now"
    echo "disable                   : disables webshield starting on boot (without actully stopping it)"
    echo "start                     : starts webshield (without enabling its starting on boot)"
    echo "stop                      : stops webshield (without disabling its starting on boot)"
    echo "activate                  : enables webshield starting on boot and starts it right away"
    echo "deactivate                : stops webshield right away and disables its starting on boot"
    echo "terminate                 : stops webshield and wafd right away and disables its starting on boot"
    echo "enable-splashscreen       : enables splashscreen functionality for webshield"
    echo "disable-splashscreen      : disables splashscreen functionality for webshield"
    echo "enable-cpanelprotection   : enables cpanelprotection functionality for webshield"
    echo "disable-cpanelprotection  : disables cpanelprotection functionality for webshield"
    echo "set-standalone            : forcibly use standalone mode"
    echo "unset-standalone          : switch from forcibly set standalone mode to the previous one (if exists)"
    echo "mode                      : shows the current module"
    echo "mode module               : if possible switch from standalone to module-based mode"
    echo "mode proxy                : if possible switch from module-based to standalone mode"
    echo "mode supported            : prints if switching to module-based mode is supported for current system"
    echo "reload                    : reload settings without restart"
}

load_mode

case "$1" in
    enable)
        do_enable
        ;;
    disable)
        do_disable
        ;;
    is-enabled)
        is_enabled
        ;;
    is-active)
        is_active
        ;;
    start)
        do_start
        ;;
    stop)
        do_stop
        ;;
    activate)
        do_activate
        ;;
    deactivate)
        do_deactivate
        ;;
    terminate)
        do_terminate
        ;;
    enable-splashscreen)
        do_enable_splashscreen
        ;;
    disable-splashscreen)
        do_disable_splashscreen
        ;;
    enable-cpanelprotection)
        do_enable_cpanelprotection
        ;;
    disable-cpanelprotection)
        do_disable_cpanelprotection
        ;;
    reload)
        do_reload
        ;;
    mode)
        if [ "$2" = module ]; then
            unset_standalone
        elif [ "$2" = proxy ]; then
            set_standalone
        elif [ "$2" = supported ]; then
            is_supported 'out'
        elif [ -z "$2" ]; then
            if [ "$MODE" = standalone ]; then
                echo proxy
            else
                echo "$MODE"
            fi
        else
            echo "Unknown mode: $2. Exit"
            exit
        fi
        ;;
    mode-proxy)
	    set_standalone
	    ;;
    mode-module)
        unset_standalone
	    ;;
    mode-supported)
	    is_supported 'out'
	    ;;
    set-standalone)
        set_standalone
        ;;
    unset-standalone)
        unset_standalone
        ;;
    detect-mode)
        detect_mode
        ;;
    is-apache)
        is_apache
        ;;
    is-nginx)
        is_nginx
        ;;
    is-cloudways)
        is_cloudways
        ;;
    is-standalone)
        is_standalone
        ;;
    configure)
        configure
        ;;
    help)
        print_help
        ;;
    *)
        echo "Usage: $0 {enable|disable|start|stop|activate|deactivate|is-enabled|is-active|enable-splashscreen|disable-splashscreen|enable-cpanelprotection|disable-cpanelprotection|reload|set_standalone|unset_standalone|mode|mode proxy|mode module|mode supported|detect-mode|is-apache|is-nginx|is-cloudways|is-standalone|help}"
        exit 2
esac

Zerion Mini Shell 1.0