#!/bin/bash # maravento.com # ################################################################################ # # SQUID ANALYSIS TOOL # squidtool.sh # # This script provides a unified interface for analyzing Squid proxy logs. # It contains six main functions: # # 1) squid_filter # - Allows the user to search the Squid access log for a specific IP # and/or keyword. # - Converts numeric timestamps in the log to human-readable dates. # - Results are saved to squid_filter.log in the current directory. # # 2) squid_audit # - Searches the Squid cache log for events where "clientAccessCheckDone" occurred. # - Converts numeric timestamps to human-readable dates. # - Requires debug_options "ALL,1 33,2 28,9" to be enabled in squid.conf. # If not enabled, the function exits with an informative error. # - Results are saved to squid_audit.log in the current directory. # # 3) squid_traffic # - Generates a traffic report from the Squid access log for a specified # analysis period (default 72 hours). # - Lists IP addresses and the number of hits to external domains. # - Flags IPs exceeding a configurable alert threshold. # - Results are saved to squid_traffic.log in the current directory. # # 4) squid_global # - Exports comprehensive Squid access log data to CSV format for a specified # analysis period (default 72 hours). # - Includes detailed fields: date, time, IP, method, HTTP code, size, cache status, # URL, domain, status classification, and error type categorization. # - Results are saved to squid_global.csv in the current directory. # # 5) squid_stats # - Generates comprehensive statistics and analytics from the Squid access log # for a specified analysis period (default 72 hours). # - Produces detailed metrics including performance data, cache hit rates, status # code analysis, bandwidth usage, top clients/domains, and error analysis. # - Generates both CSV (squid_stats.csv) and HTML (squid_stats.html) reports. # # 6) squid_ip_timeframe # - Analyzes traffic for a specific IP address within a user-defined time range # (hour range) on the current day. # - Converts UTC timestamps from the log to the system's local time zone. # - Displays detailed information including timestamp, cache code, HTTP status, # transferred bytes, method, and URL for each matching request. # - Useful for identifying activity gaps or concentrated traffic during specific # hours of the day. # - Results are saved to squid_ip_timeframe.log in the current directory. # # All logs are written to the current working directory, and the script # informs the user where to find the output files after execution. # ################################################################################ set -uo pipefail # VALIDATION -- one variable per thing validated; use directly with =~ _UH_IPV4='^(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])$' ## root check if [ "$(id -u)" != "0" ]; then echo "ERROR: This script must be run as root" exit 1 fi # prevent overlapping runs SCRIPT_LOCK="/var/lock/$(basename "$0" .sh).lock" (umask 077; : >> "$SCRIPT_LOCK") exec 200>"$SCRIPT_LOCK" if ! flock -n 200; then echo "Script $(basename "$0") is already running" exit 1 fi # DEPENDENCIES for dep in perl gawk gzip; do if ! dpkg -s "$dep" &>/dev/null; then echo "ERROR: Required dependency '$dep' is not installed." >&2 exit 1 fi done # DEPENDENCIES (squid or squid-openssl) if ! dpkg -s squid &>/dev/null && ! dpkg -s squid-openssl &>/dev/null; then echo "ERROR: 'squid' or 'squid-openssl' is not installed." >&2 exit 1 fi # LOG FILE CHECK # Modified to include all log files (current and rotated) ACCESS_LOG="/var/log/squid/access.log*" CACHE_LOG="/var/log/squid/cache.log*" # Verify at least one access log file exists if ! ls /var/log/squid/access.log* 1> /dev/null 2>&1; then echo "Access log not found: /var/log/squid/access.log*" exit 1 fi # Verify at least one cache log file exists if ! ls /var/log/squid/cache.log* 1> /dev/null 2>&1; then echo "Cache log not found: /var/log/squid/cache.log*" exit 1 fi # TIMING SCRIPT_START=$(date +%s) # FUNCTIONS squid_filter() { LOG_FILE="squid_filter.log" echo "=== Squid Filter ===" > "$LOG_FILE" read -p "Enter IP (e.g. 192.168.0.10) or leave empty: " IP read -p "Enter the word to search (e.g. google): " WORD IPNEW="" [[ "$IP" =~ $_UH_IPV4 ]] && IPNEW="$IP" if [[ "$IPNEW" ]]; then zcat -f $ACCESS_LOG 2>/dev/null | perl -pe 's/^(\d+\.\d+)/localtime($1)/e' \ | grep "$IPNEW" \ | grep -a -i -F "$WORD" >> "$LOG_FILE" else zcat -f $ACCESS_LOG 2>/dev/null | perl -pe 's/^(\d+\.\d+)/localtime($1)/e' \ | grep -a -i -F "$WORD" >> "$LOG_FILE" fi if [ $? -gt 0 ]; then echo "No records found for: $WORD" >> "$LOG_FILE" else echo "Done" >> "$LOG_FILE" fi echo "Results saved to $(pwd)/$LOG_FILE" } squid_audit() { LOG_FILE="squid_audit.log" echo "=== Squid Audit ===" > "$LOG_FILE" # Check debug_options in squid.conf SQUID_CONF="/etc/squid/squid.conf" # Adjust for your system REQUIRED_DEBUG="ALL,1 33,2 28,9" if [[ ! -f "$SQUID_CONF" ]] || ! grep -q "^debug_options\s\+$REQUIRED_DEBUG" "$SQUID_CONF"; then echo "ERROR: debug_options $REQUIRED_DEBUG is not enabled in $SQUID_CONF" | tee -a "$LOG_FILE" echo "Please enable this line and restart Squid before running this script." >> "$LOG_FILE" echo "Results saved to $(pwd)/$LOG_FILE" return fi read -p "Enter the word to search (e.g.: video): " WORD zcat -f $CACHE_LOG 2>/dev/null | perl -pe 's/^(\d+\.\d+)/localtime($1)/e' \ | grep -i "clientAccessCheckDone" \ | grep -a -i -F "$WORD" >> "$LOG_FILE" if [ $? -gt 0 ]; then echo "No records found for: $WORD" >> "$LOG_FILE" else echo "Done" >> "$LOG_FILE" fi echo "Results saved to $(pwd)/$LOG_FILE" } squid_traffic() { # Ask user for period read -p "Enter the number of hours to analyze (default 72): " USER_HOURS PERIOD_HOURS=${USER_HOURS:-72} # Variables specific to this block MIN_HITS=${MIN_HITS:-20} ALERT_THRESHOLD=${ALERT_THRESHOLD:-300} SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOG_FILE="$SCRIPT_DIR/squid_traffic.log" # Initialize log file echo "============================================================" > "$LOG_FILE" echo "SQUID TRAFFIC ANALYSIS REPORT" >> "$LOG_FILE" echo "============================================================" >> "$LOG_FILE" echo "Analysis started: $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" echo "Configuration:" >> "$LOG_FILE" echo "* Minimum hits : $MIN_HITS" >> "$LOG_FILE" echo "* Alert threshold: $ALERT_THRESHOLD" >> "$LOG_FILE" echo "* Analysis period: ${PERIOD_HOURS}H" >> "$LOG_FILE" echo "* Access log : /var/log/squid/access.log* (all files)" >> "$LOG_FILE" echo "------------------------------------------------------------" >> "$LOG_FILE" printf "%-8s %-15s %-60s\n" "Hits" "IP" "URL" >> "$LOG_FILE" echo "------------------------------------------------------------" >> "$LOG_FILE" # Verify access log exists if ! ls /var/log/squid/access.log* 1> /dev/null 2>&1; then echo "ERROR: Access log file not found: /var/log/squid/access.log*" | tee -a "$LOG_FILE" echo "Results saved to $LOG_FILE" return fi # Calculate cutoff timestamp NOW=$(date +%s) PERIOD=$((PERIOD_HOURS*3600)) CUTOFF=$((NOW - PERIOD)) # Generate traffic report zcat -f $ACCESS_LOG 2>/dev/null | gawk -v cutoff="$CUTOFF" '$1 > cutoff { match($7, /https?:\/\/([^\/]+)/, arr) if (arr[1] != "") print $3, arr[1] }' \ | sort \ | uniq -c \ | sort -nr \ | awk -v min="$MIN_HITS" '{if($1>=min) printf "%-8s %-15s %-60s\n", $1, $2, $3}' \ >> "$LOG_FILE" # Check for alerts ALERT_IPS=$(awk -v th="$ALERT_THRESHOLD" '$1 ~ /^[0-9]+$/ && $1+0 >= th {print $0}' "$LOG_FILE") if [[ -n "$ALERT_IPS" ]]; then ALERT_COUNT=$(echo "$ALERT_IPS" | wc -l) ALERT_MSG="ALERT: $ALERT_COUNT IP(s) exceed threshold of $ALERT_THRESHOLD hits" echo -e "$ALERT_MSG" | tee -a "$LOG_FILE" else INFO_MSG="No IPs exceed the alert threshold of $ALERT_THRESHOLD hits" echo -e "$INFO_MSG" | tee -a "$LOG_FILE" fi echo "Results saved to $LOG_FILE" } squid_global() { SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CSV_FILE="$SCRIPT_DIR/squid_global.csv" echo "WARNING: Generating this CSV may take some time depending on log size." read -p "Enter the number of hours to analyze (default 72): " PERIOD_HOURS PERIOD_HOURS=${PERIOD_HOURS:-72} if ! ls /var/log/squid/access.log* 1> /dev/null 2>&1; then echo "ERROR: Access log not found: /var/log/squid/access.log*" return fi START=$(date +%s) NOW=$(date +%s) PERIOD=$((PERIOD_HOURS * 3600)) CUTOFF=$((NOW - PERIOD)) echo "Starting CSV export..." echo "date,time,ip,method,http_code,size,cache_status,url,domain,status,error_type" > "$CSV_FILE" zcat -f $ACCESS_LOG 2>/dev/null | gawk -v cutoff="$CUTOFF" ' function extract_domain(u, d, h) { if (u == "" || u == "-") return "-" if (u ~ /^https?:\/\//) { delete d if (match(u, /https?:\/\/([^\/:]+)/, d)) return d[1] else return u } if (u ~ /^[^\/:]+:[0-9]+$/) { split(u, h, ":") return h[1] } return u } $1 > cutoff { dt = strftime("%Y-%m-%d,%H:%M:%S", $1) ip = $3 code = $4 size = $5 method = $6 url = $7 cache = $9 split(code, c, "/") status_code = c[2] if (status_code == "") status_code = "000" if (method == "-") method = "UNKNOWN" # Classify error types if (status_code == "000") { status = "ERROR" error_type = "CONNECTION_FAIL" } else if (status_code >= 400) { status = "ERROR" error_type = "HTTP_ERROR" } else { status = "OK" error_type = "-" } domain = extract_domain(url) print dt","ip","method","status_code","size","cache","url","domain","status","error_type }' >> "$CSV_FILE" echo "CSV export completed: $CSV_FILE" } squid_stats() { SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CSV_FILE="$SCRIPT_DIR/squid_stats.csv" HTML_FILE="$SCRIPT_DIR/squid_stats.html" echo "WARNING: Generating stats may take some time depending on log size." read -p "Enter the number of hours to analyze (default 72): " PERIOD_HOURS PERIOD_HOURS=${PERIOD_HOURS:-72} if ! ls /var/log/squid/access.log* 1> /dev/null 2>&1; then echo "ERROR: Access log not found: /var/log/squid/access.log*" return fi START=$(date +%s) NOW=$(date +%s) PERIOD=$((PERIOD_HOURS * 3600)) CUTOFF=$((NOW - PERIOD)) echo "Generating comprehensive statistics..." echo "Metric,Value" > "$CSV_FILE" # === BASIC METRICS === TOTAL_REQUESTS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff' | wc -l) UNIQUE_IPS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff {print $3}' | sort -u | wc -l) UNIQUE_DOMAINS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff {print $7}' | sed -E 's#^https?://([^/:]+).*#\1#' | sort -u | wc -l) DATA_MB=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff {sum+=$5} END {printf "%.2f", sum/1048576}') DATA_GB=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff {sum+=$5} END {printf "%.2f", sum/1073741824}') # === STATUS CODE ANALYSIS === SUCCESS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff && $4 ~ /\/200/' | wc -l) REDIRECTS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff && $4 ~ /\/30[0-9]/' | wc -l) CLIENT_ERRORS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff && $4 ~ /\/40[0-9]/' | wc -l) SERVER_ERRORS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff && $4 ~ /\/50[0-9]/' | wc -l) CACHE_HITS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff && $4 ~ /HIT/' | wc -l) CACHE_MISS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff && $4 ~ /MISS/' | wc -l) # === PERCENTAGES === SUCCESS_PCT=$(awk -v s="$SUCCESS" -v t="$TOTAL_REQUESTS" 'BEGIN {if (t>0) printf "%.2f", (s/t)*100; else print 0}') CACHE_HIT_PCT=$(awk -v h="$CACHE_HITS" -v t="$TOTAL_REQUESTS" 'BEGIN {if (t>0) printf "%.2f", (h/t)*100; else print 0}') ERROR_PCT=$(awk -v e="$((CLIENT_ERRORS + SERVER_ERRORS))" -v t="$TOTAL_REQUESTS" 'BEGIN {if (t>0) printf "%.2f", (e/t)*100; else print 0}') # === PERFORMANCE METRICS === AVG_RESPONSE_SIZE=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff {sum+=$5; count++} END {if (count>0) printf "%.2f", sum/count/1024; else print 0}') REQ_PER_HOUR=$(awk -v t="$TOTAL_REQUESTS" -v h="$PERIOD_HOURS" 'BEGIN {printf "%.0f", t/h}') BANDWIDTH_MBPS=$(awk -v mb="$DATA_MB" -v h="$PERIOD_HOURS" 'BEGIN {printf "%.2f", (mb*8)/(h*3600)}') # === WRITE CSV === echo "Analysis period (hours),$PERIOD_HOURS" >> "$CSV_FILE" echo "Total requests,$TOTAL_REQUESTS" >> "$CSV_FILE" echo "Requests per hour,$REQ_PER_HOUR" >> "$CSV_FILE" echo "Unique client IPs,$UNIQUE_IPS" >> "$CSV_FILE" echo "Unique domains accessed,$UNIQUE_DOMAINS" >> "$CSV_FILE" echo "Total data transferred,${DATA_GB} GB (${DATA_MB} MB)" >> "$CSV_FILE" echo "Average bandwidth,${BANDWIDTH_MBPS} Mbps" >> "$CSV_FILE" echo "Average response size,${AVG_RESPONSE_SIZE} KB" >> "$CSV_FILE" echo "Successful requests (2xx),${SUCCESS} (${SUCCESS_PCT}%)" >> "$CSV_FILE" echo "Redirects (3xx),${REDIRECTS}" >> "$CSV_FILE" echo "Client errors (4xx),${CLIENT_ERRORS}" >> "$CSV_FILE" echo "Server errors (5xx),${SERVER_ERRORS}" >> "$CSV_FILE" echo "Total errors,$(($CLIENT_ERRORS + $SERVER_ERRORS)) (${ERROR_PCT}%)" >> "$CSV_FILE" echo "Cache hits,${CACHE_HITS} (${CACHE_HIT_PCT}%)" >> "$CSV_FILE" echo "Cache misses,${CACHE_MISS}" >> "$CSV_FILE" # === TOP LISTS === TOP_IPS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff {count[$3]++; bytes[$3]+=$5} END { for (ip in count) printf "%s (%d req/%.1fMB)\n", ip, count[ip], bytes[ip]/1048576 }' | sort -t'(' -k2 -nr | head -5 | tr '\n' ' ') echo "Top 5 client IPs,$TOP_IPS" >> "$CSV_FILE" TOP_DOMAINS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff { gsub(/^https?:\/\//, "", $7); gsub(/\/.*/, "", $7) count[$7]++; bytes[$7]+=$5 } END { for (domain in count) printf "%s (%d req/%.1fMB)\n", domain, count[domain], bytes[domain]/1048576 }' | sort -t'(' -k2 -nr | head -5 | tr '\n' ' ') echo "Top 5 domains by requests,$TOP_DOMAINS" >> "$CSV_FILE" TOP_ERROR_DOMAINS=$(zcat -f $ACCESS_LOG 2>/dev/null | awk -v cutoff="$CUTOFF" '$1 > cutoff && ($4 ~ /\/40[0-9]/ || $4 ~ /\/50[0-9]/) { gsub(/^https?:\/\//, "", $7); gsub(/\/.*/, "", $7) errors[$7]++ } END { for (domain in errors) printf "%s (%d errors)\n", domain, errors[domain] }' | sort -t'(' -k2 -nr | head -5 | tr '\n' ' ') echo "Top 5 error domains,$TOP_ERROR_DOMAINS" >> "$CSV_FILE" # === GENERATE HTML REPORT === { echo "" echo "" echo "
" echo "" echo "" echo "| Metric | Value |
|---|---|
| $metric | $value |
| $metric | $value |
| $metric | $value |