#!/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 # ============= # --- Check Server-Side Encryption status # ============= blank_line echononl " Check Server-Side Encryption status.." _encryption_status_out="$(su -c "$PHP_BIN $INSTALL_DIR/occ encryption:status" -s /bin/bash $HTTP_USER 2> "$log_file")" ENCRYPTION_ENABLED="unknown" if [[ -s "$log_file" ]] ; then echo_failed error "$(cat "$log_file")" elif echo "$_encryption_status_out" | grep -qiE 'enabled:[[:space:]]*true' ; then echo_ok ENCRYPTION_ENABLED="yes" else echo_warning ENCRYPTION_ENABLED="no" 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"); // Nextcloud's own bootstrap (lib/base.php) unconditionally calls // set_time_limit(3600) as part of require_once above - this OVERRIDES // whatever -d max_execution_time was passed on the PHP command line, // since it is a later, explicit runtime call. Undo that here, now that // the require is done, so a full-content scan of a large account isn't // killed after exactly one hour regardless of the CLI flag. set_time_limit(0); 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 "" if [[ "$ENCRYPTION_ENABLED" = "no" ]] ; then warn "Server-Side Encryption is NOT enabled on ${WEBSITE} - this scan only makes sense on instances with encryption switched on, so it will most likely find nothing." fi 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 if $terminal ; then echo "" echo -e " \033[1mTemporäre Dateien – werden laufend aktualisiert:\033[m" echo "" echo -e " Fortschritt / Debug-Meldungen (PHP-Ausgabe auf STDERR):" echo -e " \033[1mtail -f ${log_file}\033[m" echo "" echo -e " Rohergebnis pro Account (PHP-Ausgabe auf STDOUT, Datei für Datei):" echo -e " \033[1mtail -f ${LOCK_DIR}/scan_.tsv\033[m" echo "" 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 overall_date_min="" overall_date_max="" declare -A overall_month_counts=() 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.." # -d max_execution_time=0: a full-content read of every file of a # large account can easily run well over an hour; without this the # PHP CLI process is killed by PHP's own execution-time limit # (typically inherited from the webserver's php.ini) mid-scan, long # before any actual error in the account's files. su -c "$PHP_BIN -d max_execution_time=0 $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 )) # - Zeitraum (min/max Datum) der Dateien mit 'Bad Signature' fuer # - diesen Account aus der mtime-Spalte des TSV ermitteln, und # - gleich in die Gesamt-Min/Max mit einfliessen lassen. # - if [[ $_user_bad -gt 0 ]] ; then _dates_sorted="$(tail -n +2 "$user_tsv" | cut -f4 | cut -c1-10 | sort)" _user_date_min="$(echo "$_dates_sorted" | head -n1)" _user_date_max="$(echo "$_dates_sorted" | tail -n1)" if [[ "$_user_date_min" = "$_user_date_max" ]] ; then _user_zeitraum="$_user_date_min" else _user_zeitraum="${_user_date_min} bis ${_user_date_max}" fi if [[ -z "$overall_date_min" || "$_user_date_min" < "$overall_date_min" ]] ; then overall_date_min="$_user_date_min" fi if [[ -z "$overall_date_max" || "$_user_date_max" > "$overall_date_max" ]] ; then overall_date_max="$_user_date_max" fi # - Verteilung der betroffenen Dateien nach Monat (YYYY-MM), pro # - Account und gleich mit-aggregiert fuer das Gesamtergebnis - # - zeigt, ob es sich um vereinzelte Cluster oder einen # - durchgehenden Fehlerstrom handelt. # - declare -A _user_month_counts=() while IFS= read -r _d ; do [[ -z "$_d" ]] && continue _m="${_d:0:7}" _user_month_counts["$_m"]=$(( ${_user_month_counts["$_m"]:-0} + 1 )) overall_month_counts["$_m"]=$(( ${overall_month_counts["$_m"]:-0} + 1 )) done <<< "$_dates_sorted" _user_months_sorted=($(printf '%s\n' "${!_user_month_counts[@]}" | sort)) else _user_zeitraum="-" fi # - 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" echo -e " Zeitraum betroffener Dateien.............: \033[1;33m${_user_zeitraum}\033[m" else echo -e " Dateien mit 'Bad Signature'..............: ${_user_bad} (${_pct} %)" echo -e " Zeitraum betroffener Dateien.............: ${_user_zeitraum}" fi if [[ $_user_bad -gt 0 ]] ; then echo -e " Verteilung nach Monat:" for _m in "${_user_months_sorted[@]}" ; do printf " \033[1;33m%-7s\033[m : %d\n" "$_m" "${_user_month_counts[$_m]}" done 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} %)" echo " Zeitraum betroffener Dateien.............: ${_user_zeitraum}" if [[ $_user_bad -gt 0 ]] ; then echo " Verteilung nach Monat:" for _m in "${_user_months_sorted[@]}" ; do printf " %-7s : %d\n" "$_m" "${_user_month_counts[$_m]}" done fi } >> "$report_file" done total_pct="$(calc_percent "$total_bad" "$total_checked")" # - Zeitraum (min/max Datum) ueber alle Dateien mit 'Bad Signature' # - hinweg, aus den bereits pro Account ermittelten Min/Max-Werten. # - if [[ -n "$overall_date_min" ]] ; then if [[ "$overall_date_min" = "$overall_date_max" ]] ; then overall_zeitraum="$overall_date_min" else overall_zeitraum="${overall_date_min} bis ${overall_date_max}" fi else overall_zeitraum="-" fi overall_months_sorted=($(printf '%s\n' "${!overall_month_counts[@]}" | sort)) # - 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} %)" echo "Zeitraum betroffener Dateien insgesamt......: ${overall_zeitraum}" if [[ $total_bad -gt 0 ]] ; then echo "Verteilung betroffener Dateien nach Monat (alle Accounts):" for _m in "${overall_months_sorted[@]}" ; do printf " %-7s : %d\n" "$_m" "${overall_month_counts[$_m]}" done fi } >> "$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" if [[ $total_bad -gt 0 ]] ; then echo -e " Zeitraum betroffener Dateien insgesamt......: \033[1;31m${overall_zeitraum}\033[m" echo -e " Verteilung betroffener Dateien nach Monat (alle Accounts):" for _m in "${overall_months_sorted[@]}" ; do printf " \033[1;31m%-7s\033[m : %d\n" "$_m" "${overall_month_counts[$_m]}" done else echo -e " Zeitraum betroffener Dateien insgesamt......: ${overall_zeitraum}" fi echo -e " Report-Datei................................: reports/$(basename "${report_file}")" echo "" fi clean_up 0