Useful CLI tools (bash) for Arch Linux administration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

370 lines
12 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. #!/usr/bin/env bash
  2. # archrisks - Security information tool for Arch Linux
  3. # Copyright (C) 2020 Pekka Helenius
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. ######################################################################################################################
  18. red=$'\33[91m'
  19. orange=$'\033[38;5;208m'
  20. yellow=$'\033[93m'
  21. green=$'\033[92m'
  22. reset=$'\033[0m'
  23. typeset -A ARCH_MANAGERS
  24. # List of known Arch Linux package managers + priority number for each
  25. # Add more if you wish.
  26. # Syntax guidelines:
  27. # [<package manager priority (greater is more prioritized)>,<package manager>]="
  28. # <parameter to refresh local pkg repositories>|
  29. # <parameter to get detailed local pkg info>|
  30. # <parameter to get name and version string of a pkg>|
  31. # <parameter to get detailed remote pkg info>|
  32. # <root=requires root, no_root=does not require root>
  33. # "
  34. ARCH_MANAGERS=(
  35. [0,pacman]="-Syy|-Qi|-Q|-Si|root"
  36. [1,pacaur]="-Syy|-Qi|-Q|-Si|no_root"
  37. # [2,yaourt]="-Syy|-Qi|-Q|-Si|no_root"
  38. # [3,pikaur]="-Syy|-Qi|-Q|-Si|no_root"
  39. # [4,pacget]="-Syy|-Qi|-Q|-Si|no_root"
  40. # [5,yay]="-Syy|-Qi|-Q|-Si|no_root"
  41. # [6,foxaur]="-Syy|-Qi|-Q|-Si|no_root"
  42. # [7,aurum]="-Syy|-Qi|-Q|-Si|no_root"
  43. # [8,goaur]="-Syy|-Qi|-Q|-Si|no_root"
  44. # [9,aurs]="-Syy|-Qi|-Q|-Si|no_root"
  45. # [10,magico]="-Syy|-Qi|-Q|-Si|no_root"
  46. # [11,maur]="-Syy|-Qi|-Q|-Si|no_root"
  47. # [12,pkgbuilder]="-Syy|-Qi|-Q|-Si|no_root"
  48. # [13,spinach]="-Syy|-Qi|-Q|-Si|no_root"
  49. # [14,trizen]="-Syy|-Qi|-Q|-Si|no_root"
  50. )
  51. priority_lowlimit=-1
  52. default_order="level"
  53. default_reverse=0
  54. provider="security.archlinux.org"
  55. input_count=${#@}
  56. [[ $input_count -eq 1 ]] && input_1=${1}
  57. [[ $input_count -eq 2 ]] && input_1=${1}; input_2=${2}
  58. function helpCaller() {
  59. echo -e "
  60. Usage: $0
  61. -h|--help
  62. 1st arg: --sort=<name,issues,level,status,desc> (optional)
  63. 2nd arg: --reverse (optional)
  64. "
  65. exit 0
  66. }
  67. function inputParser() {
  68. if [[ ${input_count} -gt 2 ]] || [[ ${input_1} == "-h" ]] || [[ ${input_1} == "--help" ]]; then
  69. helpCaller
  70. elif [[ ${input_count} -eq 0 ]]; then
  71. sort_order=${default_order}
  72. sort_reverse=${default_reverse}
  73. else
  74. sort_order=$(echo ${input_1} | sed -r 's/^\-\-sort=(.*)/\1/')
  75. case ${sort_order} in
  76. name|issues|level|version|desc)
  77. echo "Custom sort order selected: ${sort_order}"
  78. ;;
  79. *)
  80. echo "Unknown sorting order selected (${sort_order})."
  81. helpCaller
  82. esac
  83. if [[ ${input_count} -eq 2 ]]; then
  84. case ${input_2} in
  85. "--reverse")
  86. echo "Reverse ordering"
  87. sort_reverse=1
  88. ;;
  89. *)
  90. echo "Unknown option '${input_2}'"
  91. sort_reverse=${default_reverse}
  92. esac
  93. fi
  94. fi
  95. }
  96. function internetTest() {
  97. if [[ $(ping -c 1 $provider 2>&1 | grep -c "Name or service not known") -ne 0 ]]; then
  98. echo -e "\nCan't connect to $provider. Please check your internet connection and try again.\n"
  99. exit 0
  100. fi
  101. }
  102. function findMyPackageManager() {
  103. i=0
  104. for managerStr in ${!ARCH_MANAGERS[@]}; do
  105. manager_priority=$(echo ${managerStr} | awk -F ',' '{print $1}')
  106. manager=$(echo ${managerStr} | awk -F ',' '{print $2}')
  107. if [[ ${manager_priority} -lt ${priority_lowlimit} ]]; then
  108. echo "Minimum priority is $((${priority_lowlimit} + 1)). You have a package which has lower priority value. Exiting."
  109. exit 1
  110. fi
  111. if [[ $(echo $(which ${manager} &>/dev/null)$?) -eq 0 ]]; then
  112. managers_list[$i]=${manager}
  113. managers_priority_list[$i]=${manager_priority}
  114. let i++
  115. fi
  116. done
  117. if [[ ${#managers_list[@]} -eq 0 ]]; then
  118. echo "Not any valid package manager found. Exiting."
  119. exit 1
  120. fi
  121. if [[ $(echo ${managers_priority_list[@]} | tr ' ' '\n' | uniq -d | wc -l) -ne 0 ]]; then
  122. echo "Package managers with same priority found. Check internal manager list for duplicates. Exiting."
  123. exit 1
  124. fi
  125. # Select package manager by priority. Highest is selected.
  126. i=0
  127. while [[ ${i} -le $((${#managers_list[@]} - 1)) ]]; do
  128. if [[ ${managers_priority_list[i]} -gt ${priority_lowlimit} ]]; then
  129. priority_lowlimit=${managers_priority_list[i]}
  130. selected_manager=${managers_list[i]}
  131. fi
  132. let i++
  133. done
  134. pkg_command=${ARCH_MANAGERS["$priority_lowlimit,$selected_manager"]}
  135. command_refresh=$(echo $pkg_command | awk -F '|' '{print $1}')
  136. command_pkginfo_local=$(echo $pkg_command | awk -F '|' '{print $2}')
  137. command_pkginfo_local_short=$(echo $pkg_command | awk -F '|' '{print $3}')
  138. command_pkginfo_remote=$(echo $pkg_command | awk -F '|' '{print $4}')
  139. command_require_root=$(echo $pkg_command | awk -F '|' '{print $5}')
  140. if [[ ${command_require_root} == "root" ]]; then
  141. if [[ ! $(id -u) -eq 0 ]]; then
  142. echo -e "\nThis command requires root privileges.\n"
  143. exit 0
  144. fi
  145. fi
  146. }
  147. function runTool() {
  148. echo "Security report date: $(date '+%d-%m-%Y, %X') (TZ: $(timedatectl status | grep "Time zone:" | awk '{print $3}'))"
  149. echo -e "\nSynchronizing package databases with ${selected_manager}\n"
  150. ${selected_manager} ${command_refresh} || exit
  151. if [[ ! $(which arch-audit | wc -l) -eq 1 ]]; then
  152. echo -e "\nCouldn't find Arch Linux security utility (arch-audit) in \$PATH. Please make sure it's installed.\n"
  153. else
  154. count=0
  155. prs_count=0
  156. IFS=$'\n'
  157. for i in $(arch-audit); do
  158. package_name=$(echo "$i" | awk -F ' ' '{print $2}')
  159. risk_level=$(echo "$i" | grep -oE "Low|Medium|High|Critical")
  160. risks_count=$(echo "$i" | awk -F 'CVE' '{print NF-1}')
  161. risks[$count]="$package_name $risk_level $risks_count"
  162. let count++
  163. done
  164. echo -e "\nAnalyzing ${#risks[*]} vulnerable packages. This takes a while...\n"
  165. i=1
  166. for risk_parsed in $(echo "${risks[*]}"); do
  167. echo -en "Analysing package ${i}/${#risks[*]}... \r"
  168. # Package in question
  169. col1=$(echo "$risk_parsed" | awk -F ' ' '{print $1}')
  170. # Security issues detected
  171. col2=$(echo "$risk_parsed" | awk -F ' ' '{print $3}')
  172. #Critical, High, Medium or Low risk
  173. 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')
  174. col5=$(${selected_manager} ${command_pkginfo_local} $col1 |grep -i description | awk -F ": " '{print $2}')
  175. maxchars=35
  176. if [[ $(echo $col5 | wc -m) -gt $maxchars ]]; then
  177. col5=$(echo "$(echo $col5 | cut -c 1-$maxchars)...")
  178. fi
  179. versioncheck() {
  180. # TODO: We can't really depend on parsing output strings since they vary between Arch package managers
  181. parsedver() {
  182. echo $1 | awk -F ' ' '{print $2}' | sed -r 's/[a-z]+.*//; s/[:_+-]/\./g; s/[^0-9]$//;'
  183. }
  184. # Expected output syntax: "^<string> <version number>$"
  185. # TODO: We can't really depend on parsing output strings since they vary between Arch package managers
  186. system_version=$(${selected_manager} ${command_pkginfo_local_short} $1)
  187. repo_version=$(${selected_manager} ${command_pkginfo_remote} $1 | grep -E "^Version\s*:" | sed -r 's/.*(:\s*.*$)/\1/')
  188. version_array_1=$(parsedver $system_version)
  189. version_array_2=$(parsedver $repo_version)
  190. #Count of version elements (0 18 2 1 contains 4 numbers, for example)
  191. firstvernums=$(echo $version_array_1 | awk -F '.' '{print split($0, a)}')
  192. lastvernums=$(echo $version_array_2 | awk -F '.' '{print split($0, a)}')
  193. # Count of comparable version elements (maximum)
  194. # We compare this much of elements, not more
  195. if [[ $lastvernums -lt $firstvernums ]]; then
  196. comparables=$lastvernums
  197. else
  198. comparables=$firstvernums
  199. fi
  200. # If all numbers are same, we don't analyze them more deeply.
  201. if [[ $version_array_1 == $version_array_2 ]]; then
  202. col4="${green}Package is updated"
  203. else
  204. s=1
  205. while [ $s -le $comparables ]; do
  206. check1=$(echo -e $version_array_1 | awk -v var=$s -F '.' '{print $var}')
  207. check2=$(echo -e $version_array_2 | awk -v var=$s -F '.' '{print $var}')
  208. if [[ $check2 -gt $check1 ]]; then
  209. # Repo number is greater
  210. col4="${yellow}Update available"
  211. break
  212. elif [[ $check2 -lt $check1 ]]; then
  213. # System number is greater
  214. col4="${reset}Newer package installed"
  215. break
  216. fi
  217. let s++
  218. done
  219. fi
  220. }
  221. versioncheck $col1
  222. risk_entries[$prs_count]=$(printf "%s|%s|%s|%s|%s\n" "$col1" "$col2" "$col3" "$col4" "$col5")
  223. let prs_count++
  224. let i++
  225. done
  226. echo -e "\e[1m"
  227. printf "\n%-25s%-20s%-15s%-25s%s\n" "Package" "Security issues" "Risk level" "Version status" "Description"
  228. echo -e "\e[0m"
  229. sort_params=()
  230. case ${sort_order} in
  231. name)
  232. sort_column="-k1"
  233. ;;
  234. issues)
  235. sort_column="-k2"
  236. sort_params+=("-n")
  237. ;;
  238. level)
  239. sort_column="-k3"
  240. ;;
  241. version)
  242. sort_column="-k4"
  243. ;;
  244. desc)
  245. sort_column="-k5"
  246. ;;
  247. #*)
  248. # echo "Unknown sorting order selected. Exiting."
  249. # exit 1
  250. esac
  251. if [[ ${sort_reverse} == 1 ]]; then
  252. sort_params+=("-r")
  253. fi
  254. i=0
  255. for line in $(echo "${risk_entries[*]}" | sort ${sort_params[*]} -t'|' ${sort_column}); do
  256. if [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 0 ]]; then
  257. alert_color="${red}"
  258. importance="Critical"
  259. elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 1 ]]; then
  260. alert_color="${orange}"
  261. importance="High"
  262. elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 2 ]]; then
  263. alert_color="${yellow}"
  264. importance="Medium"
  265. elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 3 ]]; then
  266. alert_color="${green}"
  267. importance="Low"
  268. fi
  269. sec_count=$(echo "$line" | awk -F '|' '{print $2}')
  270. if [[ $sec_count -lt 5 ]]; then
  271. secclr="${green}"
  272. elif [[ $sec_count -ge 5 ]] && [[ $sec_count -lt 10 ]]; then
  273. secclr="${yellow}"
  274. elif [[ $sec_count -ge 10 ]] && [[ $sec_count -lt 20 ]]; then
  275. secclr="${orange}"
  276. elif [[ $sec_count -ge 20 ]]; then
  277. secclr="${red}"
  278. fi
  279. secsum[$i]=$sec_count
  280. 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}'
  281. let i++
  282. done
  283. secsums_total=$(echo $(printf "%d+" ${secsum[@]})0 | bc)
  284. echo -e "\nTotal:"
  285. printf "%-25s%s\n\n" "$count" "$secsums_total"
  286. printf "Check %s for more information.\n\n" "${provider}"
  287. fi
  288. }
  289. inputParser
  290. findMyPackageManager
  291. internetTest
  292. runTool