11#! /usr/bin/bash
22# -----------------------------------------------------------------------------
3- # Version: 1.1 .0
3+ # Version: 1.2 .0
44# Released: 2025-10-31
5+ # Latest update: 2025-12-16
56# Author: Azure Support
67#
78# Copyright (c) Microsoft Corporation. All rights reserved.
1617# timestamp
1718TIMESTAMP=` date +%Y%m%dT%H%M%S`
1819
20+ # this will be infinitely useful
21+ source /etc/os-release
22+
23+ # Do OS detection for use in other functions
24+ function detect_osfam() {
25+ # Normalize to lowercase
26+ local id like_str
27+ id=" $( printf ' %s' " ${ID:- } " | tr ' [:upper:]' ' [:lower:]' ) "
28+ like_str=" $( printf ' %s' " ${ID_LIKE:- } " | tr ' [:upper:]' ' [:lower:]' ) "
29+
30+ # Default
31+ OSFAM=" unknown"
32+
33+ # Helper: prefer 'fedora' over other RHEL derivatives if both appear
34+ _prefer_fedora_like () {
35+ # Takes a space-separated list; echoes the chosen token if matched.
36+ local tokens=" $1 "
37+ # First, explicit fedora
38+ for t in $tokens ; do
39+ [[ " $t " == " fedora" ]] && { echo " fedora" ; return ; }
40+ done
41+ # Then other RHEL-family tokens
42+ for t in $tokens ; do
43+ case " $t " in
44+ rhel|centos|rocky|almalinux|ol|amzn)
45+ echo " fedora" ; return
46+ ;;
47+ esac
48+ done
49+ # No match
50+ echo " "
51+ }
52+
53+ # Pass 1: exact ID
54+ case " $id " in
55+ # Treat all these as fedora lineage
56+ rhel|fedora|centos|rocky|almalinux|ol|amzn)
57+ OSFAM=" fedora"
58+ ;;
59+ ubuntu|debian)
60+ OSFAM=" debian"
61+ ;;
62+ sles|suse|opensuse* )
63+ OSFAM=" suse"
64+ ;;
65+ * )
66+ ;;
67+ esac
68+
69+ # Pass 2: scan ID_LIKE with fedora preference (handles multi-token)
70+ if [[ " $OSFAM " == " unknown" && -n " $like_str " ]]; then
71+ local chosen
72+ chosen=" $( _prefer_fedora_like " $like_str " ) "
73+ if [[ -n " $chosen " ]]; then
74+ OSFAM=" $chosen "
75+ else
76+ # Fall back to other families
77+ for like in $like_str ; do
78+ case " $like " in
79+ debian|ubuntu) OSFAM=" debian" ; break ;;
80+ suse|sles|opensuse* ) OSFAM=" suse" ; break ;;
81+ esac
82+ done
83+ fi
84+ fi
85+
86+ export $OSFAM
87+ }
88+ # call the function above to set the OSFAM variable for use elsewhere
89+ detect_osfam
90+
1991function backup() {
2092 # Create a backup of a file.
2193 # Args:
@@ -64,108 +136,208 @@ function backup() {
64136 fi
65137}
66138
67- function checkPerm() {
68- # Validate permissions, including special bits.
69- # Args: $1 = file to validate perms
70- # $2 = octal representation of desired permissions (may include special
71- # bits)
139+ # # TODO: Test these 4 functions standalone and in the sudo script
72140
73- file=" $1 "
74- desired=" $2 "
141+ # -------------------------------------------
142+ # CHECK: validate current perms vs desired
143+ # Return codes:
144+ # 0 -> match
145+ # 1 -> mismatch (fixable)
146+ # 2 -> check error (do not fix)
147+ # -------------------------------------------
148+ function checkPerm() {
149+ # Args: $1 = file path, $2 = desired octal (3 or 4 digits; may include special bits)
150+ local file=" $1 "
151+ local desired=" $2 "
152+ local actual newperm
75153
76154 if [[ -z " $file " || -z " $desired " ]]; then
77155 echo " Usage: checkPerm <file> <desired-octal-perms>"
78- return 1
156+ return 2
79157 fi
80158
81159 if [[ ! -e " $file " ]]; then
82160 echo " Error: File does not exist: $file "
83- return 1
161+ return 2
162+ fi
163+
164+ # Basic octal validation (3–4 digits)
165+ if [[ ! " $desired " =~ ^[0-7]{3,4}$ ]]; then
166+ echo " Error: Desired permissions must be 3 or 4 octal digits (e.g., 644, 0755, 4755)."
167+ return 2
84168 fi
85169
86170 # Get current permissions in full 4-digit octal form (includes special bits)
87171 actual=$( stat -c " %a" " $file " )
172+
88173 # Pad to 4 digits for consistent comparison (e.g., 755 -> 0755)
89- # there is an edge case not being handled in 'actual' output of stat - if the
174+ # There is an edge case not being handled in 'actual' output of stat - if the
90175 # 3-digit perms of the actual file start with 0 from 'stat' it will break the
91- # check because it looks like 0x to bash. This is sufficiently edge to not
176+ # check because it looks like 0x to bash. This is sufficiently edge to not
92177 # handle it, and also 0XX perms would be basically broken for most real-world
93- # uses, so lets keep the implied force change in that scenario
178+ # uses, so let's keep the implied force change in that scenario.
94179 actual=$( printf " %04d" " $actual " )
95180 desired=$( printf " %04s" " $desired " )
96181
97182 if [[ " $actual " == " $desired " ]]; then
98183 echo " OK: $file already has permissions $actual "
184+ return 0
99185 else
100- echo " WARN: $file has permissions $actual , fixing to $desired "
101- if chmod " $desired " " $file " ; then
102- newperm=$( stat -c " %a" " $file " )
103- printf -v newperm " %04d" " $newperm "
104- echo " FIXED: $file now has permissions $newperm "
105- else
106- echo " ERR: Unable to fix permissions on $file "
107- return 1
108- fi
186+ echo " MISMATCH: $file has permissions $actual ; desired $desired "
187+ return 1
109188 fi
110189}
111190
112- function checkOwner() {
113- # Validate file ownership
114- # Args:
115- # $1 = file to validate
116- # $2 = desired owner or owner:group
117- # $3 = optional group (ignored if $2 includes :)
118- #
119- # Examples:
120- # checkOwner /etc/sudoers root root
121- # checkOwner /etc/sudoers root:root
191+ # -------------------------------------------
192+ # FIX: apply chmod if check reports mismatch
193+ # Behavior:
194+ # - Calls checkPerm and relays its status.
195+ # - If checkPerm returns 1 (mismatch), attempts chmod.
196+ # - On success, echoes FIXED and returns 0.
197+ # - On failure, echoes ERR and returns 1.
198+ # - If checkPerm returns 2 (error), propagates 2 (no fix attempt).
199+ # -------------------------------------------
200+ fixPerm () {
201+ local file=" $1 "
202+ local desired=" $2 "
203+
204+ # Run check and capture status
205+ checkPerm " $file " " $desired "
206+ local rc=$?
122207
208+ case " $rc " in
209+ 0)
210+ echo " NOOP: Permissions already correct; no change applied."
211+ return 0
212+ ;;
213+ 1)
214+ # Re-normalize desired to 4 digits before chmod
215+ desired=$( printf " %04s" " $desired " )
216+ echo " FIX: Applying chmod $desired to $file ..."
217+ if chmod " $desired " " $file " ; then
218+ local newperm
219+ newperm=$( stat -c " %a" " $file " )
220+ printf -v newperm " %04d" " $newperm "
221+ echo " FIXED: $file now has permissions $newperm "
222+ return 0
223+ else
224+ echo " ERR: Unable to fix permissions on $file "
225+ return 1
226+ fi
227+ ;;
228+ 2)
229+ # Usage / file-not-found / invalid desired perms
230+ echo " ABORT: Check failed; fix not attempted."
231+ return 2
232+ ;;
233+ * )
234+ echo " ERR: Unexpected checkPerm status: $rc "
235+ return 1
236+ ;;
237+ esac
238+ }
239+
240+ # -------------------------------------------
241+ # CHECK: validate current owner/group vs desired
242+ # Return codes:
243+ # 0 -> match
244+ # 1 -> mismatch (fixable)
245+ # 2 -> check error (do not fix)
246+ # -------------------------------------------
247+ checkOwner () {
123248 local file=" $1 "
124249 local owner_group=" $2 "
125- local desired_owner desired_group
250+ local opt_group=" $3 "
251+ local desired_owner desired_group actual_owner actual_group
126252
127253 # Validate args
128254 if [[ -z " $file " || -z " $owner_group " ]]; then
129255 echo " Usage: checkOwner <file> <owner[:group]> [group]"
130- return 1
256+ return 2
257+ fi
258+
259+ # File must exist
260+ if [[ ! -e " $file " ]]; then
261+ echo " WARN: File not found: $file "
262+ return 2
131263 fi
132264
133- # Parse user/ group input
265+ # Parse owner[: group]
134266 if [[ " $owner_group " == * :* ]]; then
135267 desired_owner=" ${owner_group%%:* } "
136268 desired_group=" ${owner_group##*: } "
137269 else
138270 desired_owner=" $owner_group "
139- desired_group=" $3 "
271+ desired_group=" $opt_group "
140272 fi
141273
142- # Sanity check
274+ # Validate parsed values
143275 if [[ -z " $desired_owner " || -z " $desired_group " ]]; then
144276 echo " Usage: checkOwner <file> <owner[:group]> [group]"
145- return 1
146- fi
147-
148- # Verify file exists
149- if [[ ! -e " $file " ]]; then
150- echo " WARN: File not found: $file "
151- return 1
277+ return 2
152278 fi
153279
154- # Get current owner and group
155- local actual_owner actual_group
280+ # Get actual ownership
156281 actual_owner=$( stat -c " %U" " $file " )
157282 actual_group=$( stat -c " %G" " $file " )
158283
159- # Compare and fix if needed
284+ # Compare
160285 if [[ " $actual_owner " == " $desired_owner " && " $actual_group " == " $desired_group " ]]; then
161286 echo " OK: $file owner:group OK ($actual_owner :$actual_group )"
287+ return 0
162288 else
163- echo " WARN: $file has $actual_owner :$actual_group , fixing to $desired_owner :$desired_group "
164- if chown " $desired_owner :$desired_group " " $file " ; then
165- echo " FIXED: $file now owned by $( stat -c " %U:%G" " $file " ) "
166- else
167- echo " ERR: Failed to change ownership of $file "
168- return 1
169- fi
289+ echo " MISMATCH: $file has $actual_owner :$actual_group , desired $desired_owner :$desired_group "
290+ return 1
170291 fi
292+ }
293+
294+ # -------------------------------------------
295+ # FIX: apply chown if checkOwner reports mismatch
296+ # - If checkOwner returns 1 → try fix
297+ # - If checkOwner returns 0 → do nothing
298+ # - If checkOwner returns 2 → abort
299+ # -------------------------------------------
300+ fixOwner () {
301+ local file=" $1 "
302+ local owner_group=" $2 "
303+ local opt_group=" $3 "
304+
305+ # Run check
306+ checkOwner " $file " " $owner_group " " $opt_group "
307+ local rc=$?
308+
309+ case " $rc " in
310+ 0)
311+ echo " NOOP: Ownership already correct; no change applied."
312+ return 0
313+ ;;
314+ 1)
315+ # Determine owner:group again (same parsing logic)
316+ local desired_owner desired_group
317+ if [[ " $owner_group " == * :* ]]; then
318+ desired_owner=" ${owner_group%%:* } "
319+ desired_group=" ${owner_group##*: } "
320+ else
321+ desired_owner=" $owner_group "
322+ desired_group=" $opt_group "
323+ fi
324+
325+ echo " FIX: Applying chown $desired_owner :$desired_group to $file ..."
326+ if chown " $desired_owner :$desired_group " " $file " ; then
327+ echo " FIXED: $file now owned by $( stat -c " %U:%G" " $file " ) "
328+ return 0
329+ else
330+ echo " ERR: Failed to change ownership of $file "
331+ return 1
332+ fi
333+ ;;
334+ 2)
335+ echo " ABORT: Check failed; fix not attempted."
336+ return 2
337+ ;;
338+ * )
339+ echo " ERR: Unexpected checkOwner status: $rc "
340+ return 1
341+ ;;
342+ esac
171343}
0 commit comments