From 1058e8ce8f7a1283ec4611a664722bdc89c9b619 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Sat, 5 Sep 2020 16:18:22 -0700 Subject: [PATCH 1/5] Update pkgcheck.sh Some time in the last year, tar on Catalina has lost the ability to expand xar archives. This broke the parts of the script that attempted to look inside flat packages. The changes here use xar and pkgutil --expand to replace the lost functionality. --- pkgcheck.sh | 126 +++++++++++++++++++--------------------------------- 1 file changed, 46 insertions(+), 80 deletions(-) diff --git a/pkgcheck.sh b/pkgcheck.sh index d91e1a2..57dc94e 100755 --- a/pkgcheck.sh +++ b/pkgcheck.sh @@ -173,52 +173,41 @@ function checkComponentPKG() { # $1: pkgpath $2: level echo $indent"Type: Flat Component PKG" + # expand the flat pkg + local pkgdir="$scratchdir/$pkgname" + + if [[ -d "$pkgdir" ]] ; then + rm -r "$pkgdir" || return 1 + fi + pkgutil --expand "$pkgpath" "$pkgdir" + # determine identifier and version, if present - pkginfo=$(tar -xOf "$pkgpath" PackageInfo 2>/dev/null ) - if [[ $? == 0 ]]; then + pkginfo="$pkgdir/PackageInfo" + if [[ -f "$pkginfo" ]]; then # try to extract identifier - pkgidentifier=$(xmllint --xpath "string(//pkg-info/@identifier)" - <<<${pkginfo}) + pkgidentifier=$(xmllint --xpath "string(//pkg-info/@identifier)" ${pkginfo}) if [[ -n $pkgidentifier ]]; then echo "Identifier: $pkgidentifier" fi - pkgversion=$(xmllint --xpath "string(//pkg-info/@version)" - <<<${pkginfo}) + pkgversion=$(xmllint --xpath "string(//pkg-info/@version)" ${pkginfo}) if [[ -n $pkgversion ]]; then echo "Version: $pkgversion" fi - pkglocation=$(xmllint --xpath "string(//pkg-info/@install-location)" - <<<${pkginfo}) + pkglocation=$(xmllint --xpath "string(//pkg-info/@install-location)" ${pkginfo}) if [[ -n $pkglocation ]]; then echo "Location: $pkglocation" fi fi - - local extractiondir="$scratchdir/$pkgname" - if ! mkcleandir $extractiondir; then - #echo "couldn't clean $extractiondir" - return 1 - fi - - # does the pkg _have_ a Scripts archive - if tar -tf "$pkgpath" Scripts &>/dev/null; then - # extract the Scripts archive to scratch - if ! tar -x -C "$extractiondir" -f "$pkgpath" Scripts; then - #echo "error extracting Scripts Archive from $pkgpath" - return 2 - fi - - # extract the resources from the Scripts archive - if ! tar -x -C "$extractiondir" -f "$extractiondir/Scripts"; then - #echo "error extracting Scripts from $extractiondir/Scripts" - return 3 - fi - - # remove the ScriptsArchive - rm "$extractiondir/Scripts" + + # does the pkg have a Scripts dir? + if [[ -d "$pkgdir/Scripts" ]] ; then + checkFilesInDir "$pkgdir/Scripts" "$level" fi - checkFilesInDir "$extractiondir" "$level" - echo + # clean up + rm -rf "$pkgdir" } function checkDistributionPKG() { # $1: pkgpath @@ -228,91 +217,68 @@ function checkDistributionPKG() { # $1: pkgpath echo "Type: Flat Distribution PKG" + # expand the flat pkg + local pkgdir="$scratchdir/$pkgname" + + if [[ -d "$pkgdir" ]] ; then + rm -r "$pkgdir" || return 1 + fi + pkgutil --expand "$pkgpath" "$pkgdir" + # determine identifier and version, if present - distributionxml=$(tar -xOf "$pkgpath" Distribution 2>/dev/null ) - if [[ $? == 0 ]]; then + distributionxml="$pkgdir/Distribution" + if [[ -f "$distributionxml" ]]; then # distribution pkg, try to extract identifier - pkgidentifier=$(xmllint --xpath "string(//installer-gui-script/product/@id)" - <<<${distributionxml}) + pkgidentifier=$(xmllint --xpath "string(//installer-gui-script/product/@id)" ${distributionxml}) if [[ -n $pkgidentifier ]]; then echo "Identifier: $pkgidentifier" fi - pkgversion=$(xmllint --xpath "string(//installer-gui-script/product/@version)" - <<<${distributionxml}) + pkgversion=$(xmllint --xpath "string(//installer-gui-script/product/@version)" ${distributionxml}) if [[ -n $pkgversion ]]; then echo "Version: $pkgversion" fi fi - - local pkgdir="$scratchdir/$pkgname" - - if ! mkcleandir $pkgdir; then - #echo "couldn't clean $pkgdir" - return 1 - fi - - # does the pkg _have_ Scripts archives? + + # find component pkgs IFS=$'\n' - components=( $(tar -tf "$pkgpath" '*.pkg$' 2>/dev/null) ) + components=($(ls -d1 "$pkgdir"/*.pkg)) components_count=${#components} echo "Contains ${#components} component pkgs" echo if [[ $components_count -gt 0 ]]; then for c in $components ; do - # get the components's name - local cname=${c%.*} # remove extension - - # create a subdir in extractiondir - local extractiondir="$pkgdir/$cname" - if ! mkcleandir "$extractiondir"; then - #echo "couldn't clean $extractiondir" - return 1 - fi - echo " $bold_color$cname$reset_color" echo " Type: Flat Component PKG" # determine identifier and version, if present - pkginfo=$(tar -xOf "$pkgpath" "$c/PackageInfo" 2>/dev/null ) - if [[ $? == 0 ]]; then + pkginfo="$c/PackageInfo" + if [[ -f "$pkginfo" ]]; then # try to extract identifier - pkgidentifier=$(xmllint --xpath "string(//pkg-info/@identifier)" - <<<${pkginfo}) + pkgidentifier=$(xmllint --xpath "string(//pkg-info/@identifier)" "${pkginfo}") if [[ -n $pkgidentifier ]]; then echo " Identifier: $pkgidentifier" fi - pkgversion=$(xmllint --xpath "string(//pkg-info/@version)" - <<<${pkginfo}) + pkgversion=$(xmllint --xpath "string(//pkg-info/@version)" "${pkginfo}") if [[ -n $pkgversion ]]; then echo " Version: $pkgversion" fi - pkglocation=$(xmllint --xpath "string(//pkg-info/@install-location)" - <<<${pkginfo}) + pkglocation=$(xmllint --xpath "string(//pkg-info/@install-location)" "${pkginfo}") if [[ -n $pkglocation ]]; then echo " Location: $pkglocation" fi fi - # does the pkg _have_ a Scripts archive - if tar -tf "$pkgpath" "$c/Scripts" &>/dev/null; then - # extract the Scripts archive to scratch - if ! tar -x -C "$extractiondir" -f "$pkgpath" "$c/Scripts"; then - #echo "error extracting Scripts Archive from $pkgpath" - return 2 - fi - - # extract the resources from the Scripts archive - if ! tar -x -C "$extractiondir" -f "$extractiondir/$c/Scripts"; then - #echo "error extracting Scripts from $extractiondir/$c/Scripts" - return 3 - fi - - # remove the ScriptsArchive - rm -rf "$extractiondir/$c" + # does the pkg have a Scripts directory? + if [[ -d "$c/Scripts" ]] ; then + checkFilesInDir "$c/Scripts" 1 fi - - # check the extracted scripts - checkFilesInDir "$extractiondir" 1 echo done fi + # clean up + rm -rf "$pkgdir" } function checkPkg() { # $1: pkgpath @@ -343,8 +309,8 @@ function checkPkg() { # $1: pkgpath elif [[ -d $pkgpath ]]; then checkBundlePKG "$pkgpath" else - # flat pkg, try to extract Distribution XML - distributionxml=$(tar -xOf "$pkgpath" Distribution 2>/dev/null ) + # flat pkg, look for Distribution + distribution=$(xar -tf "$pkgpath" | grep Distribution 2>/dev/null ) if [[ $? == 0 ]]; then checkDistributionPKG "$pkgpath" else From 44a67e5dcb094f0776ed804c202b8bab4c49cced Mon Sep 17 00:00:00 2001 From: Armin Briegel <1933192+scriptingosx@users.noreply.github.com> Date: Wed, 2 Feb 2022 11:08:44 +0100 Subject: [PATCH 2/5] script now checks for more shebangs and whether a script calls 'python' --- pkgcheck.sh | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pkgcheck.sh b/pkgcheck.sh index 57dc94e..c4cd3bb 100755 --- a/pkgcheck.sh +++ b/pkgcheck.sh @@ -83,14 +83,29 @@ function checkFilesInDir() { # $1: dirpath $2: level file_description="$(file -b "$f")" #echo "$indent$file_description" if [[ "$file_description" == *"script text executable"* ]]; then + + # check for deprecated shebangs shebang=$(head -n 1 "$f" | tr -d $'\n') lastelement=${shebang##*/} if [[ $shebang == "#!/bin/bash" || \ - $shebang == "#!/usr/bin/python" || \ + $shebang == "#!/usr/bin/env bash" || \ $shebang == "#!/usr/bin/ruby" || \ - $shebang == "#!/usr/bin/perl" ]]; then + $shebang == "#!/usr/bin/env ruby" || \ + $shebang == "#!/usr/bin/perl" || \ + $shebang == "#!/usr/bin/env perl" ]]; then echo "$indent$fg[yellow]$relpath has shebang $shebang$reset_color" fi + + # python gets extra treatment since it will break in macOS 12.3+ + if [[ $shebang == "#!/usr/bin/python" || \ + $shebang == "#!/usr/bin/env python" ]]; then + echo "$indent$fg[red]$relpath has shebang $shebang$reset_color" + fi + + # check for uses of 'python' in code + if grep --invert-match '^#' "$f" | grep --quiet 'python'; then + echo "$indent$fg[red]$relpath calls 'python' in code$reset_color" + fi fi fi done From 2e6f7b1a45cb04929187eb4ba333480388b0712c Mon Sep 17 00:00:00 2001 From: Armin Briegel <1933192+scriptingosx@users.noreply.github.com> Date: Wed, 2 Feb 2022 11:39:57 +0100 Subject: [PATCH 3/5] updated README.md --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cb3eb23..f2cd700 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@ # Check Installer Pkgs for deprecated scripts -macOS 10.15 Catalina [will deprecate the built-in `/bin/bash`](https://support.apple.com/en-us/HT208050). [I have talked about this at length](https://scriptingosx.com/2019/06/moving-to-zsh/). +macOS 10.15 Catalina [deprecated the built-in `/bin/bash`](https://support.apple.com/en-us/HT208050). [I have talked about this at length](https://scriptingosx.com/2019/06/moving-to-zsh/). The [release notes for Catalina](https://developer.apple.com/documentation/macos_release_notes/macos_catalina_10_15_beta_6_release_notes) also tell us that other built-in scripting runtimes, namely Python, Perl, and Ruby. Will not be included in future macOS releases (post-Catalina) any more. +[Starting with macOS 12.3](https://developer.apple.com/documentation/macos-release-notes/macos-12_3-release-notes), the Python 2.7 binary `/usr/bin/python` will _not_ work anymore. Calls to `python` will fail. + This means, that if you want to use bash, Python, Perl, or Ruby on macOS, you will have to install, and maintain your own version in the future. However, scripts in installation packages, _cannot_ rely on any of these interpreters being available in future, post-Catalina versions of macOS. Installer pkgs can be run in all kinds of environments and at all times, and you would not want them to fail, because a dependency is missing. -The good news is that we still have time. All the runtimes mentioned above are still present in Catalina, so the packages will continue to work for now. But if you are building installation scripts, you need to check if any of the installation scripts use one of these interpreters and fix them. +If you are building installation scripts, you need to check if any of the installation scripts use one of these interpreters and fix them. -> I recommend to use `/bin/sh` for installation scripts, since that will run in _any_ macOS context, even the Recovery system. +> I recommend to use `/bin/sh` for installation scripts, since that will run in _any_ macOS context, even the Recovery system and a 'future' macOS that may not have `/bin/bash` If you are using third-party installer packages, you may also want to check them for these interpreters, and notify the developer that these packages will break in future versions of macOS. @@ -27,7 +29,9 @@ Once I had written the code to inspect all these types of pkgs, I realized I cou - Identifier and version (when present) - Install-location - for Distribution and mpkg types, shows the information for all components as well -- for every script in a pkg or component, checks the first line of the script for shebangs of the deprecated interpreters (`/bin/bash`, `/usr/bin/python`, `/usr/bin/perl`, and `/usr/bin/ruby`) and print a warning when found +- for every script in a pkg or component, checks the first line of the script for shebangs of the deprecated interpreters (`/bin/bash`, `/usr/bin/python`, `/usr/bin/perl`, and `/usr/bin/ruby`, also using these binaries with `/usr/bin/env`) and print a warning when found +- `/usr/bin/python` and `/usr/bin/env python` will show an error +- if the script contains the string `python` (that is not in a comment line) it will also show an error, this might generate some false positives (for example, calls to `python3` will also trigger this, but they would probably be problematic, as well) ## How to run pkgcheck.sh From e4d2caeaa5b752932706e6a159bdeebf472bd660 Mon Sep 17 00:00:00 2001 From: Armin Briegel <1933192+scriptingosx@users.noreply.github.com> Date: Wed, 2 Feb 2022 13:53:01 +0100 Subject: [PATCH 4/5] now ignores file starting with '._'. closes #5 --- README.md | 2 +- pkgcheck.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2cd700..ba07470 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ If you are using third-party installer packages, you may also want to check them To check a flat installer package, you would expand it with `pkgutil --expand` and then look at script files in the `Scripts` folder. This will work fine for a package or two, but gets tedious really quickly, especially with large distribution pkgs with many components (e.g. Office). -So... I wrote a script to do it. The script should handle normal component pkgs, distribution pkgs and the legacy bundle pkgs and mpkgs. +So... I wrote a script to do it. The script should handle normal component pkgs, distribution pkgs and the legacy bundle pkgs and mpkgs. It will also ## What the script does diff --git a/pkgcheck.sh b/pkgcheck.sh index c4cd3bb..e78bf22 100755 --- a/pkgcheck.sh +++ b/pkgcheck.sh @@ -369,7 +369,7 @@ function checkDirectory() { # $1: dirpath return 1 fi - local foundpkgs=$(find "$dirpath" -not -ipath '*.mpkg/*' -and \( -iname '*.pkg' -or -iname '*.mpkg' \) -print0 ) + local foundpkgs=$(find "$dirpath" -not \( -ipath '*.mpkg/*' -or -iname '._*' \) -and \( -iname '*.pkg' -or -iname '*.mpkg' \) -print0 ) local pkglist=( ${(0)foundpkgs} ) # find all pkg and mpkgs in the directory, excluding component pkgs in mpkgs for x in $pkglist ; do From c19b4d8b7aa3f7c22cc627c06ddd642dc015532d Mon Sep 17 00:00:00 2001 From: Armin Briegel <1933192+scriptingosx@users.noreply.github.com> Date: Wed, 2 Feb 2022 13:58:56 +0100 Subject: [PATCH 5/5] now correctly detects Apple Installer notarizations --- pkgcheck.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkgcheck.sh b/pkgcheck.sh index e78bf22..ec700a5 100755 --- a/pkgcheck.sh +++ b/pkgcheck.sh @@ -40,12 +40,11 @@ function getPkgSignature() { # $1: pkgpath function getPkgNotarized() { # $1: pkgpath local pkgpath=${1:?"no pkg path"} - notary_source=$(spctl --assess -vvv --type install $pkgpath 2>&1 | awk -F '=' '/source/ { print $2 }') - - if [[ $notary_source == "Notarized Developer ID" ]]; then + if notary_result=$(spctl --assess -vvv --type install $pkgpath 2>&1); then + notary_source=$(echo "$notary_result" | awk -F '=' '/source/ { print $2 }') echo "$fg[green]Yes, $notary_source$reset_color" else - echo "$fg[yellow]No${notary_source:+, $notary_source}$reset_color" + echo "$fg[yellow]No$reset_color" fi }