#!/usr/bin/env bash

# Version No. 2
# Last Update : 12/09/2025 (DD/MM/YYYY)

clear 

# --------------------------------------------------------------------------- #
# Prerequisites:

# Retrieve the server's IPv4 address
# If you have several IPv4, uncomment and specify the one you want WordPress to use.
# Then comment out the other line
# IPV4="12.23.34.45"  
IPV4=$(ipadm show-addr -p -o state,addr | awk -F: '$1=="ok"{split($2,a,"/"); if(a[1]!~/^127\./){print a[1]; exit}}')

# Check internet connectivity
check_wp() {
  /usr/sbin/ping 1.1.1.1 2 >/dev/null 2>&1 || return 1           # IP OK ?
  /usr/bin/getent hosts wordpress.org >/dev/null 2>&1 || return 2 # DNS OK ?
  curl -Is --max-time 5 https://wordpress.org/latest.zip >/dev/null 2>&1 || return 3 # HTTP OK ?
  return 0
}

if check_wp; then
  echo "-----"
  echo "| OK: access to wordpress.org is fine, I can get to work!" ; echo
else
  case $? in
    1) echo "KO: no IP access: I'm outta here!";;
    2) echo "KO: broken DNS: Ever heard of a directory?!";;
    3) echo "KO: wordpress.org unreachable: What do you take me for, some kind of idiot ? You 23glzne'fzizvzmenv moron!! :-D";;
  esac
  exit 1
fi
check_wp

# Retrieve the server's IPv4
# Determine the IP and the HTTPS URL
: "${IPV4:=$(ipadm show-addr -p -o state,addr | awk -F: '$1=="ok"{split($2,a,"/"); if(a[1]!~/^127\./){print a[1]; exit}}')}"
HOMEURL="https://$IPV4"


# --------------------------------------------------------------------------- #
# Install the packages

# Refresh the catalog
pkg refresh --full

# Install the required programs
pkg install \
  pkg:/web/server/apache-24 \
  pkg:/web/php-84 \
  pkg:/database/mariadb-114 \
  pkg:/database/mariadb-114/tests


# --------------------------------------------------------------------------- #
# MariaDB

wpdb=$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 8; echo;)
wpdbuser=$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 16; echo;)
wpdbuserpwd=$(LC_ALL=C tr -dc 'A-Za-z0-9!@#$%^*()_+=' </dev/urandom | head -c 32; echo)
MDBIN="/usr/mariadb/11.4/bin"
sock="${sock:-/tmp/mariadb114.sock}"

svcadm enable -s svc:/application/database/mariadb:version_114

MAX_WAIT=90

# Wait for the socket to be created
i=0; until [ -S "$sock" ] || [ $i -ge $MAX_WAIT ]; do sleep 1; ((i++)); done; [ $i -ge $MAX_WAIT ] && { echo "Timeout: $sock"; exit 1; }

# Wait for the server to respond via the socket
i=0; until "$MDBIN/mariadb-admin" --protocol=SOCKET --socket="$sock" -uroot ping --silent >/dev/null 2>&1; do ((i++)); [ $i -ge $MAX_WAIT ] && { echo "Timeout: mariadb, where are you ? $sock"; exit 1; }; sleep 1; done

# Create the database and the privileged user (still via the socket)
"$MDBIN/mariadb" --protocol=SOCKET --socket="$sock" -uroot <<SQL
CREATE DATABASE IF NOT EXISTS \`$wpdb\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER IF NOT EXISTS '$wpdbuser'@'localhost' IDENTIFIED BY '$wpdbuserpwd';
GRANT ALL ON \`$wpdb\`.* TO '$wpdbuser'@'localhost';
FLUSH PRIVILEGES;
SQL

# Block MariaDB from listening on the network, restrict communication to the socket only
cat >/etc/mariadb/11.4/my.cnf.d/99-socket-only.cnf <<'EOF'
[mysqld]
skip-networking
EOF
svcadm restart svc:/application/database/mariadb:version_114


# --------------------------------------------------------------------------- #
# WordPress

# Download and install into Apache's default DocumentRoot
curl --proto '=https' --tlsv1.2 -fSL --retry 3 --retry-connrefused \
  -o /tmp/latest.zip https://wordpress.org/latest.zip
unzip -q /tmp/latest.zip -d /tmp/
rm -r /var/apache2/2.4/htdocs/*
mv /tmp/wordpress/* /var/apache2/2.4/htdocs/
rm /tmp/latest.zip
rm -r /tmp/wordpress

# Set properties & permissions (mandatory for WP updates)
DOCROOT=/var/apache2/2.4/htdocs
chown -R webservd:webservd "$DOCROOT"
find "$DOCROOT" -type d -exec chmod 2755 {} \;
find "$DOCROOT" -type f -exec chmod 644 {} \;

mkdir -p "$DOCROOT/wp-content/"{uploads,languages,plugins,themes}
chown -R webservd:webservd "$DOCROOT/wp-content"
find "$DOCROOT/wp-content" -type d -exec chmod 2775 {} \;
find "$DOCROOT/wp-content" -type f -exec chmod 664 {} \;

# Random table prefix (e.g. wp_ab12cd_)
newrand=$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom | head -c 6)
newprefix="wp_${newrand}_"

# Fetch fresh new WordPress SALTS!
WP_SALTS="$(
  if [ -x /usr/php/8.4/bin/php ]; then
    /usr/php/8.4/bin/php -r 'echo @file_get_contents("https://api.wordpress.org/secret-key/1.1/salt/");'
  elif command -v curl >/dev/null 2>&1; then
    curl -fsSL https://api.wordpress.org/secret-key/1.1/salt/
  elif command -v wget >/dev/null 2>&1; then
    wget -qO- https://api.wordpress.org/secret-key/1.1/salt/
  fi
)"

# Write the final wp-config (placed outside docroot: /var/apache2/2.4/)
cat >/var/apache2/2.4/wp-config.php <<EOF
<?php
/**
 * wp-config.php generated by script (OpenIndiana)
 * - MariaDB via local socket
 * - HTTPS forced on ${HOMEURL}
 * - FS_METHOD=direct
 * - Image editor : GD instead of Imagick (no Imagick on OpenIndiana)
 */

// === SQL DB ===
define('DB_NAME',     '${wpdb}');
define('DB_USER',     '${wpdbuser}');
define('DB_PASSWORD', '${wpdbuserpwd}');
define('DB_HOST',     'localhost:/tmp/mariadb114.sock'); // socket local
define('DB_CHARSET',  'utf8mb4');
define('DB_COLLATE',  '');

// === URL ===
define('WP_HOME',    '${HOMEURL}');
define('WP_SITEURL', '${HOMEURL}');

// === Security / Updates ===
define('FS_METHOD',          'direct');
define('DISALLOW_FILE_EDIT', true);

// === Salts & Keys ===
${WP_SALTS:-"/* SALTS not retrieved (no php/curl/wget) — don't forget to add them manually) */"}

// === Table Prefix ===
\$table_prefix = '${newprefix}';

// === Image editor : GD ===
define('WP_IMAGE_EDITORS', ['WP_Image_Editor_GD']);

// === DEBUG ===
define('WP_DEBUG', false);

// === Bootstrap WordPress ===
// ABSPATH is normally defined by wp-load.php BEFORE including this file  
// We keep the safeguard in case wp-config.php is loaded directly.  
if ( ! defined('ABSPATH') ) {
    define('ABSPATH', __DIR__ . '/');
}
require_once ABSPATH . 'wp-settings.php';
EOF

# Recommended rights/ownership for wp-config (outside docroot)
chown root:webservd /var/apache2/2.4/wp-config.php
chmod 640            /var/apache2/2.4/wp-config.php


# --------------------------------------------------------------------------- #
# PHP

# Enable classic extensions
# curl, mbstring, zip, exif, openssl, mysqli and opcache are already enabled

# Prevent PHP extension tests (imagick is the only missing extension, it doesn't exist on OI)
mkdir -p /var/apache2/2.4/htdocs/wp-content/mu-plugins
cat >/var/apache2/2.4/htdocs/wp-content/mu-plugins/disable-site-health-php-extensions.php <<'PHP'
<?php
add_filter('site_status_tests', function($tests){
    if (isset($tests['direct']['php_extensions'])) {
        unset($tests['direct']['php_extensions']);
    }
    if (isset($tests['async']['php_extensions'])) {
        unset($tests['async']['php_extensions']);
    }
    return $tests;
});
PHP
chown webservd:webservd /var/apache2/2.4/htdocs/wp-content/mu-plugins/disable-site-health-php-extensions.php
chmod 644 /var/apache2/2.4/htdocs/wp-content/mu-plugins/disable-site-health-php-extensions.php


# --------------------------------------------------------------------------- #
# TLS

# Create the TLS certificate
umask 027
mkdir -p /etc/apache2/2.4/TLS/{private,certs}
openssl genrsa 4096 > /etc/apache2/2.4/TLS/private/apache.key
chmod 640 /etc/apache2/2.4/TLS/private/apache.key
chown root:webservd /etc/apache2/2.4/TLS/private/apache.key
openssl req -new -x509 -sha256 -days 365 \
  -subj "/C=FR/O=Test/CN=$IPV4" \
  -addext "subjectAltName=IP:$IPV4,IP:127.0.0.1,DNS:localhost" \
  -key /etc/apache2/2.4/TLS/private/apache.key \
  -out /etc/apache2/2.4/TLS/certs/apache.crt
chmod 644 /etc/apache2/2.4/TLS/certs/apache.crt


# --------------------------------------------------------------------------- #
# Apache

# Fix permission issues on Apache logs
mkdir -p /var/apache2/2.4/logs && chown -R webservd:webservd /var/apache2/2.4/logs && chmod 0750 /var/apache2/2.4/logs
: >/var/apache2/2.4/logs/error_log; : >/var/apache2/2.4/logs/access_log; chown webservd:webservd /var/apache2/2.4/logs/error_log /var/apache2/2.4/logs/access_log

# Create a folder containing all the configuration
mkdir /etc/apache2/2.4/conf.d

cat >/etc/apache2/2.4/conf.d/00-load-cache.conf <<'EOF'
LoadModule cache_module       libexec/mod_cache.so
LoadModule cache_disk_module  libexec/mod_cache_disk.so
EOF

cat >/etc/apache2/2.4/conf.d/00-load-ssl-fpm.conf <<'EOF'
LoadModule socache_shmcb_module libexec/mod_socache_shmcb.so
LoadModule ssl_module           libexec/mod_ssl.so
LoadModule proxy_module         libexec/mod_proxy.so
LoadModule proxy_fcgi_module    libexec/mod_proxy_fcgi.so
LoadModule rewrite_module       libexec/mod_rewrite.so
EOF

cat >/etc/apache2/2.4/conf.d/02-headers-expires.conf <<'EOF'
# Headers for CSS/JS/images/fonts
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresDefault "access plus 1 hour"

    ExpiresByType text/css                    "access plus 1 year"
    ExpiresByType application/javascript      "access plus 1 year"
    ExpiresByType application/font-woff2      "access plus 1 year"
    ExpiresByType image/svg+xml               "access plus 1 year"
    ExpiresByType image/png                   "access plus 1 year"
    ExpiresByType image/jpeg                  "access plus 1 year"
    ExpiresByType image/gif                   "access plus 1 year"
</IfModule>

<IfModule mod_headers.c>
    <FilesMatch "\.(css|js|woff2?|svg|png|jpe?g|gif)$">
        Header merge Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>
</IfModule>
EOF

cat >/etc/apache2/2.4/conf.d/06-auth-to-fpm.conf <<'EOF'
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP:Authorization} .
    RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
</IfModule>

# Belt and suspenders (useful with certain FCGI paths)
<IfModule mod_setenvif.c>
    SetEnvIfNoCase Authorization "^(.*)$" HTTP_AUTHORIZATION=$1
</IfModule>
EOF

cat >/etc/apache2/2.4/conf.d/20-cache.conf <<'EOF'
# Disk cache directory and options
CacheRoot "/var/apache2/2.4/cache"
CacheQuickHandler On
CacheLock On
CacheIgnoreHeaders Set-Cookie
CacheIgnoreNoLastMod On
CacheStorePrivate On
CacheStoreExpired On
CacheDirLevels 2
CacheDirLength 1

# Enable HTML cache everywhere, except admin/login  
CacheEnable disk "/"
CacheDisable "/wp-admin"
CacheDisable "/wp-admin/admin-ajax.php"
CacheDisable "/wp-login.php"
CacheDisable "/wp-json/"
CacheDefaultExpire 300
CacheMaxExpire     3600

# Global headers (WordPress detection)
<IfModule mod_headers.c>
    Header always unset X-Cache-Enabled
    Header always set   X-Cache-Enabled "true"
</IfModule>

# Mark requests WITHOUT cookies as ANON
<Location "/">
    SetEnvIfNoCase Cookie "wordpress_logged_in" skipcache
    SetEnvIfNoCase Cookie "comment_author"      skipcache
    SetEnvIfNoCase Request_Method "POST"        skipcache
    SetEnvIfNoCase Authorization ".+"           skipcache

    <IfModule mod_headers.c>
        Header always set Cache-Control "private, no-store" env=skipcache
    </IfModule>

    # Mark requests WITHOUT cookies as ANON
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{HTTP:Cookie} !.
        RewriteRule ^ - [E=ANON:1]
    </IfModule>

    # Anonymous HTML → client-side cache 5 min
    <IfModule mod_headers.c>
        Header always set Cache-Control "public, max-age=300" env=ANON
    </IfModule>
</Location>

# Force no-store for login/admin (GET and POST)
<LocationMatch "^/(wp-login\.php|wp-admin/)">
    <IfModule mod_headers.c>
        Header always set Cache-Control "private, no-store"
    </IfModule>
</LocationMatch>
EOF


cat >/etc/apache2/2.4/conf.d/redirect-https.conf <<'EOF'
<VirtualHost *:80>
    ServerName _default_
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteRule ^/(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
</VirtualHost>
EOF

cat >/etc/apache2/2.4/conf.d/ssl.conf <<'EOF'
Listen 443
SSLCryptoDevice builtin
SSLSessionCache        "shmcb:/var/run/apache2/2.4/ssl_scache(512000)"
SSLSessionCacheTimeout 300

<VirtualHost *:443>
    ServerName localhost
    ServerAdmin you@example.com

    DocumentRoot "/var/apache2/2.4/htdocs"
    <Directory "/var/apache2/2.4/htdocs">
        Require all granted
		AllowOverride All
		Options FollowSymLinks
    </Directory>

    ErrorLog  "/var/apache2/2.4/logs/error_log"
    CustomLog "/var/apache2/2.4/logs/ssl_access_log"   combined
    CustomLog "/var/apache2/2.4/logs/ssl_request_log" "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"

    SSLEngine on
    SSLCertificateFile    "/etc/apache2/2.4/TLS/certs/apache.crt"
    SSLCertificateKeyFile "/etc/apache2/2.4/TLS/private/apache.key"

    SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite          HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES
    SSLProxyCipherSuite     HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES
    SSLHonorCipherOrder     off

    # PHP-FPM (FPM sur 127.0.0.1:9000)
    <FilesMatch \.php$>
        SetHandler "proxy:fcgi://127.0.0.1:9000"
    </FilesMatch>
    DirectoryIndex index.php index.html
</VirtualHost>
EOF

cat >/etc/apache2/2.4/conf.d/wordpress-hardening.conf <<'EOF'
<Directory "/var/apache2/2.4/htdocs/wp-content/uploads">
    <FilesMatch "\.ph(p[0-9]?|t|tml)$">
        Require all denied
    </FilesMatch>
</Directory>
EOF

# Prepare the cache directory  
mkdir -p /var/apache2/2.4/cache
chown -R webservd:webservd /var/apache2/2.4/cache
chmod 750 /var/apache2/2.4/cache


# --------------------------------------------------------------------------- #
# Start the services  

svcadm enable -s svc:/network/php-fpm-84:default
svcadm refresh svc:/network/http:apache24
svcadm enable -s svc:/network/http:apache24


# --------------------------------------------------------------------------- #
# Summary

# Generate examples for the WordPress admin user
adminwp=$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 16; echo;)
adminwppwd=$(LC_ALL=C tr -dc 'A-Za-z0-9!@#$%^&*()_+=' </dev/urandom | head -c 32; echo)

clear
cat <<EOF
# --------------------------------------------------------------------------- #
# Summary  :

# The installation is complete!

# MariaDB database for WordPress      : $wpdb
# Privileged user on the database     : $wpdbuser
# User's password                     : $wpdbuserpwd

# WordPress is accessible at          : https://$IPV4/

# Above all, NEVER use 'admin' as the WordPress administrator login
# Use random combinations instead. Example:
# User         : $adminwp
# Password     : $adminwppwd

EOF


# --------------------------------------------------------------------------- #
# Apache WAF (ModSecurity + OWASP CRS v4 + Plugin WordPress)

echo -n "Do you want to configure Apache ModSecurity? [Y/n] "
read -r reply

case "$reply" in
    [Nn])
        echo "OK: Good bye !" ; echo
		exit 0
        ;;
esac
echo

pkg install -v web/server/apache-24/module/apache-security pkg:/archiver/gnu-tar

CRS_VER="${CRS_VER:-4.18.0}"
CRS_DIR="/etc/apache2/2.4/modsecurity.d/crs"
PLUG_DIR="${CRS_DIR}/plugins"
WPPLUG_VER="${WPPLUG_VER:-1.1.0}"

# Install CRS v4
mkdir -p "$CRS_DIR"
curl -fsSL -o /tmp/crs.tar.gz "https://github.com/coreruleset/coreruleset/archive/refs/tags/v${CRS_VER}.tar.gz"
gtar -xzf /tmp/crs.tar.gz -C /tmp/
rm -rf "${CRS_DIR:?}"/*
/bin/mv "/tmp/coreruleset-${CRS_VER}/"* "$CRS_DIR/"
rm -rf "/tmp/coreruleset-${CRS_VER}" /tmp/crs.tar.gz
[ -f "${CRS_DIR}/crs-setup.conf" ] || cp "${CRS_DIR}/crs-setup.conf.example" "${CRS_DIR}/crs-setup.conf"

# WordPress exclusion plugin (CRS v4)
mkdir -p "$PLUG_DIR"
TMP="/tmp/wpcrs-plugin"; rm -rf "$TMP"; mkdir -p "$TMP"
curl -fsSL -o "$TMP/wpplug.tar.gz" "https://github.com/coreruleset/wordpress-rule-exclusions-plugin/archive/refs/tags/v${WPPLUG_VER}.tar.gz"
gtar -xzf "$TMP/wpplug.tar.gz" -C "$TMP"
/bin/cp -f "$TMP/wordpress-rule-exclusions-plugin-${WPPLUG_VER}/plugins/"*.conf "$PLUG_DIR"/
rm -rf "$TMP"

mkdir -p /var/apache2/2.4/modsec/tmp
chown -R webservd:webservd /var/apache2/2.4/modsec
chmod 750 /var/apache2/2.4/modsec /var/apache2/2.4/modsec/tmp

# Main ModSecurity configuration (hardened, engine initially in DetectionOnly mode)
CONF="/etc/apache2/2.4/conf.d/security2.conf"
cat >"$CONF" <<'SEC'
<IfModule !unique_id_module>
LoadModule unique_id_module libexec/mod_unique_id.so
</IfModule>
LoadModule security2_module libexec/mod_security2.so

<IfModule mod_security2.c>
  SecRuleEngine DetectionOnly

  SecRequestBodyAccess On
  SecRequestBodyLimit           134217728
  SecRequestBodyNoFilesLimit    1048576
  SecRequestBodyInMemoryLimit   131072
  SecRequestBodyLimitAction     Reject
  SecRequestBodyJsonDepthLimit  512

  SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \
       "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
  SecRule REQUEST_HEADERS:Content-Type "^application/json" \
       "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"

  SecRule REQBODY_ERROR "!@eq 0" \
    "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',severity:2"
  SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
    "id:'200003',phase:2,t:none,log,deny,status:400,msg:'Multipart strict validation failed'"
  SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \
    "id:'200004',phase:2,t:none,log,deny,msg:'Multipart unmatched boundary'"

  SecPcreMatchLimit           100000
  SecPcreMatchLimitRecursion  100000
  SecRule TX:/^MSC_/ "!@streq 0" "id:'200005',phase:2,t:none,log,deny,msg:'ModSecurity internal error: %{MATCHED_VAR_NAME}'"

  SecResponseBodyAccess Off
  #SecResponseBodyMimeType text/plain text/html text/xml
  #SecResponseBodyLimit 262144
  #SecResponseBodyLimitAction ProcessPartial

  SecTmpDir  /var/apache2/2.4/modsec/tmp/
  SecDataDir /var/apache2/2.4/modsec/tmp/

  SecAuditEngine RelevantOnly
  SecAuditLogRelevantStatus "^(?:5|4(?!04))"
  SecAuditLogParts ABIJDEFHKZ
  SecAuditLogType Serial
  SecAuditLog /var/apache2/2.4/logs/modsec_audit.log

  SecArgumentSeparator &
  SecCookieFormat 0
  SecUnicodeMapFile /etc/apache2/2.4/samples-conf.d/unicode.mapping 20127
  SecStatusEngine Off

  # --- OWASP CRS v4 + plugins WordPress ---
  Include "/etc/apache2/2.4/modsecurity.d/crs/crs-setup.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/plugins/*-config.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/plugins/*-before.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/local-overrides.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/rules/*.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/plugins/*-after.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/99-local-exclusions.conf"
</IfModule>
SEC

# Local overrides (PL1, relaxed thresholds, static assets, optional xmlrpc)
cat >"${CRS_DIR}/local-overrides.conf" <<'OVR'
SecAction "id:900110,phase:1,pass,nolog,t:none,setvar:tx.blocking_paranoia_level=1"
SecAction "id:900120,phase:1,pass,nolog,t:none,setvar:tx.inbound_anomaly_score_threshold=10,setvar:tx.outbound_anomaly_score_threshold=5"

SecRule REQUEST_URI "@rx \.(?:css|js|png|jpe?g|gif|ico|svg|webp|woff2?)$" \
  "id:100100,phase:1,pass,t:none,log,ctl:ruleEngine=Off"

SecRule REQUEST_URI "@endsWith /xmlrpc.php" \
  "id:100120,phase:1,deny,status:403,log,msg:'xmlrpc disabled'"
OVR

# Writable audit log + rotation
mkdir -p /var/apache2/2.4/logs
touch /var/apache2/2.4/logs/modsec_audit.log
chown webservd:webservd /var/apache2/2.4/logs/modsec_audit.log
chmod 640 /var/apache2/2.4/logs/modsec_audit.log
if ! grep -q 'modsec_audit.log' /etc/logadm.conf 2>/dev/null; then
  logadm -w modsec_audit -p 1w -C 8 -s 20m \
    -a 'svcadm refresh svc:/network/http:apache24' \
    -t '/var/apache2/2.4/logs/modsec_audit.log'
fi


cat > /etc/apache2/2.4/modsecurity.d/crs/99-local-exclusions.conf <<'EOF'
# ---------------------------------------------------------------------------
# 99-local-exclusions.conf
#
# Local exclusion rules for ModSecurity + CRS v4
# (tuned for WordPress and its specific plugins/themes)
#
# This file is loaded last → it overrides/completes the rest.
# ---------------------------------------------------------------------------

# Example: exclude some variables on the WordPress REST API (wp-json)
SecRule REQUEST_URI "@beginsWith /wp-json/" \
    "id:100300,phase:1,pass,ctl:ruleRemoveTargetById=941100;ARGS:filter"

# Example: whitelist admin-ajax.php (often noisy due to some plugins)
SecRule REQUEST_URI "@endsWith /wp-admin/admin-ajax.php" \
    "id:100310,phase:1,pass,ctl:ruleEngine=DetectionOnly"

# Example: allow larger media uploads (avoids false positives)
SecRule REQUEST_URI "@beginsWith /wp-admin/async-upload.php" \
    "id:100320,phase:1,pass,ctl:ruleRemoveById=920420"

# TODO: add your own specific exclusions here (plugin, theme, custom API)
# Use IDs >= 100000 to stay outside the CRS namespace.

EOF
chown root:webservd /etc/apache2/2.4/modsecurity.d/crs/99-local-exclusions.conf
chmod 640           /etc/apache2/2.4/modsecurity.d/crs/99-local-exclusions.conf

/usr/apache2/2.4/bin/apachectl -t && svcadm refresh svc:/network/http:apache24

echo
echo -n "Do you want to enable ModSecurity blocking? [Y/n] "
read -r reply
case "$reply" in
    [Nn])
        echo "OK: No worries, take your time..." ; echo
		exit 0
        ;;
esac
echo

cat >"$CONF" <<'SEC'
# Load mod_unique_id if missing (required for logs/audit)
<IfModule !unique_id_module>
LoadModule unique_id_module libexec/mod_unique_id.so
</IfModule>

# ModSecurity v2
LoadModule security2_module libexec/mod_security2.so

<IfModule mod_security2.c>
  # --- WAF engine: BLOCKING ENABLED ---
  SecRuleEngine On

  # --- Request body ---
  SecRequestBodyAccess On
  SecRequestBodyLimit           134217728
  SecRequestBodyNoFilesLimit    1048576
  SecRequestBodyInMemoryLimit   131072
  SecRequestBodyLimitAction     Reject
  SecRequestBodyJsonDepthLimit  512

  # Enable XML/JSON parsers based on Content-Type
  SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \
       "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
  SecRule REQUEST_HEADERS:Content-Type "^application/json" \
       "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"

  # Multipart & parsing errors
  SecRule REQBODY_ERROR "!@eq 0" \
    "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',severity:2"
  SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
    "id:'200003',phase:2,t:none,log,deny,status:400,msg:'Multipart strict validation failed'"
  SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \
    "id:'200004',phase:2,t:none,log,deny,msg:'Multipart unmatched boundary'"

  # PCRE limits
  SecPcreMatchLimit           100000
  SecPcreMatchLimitRecursion  100000
  SecRule TX:/^MSC_/ "!@streq 0" \
    "id:'200005',phase:2,t:none,log,deny,msg:'ModSecurity internal error: %{MATCHED_VAR_NAME}'"

  # --- Response body (enable if needed) ---
  SecResponseBodyAccess Off
  #SecResponseBodyMimeType text/plain text/html text/xml
  #SecResponseBodyLimit 524288
  #SecResponseBodyLimitAction ProcessPartial

  # Working directories
  SecTmpDir  /var/apache2/2.4/modsec/tmp/
  SecDataDir /var/apache2/2.4/modsec/tmp/

  # --- Audit log ---
  SecAuditEngine RelevantOnly
  SecAuditLogRelevantStatus "^(?:5|4(?!04))"
  SecAuditLogParts ABIJDEFHKZ
  SecAuditLogType Serial
  SecAuditLog /var/apache2/2.4/logs/modsec_audit.log

  # Others
  SecArgumentSeparator &
  SecCookieFormat 0
  SecUnicodeMapFile /etc/apache2/2.4/samples-conf.d/unicode.mapping 20127
  SecStatusEngine Off

  # --- OWASP CRS v4 + plugins WordPress ---
  Include "/etc/apache2/2.4/modsecurity.d/crs/crs-setup.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/plugins/*-config.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/plugins/*-before.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/local-overrides.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/rules/*.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/plugins/*-after.conf"
  Include "/etc/apache2/2.4/modsecurity.d/crs/99-local-exclusions.conf"
</IfModule>
SEC

/usr/apache2/2.4/bin/apachectl -t && svcadm refresh svc:/network/http:apache24

echo
echo "Done ! Good Bye !"
echo

