extract: opt-in mail tree (IMPORT_INCLUDE_MAIL=true) + mail_included marker

WHP's cPanel mailbox import needs cpmove-*/homedir/mail, which extract.sh
always excluded: in sanitization-container mode every mailbox was queued
with no Maildir and "verified" empty (B5, whp02 canary 2026-10-20).

- IMPORT_INCLUDE_MAIL=true (exactly "true"; default off) keeps the mail
  tree in the extract. Default behaviour is unchanged. The DANGEROUS
  symlink strips still apply inside the mail tree (scan-symlinks.php lists
  every archive entry).
- scan-files.php --skip-mail (passed by entrypoint.sh with the opt-in):
  clamscan --exclude-dir for the mail tree, the file count skips it, and a
  hit inside it is never quarantined. Logged with the reason: mail is
  copied verbatim into the customer's own mailbox and verified against the
  source, and multi-GB maildirs would dominate the run.
- report.json gains "mail_included" (true only with the opt-in) so the
  panel can tell this image from an older one, which never writes the key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shadowdaoandClaude Opus 5.5 committed 2026-10-20 09:04:44 -07:00
1 parent 08b995a29c
commit 8217397154
5 files changed
+111 -22

No files matched your search

+25 -2
View File
@@ -8,6 +8,11 @@
# IMPORT_BACKUP_FILE absolute path inside the container, typically
# /host/backup/cpmove-<user>.tar.gz
# CLAMAV_REFRESH "true" to run freshclam at start (default: true)
# IMPORT_INCLUDE_MAIL "true" to extract cpmove-*/homedir/mail for WHP's
# mailbox import (default: false = mail tree skipped).
# The mail tree is never ClamAV-scanned. report.json
# records "mail_included" so the panel can tell this
# image (which honours the opt-in) from an older one.
#
# Flow (spec §0):
# 1. validate env
@@ -71,8 +76,15 @@ log "cpanel-importer starting (container UID=$(id -u) GID=$(id -g))"
: "${IMPORT_USERNAME:?IMPORT_USERNAME env var is required}"
: "${IMPORT_BACKUP_FILE:?IMPORT_BACKUP_FILE env var is required}"
CLAMAV_REFRESH="${CLAMAV_REFRESH:-true}"
# Exactly "true" opts in; anything else is the default (no mail tree).
if [[ "${IMPORT_INCLUDE_MAIL:-false}" == "true" ]]; then
IMPORT_INCLUDE_MAIL=true
else
IMPORT_INCLUDE_MAIL=false
fi
export IMPORT_INCLUDE_MAIL
log "import_id=$IMPORT_ID username=$IMPORT_USERNAME backup=$IMPORT_BACKUP_FILE"
log "import_id=$IMPORT_ID username=$IMPORT_USERNAME backup=$IMPORT_BACKUP_FILE include_mail=$IMPORT_INCLUDE_MAIL"
if [[ ! -f "$IMPORT_BACKUP_FILE" ]]; then
die "backup file does not exist or is not a regular file: $IMPORT_BACKUP_FILE"
@@ -140,11 +152,17 @@ fi
STAGE="scan_files"
log "stage: scan_files"
SCAN_FILES_EXTRA=()
if [[ "$IMPORT_INCLUDE_MAIL" == "true" ]]; then
# The extracted mail tree is not scanned (see scan-files.php --skip-mail).
SCAN_FILES_EXTRA+=(--skip-mail)
fi
php /scripts/scan-files.php \
--extract "$EXTRACT_DIR" \
--quarantine "$QUARANTINE_DIR" \
--report /tmp/scan-files-report.json \
--import-id "$IMPORT_ID" \
"${SCAN_FILES_EXTRA[@]}" \
|| die "scan-files.php failed; see stderr above"
# --- DB engine swap + WP content scan -------------------------------------
@@ -192,6 +210,7 @@ $filesPath = $argv[3];
$dbsPath = $argv[4];
$strippedPath = $argv[5];
$outPath = $argv[6];
$mailIncluded = $argv[7] === "true";
$files = is_file($filesPath) ? json_decode(file_get_contents($filesPath), true) : null;
$dbs = is_file($dbsPath) ? json_decode(file_get_contents($dbsPath), true) : null;
@@ -252,6 +271,10 @@ $report = [
"files_clean" => $filesClean,
"files_cleaned" => $filesCleaned,
"files_quarantined" => $filesQuarantined,
// true only when IMPORT_INCLUDE_MAIL=true AND this image extracted the
// mail tree. Older images never write the key; the panel treats a
// missing/false value as "mail was not extracted".
"mail_included" => $mailIncluded,
"actions" => $actions,
"databases" => $databases,
"summary_for_panel" => [
@@ -263,7 +286,7 @@ $report = [
file_put_contents($outPath, json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n");
fprintf(STDERR, "report written: %s\n", $outPath);
' "$IMPORT_ID" "$DURATION" /tmp/scan-files-report.json /tmp/scan-dbs-report.json "$STRIPPED_SYMLINKS_FILE" "$SANITIZED_DIR/report.json" \
' "$IMPORT_ID" "$DURATION" /tmp/scan-files-report.json /tmp/scan-dbs-report.json "$STRIPPED_SYMLINKS_FILE" "$SANITIZED_DIR/report.json" "$IMPORT_INCLUDE_MAIL" \
|| die "report merge failed"
log "done — exited cleanly after ${DURATION}s"