#!/bin/sh
### Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved.
#

#
# Plesk script
#


### Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved.
# Migration manager tables will be managed by plesk


# echo message to product log, unless debug
p_echo()
{
    if [ -n "$PLESK_INSTALLER_DEBUG" -o -n "$PLESK_INSTALLER_VERBOSE" -o -z "$product_log" ] ; then
        echo "$@"
    else
        echo "$@" >> "$product_log" 2>&1
    fi
}

# echo message to product log without new line, unless debug
pnnl_echo()
{
    if [ -n "$PLESK_INSTALLER_DEBUG" -o -n "$PLESK_INSTALLER_VERBOSE" -o -z "$product_log" ] ; then
        echo -n "$*"
    else
        echo -n "$*" >> "$product_log" 2>&1
    fi
}

echo_try()
{
	msg="$*"
	pnnl_echo " Trying to $msg... "
}

suc()
{
	p_echo "done"
}

detect_vz()
{
	PLESK_VZ=0
	PLESK_VE_HW_NODE=0
	PLESK_VZ_TYPE=

	local issue_file="/etc/issue"
	local vzcheck_file="/proc/self/status"
	[ -f "$vzcheck_file" ] || return 1

	local env_id=`sed -ne 's|^envID\:[[:space:]]*\([[:digit:]]\+\)$|\1|p' "$vzcheck_file"`
	[ -n "$env_id" ] || return 1
	if [ "$env_id" = "0" ]; then
		# Either VZ/OpenVZ HW node or unjailed CloudLinux
		PLESK_VE_HW_NODE=1
		return 1
	fi

	if grep -q "CloudLinux" "$issue_file" >/dev/null 2>&1 ; then
		return 1
	fi

	if [ -f "/proc/vz/veredir" ]; then
		PLESK_VZ_TYPE="vz"
	elif [ -d "/proc/vz" ]; then
		PLESK_VZ_TYPE="openvz"
	fi

	PLESK_VZ=1
	return 0
}

call_optional_function()
{
	export LANG=C LC_MESSAGES=C LC_ALL=C
	local type_output="`type \"$1\" 2>/dev/null | head -n 1`"
	case "$type_output" in
		*function)
			"$@"
			;;
		*)
			return 0
			;;
	esac
}
### Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved.

#
# Support for runtime patching of shell scripts (including utilities and package scripts).
#

# --- Service functions ---

# Load and apply a patch in a relatively safe way
rp_safe_load_patch()
{
	local patch_file="$1"
	echo_try "load shell patch '$patch_file'"
	/bin/sh -n "$RP_BASEDIR/$patch_file" && 
	{
		. "$RP_BASEDIR/$patch_file"
		RP_LOADED_PATCHES="$RP_LOADED_PATCHES $patch_file"
	} &&
	suc
}

# Apply patches specific to the current context (e.g., depending on utility basename or package name)
# This is currently not implemented. This may be overriden by "spark".
rp_patch_runtime_context_specific()
{
	:
}

# --- Main entry points ---

rp_patch_runtime()
{
	# List of loaded patch files
	RP_LOADED_PATCHES=

	local RP_BASEDIR="$PRODUCT_BOOTSTRAPPER_DIR/rp"
	[ -d "$RP_BASEDIR" ] || return 0

	if [ -r "$RP_BASEDIR/spark" ]; then
		rp_safe_load_patch "spark"
	fi

	call_optional_function rp_patch_runtime_context_specific "$@"
}
### Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved.
# vim:ft=sh:

#set_params

set_common_params()
{
	common_var=0

	PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
	LANG=C
	export PATH LANG
	umask 022
	ulimit -n 65535 2>/dev/null

	K_HUP="/bin/kill -HUP"
	K_KILL="/bin/kill -KILL"
	K_TERM="/bin/kill -TERM"
	K_USR2="/bin/kill -USR2"
	K_TEST="/bin/kill -0"

	users_created=""
	groups_created=""

	certificate_file="$PRODUCT_ETC_D/httpsd.pem"
	services="/etc/services"
	mtab="/etc/mtab"
	get_hostname="hostname"
	get_domainname="domainname"

	#VZP used to determine that we're inside SVE
	vza_file="/var/vzagent"

	#default parameters
	tar="tar"
	crontab="/usr/bin/crontab"

	cp_preserve="cp -p"
	SYSTEM_RC_D=/etc/init.d
	PLESK_LIBEXEC_DIR="/usr/lib/plesk-9.0"
	PLESK_DB_DIR="/var/lib/plesk"
	POSTFIX_LIBEXEC_DIR="/usr/lib/postfix"
	PRODUCT_BOOTSTRAPPER_DIR="/usr/local/psa/bootstrapper/pp12.0.18-bootstrapper"
	AUTOGENERATED_CONFIGS="#ATTENTION!\n#\n#DO NOT MODIFY THIS FILE BECAUSE IT WAS GENERATED AUTOMATICALLY,\n#SO ALL YOUR CHANGES WILL BE LOST THE NEXT TIME THE FILE IS GENERATED.\n"
	AUTOGENERATED_CONFIGS_UPGRADE="#ATTENTION!\n#\n#DO NOT MODIFY THIS FILE BECAUSE IT WAS GENERATED AUTOMATICALLY,\n#SO ALL YOUR CHANGES WILL BE LOST AFTER YOU UPGRADE PARALLELS PLESK PANEL.\n"
	PRODUCT_LOGS_D="/var/log/plesk"

	set_common_params_linux 

	rp_patch_runtime

	detect_vz
}

set_common_params_linux()
{
	get_hostname="hostname -f"
	fstab="/etc/fstab"
	cp_preserve="cp --preserve=all --remove-destination"
	machine="linux"
	sendmail="/usr/sbin/sendmail"
	ps="ps axw"
	ps_long="ps axuw"
	false_shell="/bin/false"
	dummy_home="/"
	compress="gzip -9 -c"
	uncompress="gunzip -c"
	uudecode="uudecode -o /dev/stdout"
	ifconfig="/sbin/ifconfig -a"
	inet_str="inet addr"

	useradd_options="-r"
	if [ -f /etc/SuSE-release ]; then
		linux_distr="suse"
	elif [ -f /etc/debian_version ]; then
		linux_distr="debian"
		get_domainname="dnsdomainname"
	else
		linux_distr="redhat"
	fi

	sndml_ini="/etc/init.d/sendmail"
	mail_local="/usr/libexec/mail.local"
	if [ -x /sbin/nologin ]; then
		dummy_shell="/sbin/nologin"
	else
		dummy_shell="/bin/false"
	fi
	bash_shell="/bin/bash"
	rbash_shell="/bin/rbash"
	uudecode_full="/usr/bin/uudecode"
	named_osrelease=`cat /proc/sys/kernel/osrelease | perl -F"/[.-]/" -n -a  -e 'printf "%02u%02u%02u\n", $F[0],$F[1],$F[2]'`

	return 0
}
### Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved.

modsec_list_rulesets() {
	local dir="$1"
	local enabled_only="$2"
	local rules_conf_path="$3"
	if [ -z "$enabled_only" ]; then
		cd "$dir" || { echo >&2 "Unable to cd into '$dir'"; return 1; }
		ls | grep -v '\.backup$' | egrep -v '\.saved-[0-9]+'
		cd - >/dev/null 2>&1
	else
		[ -r "$rules_conf_path" ] || return 0
		local dir_len=`echo "$dir//" | wc -L`
		cat "$rules_conf_path" | grep --only-matching "$dir/[^/]\+" | cut -c "${dir_len}-" | sort | uniq
	fi
}

modsec_list_configs() {
	local dir="$1"
	local enabled_only="$2"
	local rules_conf_path="$3"
	local ruleset="$4"

	if [ -z "$enabled_only" ]; then
		local ret=0
		cd "$dir" || { echo >&2 "Unable to cd into '$dir'"; return 1; }
		if [ -z "$ruleset" ]; then
			modsec_list_rulesets "$dir" "" "$rules_conf_path" | xargs -I{} -n 1 find {} -name \*.conf | sort
		else
			if ls "$ruleset" > /dev/null 2>&1; then
				find "$ruleset" -name \*.conf | sort
			else
				echo >&2 "No such ruleset: $ruleset"
				ret=1
			fi
		fi
		cd - >/dev/null 2>&1
		return $ret
	else
		[ -r "$rules_conf_path" ] || return 0
		local dir_ruleset="${dir}${ruleset:+/$ruleset}"
		local dir_len=`echo "$dir//" | wc -L`
		if [ "$enabled_only" = "raw" ]; then
			cat "$rules_conf_path" | grep -o "^[^#]*" | grep -o "$dir_ruleset/.*\.conf" | cut -c ${dir_len}-
		else
			for c in `cat "$rules_conf_path" | grep -o "^[^#]*" | grep -o "$dir_ruleset/.*\.conf"`; do
				echo $c | cut -c ${dir_len}-
				modsec_list_configs "$dir" "$enabled_only" "$c"
			done
		fi
	fi
}
### Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved.

# constants
PN=`basename $0`
RULES_CONFIG="/etc/apache2/modsecurity.d/zz_rules.conf"
RULES_BASE_DIR="/etc/apache2/modsecurity.d/rules"
HTTPD_MODULES_CTL="/usr/local/psa//admin/sbin/httpd_modules_ctl"
DEFAULT_RULESET="modsecurity_crs-plesk"

set_common_params

usage() {
	cat << EOT
Usage: modsecurity_ctl [COMMANDS] [OPTIONS]...

commands:
    -e, --enable                 enable plesk modsecurity backend
    -d, --disable                disable plesk modsecurity backend
    -s, --status                 show status of modsecurity backend

    -E, --enable-ruleset         enable specified rule sets
    -l, --list-rules             list separate rules (one rule for each .conf file)
    -L, --list-rulesets          list rule sets
    -t, --list-tags              list tags
    -i, --install                install new rule set
    -u, --uninstall              uninstall rule set
    --rollback                   rollback rule set on previous state
    --enable-only-rules          enable specified rules
    -D, --disable-all-rules      disable all rules

    -B, --rules-base-dir         show base directory for rule sets

    -h, --help                   show this help

options:
    -a, --archive-path <PATH>    path to archive contained rule set
    -r, --rule <CONF>            rule to be enabled by --enable-rules command (can be specified several times)
    -R, --ruleset <RULESET>          rule set for --install, --enable-ruleset and --uninstall commands
                                 possible values: crs, atomic, comodo, custom.
    --with-backup                backup rule set before installing new rule set
    --enabled                    list only enabled rules or rulesets (for --list-rules, --list-sets and --list-tags commands)
    --enabled-raw                display list of includes as-is (for --list-rules)

examples:
    Install and enable custom rule set, backup previous custom set if exists:
    # modsecurity_ctl --install --with-backup --enable-ruleset --ruleset custom --archive-path <PATH>

    Install (download and unpack) comodo rule set, backup previous comodo set if exists:
    # env MODSEC_VENDOR_LOGIN=<login> MODSEC_VENDOR_PASS=<pass> modsecurity_ctl --install --with-backup --ruleset comodo

    Rollback custom rule set and enable previous custom configuration if exists:
    # modsecurity_ctl --rollback --ruleset custom
EOT
}

modsecurity_enable() {
# enable apache modules mod_security and unique_id
	"$HTTPD_MODULES_CTL" --enable security2,unique_id
}

modsecurity_disable() {
# disable mod_security module
	"$HTTPD_MODULES_CTL" --disable security2
}

modsecurity_status() {
	local modules_list
	modules_list=`"$HTTPD_MODULES_CTL" --status --all-modules`
	modsecurity_loaded=`echo "$modules_list" | grep "security2 on"`
	unique_id_loaded=`echo "$modules_list" | grep "unique_id on"`
	if [ -n "$modsecurity_loaded" -a -n "$unique_id_loaded" ]; then
		echo "Enabled"
	else
		echo "Disabled"
	fi
}

unpack_zip_archive() {
	local archive="$1"
	local target_dir="$2"
	unzip -j -d "$target_dir" "$archive"
}

unpack_tarxx_archive() {
	local archive="$1"
	local target_dir="$2"
	tar -C "$target_dir" -x -f "$archive"
}

install_config() {
	local config="$1"
	local target_dir="$2"
	cp -f "$config" "$target_dir/$config"
}

install_archive() {
	local archive="$1"
	local base_dir="$2"
	local target_name="$3"
	local type="$4"

	if [ -z "$type" ]; then
# detect archive type
		if expr match "$archive" '.*\.zip' >/dev/null ; then
			type="zip"
		elif expr match "$archive" '.*\.tar\.gz' >/dev/null ; then
			type="tar.gz"
		elif expr match "$archive" '.*\.tgz' >/dev/null ; then
			type="tgz"
		elif expr match "$archive" '.*\.tar\.bz2' >/dev/null ; then
			type="tar.bz2"
		elif expr match "$archive" '.*\.conf' >/dev/null ; then
			type="conf"
		else
			echo >&2 "Unknown archive type: '$archive'"
			return 1
		fi
	fi

	if [ -z "$target_name" ]; then
# get filename
		filename=`basename "$archive" ".$type"`
# create target dir
		target_dir=`mktemp -d "$base_dir/$filename.XXXX"`
	else
		target_dir="$base_dir/$target_name"
		rm -rf "$target_dir"
		mkdir -p "$target_dir"
	fi
# extract
	case "$type" in
		zip) unpack_zip_archive "$archive" "$target_dir";;
		tar.gz|tar.bz2|tgz) unpack_tarxx_archive "$archive" "$target_dir";;
		conf) install_config "$archive" "$target_dir";;
		*) echo >&2 "Unknown archive type: '$archive'"; exit 1;;
	esac
}

uninstall_ruleset() {
	local ruleset="$1"
	local base_dir="$2"
	rm -rf "$base_dir/$ruleset"
}

enable_ruleset() {
	local rules_conf_path="$1"
	local base_dir="$2"
	local ruleset="$3"
	enable_only_configs "$rules_conf_path" "$base_dir" "$ruleset/*.conf"
}

enable_only_configs() {
	local rules_conf_path="$1"
	local base_dir="$2"
	local configs="$3"

	printf "$AUTOGENERATED_CONFIGS\n" > "$rules_conf_path"
	local old_ifs="$IFS"
	IFS="
"
	for conf in "$configs"; do
		process_init_config_template `dirname "$base_dir/$conf"`
		local conf_trimmed=`echo "$conf" | sed -e 's/\s*$//g' -e 's/^\s*//g'`
		[ -z "$conf_trimmed" ] || echo "Include \"$base_dir/$conf_trimmed\"" >> "$rules_conf_path"
	done
}

process_init_config_template()
{
	local dir="$1"
	local init_conf="$dir/plesk_init.conf"
	if [ -r "${init_conf}.tpl" ]; then
		printf "$AUTOGENERATED_CONFIGS\n" > "${init_conf}"
		sed -e "s#@ruleset_base_dir@#$dir#g" < "${init_conf}.tpl" >> "${init_conf}"
	fi
}

list_tags() {
	local dir="$1"
	local enabled_only="$2"
	local rules_conf_path="$3"
	cd "$dir" || { echo >&2 "Unable to cd into '$dir'"; return 1; }
	egrep -o "tag:'([^']*)'" `modsec_list_configs "$dir" "$enabled_only" "$rules_conf_path"` | cut -d"'" -f2 | sort | uniq
	cd - >/dev/null 2>&1
}

backup_rules_conf() {
	local rules_conf_path="$1"
	local rules_conf_backup="${rules_conf_path}.backup"
	[ ! -e "$rules_conf_path" ] || cp -f "$rules_conf_path" "$rules_conf_backup"
}

rollback_rules_conf() {
	local rules_conf_path="$1"
	local rules_conf_backup="${rules_conf_path}.backup"
	[ ! -e "$rules_conf_backup" ] || cp -f "$rules_conf_backup" "$rules_conf_path"
}

backup_ruleset() {
	local dir="$1"
	local backup_dir="${dir}.backup"
	if [ ! -d "$dir" ]; then
		echo >&2 "Unable to backup: directory doesn't exist: '$dir'"
		return 1
	fi
	if [ -e "$backup_dir" ]; then
		rm -rf "$backup_dir"
	fi
	cp -r "$dir" "$backup_dir"
}

rollback_ruleset() {
	local base_dir="$1"
	local ruleset="$2"
	local rules_conf_path="$3"
	rm -f "$rules_conf_path" # disable ruleset
	local ruleset_dir="$base_dir/$ruleset"
	local backup_dir="${ruleset_dir}.backup"
	[ ! -d "${ruleset_dir}.new" ] || rm -rf "${ruleset_dir}.new"
	[ ! -d "$ruleset_dir" ] || mv "$ruleset_dir" "${ruleset_dir}.new"
	[ ! -d "$backup_dir" ] || mv "$backup_dir" "$ruleset_dir"
}

#parse options
TEMP=`getopt -o edsElLtiua:r:R:BDh --long \
	enable,disable,status,enable-ruleset,list-rules,list-rulesets,list-tags,install,uninstall,enable-only-rules,rules-base-dir,archive-path:,rule:,ruleset:,with-backup,rollback,enabled,enabled-raw,disable-all-rules,help \
     -n "$PN" -- "$@"`

if [ $? != 0 ] ; then echo "Inrernal error!" >&2 ; exit 1 ; fi
eval set -- "$TEMP"

opt_enable=0
opt_disable=0
opt_status=0
opt_enable_ruleset=0
opt_list_rules=0
opt_list_sets=0
opt_list_tags=0
opt_install=0
opt_uninstall=0
opt_enable_only_rules=0
opt_disable_all_rules=0
opt_rollback=0
opt_archive_path=
opt_ruleset=
opt_rules=
opt_with_backup=0
opt_rules_base_dir=0


while true ; do
	case "$1" in
		-e|--enable) opt_enable=1; shift;;
		-d|--disable) opt_disable=1; shift;;
		-s|--status) opt_status=1; shift;;
		-E|--enable-ruleset) opt_enable_ruleset=1; shift;;
		-l|--list-rules) opt_list_rules=1; shift;;
		-L|--list-rulesets) opt_list_sets=1; shift;;
		-t|--list-tags) opt_list_tags=1; shift;;
		-i|--install) opt_install=1; shift;;
		-u|--uninstall) opt_uninstall=1; shift;;
		--rollback) opt_rollback=1; shift;;
		--enable-only-rules) opt_enable_only_rules=1; shift;;
		-D|--disable-all-rules) opt_disable_all_rules=1; shift;;
		-B|--rules-base-dir) opt_rules_base_dir=1; shift;;
		-a|--archive-path) opt_archive_path="$2"; shift; shift;;
		-r|--rule)
			if [ -z "$opt_rules" ]; then
				opt_rules="$2"
			else
				opt_rules="$opt_rules
$2"
				opt_rules=`echo "$opt_rules" | sed 's/^\s*//g'`
				shift 2
			fi;;
		-R|--ruleset) opt_ruleset="$2"; [ ! "$opt_ruleset" = "crs" ] || opt_ruleset="$DEFAULT_RULESET"; shift; shift;;
		--with-backup) opt_with_backup=1; shift;;
		--enabled) opt_enabled="enabled_only"; shift;;
		--enabled-raw) opt_enabled="raw"; shift;;
		-h|--help) usage; exit 0;;
		--) shift ; break ;;
		*) echo "Internal error!" ; exit 1 ;;
	esac
done

if [ "$opt_enable" = "1" ]; then
	modsecurity_enable
elif [ "$opt_disable" = "1" ]; then
	modsecurity_disable
elif [ "$opt_status" = "1" ]; then
	modsecurity_status
	exit $?
elif [ "$opt_install" = "1" ]; then
	if [ -z "$opt_ruleset" ]; then
		echo >&2 "--ruleset is not specified"
		exit 1
	fi

	rm -rf "$RULES_BASE_DIR/${opt_ruleset}.new"

	if [ "$opt_with_backup" = "1" -a -d "$RULES_BASE_DIR/$opt_ruleset" ]; then
		backup_ruleset "$RULES_BASE_DIR/$opt_ruleset" || exit 1
	fi

	archive_type=
	if [ "$opt_ruleset" = "comodo" -o "$opt_ruleset" = "atomic" -o "$opt_ruleset" = "tortix" ]; then
		archive_type="tar.gz"
		temp_archive_path=`mktemp /tmp/vendor_rule_set.tar.gz.XXXXXXXX`
		"$PLESK_LIBEXEC_DIR/modsecurity_get_${opt_ruleset}_ruleset" "$temp_archive_path" "$RULES_BASE_DIR/$opt_ruleset"
		[ "$?" = "0" ] || { echo >&2 "Unable to download $opt_ruleset rule set"; rm -f "$temp_archive_path"; exit 1; }
		opt_archive_path="$temp_archive_path"
	fi

	[ -n "$opt_archive_path" ] || { echo >&2 "--archive-path is not specified"; exit 1; }
	[ ! -s "$opt_archive_path" ] || install_archive "$opt_archive_path" "$RULES_BASE_DIR" "$opt_ruleset" "$archive_type"
	[ -z "$temp_archive_path" ] || rm -f "$temp_archive_path"

	if [ "$opt_enable_ruleset" = "1" ]; then
		backup_rules_conf "$RULES_CONFIG"
		enable_ruleset "$RULES_CONFIG" "$RULES_BASE_DIR" "$opt_ruleset"
	fi
elif [ "$opt_rollback" = "1" ]; then
	if [ -z "$opt_ruleset" ]; then
		echo >&2 "--ruleset is not specified"
		exit 1
	fi
	rollback_ruleset "$RULES_BASE_DIR" "$opt_ruleset" "$RULES_CONFIG"
	rollback_rules_conf "$RULES_CONFIG"
elif [ "$opt_enable_ruleset" = "1" ]; then
	if [ -z "$opt_ruleset" ]; then
		echo >&2 "--ruleset is not specified"
		exit 1
	fi
	backup_rules_conf "$RULES_CONFIG"
	enable_ruleset "$RULES_CONFIG" "$RULES_BASE_DIR" "$opt_ruleset"
elif [ "$opt_uninstall" = "1" ]; then
	if [ -z "$opt_ruleset" ]; then
		echo >&2 "--ruleset is not specified"
		exit 1
	fi
	uninstall_ruleset "$opt_ruleset" "$RULES_BASE_DIR"
elif [ "$opt_enable_only_rules" = "1" ]; then
	backup_rules_conf "$RULES_CONFIG"
	enable_only_configs "$RULES_CONFIG" "$RULES_BASE_DIR" "$opt_rules"
elif [ "$opt_disable_all_rules" = "1" ]; then
	backup_rules_conf "$RULES_CONFIG"
	enable_only_configs "$RULES_CONFIG" "$RULES_BASE_DIR"
elif [ "$opt_list_rules" = "1" ]; then
	modsec_list_configs "$RULES_BASE_DIR" "$opt_enabled" "$RULES_CONFIG" "$opt_ruleset"
elif [ "$opt_list_sets" = "1" ]; then
	modsec_list_rulesets "$RULES_BASE_DIR" "$opt_enabled" "$RULES_CONFIG"
elif [ "$opt_list_tags" = "1" ]; then
	list_tags "$RULES_BASE_DIR" "$opt_enabled" "$RULES_CONFIG"
elif [ "$opt_rules_base_dir" = "1" ]; then
	echo "$RULES_BASE_DIR"
else
	usage
fi

# vim: ft=sh
