diff --git a/.gitignore b/.gitignore index 28ba115..14f0d1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +/reports /conf/*.conf *.swp diff --git a/scan_bad_signature.sh b/scan_bad_signature.sh new file mode 100755 index 0000000..345c892 --- /dev/null +++ b/scan_bad_signature.sh @@ -0,0 +1,887 @@ +#!/usr/bin/env bash + +CUR_IFS=$IFS + +script_name="$(basename $(realpath $0))" +script_dir="$(dirname $(realpath $0))" + +conf_dir="${script_dir}/conf" +snippet_dir="${script_dir}/snippets" +report_dir="${script_dir}/reports" + +declare -a unsorted_website_arr +declare -a website_arr + +declare -a unsorted_account_arr +declare -a account_arr + +declare -a unsorted_selected_user_arr +declare -a selected_user_arr + +LOCK_DIR="/tmp/${script_name%%.*}.LOCK" +log_file="${LOCK_DIR}/${script_name%%.*}.log" + +run_date=$(date +%Y-%m-%d-%H%M) + + +# ============= +# --- Some functions +# ============= + +usage() { + + [[ -n "$1" ]] && error "$1" + + [[ $terminal ]] && echo -e " +\033[1mUsage:\033[m + + $(basename $0) -s + +\033[1mDescription\033[m + + Scans the encrypted files of one, several or all Nextcloud accounts + and reports every file that fails to decrypt (e.g. 'Bad Signature' + errors thrown by Server-Side Encryption). Every file is read once + and its content discarded - the script is READ-ONLY and changes, + moves or deletes nothing. + + You will be asked interactively which account(s) to scan: + + - a blank separated list of account names + - or 'all' to scan every existing account + +\033[1mOptions\033[m + + -s + The site of the nextcloud instance. + +\033[1mExample:\033[m + + Runs this script on system 'cloud-01.oopen.de' + + $(basename $0) -s cloud-01.oopen.de +" + + clean_up 1 + +} + + +clean_up() { + + # Perform program exit housekeeping + [[ -n "$scan_php_file" ]] && rm -f "$scan_php_file" 2> /dev/null + rm -rf "$LOCK_DIR" + blank_line + exit $1 +} + +is_number() { + + return $(test ! -z "${1##*[!0-9]*}" > /dev/null 2>&1); + + # - also possible + # - + #[[ ! -z "${1##*[!0-9]*}" ]] && return 0 || return 1 + #return $([[ ! -z "${1##*[!0-9]*}" ]]) +} + +echononl(){ + if $terminal ; then + echo X\\c > /tmp/shprompt$$ + if [ `wc -c /tmp/shprompt$$ | awk '{print $1}'` -eq 1 ]; then + echo -e -n "$*\\c" 1>&2 + else + echo -e -n "$*" 1>&2 + fi + rm /tmp/shprompt$$ + fi +} +echo_done() { + if $terminal ; then + echo -e "\033[75G[ \033[32mdone\033[m ]" + fi +} +echo_ok() { + if $terminal ; then + echo -e "\033[75G[ \033[32mok\033[m ]" + fi +} +echo_warning() { + if $terminal ; then + echo -e "\033[75G[ \033[33m\033[1mwarn\033[m ]" + fi +} +echo_failed(){ + if $terminal ; then + echo -e "\033[75G[ \033[1;31mfailed\033[m ]" + fi +} +echo_skipped() { + if $terminal ; then + echo -e "\033[75G[ \033[37mskipped\033[m ]" + fi +} +echo_funde() { + if $terminal ; then + echo -e "\033[75G[ \033[33m\033[1mFunde!\033[m ]" + fi +} + +fatal (){ + echo "" + echo "" + if $terminal ; then + echo -e " [ \033[31m\033[1mFatal\033[m ]: \033[37m\033[1m$*\033[m" + echo "" + echo -e " \033[31m\033[1mScript will be interrupted..\033[m!" + else + echo " [ Fatal ]: $*" + echo "" + echo " Script was terminated...." + fi + clean_up 1 +} + +error(){ + echo "" + if $terminal ; then + echo -e " [ \033[31m\033[1mError\033[m ]: $*" + else + echo " [ Error ]: $*" + fi + echo "" +} + +warn (){ + if $terminal ; then + echo "" + echo -e " [ \033[33m\033[1mWarning\033[m ]: $*" + echo "" + fi +} + +info (){ + if $terminal ; then + echo "" + echo -e " [ \033[32m\033[1mInfo\033[m ]: $*" + echo "" + fi +} + +# - Remove leading/trailling whitespaces +# - +trim() { + local var="$*" + var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters + var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters + echo -n "$var" +} + +## - Check if a given array (parameter 2) contains a given string (parameter 1) +## - +containsElement () { + local e + for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done + return 1 +} + +## - Percentage (1 decimal) of parameter 1 (part) against parameter 2 (total) +## - Returns "0.0" if total is empty, zero or non-numeric. +## - +calc_percent() { + local _part="$1" + local _total="$2" + if [[ -z "$_total" ]] || ! [[ "$_total" =~ ^[0-9]+$ ]] || [[ "$_total" -eq 0 ]] ; then + echo "0.0" + else + awk -v b="$_part" -v t="$_total" 'BEGIN { printf "%.1f", (b/t)*100 }' + fi +} + +blank_line() { + if $terminal ; then + echo "" + fi +} + + + +# - Running in a terminal? +# - +if [[ -t 1 ]] ; then + terminal=true +else + terminal=false +fi + +# ---------- +# - Jobhandling +# ---------- + +if pgrep -f "$(basename $0)" | grep -q -v $$ ; then + + msg="A previos instance of script \"`basename $0`\" seems already be running." + + echo "" + if $terminal ; then + echo -e "[ \033[31m\033[1mFatal\033[m ]: $msg" + echo "" + echo -e " \033[31m\033[1mScript was interupted\033[m!" + else + echo " [ Fatal ]: $msg" + echo "" + echo " Script was interupted!" + fi + echo + + exit 1 +else + if [[ -d "$LOCK_DIR" ]] ; then + rm -rf "$LOCK_DIR" 2> /dev/null + fi +fi + + +# - If job already runs, stop execution.. +# - +if mkdir "$LOCK_DIR" 2> /dev/null ; then + + # - Remove lockdir when the script finishes, or when it receives a signal + # - + trap clean_up SIGHUP SIGINT SIGTERM + +else + + msg="A previos instance of script \"`basename $0`\" seems already be running." + + echo "" + if $terminal ; then + echo -e "[ \033[31m\033[1mFatal\033[m ]: $msg" + echo "" + echo -e " \033[31m\033[1mScript was interupted\033[m!" + else + echo " [ Fatal ]: $msg" + echo "" + echo " Script was interupted!" + fi + echo + + exit 1 + +fi + + +# ------------- +# - Read in Commandline arguments +# ------------- +while getopts hs: opt ; do + case $opt in + h) usage ;; + s) WEBSITE=$OPTARG ;; + \?) usage + esac +done + +if [[ -z "$WEBSITE" ]] ; then + + while IFS='' read -r -d '' _conf_file ; do + source $_conf_file + if [[ -n "$WEBSITE" ]] ; then + unsorted_website_arr+=("${WEBSITE}:$_conf_file") + fi + WEBSITE="" + done < <(find "${conf_dir}" -maxdepth 1 -type f -name "*.conf" -print0) + + # - Sort array + # - + IFS=$'\n' website_arr=($(sort <<<"${unsorted_website_arr[*]}")) + + # Which cloud instance (website) would you like to update + # + source ${snippet_dir}/get-cloud-instance-to-update.sh + +else + + while IFS='' read -r -d '' _conf_file ; do + if $(grep -E -q "WEBSITE=\"?${WEBSITE}\"?" ${_conf_file} 2> /dev/null) ; then + conf_file="${_conf_file}" + break + fi + done < <(find "${conf_dir}" -maxdepth 1 -type f -name "*.conf" -print0) + +fi + + +# - Reset IFS +# - +IFS=$CUR_IFS + +DEFAULT_SRC_BASE_DIR="/usr/local/src/nextcloud" +DEFAULT_HTTP_USER="www-data" +DEFAULT_HTTP_GROUP="www-data" +DEFAULT_PHP_ENGINE='FPM' + +blank_line +echononl " Include Configuration file '$(basename "${conf_file}")'.." +if [[ ! -f $conf_file ]]; then + echo_skipped + fatal "Missing configuration file '$conf_file'." +else + source $conf_file + echo_ok +fi + +DEFAULT_WEB_BASE_DIR="/var/www/${WEBSITE}" +[[ -n "$WEB_BASE_DIR" ]] || WEB_BASE_DIR=$DEFAULT_WEB_BASE_DIR + +if [[ ! -d ${WEB_BASE_DIR} ]] ; then + fatal "Web base directory '$WEB_BASE_DIR' not found!" +fi + +DATA_DIR="$(realpath ${WEB_BASE_DIR}/data)" + +[[ -n "$PHP_ENGINE" ]] || PHP_ENGINE=$DEFAULT_PHP_ENGINE + +INSTALL_DIR="$(realpath ${WEB_BASE_DIR}/nextcloud)" +CURRENT_VERSION="$(basename $INSTALL_DIR | cut -d"-" -f2)" + + +# ============= +# --- Some +# ============= + +# - Support systemd ? +# - +SYSTEMD_EXISTS=false +systemd=$(which systemd) +systemctl=$(which systemctl) + +if [[ -n "$systemd" ]] || [[ -n "$systemctl" ]] ; then + SYSTEMD_EXISTS=true +fi + +#clear + +if $terminal ; then + echo "" + echo -e "\033[32m-----\033[m" + echo -e "Scan encrypted files for \033[1mBad Signature\033[m errors on system \033[1m${WEB_BASE_DIR}\033[m" + echo -e "\033[1m + Read-only: every file is decrypted once and the content discarded, + reporting every file that fails Server-Side Encryption's signature + check. Nothing is changed, moved or deleted.\033[m" + echo -e "\033[32m-----\033[m" +fi + + +# ============= +# --- Some checks +# ============= + +DEFAULT_HTTP_USER="www-data" +DEFAULT_HTTP_GROUP="www-data" + + +NGINX_IS_ENABLED=false +APACHE2_IS_ENABLED=false + +# Get Webservice environment as IS_HTTPD_RUNNING, HTTP_USER, HTTP_GROUP.. +# +source ${snippet_dir}/get-webservice-environment.sh + + +# Check PHP Version +# +source ${snippet_dir}/get-php-major-version.sh + + +# Get full qualified PHP command +# +source ${snippet_dir}/get-path-of-php-command.sh + + +if [[ ! -x "$PHP_BIN" ]]; then + fatal "No PHP binary found!" +fi + + +# ============= +# --- Determine existing accounts +# ============= + +blank_line +echononl " Get list of current accounts.." + +mapfile -t unsorted_account_arr < <(su -c "${PHP_BIN} ${INSTALL_DIR}/occ user:list" -s /bin/bash $HTTP_USER 2> ${log_file} | awk -F'[:[:space:]]+' '{print $3}' 2>> ${log_file} ) + +if [[ $? -gt 0 ]] || [[ -s "${log_file}" ]] ; then + echo_failed + fatal "$(cat $log_file)" +else + echo_ok + > "$log_file" +fi + +# - Sort array +# - +IFS=$'\n' account_arr=($(sort <<<"${unsorted_account_arr[*]}")) +IFS=$CUR_IFS + + +# ============= +# --- Ask which account(s) to scan +# ============= + +echo "" +echo "" +echo -e "\033[32m-----\033[m" +echo "" +echo -e " Existing accounts on \033[1m${WEBSITE}\033[m\n" +echo -en "\033[33m" +printf " %s\n" "${account_arr[@]}" +echo -en "\033[m" +blank_line + +echo "" +echo "" +echo -e "\033[32m-----\033[m" +echo "" +echo -e " Which account(s) would you like to scan for 'Bad Signature' errors?" +echo "" +echo " - enter a list separated by spaces" +echo -e " - or type \033[33mall\033[m to scan every existing account" +echo "" +SELECTED_USERS= +while [[ -z "$(trim ${SELECTED_USERS})" ]] ; do + + echononl " Account(s) to scan: " + read SELECTED_USERS + + if [[ -z "$(trim ${SELECTED_USERS})" ]] ; then + echo -e "\n\t\033[33m\033[1mEntry must not be empty. Type \033[m\033[1mall\033[33m to scan every account.\033[m\n" + continue + fi + + if [[ "${SELECTED_USERS,,}" = "all" ]] ; then + selected_user_arr=("${account_arr[@]}") + break + fi + + IFS=' ' read -ra unsorted_selected_user_arr <<< "${SELECTED_USERS}" + + _all_valid=true + for _user in "${unsorted_selected_user_arr[@]}" ; do + if ! containsElement "${_user}" "${account_arr[@]}" ; then + echo -e "\n\t\033[33m\033[1m No account \033[m\033[1m${_user}\033[33m present!\033[m - Try again..\n" + SELECTED_USERS="" + _all_valid=false + break + fi + done + + $_all_valid || continue + + # - Sort array + # - + IFS=$'\n' selected_user_arr=($(sort <<<"${unsorted_selected_user_arr[*]}")) + IFS=$CUR_IFS + +done + + +# ============= +# --- Write out the (embedded) PHP scan script into the Nextcloud +# --- install directory - it needs to sit next to 'occ' since it +# --- bootstraps Nextcloud the same way occ does. +# ============= + +scan_php_file="${INSTALL_DIR}/.occ_scan_bad_signature_$$.php" + +cat > "$scan_php_file" <<'PHP_SCAN_EOF' + [--before=YYYY-MM-DD] [--after=YYYY-MM-DD] + * + * Ausgabe (TSV, nach STDOUT): + * user \t path \t size_bytes \t mtime \t error + * Eine Zeile pro Datei, bei der das Lesen fehlgeschlagen ist. + * Fortschritts-/Debug-/Zusammenfassungsmeldungen gehen nach STDERR. + */ + +error_reporting(E_ALL); +ini_set('display_errors', '1'); + +fwrite(STDERR, "DEBUG: Skript gestartet, PID=" . getmypid() . "\n"); + +// Faengt fatale, NICHT als Throwable auftretende Fehler ab (z.B. Memory +// Limit exhausted), die sonst komplett stumm bleiben wuerden. +register_shutdown_function(function () { + $err = error_get_last(); + if ($err !== null && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) { + fwrite(STDERR, "DEBUG: FATAL bei Shutdown: [{$err['type']}] {$err['message']} in {$err['file']}:{$err['line']}\n"); + } else { + fwrite(STDERR, "DEBUG: Shutdown ohne erkannten Fatal-Error.\n"); + } +}); + +// WICHTIG: Muss VOR dem require von lib/base.php gesetzt werden. +// Ohne diese Konstante fuehrt lib/base.php am Ende automatisch +// OC::handleRequest() aus (als waere dies ein normaler HTTP-Request). +define('OC_CONSOLE', 1); + +$oldWorkingDir = getcwd(); +if ($oldWorkingDir === false) { + fwrite(STDERR, "Konnte aktuelles Arbeitsverzeichnis nicht ermitteln. Bitte mit absolutem Pfad aufrufen.\n"); + exit(1); +} +chdir(__DIR__); + +fwrite(STDERR, "DEBUG: vor require lib/base.php\n"); +require_once __DIR__ . '/lib/base.php'; +fwrite(STDERR, "DEBUG: nach require lib/base.php - Bootstrap abgeschlossen\n"); + +chdir($oldWorkingDir); + +if (function_exists('posix_getuid') && posix_getuid() === 0) { + fwrite(STDERR, "Bitte NICHT als root ausfuehren, sondern als Webserver-User, z.B.:\n"); + fwrite(STDERR, " sudo -u www-data php " . __FILE__ . "\n"); + exit(1); +} + +// Ab hier alles in einem Catch-All, damit ein Throwable garantiert HIER +// landet und nicht beim (auf STDOUT/STDERR ggf. still bleibenden) +// globalen Nextcloud-Handler. +try { + fwrite(STDERR, "DEBUG: hole UserManager/RootFolder ...\n"); + + // Argumente einlesen: erstes nicht-Options-Argument = Benutzername + // (optional, sonst alle Benutzer). --before=YYYY-MM-DD prueft nur + // Dateien, die VOR diesem Datum zuletzt geaendert wurden - spart bei + // grossen Accounts viel Zeit, sobald der Cutoff-Zeitpunkt bekannt ist. + $onlyUser = null; + $beforeTs = null; + $afterTs = null; + foreach (array_slice($argv, 1) as $arg) { + if (str_starts_with($arg, '--before=')) { + $beforeTs = strtotime(substr($arg, 9)); + } elseif (str_starts_with($arg, '--after=')) { + $afterTs = strtotime(substr($arg, 8)); + } elseif ($onlyUser === null) { + $onlyUser = $arg; + } + } + if ($beforeTs !== null) { + fwrite(STDERR, "DEBUG: Filter --before " . date('Y-m-d', $beforeTs) . "\n"); + } + if ($afterTs !== null) { + fwrite(STDERR, "DEBUG: Filter --after " . date('Y-m-d', $afterTs) . "\n"); + } + + // Neuere Nextcloud-Versionen haben die alten Komfort-Methoden wie + // getUserManager()/getRootFolder() auf \OC::$server entfernt - der + // unterstuetzte Weg ist jetzt der PSR-11-Container ->get(...). + $userManager = \OC::$server->get(\OCP\IUserManager::class); + $rootFolder = \OC::$server->get(\OCP\Files\IRootFolder::class); + fwrite(STDERR, "DEBUG: UserManager/RootFolder OK\n"); + + $total = 0; + $skipped = 0; + $bad = 0; + $usersScanned = 0; + + echo "user\tpath\tsize_bytes\tmtime\terror\n"; + flush(); + + /** + * Rekursiv durch einen Ordner gehen und jede Datei vollstaendig lesen + * (ausser sie liegt ausserhalb von --before/--after). + */ + $scanFolder = function (\OCP\Files\Folder $folder, string $uid) use (&$scanFolder, &$total, &$skipped, &$bad, $beforeTs, $afterTs) { + foreach ($folder->getDirectoryListing() as $node) { + if ($node instanceof \OCP\Files\Folder) { + $scanFolder($node, $uid); + continue; + } + if (!($node instanceof \OCP\Files\File)) { + continue; + } + + $mtimeRaw = $node->getMTime(); + if (($beforeTs !== null && $mtimeRaw >= $beforeTs) || ($afterTs !== null && $mtimeRaw <= $afterTs)) { + $skipped++; + continue; + } + + $total++; + $path = $node->getPath(); + + try { + $stream = $node->fopen('r'); + if ($stream === false) { + throw new \RuntimeException('fopen lieferte false'); + } + while (!feof($stream)) { + $chunk = fread($stream, 8 * 1024 * 1024); + if ($chunk === false) { + throw new \RuntimeException('fread schlug mitten im Stream fehl'); + } + } + fclose($stream); + } catch (\Throwable $e) { + $bad++; + $mtime = date('c', $node->getMTime()); + $msg = str_replace(["\t", "\n", "\r"], ' ', $e->getMessage()); + echo "$uid\t$path\t{$node->getSize()}\t$mtime\t" . get_class($e) . ": $msg\n"; + flush(); + } + } + }; + + $scanUser = function (\OCP\IUser $user) use (&$scanFolder, $rootFolder, &$usersScanned) { + $uid = $user->getUID(); + fwrite(STDERR, "Scanne Benutzer: $uid ...\n"); + + \OC_Util::setupFS($uid); + + try { + $userFolder = $rootFolder->getUserFolder($uid); + } catch (\Throwable $e) { + fwrite(STDERR, " Ueberspringe $uid: kann User-Folder nicht laden (" . $e->getMessage() . ")\n"); + return; + } + + $scanFolder($userFolder, $uid); + $usersScanned++; + }; + + if ($onlyUser !== null) { + fwrite(STDERR, "DEBUG: suche Benutzer '$onlyUser' ...\n"); + $user = $userManager->get($onlyUser); + if ($user === null) { + fwrite(STDERR, "Benutzer '$onlyUser' nicht gefunden.\n"); + exit(1); + } + fwrite(STDERR, "DEBUG: Benutzer gefunden, starte Scan ...\n"); + $scanUser($user); + } else { + $userManager->callForAllUsers($scanUser); + } + + fwrite(STDERR, "\nFertig. Benutzer gescannt: $usersScanned, Dateien geprueft: $total, uebersprungen (Filter): $skipped, fehlgeschlagen: $bad\n"); +} catch (\Throwable $e) { + fwrite(STDERR, "\n!!! UNBEHANDELTE AUSNAHME !!!\n"); + fwrite(STDERR, get_class($e) . ": " . $e->getMessage() . "\n"); + fwrite(STDERR, "in " . $e->getFile() . ":" . $e->getLine() . "\n"); + fwrite(STDERR, $e->getTraceAsString() . "\n"); + exit(2); +} +PHP_SCAN_EOF + +chmod 644 "$scan_php_file" 2> /dev/null + +mkdir -p "$report_dir" 2> /dev/null +report_file="${report_dir}/bad_signature_${WEBSITE}_${run_date}.tsv" + +{ + echo "==================================================================" + echo " Bad-Signature-Scan ${WEBSITE} ${run_date}" + echo "==================================================================" + echo "" + echo -e "user\tpath\tsize_bytes\tmtime\terror" +} > "$report_file" + + +if $terminal ; then + echo "" + echo -e "\033[1;32mStarting Script for \033[1;37m${WEBSITE}\033[m" + echo "" + echo -e " Cloud instance to be scanned.........: $WEBSITE" + echo "" + echo -e " Current version of nextcloud.........: $CURRENT_VERSION" + echo "" + echo -e " Web base directory...................: $WEB_BASE_DIR" + echo -e " Install directory....................: $INSTALL_DIR" + echo -e " Data directory.......................: $DATA_DIR" + echo "" + echo -e " Webserver user.......................: $HTTP_USER" + echo -e " Webserver group......................: $HTTP_GROUP" + echo "" + echo -e " PHP command..........................: $PHP_BIN" + echo "" + echo -e " Account(s) to scan...................: \033[33m${selected_user_arr[*]}\033[m" + echo "" + echo -e " Report file...........................: $report_file" + echo "" + + echo "" + echo -n " Type upper case 'YES' to continue executing with this parameters: " + read OK + if [[ "$OK" = "YES" ]] ; then + echo "" + echo "" + echo -e "\033[1;32mGoing to scan encrypted files for each selected account on \033[1;37m$WEBSITE \033[m" + else + fatal "Abort by user request - Answer as not 'YES'" + fi +fi + + +# ----- +# - Main part of the script +# ----- + +if $terminal ; then + echo "" + echo "" + echo -e "\033[37m\033[1mMain part of the script\033[m" + echo "" +fi + +declare -i total_checked=0 +declare -i total_bad=0 +declare -i total_failed_users=0 + +for _user in "${selected_user_arr[@]}" ; do + + user_tsv="${LOCK_DIR}/scan_${_user}.tsv" + + # - Gut sichtbarer Account-Abschnitt im Reportfile - wird IMMER + # - angelegt, auch wenn der Scan fuer diesen Account fehlschlaegt. + # - + _sep_len=${#_user} + [[ $_sep_len -lt 9 ]] && _sep_len=9 + _sep_line="$(printf '%*s' "$_sep_len" '' | tr ' ' '-')" + + { + echo "" + echo "" + echo "$_sep_line" + echo "$_user" + echo "$_sep_line" + } >> "$report_file" + + echononl " Scanning account \033[1;37m${_user}\033[m.." + su -c "$PHP_BIN $scan_php_file $_user" -s /bin/bash $HTTP_USER > "$user_tsv" 2> "$log_file" + _rc=$? + + if [[ $_rc -ne 0 ]]; then + echo_failed + error "$(cat "$log_file")" + echo " [ FEHLER beim Scan - siehe Server-Log ]" >> "$report_file" + (( total_failed_users++ )) + continue + fi + + # - Kopfzeile weglassen und im Account-Abschnitt anhaengen + # - + tail -n +2 "$user_tsv" >> "$report_file" + + _user_bad=$(tail -n +2 "$user_tsv" | wc -l) + (( total_bad += _user_bad )) + + # - Scan lief durch: bei Funden gelb hervorheben statt schlichtem 'ok' + # - + if [[ $_user_bad -gt 0 ]] ; then + echo_funde + else + echo_ok + fi + + # - Leerzeile NACH der 'Scanning account ..'-Zeile, vor der + # - Account-Statistik. + # - + $terminal && echo "" + + # - Kennzahlen aus der STDERR-Zusammenfassung herausziehen + # - + _summary="$(grep -E '^Fertig\.' "$log_file")" + _checked="$(echo "$_summary" | grep -oE 'Dateien geprueft: [0-9]+' | grep -oE '[0-9]+')" + _skipped="$(echo "$_summary" | grep -oE 'uebersprungen \(Filter\): [0-9]+' | grep -oE '[0-9]+')" + [[ -z "$_skipped" ]] && _skipped="0" + + if [[ "$_checked" =~ ^[0-9]+$ ]] ; then + (( total_checked += _checked )) + _pct="$(calc_percent "$_user_bad" "$_checked")" + else + _checked="?" + _pct="?" + fi + + # - Ergebnis-Block pro Account - gleiches Looking wie das + # - Gesamtergebnis am Ende (Konsole + Reportfile) + # - + if $terminal ; then + echo -e " \033[1;37mAccount ${_user}\033[m" + echo -e " Gescannte Dateien.......................: ${_checked}" + echo -e " Uebersprungene Dateien...................: ${_skipped}" + if [[ $_user_bad -gt 0 ]] ; then + echo -e " Dateien mit 'Bad Signature'..............: \033[1;33m${_user_bad} (${_pct} %)\033[m" + else + echo -e " Dateien mit 'Bad Signature'..............: ${_user_bad} (${_pct} %)" + fi + # - Leerzeile HINTER der Account-Statistik, damit sie nicht am + # - 'Scanning account ..' des naechsten Accounts klebt. + # - + echo "" + fi + + { + echo " Account ${_user}" + echo " Gescannte Dateien.......................: ${_checked}" + echo " Uebersprungene Dateien...................: ${_skipped}" + echo " Dateien mit 'Bad Signature'..............: ${_user_bad} (${_pct} %)" + } >> "$report_file" + +done + +total_pct="$(calc_percent "$total_bad" "$total_checked")" + +# - Gesamtergebnis ans Ende des Reportfiles - gleiches Looking wie +# - die Ergebnis-Bloecke pro Account +# - +{ + echo "" + echo "" + echo "==================================================================" + echo " Gesamtergebnis" + echo "==================================================================" + echo "" + echo "Gescannte Accounts.........................: ${#selected_user_arr[@]}" + [[ $total_failed_users -gt 0 ]] && echo "Accounts mit Scan-Fehler...................: ${total_failed_users}" + echo "Gescannte Dateien insgesamt.................: ${total_checked}" + echo "Dateien mit 'Bad Signature' insgesamt.......: ${total_bad} (${total_pct} %)" +} >> "$report_file" + +blank_line + +if $terminal ; then + echo -e "\033[37m\033[1mErgebnis\033[m" + echo "" + echo -e " Gescannte Accounts.........................: ${#selected_user_arr[@]}" + [[ $total_failed_users -gt 0 ]] && echo -e " Accounts mit Scan-Fehler...................: \033[1;31m${total_failed_users}\033[m" + echo -e " Gescannte Dateien insgesamt.................: ${total_checked}" + echo -e " Dateien mit 'Bad Signature' insgesamt.......: \033[1;31m${total_bad} (${total_pct} %)\033[m" + echo -e " Report-Datei.................................: reports/$(basename "${report_file}")" + echo "" +fi + +clean_up 0