Skip to content

Commit 1ef10e0

Browse files
authored
Merge pull request #27 from pagienge/main
Updates to sudo-impl.sh and related
2 parents 6e1f9ab + c0538ab commit 1ef10e0

5 files changed

Lines changed: 264 additions & 61 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "alar"
3-
version = "1.3.2"
3+
version = "1.3.3"
44
authors = ["malachma <malachma@microsoft.com>", "pagienge <pagienge@microsoft.com>"]
55
edition = "2021"
66
license = "MIT OR Apache-2.0"

Changelog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ All notable changes to this project will be documented in this file.
66
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
77
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
## 1.3.3 (2025-12-15)
10+
Enhanced 'sudo' action to include more checks
11+
- rev sudo-impl.sh to 1.1.0
12+
- add sudo setuid check
13+
- add /etc check
14+
Updated helpers.sh
15+
- rev helpers.sh to 1.2.0
16+
- adding OS detection, first used in sudo-impl.sh
17+
- refactor the check functions into a check and fix functions so perms/owners can check w/o fixing
18+
919
## 1.3.2 (2025-12-12)
1020
- Minimal refactoring of the code.
1121
- Add 'partx' support as replacement for sgdisk on RHEL 10
Lines changed: 226 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
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.
@@ -16,6 +17,77 @@
1617
# timestamp
1718
TIMESTAMP=`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+
1991
function 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

Comments
 (0)