diff --git a/tools/archrisks.sh b/tools/archrisks.sh index 000a98b..12f6039 100755 --- a/tools/archrisks.sh +++ b/tools/archrisks.sh @@ -1,7 +1,7 @@ -#!/bin/bash +#!/usr/bin/env bash # archrisks - Security information tool for Arch Linux -# Copyright (C) 2018 Pekka Helenius +# Copyright (C) 2020 Pekka Helenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,114 +18,229 @@ ###################################################################################################################### -if [[ ! $(id -u) -eq 0 ]]; then - echo -e "\nThis command requires root privileges.\n" - exit -fi - -echo "Security report date: $(date '+%d-%m-%Y, %X') (TZ: $(timedatectl status | grep "Time zone:" | awk '{print $3}'))" - red=$'\33[91m' orange=$'\033[38;5;208m' yellow=$'\033[93m' green=$'\033[92m' reset=$'\033[0m' -PROVIDER="security.archlinux.org" +typeset -A ARCH_MANAGERS + +# List of known Arch Linux package managers + priority number for each +# Add more if you wish. +# Syntax guidelines: +# [,]=" +# | +# | +# | +# | +# +# " +ARCH_MANAGERS=( + [0,pacman]="-Syy|-Qi|-Q|-Si|root" + [1,pacaur]="-Syy|-Qi|-Q|-Si|no_root" +# [2,yaourt]="-Syy|-Qi|-Q|-Si|no_root" +# [3,pikaur]="-Syy|-Qi|-Q|-Si|no_root" +# [4,pacget]="-Syy|-Qi|-Q|-Si|no_root" +# [5,yay]="-Syy|-Qi|-Q|-Si|no_root" +# [6,foxaur]="-Syy|-Qi|-Q|-Si|no_root" +# [7,aurum]="-Syy|-Qi|-Q|-Si|no_root" +# [8,goaur]="-Syy|-Qi|-Q|-Si|no_root" +# [9,aurs]="-Syy|-Qi|-Q|-Si|no_root" +# [10,magico]="-Syy|-Qi|-Q|-Si|no_root" +# [11,maur]="-Syy|-Qi|-Q|-Si|no_root" +# [12,pkgbuilder]="-Syy|-Qi|-Q|-Si|no_root" +# [13,spinach]="-Syy|-Qi|-Q|-Si|no_root" +# [14,trizen]="-Syy|-Qi|-Q|-Si|no_root" +) + +priority_lowlimit=-1 +default_order="level" +default_reverse=0 + +provider="security.archlinux.org" + + + + + +input_count=${#@} +[[ $input_count -eq 1 ]] && input_1=${1} +[[ $input_count -eq 2 ]] && input_1=${1}; input_2=${2} + +function helpCaller() { + echo -e " +Usage: $0 + -h|--help +1st arg: --sort= (optional) +2nd arg: --reverse (optional) +" + exit 0 +} + +function inputParser() { + + if [[ ${input_count} -gt 2 ]] || [[ ${input_1} == "-h" ]] || [[ ${input_1} == "--help" ]]; then + helpCaller + elif [[ ${input_count} -eq 0 ]]; then + sort_order=${default_order} + sort_reverse=${default_reverse} + else + sort_order=$(echo ${input_1} | sed -r 's/^\-\-sort=(.*)/\1/') + + case ${sort_order} in + name|issues|level|version|desc) + echo "Custom sort order selected: ${sort_order}" + ;; + *) + echo "Unknown sorting order selected (${sort_order})." + helpCaller + esac + + if [[ ${input_count} -eq 2 ]]; then + case ${input_2} in + "--reverse") + echo "Reverse ordering" + sort_reverse=1 + ;; + *) + echo "Unknown option '${input_2}'" + sort_reverse=${default_reverse} + esac + fi + + fi +} + +function internetTest() { + if [[ $(ping -c 1 $provider 2>&1 | grep -c "Name or service not known") -ne 0 ]]; then + echo -e "\nCan't connect to $provider. Please check your internet connection and try again.\n" + exit 0 + fi +} + +function findMyPackageManager() { + + i=0 + for managerStr in ${!ARCH_MANAGERS[@]}; do + + manager_priority=$(echo ${managerStr} | awk -F ',' '{print $1}') + manager=$(echo ${managerStr} | awk -F ',' '{print $2}') -# pacman, pacaur or yaourt. Please make sure all parameters used in this script are accepted by PKGMGR -PKGMGR="pacman" + if [[ ${manager_priority} -lt ${priority_lowlimit} ]]; then + echo "Minimum priority is $((${priority_lowlimit} + 1)). You have a package which has lower priority value. Exiting." + exit 1 + fi -INTERNET_TEST=$(ping -c 1 $PROVIDER 2>&1 | grep -c "Name or service not known") + if [[ $(echo $(which ${manager} &>/dev/null)$?) -eq 0 ]]; then + managers_list[$i]=${manager} + managers_priority_list[$i]=${manager_priority} + let i++ + fi + done + + if [[ ${#managers_list[@]} -eq 0 ]]; then + echo "Not any valid package manager found. Exiting." + exit 1 + fi + + if [[ $(echo ${managers_priority_list[@]} | tr ' ' '\n' | uniq -d | wc -l) -ne 0 ]]; then + echo "Package managers with same priority found. Check internal manager list for duplicates. Exiting." + exit 1 + fi -if [[ ! $INTERNET_TEST -eq 0 ]]; then - echo -e "\nCan't connect to $PROVIDER. Please check your internet connection and try again.\n" -else + # Select package manager by priority. Highest is selected. + i=0 + while [[ ${i} -le $((${#managers_list[@]} - 1)) ]]; do + if [[ ${managers_priority_list[i]} -gt ${priority_lowlimit} ]]; then + priority_lowlimit=${managers_priority_list[i]} + selected_manager=${managers_list[i]} + fi + let i++ + done + + pkg_command=${ARCH_MANAGERS["$priority_lowlimit,$selected_manager"]} + + command_refresh=$(echo $pkg_command | awk -F '|' '{print $1}') + command_pkginfo_local=$(echo $pkg_command | awk -F '|' '{print $2}') + command_pkginfo_local_short=$(echo $pkg_command | awk -F '|' '{print $3}') + command_pkginfo_remote=$(echo $pkg_command | awk -F '|' '{print $4}') + command_require_root=$(echo $pkg_command | awk -F '|' '{print $5}') + + if [[ ${command_require_root} == "root" ]]; then + if [[ ! $(id -u) -eq 0 ]]; then + echo -e "\nThis command requires root privileges.\n" + exit 0 + fi + fi + +} + +function runTool() { + echo "Security report date: $(date '+%d-%m-%Y, %X') (TZ: $(timedatectl status | grep "Time zone:" | awk '{print $3}'))" - echo -e "\nSynchronizing package databases with $PKGMGR\n" - $PKGMGR -Syy || return + echo -e "\nSynchronizing package databases with ${selected_manager}\n" + ${selected_manager} ${command_refresh} || exit if [[ ! $(which arch-audit | wc -l) -eq 1 ]]; then - echo -e "\nCouldn't find Arch Linux security utility (arch-audit). Please make sure it's installed.\n" + echo -e "\nCouldn't find Arch Linux security utility (arch-audit) in \$PATH. Please make sure it's installed.\n" else - CNT=0 - PRSCNT=0 - - # Internal Field Separator. This variable determines how Bash recognizes fields, or word boundaries, when it interprets character strings. + count=0 + prs_count=0 IFS=$'\n' for i in $(arch-audit); do - PACKAGENAME=$(echo "$i" | awk -F ' ' '{print $2}') - RISKLEVEL=$(echo "$i" | grep -oE "Low|Medium|High|Critical") - RISKSCOUNT=$(echo "$i" | awk -F 'CVE' '{print NF-1}') + package_name=$(echo "$i" | awk -F ' ' '{print $2}') + risk_level=$(echo "$i" | grep -oE "Low|Medium|High|Critical") + risks_count=$(echo "$i" | awk -F 'CVE' '{print NF-1}') - RISKS[$CNT]="$PACKAGENAME $RISKLEVEL $RISKSCOUNT" + risks[$count]="$package_name $risk_level $risks_count" - let CNT++ + let count++ done - echo -e "\e[1m" - printf "%-25s%-20s%-15s%-25s%s\n" "Package" "Security issues" "Risk level" "Version status" "Description" - echo -e "\e[0m" + echo -e "\nAnalyzing ${#risks[*]} vulnerable packages. This takes a while...\n" + + i=1 + for risk_parsed in $(echo "${risks[*]}"); do - for RISK_PARSED in $(echo "${RISKS[*]}"); do + echo -en "Analysing package ${i}/${#risks[*]}... \r" # Package in question - col1=$(echo "$RISK_PARSED" | awk -F ' ' '{print $1}') + col1=$(echo "$risk_parsed" | awk -F ' ' '{print $1}') # Security issues detected - col2=$(echo "$RISK_PARSED" | awk -F ' ' '{print $3}') + col2=$(echo "$risk_parsed" | awk -F ' ' '{print $3}') #Critical, High, Medium or Low risk - col3=$(echo "$RISK_PARSED" | awk -F ' ' '{print $2}' | sed 's/Critical/0/g; s/High/1/g; s/Medium/2/g; s/Low/3/g') + col3=$(echo "$risk_parsed" | awk -F ' ' '{print $2}' | sed 's/Critical/0/g; s/High/1/g; s/Medium/2/g; s/Low/3/g') - col5=$($PKGMGR -Qi $col1 |grep -i description | awk -F ": " '{print $2}') + col5=$(${selected_manager} ${command_pkginfo_local} $col1 |grep -i description | awk -F ": " '{print $2}') maxchars=35 if [[ $(echo $col5 | wc -m) -gt $maxchars ]]; then col5=$(echo "$(echo $col5 | cut -c 1-$maxchars)...") fi - #pkgurl=$(pacaur -Qi $col1 |grep URL | awk -F ": " '{print $2}') - versioncheck() { + # TODO: We can't really depend on parsing output strings since they vary between Arch package managers parsedver() { - - TRIMMED=$(echo $1 | sed 's/[:.-]/./g') - - if [[ $TRIMMED =~ [A-Za-z] ]]; then - - PKGVERCHARS=$(echo $TRIMMED | sed 's/[^a-zA-Z]//g') - - CHARNUM=0 - for char in $PKGVERCHARS; do - charnum=$(echo {a..z} | sed 's/ //g' | grep -b -o $char | awk 'BEGIN {FS=":"}{print $1}') - REP[$CHARNUM]="s/$char/$charnum/g;" - let CHARNUM++ - done - - for seds in "${REP[*]}"; do - SEDSTR=$seds - done - - echo $TRIMMED | sed "$SEDSTR" - - else - echo $TRIMMED - fi + echo $1 | awk -F ' ' '{print $2}' | sed -r 's/[a-z]+.*//; s/[:_+-]/\./g; s/[^0-9]$//;' } - SYSVER=$($PKGMGR -Q $1 | awk -F ' ' '{print $2}') - REPOVER=$($PKGMGR -Si $1 | grep "Version.*:" | awk -F ' ' '{print $3}') + # Expected output syntax: "^ $" + # TODO: We can't really depend on parsing output strings since they vary between Arch package managers + system_version=$(${selected_manager} ${command_pkginfo_local_short} $1) + repo_version=$(${selected_manager} ${command_pkginfo_remote} $1 | grep -E "^Version\s*:" | sed -r 's/.*(:\s*.*$)/\1/') - VERARRAY1=$(parsedver $SYSVER) - VERARRAY2=$(parsedver $REPOVER) + version_array_1=$(parsedver $system_version) + version_array_2=$(parsedver $repo_version) #Count of version elements (0 18 2 1 contains 4 numbers, for example) - firstvernums=$(echo $VERARRAY1 | awk -F '.' '{print split($0, a)}') - lastvernums=$(echo $VERARRAY2 | awk -F '.' '{print split($0, a)}') + firstvernums=$(echo $version_array_1 | awk -F '.' '{print split($0, a)}') + lastvernums=$(echo $version_array_2 | awk -F '.' '{print split($0, a)}') # Count of comparable version elements (maximum) # We compare this much of elements, not more @@ -136,27 +251,27 @@ else fi # If all numbers are same, we don't analyze them more deeply. - if [[ $VERARRAY1 == $VERARRAY2 ]]; then + if [[ $version_array_1 == $version_array_2 ]]; then col4="${green}Package is updated" else s=1 while [ $s -le $comparables ]; do - check1=$(echo -e $VERARRAY1 | awk -v var=$s -F '.' '{print $var}') - check2=$(echo -e $VERARRAY2 | awk -v var=$s -F '.' '{print $var}') - + check1=$(echo -e $version_array_1 | awk -v var=$s -F '.' '{print $var}') + check2=$(echo -e $version_array_2 | awk -v var=$s -F '.' '{print $var}') + if [[ $check2 -gt $check1 ]]; then # Repo number is greater col4="${yellow}Update available" break - + elif [[ $check2 -lt $check1 ]]; then # System number is greater col4="${reset}Newer package installed" break fi - + let s++ done fi @@ -164,47 +279,79 @@ else versioncheck $col1 - RISKENTRIES[$PRSCNT]=$(printf "%s|%s|%s|%s|%s\n" "$col1" "$col2" "$col3" "$col4" "$col5") - - let PRSCNT++ + risk_entries[$prs_count]=$(printf "%s|%s|%s|%s|%s\n" "$col1" "$col2" "$col3" "$col4" "$col5") + let prs_count++ + let i++ + done + echo -e "\e[1m" + printf "\n%-25s%-20s%-15s%-25s%s\n" "Package" "Security issues" "Risk level" "Version status" "Description" + echo -e "\e[0m" + + sort_params=() + case ${sort_order} in + name) + sort_column="-k1" + ;; + issues) + sort_column="-k2" + sort_params+=("-n") + ;; + level) + sort_column="-k3" + ;; + version) + sort_column="-k4" + ;; + desc) + sort_column="-k5" + ;; + #*) + # echo "Unknown sorting order selected. Exiting." + # exit 1 + esac + + if [[ ${sort_reverse} == 1 ]]; then + sort_params+=("-r") + fi + i=0 - for line in $(echo "${RISKENTRIES[*]}" | sort -t'|' -k3); do + for line in $(echo "${risk_entries[*]}" | sort ${sort_params[*]} -t'|' ${sort_column}); do if [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 0 ]]; then - alertclr="${red}" - IMPT="Critical" + alert_color="${red}" + importance="Critical" elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 1 ]]; then - alertclr="${orange}" - IMPT="High" + alert_color="${orange}" + importance="High" elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 2 ]]; then - alertclr="${yellow}" - IMPT="Medium" + alert_color="${yellow}" + importance="Medium" elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 3 ]]; then - alertclr="${green}" - IMPT="Low" + alert_color="${green}" + importance="Low" fi - SECCOUNT=$(echo "$line" | awk -F '|' '{print $2}') + sec_count=$(echo "$line" | awk -F '|' '{print $2}') - if [[ $SECCOUNT -lt 5 ]]; then + if [[ $sec_count -lt 5 ]]; then secclr="${green}" - elif [[ $SECCOUNT -ge 5 ]] && [[ $SECCOUNT -lt 10 ]]; then + elif [[ $sec_count -ge 5 ]] && [[ $sec_count -lt 10 ]]; then secclr="${yellow}" - elif [[ $SECCOUNT -ge 10 ]] && [[ $SECCOUNT -lt 20 ]]; then + elif [[ $sec_count -ge 10 ]] && [[ $sec_count -lt 20 ]]; then secclr="${orange}" - elif [[ $SECCOUNT -ge 20 ]]; then + elif [[ $sec_count -ge 20 ]]; then secclr="${red}" fi - secsum[$i]=$SECCOUNT + secsum[$i]=$sec_count - echo "$line" | awk -F '|' -v clr1="${alertclr}" -v clr2="${secclr}" -v rs="${reset}" -v var="${IMPT}" '{printf "%-25s%s%-20s%s%-15s%-30s%s%s\n",$1,clr2,$2,clr1,var,$4,rs,$5}' + echo "$line" | awk -F '|' -v clr1="${alert_color}" -v clr2="${secclr}" -v rs="${reset}" -v var="${importance}" '{printf "%-25s%s%-20s%s%-15s%-30s%s%s\n",$1,clr2,$2,clr1,var,$4,rs,$5}' let i++ done @@ -212,6 +359,12 @@ else secsums_total=$(echo $(printf "%d+" ${secsum[@]})0 | bc) echo -e "\nTotal:" - printf "%-25s%s\n\n" "$CNT" "$secsums_total" + printf "%-25s%s\n\n" "$count" "$secsums_total" + printf "Check %s for more information.\n\n" "${provider}" fi -fi +} + +inputParser +findMyPackageManager +internetTest +runTool