Files
gniza4cp/lib/notify.sh
shuki 1f68ea1058 Security hardening, static analysis fixes, and expanded test coverage
- Fix CRITICAL: safe config parser replacing shell source, sshpass -e,
  CSRF with /dev/urandom, symlink-safe file I/O
- Fix HIGH: input validation for timestamps/accounts, path traversal
  prevention in Runner.pm, AJAX CSRF on all endpoints
- Fix MEDIUM: umask 077, chmod 700 on config dirs, Config.pm TOCTOU lock,
  rsync exit code capture bug, RSYNC_EXTRA_OPTS character validation
- ShellCheck: fix word-splitting in notify.sh, safe rm in pkgacct.sh,
  suppress cross-file SC2034 false positives
- Perl::Critic: return undef→bare return, return (sort), unpack @_,
  explicit return on void subs, rename Config::write→save
- Remove dead code: enforce_retention_all(), rsync_dry_run()
- Add require_cmd checks for rsync/ssh/hostname/gzip at startup
- Escape $hint/$tip in CGI helper functions for defense-in-depth
- Expand tests from 17→40: validate_timestamp, validate_account_name,
  _safe_source_config (including malicious input), numeric validation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:57:26 +02:00

176 lines
4.6 KiB
Bash

#!/usr/bin/env bash
# gniza/lib/notify.sh — Email notifications (SMTP via curl or legacy mail/sendmail)
_send_via_smtp() {
local subject="$1"
local body="$2"
local from="${SMTP_FROM:-$SMTP_USER}"
if [[ -z "$from" ]]; then
log_error "SMTP_FROM or SMTP_USER must be set for SMTP delivery"
return 1
fi
# Build the RFC 2822 message
local message=""
message+="From: $from"$'\r\n'
message+="To: $NOTIFY_EMAIL"$'\r\n'
message+="Subject: $subject"$'\r\n'
message+="Content-Type: text/plain; charset=UTF-8"$'\r\n'
message+="Date: $(date -R)"$'\r\n'
message+=$'\r\n'
message+="$body"
# Build curl command
local -a curl_args=(
'curl' '--silent' '--show-error'
'--connect-timeout' '30'
'--max-time' '60'
)
# Protocol URL based on security setting
case "${SMTP_SECURITY:-tls}" in
ssl)
curl_args+=("--url" "smtps://${SMTP_HOST}:${SMTP_PORT}")
;;
tls)
curl_args+=("--url" "smtp://${SMTP_HOST}:${SMTP_PORT}" "--ssl-reqd")
;;
none)
curl_args+=("--url" "smtp://${SMTP_HOST}:${SMTP_PORT}")
;;
esac
# Auth credentials
if [[ -n "${SMTP_USER:-}" ]]; then
curl_args+=("--user" "${SMTP_USER}:${SMTP_PASSWORD}")
fi
# Sender
curl_args+=("--mail-from" "$from")
# Recipients (split NOTIFY_EMAIL on commas)
local -a recipients
IFS=',' read -ra recipients <<< "$NOTIFY_EMAIL"
local rcpt
for rcpt in "${recipients[@]}"; do
rcpt="${rcpt## }" # trim leading space
rcpt="${rcpt%% }" # trim trailing space
[[ -n "$rcpt" ]] && curl_args+=("--mail-rcpt" "$rcpt")
done
# Upload the message from stdin
curl_args+=("-T" "-")
log_debug "Sending via SMTP to ${SMTP_HOST}:${SMTP_PORT} (${SMTP_SECURITY})"
local curl_output
curl_output=$(echo "$message" | "${curl_args[@]}" 2>&1)
local rc=$?
if (( rc == 0 )); then
return 0
else
log_error "SMTP delivery failed (curl exit code: $rc): $curl_output"
return 1
fi
}
_send_via_legacy() {
local subject="$1"
local body="$2"
# Split comma-separated emails for mail command
local -a recipients
IFS=',' read -ra recipients <<< "$NOTIFY_EMAIL"
if command -v mail &>/dev/null; then
echo "$body" | mail -s "$subject" "${recipients[@]}"
elif command -v sendmail &>/dev/null; then
{
echo "To: $NOTIFY_EMAIL"
echo "Subject: $subject"
echo "Content-Type: text/plain; charset=UTF-8"
echo ""
echo "$body"
} | sendmail -t
else
log_warn "No mail command available, cannot send notification"
return 1
fi
return 0
}
send_notification() {
local subject="$1"
local body="$2"
local success="${3:-true}"
# Check if notifications are configured
[[ -z "${NOTIFY_EMAIL:-}" ]] && return 0
case "${NOTIFY_ON:-$DEFAULT_NOTIFY_ON}" in
never) return 0 ;;
failure) [[ "$success" == "true" ]] && return 0 ;;
always) ;;
esac
local hostname; hostname=$(hostname -f)
local full_subject="[gniza] [$hostname] $subject"
log_debug "Sending notification to $NOTIFY_EMAIL: $full_subject"
if [[ -n "${SMTP_HOST:-}" ]]; then
_send_via_smtp "$full_subject" "$body"
else
_send_via_legacy "$full_subject" "$body"
fi
local rc=$?
if (( rc == 0 )); then
log_debug "Notification sent"
fi
return $rc
}
send_backup_report() {
local total="$1"
local succeeded="$2"
local failed="$3"
local duration="$4"
local failed_accounts="$5"
local success="true"
local status="SUCCESS"
if (( failed > 0 )); then
if (( succeeded > 0 )); then
status="PARTIAL FAILURE"
else
status="FAILURE"
fi
success="false"
fi
local body=""
body+="Backup Report: $status"$'\n'
body+="=============================="$'\n'
body+="Hostname: $(hostname -f)"$'\n'
body+="Timestamp: $(date -u +"%Y-%m-%d %H:%M:%S UTC")"$'\n'
body+="Duration: $(human_duration "$duration")"$'\n'
body+=""$'\n'
body+="Accounts: $total total, $succeeded succeeded, $failed failed"$'\n'
if [[ -n "$failed_accounts" ]]; then
body+=""$'\n'
body+="Failed accounts:"$'\n'
body+="$failed_accounts"$'\n'
fi
if [[ -n "$LOG_FILE" ]]; then
body+=""$'\n'
body+="Log file: $LOG_FILE"$'\n'
fi
send_notification "Backup $status ($succeeded/$total)" "$body" "$success"
}