#!/usr/bin/env bash # gniza4cp/lib/config.sh — Shell-variable config loading & validation # Safe config parser — reads KEY=VALUE lines without executing arbitrary code. # Only processes lines matching ^[A-Z_][A-Z_0-9]*= and strips surrounding quotes. _safe_source_config() { local filepath="$1" local line key value while IFS= read -r line || [[ -n "$line" ]]; do # Skip blank lines and comments [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue # Match KEY=VALUE (optional quotes) if [[ "$line" =~ ^([A-Z_][A-Z_0-9]*)=(.*) ]]; then key="${BASH_REMATCH[1]}" value="${BASH_REMATCH[2]}" # Strip surrounding double or single quotes if [[ "$value" =~ ^\"(.*)\"$ ]]; then value="${BASH_REMATCH[1]}" elif [[ "$value" =~ ^\'(.*)\'$ ]]; then value="${BASH_REMATCH[1]}" fi declare -g "$key=$value" fi done < "$filepath" } load_config() { local config_file="${1:-$DEFAULT_CONFIG_FILE}" if [[ ! -f "$config_file" ]]; then die "Config file not found: $config_file (create via WHM or copy gniza4cp.conf.example)" fi # Parse the config (safe key=value reader, no code execution) _safe_source_config "$config_file" || die "Failed to parse config file: $config_file" # Apply defaults for optional settings TEMP_DIR="${TEMP_DIR:-$DEFAULT_TEMP_DIR}" INCLUDE_ACCOUNTS="${INCLUDE_ACCOUNTS:-}" EXCLUDE_ACCOUNTS="${EXCLUDE_ACCOUNTS:-$DEFAULT_EXCLUDE_ACCOUNTS}" LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}" LOG_LEVEL="${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" LOG_RETAIN="${LOG_RETAIN:-$DEFAULT_LOG_RETAIN}" NOTIFY_EMAIL="${NOTIFY_EMAIL:-}" NOTIFY_ON="${NOTIFY_ON:-$DEFAULT_NOTIFY_ON}" SMTP_HOST="${SMTP_HOST:-}" SMTP_PORT="${SMTP_PORT:-$DEFAULT_SMTP_PORT}" SMTP_USER="${SMTP_USER:-}" SMTP_PASSWORD="${SMTP_PASSWORD:-}" SMTP_FROM="${SMTP_FROM:-}" SMTP_SECURITY="${SMTP_SECURITY:-$DEFAULT_SMTP_SECURITY}" LOCK_FILE="${LOCK_FILE:-$DEFAULT_LOCK_FILE}" SSH_TIMEOUT="${SSH_TIMEOUT:-$DEFAULT_SSH_TIMEOUT}" SSH_RETRIES="${SSH_RETRIES:-$DEFAULT_SSH_RETRIES}" RSYNC_EXTRA_OPTS="${RSYNC_EXTRA_OPTS:-}" USER_RESTORE_REMOTES="${USER_RESTORE_REMOTES:-$DEFAULT_USER_RESTORE_REMOTES}" # --debug flag overrides config [[ "${GNIZA4CP_DEBUG:-false}" == "true" ]] && LOG_LEVEL="debug" export TEMP_DIR INCLUDE_ACCOUNTS EXCLUDE_ACCOUNTS BWLIMIT RETENTION_COUNT export LOG_DIR LOG_LEVEL LOG_RETAIN NOTIFY_EMAIL NOTIFY_ON export SMTP_HOST SMTP_PORT SMTP_USER SMTP_PASSWORD SMTP_FROM SMTP_SECURITY export LOCK_FILE SSH_TIMEOUT SSH_RETRIES RSYNC_EXTRA_OPTS USER_RESTORE_REMOTES } validate_config() { local errors=0 # Per-remote validation is handled by validate_remote() in remotes.sh. # Here we only validate local/global settings. case "$NOTIFY_ON" in always|failure|never) ;; *) log_error "NOTIFY_ON must be always|failure|never, got: $NOTIFY_ON"; ((errors++)) || true ;; esac case "$LOG_LEVEL" in debug|info|warn|error) ;; *) log_error "LOG_LEVEL must be debug|info|warn|error, got: $LOG_LEVEL"; ((errors++)) || true ;; esac # SMTP validation (only when SMTP_HOST is set) if [[ -n "${SMTP_HOST:-}" ]]; then case "$SMTP_SECURITY" in tls|ssl|none) ;; *) log_error "SMTP_SECURITY must be tls|ssl|none, got: $SMTP_SECURITY"; ((errors++)) || true ;; esac if [[ -n "${SMTP_PORT:-}" ]] && { [[ ! "$SMTP_PORT" =~ ^[0-9]+$ ]] || (( SMTP_PORT < 1 || SMTP_PORT > 65535 )); }; then log_error "SMTP_PORT must be 1-65535, got: $SMTP_PORT" ((errors++)) || true fi fi # Validate numeric fields if [[ -n "${SSH_TIMEOUT:-}" ]] && [[ ! "$SSH_TIMEOUT" =~ ^[0-9]+$ ]]; then log_error "SSH_TIMEOUT must be a non-negative integer, got: $SSH_TIMEOUT" ((errors++)) || true fi if [[ -n "${SSH_RETRIES:-}" ]] && [[ ! "$SSH_RETRIES" =~ ^[0-9]+$ ]]; then log_error "SSH_RETRIES must be a non-negative integer, got: $SSH_RETRIES" ((errors++)) || true fi if [[ -n "${LOG_RETAIN:-}" ]] && [[ ! "$LOG_RETAIN" =~ ^[0-9]+$ ]]; then log_error "LOG_RETAIN must be a non-negative integer, got: $LOG_RETAIN" ((errors++)) || true fi # Validate RSYNC_EXTRA_OPTS characters (prevent flag injection) if [[ -n "${RSYNC_EXTRA_OPTS:-}" ]] && [[ ! "$RSYNC_EXTRA_OPTS" =~ ^[a-zA-Z0-9\ ._=/,-]+$ ]]; then log_error "RSYNC_EXTRA_OPTS contains invalid characters: $RSYNC_EXTRA_OPTS" ((errors++)) || true fi if (( errors > 0 )); then log_error "Configuration has $errors error(s)" return 1 fi return 0 }