From 4505179db6afb0dfa28f184751fa3767cf227c8c Mon Sep 17 00:00:00 2001 From: Anthony Date: Fri, 28 Mar 2025 01:20:04 +0800 Subject: [PATCH] Improved debugging and error checking using xmlstarlet --- scripts/ssl-manager/ssl_remover.sh | 62 +++++++++--- scripts/ssl-manager/xmlchecker.sh | 148 ++++++++++++++++++++++++----- 2 files changed, 173 insertions(+), 37 deletions(-) diff --git a/scripts/ssl-manager/ssl_remover.sh b/scripts/ssl-manager/ssl_remover.sh index 84ddf06..6d8704d 100644 --- a/scripts/ssl-manager/ssl_remover.sh +++ b/scripts/ssl-manager/ssl_remover.sh @@ -349,35 +349,63 @@ verify_cleanup_state() { validate_xml_structure() { local file="$1" + local validation_errors=0 - # Check for basic XML structure - if ! xmllint --noout "$file" 2>/dev/null; then + # Check for basic XML structure with detailed error reporting + if ! xmlstarlet val --err --well-formed "$file" 2> >(while IFS= read -r line; do + log_error "XML Structure Error: $line" + validation_errors=$((validation_errors + 1)) + done); then log_error "Invalid XML structure in $file" - return 1 + validation_errors=$((validation_errors + 1)) fi # Check for required root elements - if ! xmlstarlet sel -t -v "//httpServerConfig" "$file" >/dev/null 2>&1; then + if ! xmlstarlet sel -Q -t -v "//httpServerConfig" "$file" >/dev/null 2>&1; then log_error "Missing httpServerConfig root element in $file" - return 1 + validation_errors=$((validation_errors + 1)) fi - # Check for required sections + # Check for required sections with detailed reporting local required_sections=("listenerList" "virtualHostList") for section in "${required_sections[@]}"; do - if ! xmlstarlet sel -t -v "//$section" "$file" >/dev/null 2>&1; then + if ! xmlstarlet sel -Q -t -v "//$section" "$file" >/dev/null 2>&1; then log_error "Missing required section '$section' in $file" - return 1 + # Additional check for malformed section + if xmlstarlet sel -Q -t -c "//*[contains(name(), '$section')]" "$file" >/dev/null 2>&1; then + log_error "Found malformed <$section> element (possibly with incorrect case or attributes)" + fi + validation_errors=$((validation_errors + 1)) fi done - # Check for orphaned elements - if xmlstarlet sel -t -v "//vhostMapList[not(parent::listener)]" "$file" >/dev/null 2>&1; then + # Check for orphaned elements with detailed reporting + if xmlstarlet sel -Q -t -v "//vhostMapList[not(parent::listener)]" "$file" >/dev/null 2>&1; then log_error "Found orphaned vhostMapList elements in $file" - return 1 + # Report the orphaned elements + xmlstarlet sel -Q -t -m "//vhostMapList[not(parent::listener)]" -v "concat('Orphaned vhostMapList: ', .)" -n "$file" 2>/dev/null | while read -r line; do + log_error "$line" + done + validation_errors=$((validation_errors + 1)) fi - return 0 + # Check for orphaned listeners + if xmlstarlet sel -Q -t -v "//listener[not(parent::listenerList)]" "$file" >/dev/null 2>&1; then + log_error "Found orphaned listener elements in $file" + # Report the orphaned listeners + xmlstarlet sel -Q -t -m "//listener[not(parent::listenerList)]" -v "concat('Orphaned Listener: ', name)" -n "$file" 2>/dev/null | while read -r line; do + log_error "$line" + done + validation_errors=$((validation_errors + 1)) + fi + + if [[ $validation_errors -eq 0 ]]; then + log_success "XML structure validation passed for '$file'" + return 0 + else + log_error "Found $validation_errors XML structure issue(s) in '$file'" + return 1 + fi } cleanup_xml() { @@ -424,14 +452,18 @@ cleanup_xml() { validate_xml() { log "Validating XML configuration..." - # First check with xmlstarlet - if ! sudo xmlstarlet val --well-formed "$CONF_FILE" >/dev/null 2>&1; then + # First check with xmlstarlet with detailed error reporting + if ! xmlstarlet val --err --well-formed "$CONF_FILE" 2> >(while IFS= read -r line; do + log_error "XML Validation Error: $line" + done); then log "❌ ERROR: XML configuration is not well-formed according to xmlstarlet. Check backups." return 1 fi # Then check with xmllint for additional validation - if ! sudo xmllint --noout "$CONF_FILE" 2>/dev/null; then + if ! xmllint --noout "$CONF_FILE" 2> >(while IFS= read -r line; do + log_error "XML Validation Error (xmllint): $line" + done); then log "❌ ERROR: XML configuration is invalid according to xmllint. Check backups." return 1 fi diff --git a/scripts/ssl-manager/xmlchecker.sh b/scripts/ssl-manager/xmlchecker.sh index 0613a9f..f6dfdb2 100644 --- a/scripts/ssl-manager/xmlchecker.sh +++ b/scripts/ssl-manager/xmlchecker.sh @@ -303,6 +303,115 @@ apply_and_restart() { fi } +validate_xml_structure() { + local file_to_check="$1" + log_verbose "Performing comprehensive XML validation on '$file_to_check'..." + local validation_errors=0 + + # 1. Check if file is well-formed with detailed error reporting + if ! xmlstarlet val --err --well-formed "$file_to_check" 2> >(while IFS= read -r line; do + log_error "XML Validation Error: $line" + validation_errors=$((validation_errors + 1)) + done); then + log_error "File '$file_to_check' is not well-formed XML" + validation_errors=$((validation_errors + 1)) + fi + + # 2. Check for required root elements + if ! xmlstarlet sel -Q -t -c "//httpServerConfig" "$file_to_check" 2>/dev/null; then + log_error "Missing required root element " + validation_errors=$((validation_errors + 1)) + fi + + # 3. Check for required sections with detailed error reporting + local required_sections=("listenerList" "virtualHostList" "extProcessorList") + for section in "${required_sections[@]}"; do + if ! xmlstarlet sel -Q -t -c "//$section" "$file_to_check" 2>/dev/null; then + log_error "Missing required section <$section>" + # Additional check for malformed section + if xmlstarlet sel -Q -t -c "//*[contains(name(), '$section')]" "$file_to_check" 2>/dev/null; then + log_error "Found malformed <$section> element (possibly with incorrect case or attributes)" + fi + validation_errors=$((validation_errors + 1)) + fi + done + + # 4. Check for orphaned elements with detailed reporting + if xmlstarlet sel -Q -t -c "//listener[not(parent::listenerList)]" "$file_to_check" 2>/dev/null; then + log_error "Found orphaned elements" + # Report the orphaned listener names + xmlstarlet sel -Q -t -m "//listener[not(parent::listenerList)]" -v "concat('Orphaned Listener: ', name)" -n "$file_to_check" 2>/dev/null | while read -r line; do + log_error "$line" + done + validation_errors=$((validation_errors + 1)) + fi + + if xmlstarlet sel -Q -t -c "//virtualHost[not(parent::virtualHostList)]" "$file_to_check" 2>/dev/null; then + log_error "Found orphaned elements" + # Report the orphaned virtualHost names + xmlstarlet sel -Q -t -m "//virtualHost[not(parent::virtualHostList)]" -v "concat('Orphaned VirtualHost: ', name)" -n "$file_to_check" 2>/dev/null | while read -r line; do + log_error "$line" + done + validation_errors=$((validation_errors + 1)) + fi + + # 5. Validate listener structure with detailed reporting + xmlstarlet sel -Q -t -m "//listener" -v "concat('Checking listener: ', name)" -n "$file_to_check" 2>/dev/null | while read -r listener; do + if [[ -n "$listener" ]]; then + log_verbose "$listener" + # Check if listener has required attributes + if ! xmlstarlet sel -Q -t -c "//listener[name='${listener#Checking listener: }']/address" "$file_to_check" 2>/dev/null; then + log_error "Listener '${listener#Checking listener: }' is missing required
element" + validation_errors=$((validation_errors + 1)) + fi + # Additional check for SSL listeners + if [[ "${listener#Checking listener: }" == HTTPS-* ]]; then + if ! xmlstarlet sel -Q -t -c "//listener[name='${listener#Checking listener: }']/certFile" "$file_to_check" 2>/dev/null; then + log_error "HTTPS Listener '${listener#Checking listener: }' is missing required element" + validation_errors=$((validation_errors + 1)) + fi + if ! xmlstarlet sel -Q -t -c "//listener[name='${listener#Checking listener: }']/keyFile" "$file_to_check" 2>/dev/null; then + log_error "HTTPS Listener '${listener#Checking listener: }' is missing required element" + validation_errors=$((validation_errors + 1)) + fi + fi + fi + done + + # Return result + if [[ "$validation_errors" -eq 0 ]]; then + log_success "XML structure validation passed for '$file_to_check'" + return 0 + else + log_error "Found $validation_errors XML structure issue(s) in '$file_to_check'" + return 1 + fi +} + +cleanup_xml() { + local file_to_clean="$1" + log "Performing XML cleanup on '$file_to_clean'..." + + # 1. Remove empty nodes + xmlstarlet ed -L -d '//*[not(node())]' "$file_to_clean" || log "Warning: Failed to remove empty nodes" + + # 2. Remove orphaned elements + xmlstarlet ed -L -d '//listener[not(parent::listenerList)]' "$file_to_clean" || log "Warning: Failed to remove orphaned listeners" + xmlstarlet ed -L -d '//virtualHost[not(parent::virtualHostList)]' "$file_to_clean" || log "Warning: Failed to remove orphaned virtualHosts" + + # 3. Clean up whitespace + xmlstarlet ed -L -d '//text()[normalize-space()=""]' "$file_to_clean" || log "Warning: Failed to clean whitespace" + + # 4. Validate after cleanup + if ! validate_xml_structure "$file_to_clean"; then + log_error "XML validation failed after cleanup" + return 1 + fi + + log "XML cleanup completed for '$file_to_clean'" + return 0 +} + # === Main Script Logic === main() { # Setup logging first @@ -324,30 +433,28 @@ main() { log "Backing up '$CONF_FILE' to '$BACKUP_FILE'..." sudo cp -a "$CONF_FILE" "$BACKUP_FILE" - # Initial Validation - log_verbose "Running initial xmllint validation on '$CONF_FILE'..." - if sudo xmllint --noout "$CONF_FILE" 2>/dev/null; then - log_success "Initial Check: '$CONF_FILE' is valid XML" - INITIAL_FILE_VALID=1 + # Initial Validation using xmlstarlet + log_verbose "Running initial xmlstarlet validation on '$CONF_FILE'..." + if xmlstarlet val --well-formed "$CONF_FILE" 2>/dev/null; then + log_success "Initial Check: '$CONF_FILE' is well-formed XML" + if validate_xml_structure "$CONF_FILE"; then + log_success "Initial Check: '$CONF_FILE' has valid structure" + INITIAL_FILE_VALID=1 + else + log_error "Initial Check: '$CONF_FILE' has structural issues" + fi else - log_error "Initial Check: '$CONF_FILE' is invalid XML. Will attempt recovery/cleanup" + log_error "Initial Check: '$CONF_FILE' is not well-formed XML" fi # Recovery Attempt if [[ "$INITIAL_FILE_VALID" -eq 0 ]]; then - log "Attempting automatic recovery with 'xmllint --recover'..." - sudo rm -f "$RECOVERY_TMP_FILE" - if xmllint --recover "$CONF_FILE" 2>/dev/null > "$RECOVERY_TMP_FILE"; then - if sudo xmllint --noout "$RECOVERY_TMP_FILE" 2>/dev/null; then - log_success "'xmllint --recover' produced structurally valid XML output" - RECOVERY_PRODUCED_VALID_XML=1 - else - log_error "'xmllint --recover' ran but the output is STILL invalid XML. Discarding recovery" - sudo rm -f "$RECOVERY_TMP_FILE" - fi + log "Attempting XML cleanup..." + if cleanup_xml "$CONF_FILE"; then + log_success "XML cleanup completed successfully" + INITIAL_FILE_VALID=1 else - log_error "'xmllint --recover' command failed or produced errors. Discarding recovery" - sudo rm -f "$RECOVERY_TMP_FILE" + log_error "XML cleanup failed" fi fi @@ -361,11 +468,8 @@ main() { if [[ "$INITIAL_FILE_VALID" -eq 1 ]]; then FINAL_CONFIG_SOURCE_FILE="$CONF_FILE" FINAL_CONFIG_SOURCE_DESC="original" - elif [[ "$RECOVERY_PRODUCED_VALID_XML" -eq 1 ]]; then - FINAL_CONFIG_SOURCE_FILE="$RECOVERY_TMP_FILE" - FINAL_CONFIG_SOURCE_DESC="recovered" else - log_error "Neither original nor recovered file is valid. Falling back to default or minimal config" + log_error "Neither original nor cleaned file is valid. Falling back to default or minimal config" FINAL_EXIT_CODE=2 fi