Backup pfSenseΒΆ

See also

#!/bin/bash
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
# guisam

set -u
set -o pipefail

# General variables
typeset    TARGET="false"
typeset    USER="admin"
typeset    PASSWD="pfsense"
typeset    WGET="wget --no-check-certificate --quiet"
typeset -r TMPDIR=$(mktemp -d)
typeset -r COOKIE="${TMPDIR}/cookie.txt"
typeset -r CSRF1_FILE="${TMPDIR}/csrf1.txt"
typeset -r CSRF2_FILE="${TMPDIR}/csrf2.txt"
typeset -r TIMESTAMP="$(date +%F%T|sed 's/[-:]//g')"
typeset    BACKUPDIR="$HOME"
typeset    BACKUPNAME="config-router"
typeset -r BACKUPTMP="${TMPDIR}/${BACKUPNAME}.${TIMESTAMP}.xml"
typeset -r RETENTION_PATTERN="^[0-9]+$"
typeset    RETENTION="false"

# Display variables
typeset -r  NORMAL="\e[0m"
typeset -r  BOLD="\e[1m"
typeset -r  RED="\e[0;31m"

# Functions
backup_config () {
    local BACKUPFILE="${BACKUPDIR}/${BACKUPNAME}.${TIMESTAMP}.xml"
    local BACKUPTMP_HASH=$(get_file_hash ${BACKUPTMP})
    local BACKUPFILE_PATTERN="${BACKUPDIR}/${BACKUPNAME}*.xml"
    ls ${BACKUPFILE_PATTERN} &> /dev/null
    [[ $? == 0 ]] && \
        for BACKUPLIST_FILE in ${BACKUPFILE_PATTERN}; do
            local BACKUPLIST_FILE_HASH=$(get_file_hash ${BACKUPLIST_FILE})
            [[ ${BACKUPTMP_HASH} == ${BACKUPLIST_FILE_HASH} ]] && \
                return
        done
    cp "${BACKUPTMP}" "${BACKUPFILE}"
}

check_dir_option () {
    local DIR_OPTION="$1"
    [[ -d ${DIR_OPTION} ]] && \
        BACKUPDIR="${DIR_OPTION}" || \
        error "${DIR_OPTION}: no such directory found on ${HOSTNAME}."
}

check_ret_option () {
    local RETENTION_OPTION="$1"
    [[ ${RETENTION_OPTION} =~ ${RETENTION_PATTERN} ]] || \
        error "${RETENTION_OPTION} is not an integer." && \
        [[ ${RETENTION_OPTION} == 0 ]] && \
        error "Option -r have to be greater than 0." || \
        RETENTION="${RETENTION_OPTION}"
}

check_target_option () {
    local TARGET_OPTION="$1"
    TARGET=${TARGET_OPTION}
    nc -z -w1 ${TARGET_OPTION} 443 || \
        error "Port 443 is unreachable on ${TARGET_OPTION}."
}

clean_backup_directory () {
    local CURRENT_DIR=$(ls -tr1 ${BACKUPDIR}/${BACKUPNAME}.*.xml|wc -l)
    [[ ${RETENTION} != false ]] && \
        [[ ${RETENTION} -lt ${CURRENT_DIR} ]] && \
        ls -tr1 ${BACKUPDIR}/${BACKUPNAME}.*.xml | \
        head -n -"${RETENTION}" | xargs -d '\n' rm --
}

clean_tmp () {
    rm -rf "${TMPDIR}"
}

error ()
{
    echo -e "${RED}${BOLD}$*${NORMAL}\n" >&2
    exit 1
}

get_current_config () {
    local URL="https://${TARGET}/diag_backup.php"
    ${WGET} -O- --keep-session-cookies --save-cookies ${COOKIE} \
        ${URL} | grep "name='__csrf_magic'" \
        | sed 's/.*value="\(.*\)".*/\1/' > ${CSRF1_FILE}
    ${WGET} -O- --keep-session-cookies --load-cookies ${COOKIE} --save-cookies ${COOKIE} \
        --post-data "login=Login&usernamefld=${USER}&passwordfld=${PASSWD}&__csrf_magic=$(cat ${CSRF1_FILE})" \
        ${URL}  | grep "name='__csrf_magic'" | sed 's/.*value="\(.*\)".*/\1/' > ${CSRF2_FILE}
    ${WGET} --keep-session-cookies --load-cookies ${COOKIE} \
        --post-data "download=download&donotbackuprrd=yes&__csrf_magic=$(head -n 1 ${CSRF2_FILE})" \
        ${URL} -O ${BACKUPTMP}
}

get_file_hash () {
    local BACKUPFILE="$1"
    local BACKUPFILE_HASH=$(sha256sum < ${BACKUPFILE} | awk '{print $1}')
    echo ${BACKUPFILE_HASH}
}

parse_options ()
{
    while getopts ":ht:u:p:vd:n:r:" OPTION
    do
        case ${OPTION} in
        h)
            usage
            clean_tmp
            exit 0
            ;;
        t)
            check_target_option "${OPTARG}"
            ;;
        u)
            USER="${OPTARG}"
            ;;
        p)
            PASSWD="${OPTARG}"
            ;;
        v)
            WGET="wget --no-check-certificate"
            ;;
        d)
            check_dir_option "${OPTARG%\/}"
            ;;
        n)
            BACKUPNAME="${OPTARG}"
            ;;
        r)
            check_ret_option "${OPTARG}"
            ;;
        :)
            error "Missing option argument for -${OPTARG}."
            ;;
        *)
            error "Unknown option: -${OPTARG}."
            ;;
    esac
    done
    [[ ${TARGET} = false ]] && \
        error "Option -t (target) is mandatory.\nSee help with -h option."
}

usage ()
{
    local CMD=$(basename $0)
    cat <<-EOL
    backup_pfsense
    Create a backup for Pfsense version 2.3.3 and later.
    Back up the configuration only if this one changed.

    Usage : ${CMD} -t <target> [-u <user>] [-p <passwd>] [-v] [-d <dir>] [-n <name>] [-r <num>]

    Options:
        -h  display this help
        -t  target address IP, mandatory
        -u  target account user, default: admin
        -p  target account password, default: pfsense
        -d  local backup directory, default: \$HOME
        -n  local backup file name, default: config-router
        -r  local backup retention, default: keep all
        -v  verbose donwload

    Example:
        backup_pfsense -t 172.16.0.254 -u backup -p toto1234 -d /home/guigui/Desktop -n my_pfsense -r 2
      EOL
}

# Main
parse_options "$@"
get_current_config
backup_config
clean_backup_directory
clean_tmp

exit 0