Examine all the com.apple.macl entries on files and folders
#!/bin/bash | |
: <<-EOL | |
MIT License | |
Copyright (c) 2020 Joel Bruner | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
EOL | |
#hold down command key at launch or touch /tmp/debug to enable xtrace command expansion | |
commandKeyDown=$(/usr/bin/python -c 'import Cocoa; print Cocoa.NSEvent.modifierFlags() & Cocoa.NSCommandKeyMask > 1') | |
[ "$commandKeyDown" == "True" -o -f /tmp/debug ] && set -x && xtraceFlag=1 | |
############# | |
# VARIABLES # | |
############# | |
#header for CSV output | |
CSVHeader="Filename,Header,App UUID" | |
#the name of this script | |
myName=$(basename "${0}") | |
############# | |
# FUNCTIONS # | |
############# | |
function printUsage | |
{ | |
echo -e "Usage: ${myName} [-d depth] [-s] target [...]" | |
echo -e "\nSwitches:" | |
echo -e "-d depth - specify the maxdepth a folder should be traversed into" | |
echo -e "-s - silence output for items without com.apple.macl XA" | |
echo -e "\nArguments:\ntarget - can be one or more files or folders, folders will be fully traversed unless a maxdepth is specified\n" | |
} | |
function processTarget | |
{ | |
#strip any trailing slash off | |
local target="${1%/}" | |
local item; | |
IFS=$'\n' | |
#if directory get files | |
if [ -d "${target}" ]; then | |
#a directory can have com.apple.macl applied to it - https://lapcatsoftware.com/articles/macl.html | |
if [ -z "${maxdepth}" ]; then | |
fileList=$(find "${target}" | sort) | |
else | |
fileList=$(find "${target}" -maxdepth "$maxdepth" | sort) | |
fi | |
elif [ -f "${target}" ]; then | |
fileList="${target}" | |
else | |
return | |
fi | |
#got through each file | |
for item in ${fileList}; do | |
maclXA "${item}" | |
done | |
} | |
function maclXA | |
{ | |
local file="${1}" | |
#if the file is not readable return | |
[ ! -r "$file" ] && return | |
#test for com.apple.macl attribute | |
local attributeRAW=$(xattr -p com.apple.macl "$file" 2>/dev/null) | |
#if there is no attribute data | |
if [ -z "$attributeRAW" ]; then | |
#if silent flag is not set (0) print out a line, otherwise we print nothing | |
[ "${siletFlag:=0}" -eq 0 ] && echo "\"$file\",," | |
return | |
fi | |
#create a string with no spaces and no linefeeds (unquoted is enough for bash but zsh would not do so) | |
local attribute=$(tr -d ' ' <<< $attributeRAW) | |
#subtract one because here strings always have a newline at the end | |
local attribute_length=$(( $(wc -c <<< "$attribute") - 1 )) | |
#com.apple.macl XA is allocated in 144 byte chunksUUIDs that hold up to 4 entries | |
#each entry is the 16 byte UUID plus 2 byte headers, 18 bytes expressed as an ASCII string is 36 characters | |
local possibleUUIDs=$(( $attribute_length / 36 )) | |
#echo "data size: $attribute_length" | |
#echo "Possible UUIDs: $possibleUUIDs" | |
#loop through the possible UUIDs | |
for ((i=0; i < $possibleUUIDs; i++ )); do | |
#keep advancing 36 chars for each UUID slot | |
local offset=$(( i * 36 )) | |
#if the header is "0000" then skip otherwise observed values are "0100" and "0200" although the reason is not | |
local header=${attribute:$offset:4} | |
[ "$header" == "0000" ] && continue | |
#put header into an array now that we see there are different types | |
local headerEntryArray[i]="$header" | |
#assign 32 char string to an array entry, skipping the 4 char header | |
local UUIDEntryArray[i]="${attribute:$(( $offset + 4 )):32}" | |
done | |
#loop though array and print hyphen punctuated UUID | |
for (( i=0; i < ${#UUIDEntryArray[@]}; i++ )); do | |
echo "\"$file\",${headerEntryArray[i]},${UUIDEntryArray[i]:0:8}-${UUIDEntryArray[i]:8:4}-${UUIDEntryArray[i]:12:4}-${UUIDEntryArray[i]:16:4}-${UUIDEntryArray[i]:20:12}" | |
done | |
} | |
######## | |
# MAIN # | |
######## | |
#options processing | |
while getopts ":hd:s" option; do | |
case "${option}" in | |
#print all certs | |
'd') | |
#flag vars simply need to be set or not | |
maxdepth="$OPTARG" | |
;; | |
's') | |
siletFlag=1 | |
;; | |
'h') | |
printUsage | |
exit | |
;; | |
esac | |
done | |
#shift past options so $1 is still $1 after the options are processed | |
shift $((OPTIND-1)) | |
#you cannot test ${@} because multiple items appear as multiple arguments to [ (test) and it errors, so we eval an echo of it | |
if [ -z "$(eval echo ${@})" ]; then | |
#let you know there an option or two to be had | |
echo "Please provide target file(s) or folder(s) to examine..." | |
exit | |
fi | |
#make our CSV header | |
echo "${CSVHeader}" | |
#only respect newlines in the for loop | |
IFS=$'\n' | |
#keep going until you can't do no more | |
for target in ${@}; do | |
processTarget "${target}" | |
done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment