diff --git a/zimbra_backup.sh b/zimbra_backup.sh index 24f959d..ad88ea2 100644 --- a/zimbra_backup.sh +++ b/zimbra_backup.sh @@ -24,6 +24,14 @@ fi read -p "Enter Zimbra username (email address): " EMAIL read -p "Enter backup directory (absolute path) [/opt/zimbra/backups]: " BACKUP_DIR +# Reject anything that is not a plain address. $EMAIL is used both as a +# command argument and as part of the backup filename, so a "/" here +# would write outside $BACKUP_DIR entirely. +if ! [[ "$EMAIL" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then + echo "❌ Not a valid email address: $EMAIL" + exit 1 +fi + # Use default if none provided BACKUP_DIR=${BACKUP_DIR:-/opt/zimbra/backups} @@ -45,7 +53,11 @@ fi # Run zmmailbox command as zimbra user echo "📦 Starting backup..." -sudo -u zimbra bash -c "/opt/zimbra/bin/zmmailbox -z -m '$EMAIL' getRestURL '//?fmt=tgz'" > "$BACKUP_FILE" +# The script body is single-quoted so nothing is interpolated into it; +# $EMAIL arrives as a positional argument instead. Interpolating it (as +# this line previously did) let any shell metacharacter in the address +# run commands as the zimbra user, which owns the whole mail store. +sudo -u zimbra bash -c '/opt/zimbra/bin/zmmailbox -z -m "$1" getRestURL "//?fmt=tgz"' _ "$EMAIL" > "$BACKUP_FILE" # Verify success if [ $? -eq 0 ]; then diff --git a/zimbra_restore.sh b/zimbra_restore.sh index fa00c75..d6a70ee 100644 --- a/zimbra_restore.sh +++ b/zimbra_restore.sh @@ -35,11 +35,19 @@ fi # List available backups for that user echo "📁 Available backups for $EMAIL:" -ls "$BACKUP_DIR" | grep "$EMAIL" | grep '\.tgz$' +# -F: match the address literally. Unquoted it was a regex, so "." in +# any address matched any character. +find "$BACKUP_DIR" -maxdepth 1 -type f -name '*.tgz' -printf '%f\n' | grep -F "$EMAIL" || true echo # Prompt for filename read -p "Enter the exact filename of the backup to restore (e.g., user@example.com_2024-06-11_10-20-30.tgz): " FILENAME +# A bare filename only. Without this, "../../etc/shadow" would resolve +# outside $BACKUP_DIR and be handed to the restore command. +if [[ "$FILENAME" != "${FILENAME##*/}" || -z "$FILENAME" ]]; then + echo "❌ Enter a filename only, not a path: $FILENAME" + exit 1 +fi FULL_PATH="${BACKUP_DIR}/${FILENAME}" # Validate file exists @@ -58,7 +66,11 @@ fi # Run the restore command as zimbra user echo "🔄 Restoring backup..." -sudo -u zimbra bash -c "/opt/zimbra/bin/zmmailbox -z -m '$EMAIL' postRestURL '/?fmt=tgz&resolve=skip' --file '$FULL_PATH'" +# Single-quoted body: nothing is interpolated. $EMAIL and $FULL_PATH +# arrive as positional arguments. Interpolating them (as this line +# previously did) let shell metacharacters in either value run commands +# as the zimbra user. +sudo -u zimbra bash -c '/opt/zimbra/bin/zmmailbox -z -m "$1" postRestURL "/?fmt=tgz&resolve=skip" --file "$2"' _ "$EMAIL" "$FULL_PATH" # Check result if [ $? -eq 0 ]; then