CH-J Server ManagerSSH के ज़रिए सर्वर प्रबंधन
मेनू
प्रकाशित सोर्स

CH-J Server Manager

किसी विशेष ऐप्लिकेशन रिलीज़ की डायरेक्टरी और फ़ाइलें देखें।

सोर्स ZIP डाउनलोड करें
CH-J / Server Manager 368 फ़ाइलें
core-20261017T201717Z-057348c4a70f जोड़ा गया: तारीख दर्ज नहीं · बिल्ड किया गया:
remoteUsers.sh जोड़ा गया: तारीख दर्ज नहीं 28.1 KB · 582 पंक्तियाँ
कोड फ़ाइल डाउनलोड करें
1# CH-J Server Manager - fixed remote helper for the chj.users capability (POSIX sh).
2# Core runs this text with "sh -c SCRIPT chj-users OPERATION ARGS...". Plugins cannot supply code.
3# Arguments are validated by Core and again here. Secrets and file contents never appear in argv:
4# they arrive as base64 stdin frames ("CHJ<TAG>:<base64>") only after the READY marker.
5set -u
6umask 077
7LC_ALL=C; export LC_ALL
8PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH
9SSHD_MAIN=/etc/ssh/sshd_config
10SSHD_DROPIN=/etc/ssh/sshd_config.d
11SSHD_MANAGED=$SSHD_DROPIN/01-chj-users.conf
12STATE_DIR=/var/lib/chj-server-manager/users
13FRAME=
14op=${1:-}
15[ $# -gt 0 ] && shift
16printf '\036CHJ_READY\n'
18fail() { printf '\036CHJ_ERR=%s\n' "$1"; [ $# -gt 1 ] && printf '%s\n' "$2" | head -c 2000 >&2; exit 3; }
19section() { printf '\036CHJ_SECTION=%s\n' "$1"; }
20oneline() { tr '\r\n|\036' ' ' | cut -c1-400; }
21step() { printf '\036CHJ_STEP=%s|%s|%s\n' "$1" "$2" "$(printf '%s' "${3:-}" | oneline)"; }
22is_root() { [ "$(id -u)" = 0 ]; }
23need_root() { is_root || fail PRIVILEGE_REQUIRED "Root privileges are required."; }
24valid_name() { case $1 in ''|[!a-z_]*|*[!a-z0-9_-]*) return 1 ;; esac; [ ${#1} -le 32 ]; }
25valid_addr() { case $1 in ''|*[!0-9a-fA-F:.]*) return 1 ;; esac; [ ${#1} -le 45 ]; }
26valid_path() {
27 case $1 in /*) ;; *) return 1 ;; esac
28 case $1 in *[!A-Za-z0-9._/@+-]*|*/../*|*/..|*/./*|*//*) return 1 ;; esac
29 [ ${#1} -le 255 ]
31have() { command -v "$1" >/dev/null 2>&1; }
32uid_min() {
33 value=$(awk '$1=="UID_MIN"{print $2; exit}' /etc/login.defs 2>/dev/null)
34 case $value in ''|*[!0-9]*) value=1000 ;; esac
35 printf '%s' "$value"
37b64d() {
38 if printf 'YQ==' | base64 -d >/dev/null 2>&1; then base64 -d
39 elif printf 'YQ==' | openssl base64 -d -A >/dev/null 2>&1; then openssl base64 -d -A
40 else return 127; fi
42b64e() { base64 | tr -d '\r\n'; }
43sha256_of() {
44 if [ -f "$1" ]; then
45 if have sha256sum; then sha256sum < "$1" | cut -c1-64; else openssl dgst -sha256 < "$1" | sed 's/^.*= *//'; fi
46 else printf 'absent'; fi
48read_frame() {
49 FRAME=
50 while IFS= read -r line; do
51 case $line in "CHJ$1:"*) FRAME=${line#"CHJ$1:"}; return 0 ;; esac
52 done
53 return 1
55passwd_entry() { getent passwd "$1" 2>/dev/null | head -n 1; }
56user_uid() { passwd_entry "$1" | cut -d: -f3; }
57user_home() { passwd_entry "$1" | cut -d: -f6; }
58group_members() { getent group "$1" 2>/dev/null | cut -d: -f4 | tr ',' '\n'; }
59is_member() { group_members "$2" | grep -qx "$1"; }
61# Destructive account changes are refused for root, system accounts and the sudo caller.
62protect() {
63 valid_name "$1" || fail NAME_INVALID "Invalid account name."
64 uid=$(user_uid "$1")
65 [ -n "$uid" ] || fail USER_NOT_FOUND "The account does not exist."
66 [ "$uid" -ne 0 ] || fail PROTECTED_USER "The root account is protected."
67 [ "$uid" -ge "$(uid_min)" ] && [ "$uid" -ne 65534 ] || fail PROTECTED_USER "System accounts are protected."
68 [ "$1" != "${SUDO_USER:-}" ] || fail PROTECTED_USER "The account of the active SSH session is protected."
71# Runs a command as the target account so file permissions, not root, govern access.
72# BusyBox getent (Alpine) has no shadow database; root can read /etc/shadow directly. Only Core-side states leave the server.
73shadow_db() {
74 out=$(getent shadow 2>/dev/null)
75 if [ -n "$out" ]; then printf '%s\n' "$out"; else cat /etc/shadow 2>/dev/null; fi
77run_as() {
78 target=$1; shift
79 if ! is_root; then
80 [ "$(id -un)" = "$target" ] || return 125
81 "$@"; return $?
82 fi
83 if have runuser; then runuser -u "$target" -- "$@"
84 # BusyBox setpriv (Alpine) has no --reuid; only the util-linux variant is used.
85 elif have setpriv && setpriv --help 2>&1 | grep -q -e '--reuid'; then setpriv --reuid="$(id -u "$target")" --regid="$(id -g "$target")" --init-groups -- "$@"
86 else
87 # BusyBox su (Alpine) and util-linux su hand extra arguments to the shell differently, so the command is passed
88 # as one single-quoted string instead of positional parameters.
89 cmd=exec
90 for arg in "$@"; do cmd="$cmd '$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")'"; done
91 su -s /bin/sh -c "$cmd" "$target"
92 fi
95sudo_summary() {
96 have sudo || { printf 'unavailable'; return; }
97 sudo -n -l -U "$1" 2>/dev/null | awk '
98 /is not allowed to run sudo/ { none=1 }
99 /may run the following commands/ { rules=1; next }
100 rules && /^[ \t]+\(/ {
101 line=$0; nopw=(line ~ /NOPASSWD:/)
102 sub(/^[ \t]+\([^)]*\)[ \t]*/, "", line)
103 while (line ~ /^[A-Z_]+:[ \t]*/) sub(/^[A-Z_]+:[ \t]*/, "", line)
104 gsub(/[ \t]+$/, "", line)
105 if (line == "ALL") { full=1; if (nopw) fullnopw=1 } else any=1
106 }
107 END { if (full) print (fullnopw ? "full-nopasswd" : "full"); else if (any) print "limited"; else print "none" }'
109sudoers_group_rule() {
110 for file in /etc/sudoers /etc/sudoers.d/*; do
111 [ -f "$file" ] || continue
112 grep -hE "^[[:space:]]*%$1[[:space:]]" "$file" 2>/dev/null
113 done | grep -q .
115admin_group() {
116 for group in sudo wheel admin; do
117 getent group "$group" >/dev/null 2>&1 && sudoers_group_rule "$group" && { printf '%s' "$group"; return 0; }
118 done
119 return 1
122sshd_bin() {
123 if have sshd; then command -v sshd
124 elif [ -x /usr/sbin/sshd ]; then printf '/usr/sbin/sshd'; fi
126# "ok" only when sshd_config includes sshd_config.d before its first Match block.
127layout_state() {
128 [ -f "$SSHD_MAIN" ] || { printf 'missing-main'; return; }
129 awk '{ l=tolower($0); sub(/^[ \t]+/, "", l) }
130 l ~ /^include[ \t]/ && l ~ /sshd_config\.d\/\*/ && !inc { inc=NR }
131 l ~ /^match[ \t]/ && !m { m=NR }
132 END { if (inc && (!m || inc < m)) print "ok"; else if (inc) print "include-after-match"; else print "include-missing" }' "$SSHD_MAIN" 2>/dev/null || printf 'unreadable'
134service_state() {
135 if have systemctl && [ -d /run/systemd/system ]; then
136 for unit in ssh.service sshd.service; do
137 [ "$(systemctl is-active "$unit" 2>/dev/null)" = active ] && { printf 'systemd:%s:active' "$unit"; return; }
138 done
139 for unit in ssh.socket sshd.socket; do
140 [ "$(systemctl is-active "$unit" 2>/dev/null)" = active ] && { printf 'systemd:%s:active' "$unit"; return; }
141 done
142 printf 'systemd:none:inactive'; return
143 fi
144 # OpenRC (Alpine) also writes /run/sshd.pid, so it is checked before the bare pid file.
145 if have rc-service; then
146 rc-service sshd status >/dev/null 2>&1 && { printf 'openrc:sshd:active'; return; }
147 fi
148 for file in /run/sshd.pid /var/run/sshd.pid; do
149 [ -r "$file" ] || continue
150 pid=$(head -n 1 "$file" 2>/dev/null)
151 case $pid in ''|*[!0-9]*) continue ;; esac
152 if kill -0 "$pid" 2>/dev/null; then printf 'pidfile:%s:active' "$file"; else printf 'pidfile:%s:unknown' "$file"; fi
153 return
154 done
155 printf 'unknown:none:unknown'
157# After SIGHUP sshd re-executes; some builds come back under a new PID and rewrite the pid file.
158# Success = a live sshd listed in the pid file within 10 s.
159sshd_running_after_reload() {
160 tries=0
161 while [ "$tries" -lt 20 ]; do
162 for file in /run/sshd.pid /var/run/sshd.pid; do
163 [ -r "$file" ] || continue
164 pid=$(head -n 1 "$file" 2>/dev/null)
165 case $pid in ''|*[!0-9]*) continue ;; esac
166 kill -0 "$pid" 2>/dev/null && return 0
167 done
168 sleep 0.5 2>/dev/null || sleep 1
169 tries=$((tries + 1))
170 done
171 return 1
173reload_sshd() {
174 state=$(service_state); kind=${state%%:*}; rest=${state#*:}; unit=${rest%%:*}
175 case $kind in
176 systemd)
177 case $unit in
178 *.socket) printf 'socket-activated'; return 0 ;;
179 none) printf 'sshd service is not active'; return 1 ;;
180 esac
181 systemctl reload "$unit" 2>&1 ;;
182 pidfile)
183 pid=$(head -n 1 "$unit"); kill -HUP "$pid" 2>&1 || return 1
184 sleep 1; sshd_running_after_reload || { printf 'sshd stopped after reload'; return 1; } ;;
185 openrc)
186 rc-service sshd reload 2>&1 || return 1
187 sleep 1; sshd_running_after_reload || rc-service sshd status >/dev/null 2>&1 || { printf 'sshd stopped after reload'; return 1; } ;;
188 *) printf 'sshd service manager was not recognised'; return 1 ;;
189 esac
191effective() {
192 # $1 user, $2 address; prints only the authentication and access keywords Core evaluates.
193 "$SSHD" -T -C "user=$1,host=$2,addr=$2" 2>&1 | grep -E '^(passwordauthentication|kbdinteractiveauthentication|challengeresponseauthentication|pubkeyauthentication|authenticationmethods|permitrootlogin|denyusers|allowusers|denygroups|allowgroups|authorizedkeysfile|authorizedkeyscommand|addressfamily|listenaddress|port|usepam|strictmodes) |^[^a-z]' | head -n 300
195expand_keys_path() {
196 awk -v s="$1" -v h="$2" -v u="$3" -v id="$4" 'BEGIN {
197 out=""; n=length(s)
198 for (i=1; i<=n; i++) {
199 c=substr(s,i,1)
200 if (c=="%" && i<n) { d=substr(s,i+1,1); i++
201 if (d=="%") out=out "%"; else if (d=="h") out=out h; else if (d=="u") out=out u; else if (d=="U") out=out id; else { print "UNSUPPORTED"; exit }
202 } else out=out c
203 }
204 if (substr(out,1,1)!="/") out=h "/" out
205 print out }'
207file_meta() {
208 if [ -L "$1" ]; then printf 'symlink'
209 elif [ -e "$1" ]; then stat -c '%U:%G:%a:%F' "$1" 2>/dev/null || printf 'unknown'
210 else printf 'absent'; fi
213print_managed() {
214 section managed
215 if [ -L "$SSHD_MANAGED" ]; then printf 'unsafe=1\n'
216 elif [ -f "$SSHD_MANAGED" ] && [ -r "$SSHD_MANAGED" ]; then
217 printf 'sha256=%s\ndata=' "$(sha256_of "$SSHD_MANAGED")"; b64e < "$SSHD_MANAGED"; printf '\n'
218 elif [ -e "$SSHD_MANAGED" ]; then printf 'unreadable=1\n'
219 else printf 'sha256=absent\n'; fi
222READ_KEYS='p=$1
223if [ -L "$p" ]; then printf "type=symlink\n"; exit 0; fi
224if [ ! -e "$p" ]; then if [ -d "${p%/*}" ]; then printf "type=absent\n"; else printf "type=absent-dir\n"; fi; exit 0; fi
225if [ ! -f "$p" ]; then printf "type=other\n"; exit 0; fi
226if [ ! -r "$p" ]; then printf "type=unreadable\n"; exit 0; fi
227size=$(wc -c < "$p" | tr -d " ")
228printf "type=file\nsize=%s\n" "$size"
229[ "$size" -le 262144 ] || { printf "tooLarge=1\n"; exit 0; }
230printf "data="; base64 < "$p" | tr -d "\r\n"; printf "\n"'
232# Executed as the key owner. Exit codes are mapped by Core.
233WRITE_KEYS='set -u; target=$1; expected=$2; dir=${target%/*}
234[ ! -L "$dir" ] || exit 21
235if [ ! -e "$dir" ]; then mkdir -m 700 -- "$dir" || exit 22; fi
236[ -d "$dir" ] || exit 23
237[ ! -L "$target" ] || exit 24
238if [ -e "$target" ] && [ ! -f "$target" ]; then exit 25; fi
239if [ -f "$target" ]; then
240 if command -v sha256sum >/dev/null 2>&1; then current=$(sha256sum < "$target" | cut -c1-64); else current=$(openssl dgst -sha256 < "$target" | sed "s/^.*= *//"); fi
241else current=absent; fi
242[ "$current" = "$expected" ] || exit 26
243tmp=$(mktemp "$dir/.authorized_keys.chj-XXXXXX") || exit 27
244if ! cat > "$tmp"; then rm -f -- "$tmp"; exit 28; fi
245chmod 600 "$tmp" || { rm -f -- "$tmp"; exit 28; }
246mv -f -- "$tmp" "$target" || { rm -f -- "$tmp"; exit 29; }'
248op_probe() {
249 printf 'user=%s\nuid=%s\n' "$(id -un)" "$(id -u)"
250 printf 'groups=%s\n' "$(id -Gn 2>/dev/null | tr ' ' ',')"
251 printf 'client=%s\n' "${SSH_CLIENT%% *}"
252 if have sudo; then
253 out=$(sudo -n -p '' true 2>&1 </dev/null); code=$?
254 printf 'sudoInstalled=1\nsudoNonInteractive=%s\nsudoMessage=%s\n' "$code" "$(printf '%s' "$out" | head -n 1 | oneline)"
255 else
256 printf 'sudoInstalled=0\n'
257 fi
260op_inventory() {
261 section meta
262 printf 'whoami=%s\nuid=%s\nuidMin=%s\n' "$(id -un)" "$(id -u)" "$(uid_min)"
263 printf 'os=%s\n' "$(sed -n 's/^PRETTY_NAME=//p' /etc/os-release 2>/dev/null | head -n 1 | tr -d '"' | oneline)"
264 printf 'osId=%s\n' "$(sed -n 's/^ID=//p' /etc/os-release 2>/dev/null | head -n 1 | tr -d '"' | oneline)"
265 for tool in useradd usermod userdel gpasswd chpasswd runuser setpriv sudo systemctl pgrep base64; do
266 have "$tool" && printf 'tool=%s\n' "$tool"
267 done
268 SSHD=$(sshd_bin); [ -n "$SSHD" ] && printf 'tool=sshd\n'
269 printf 'layout=%s\n' "$(layout_state)"
270 printf 'managedPath=%s\n' "$SSHD_MANAGED"
271 section passwd; getent passwd | head -n 5000
272 section group; getent group | head -n 5000
273 section shells; grep -E '^/' /etc/shells 2>/dev/null
274 print_managed
275 if is_root; then
276 section admingroups
277 for group in sudo wheel admin; do
278 getent group "$group" >/dev/null 2>&1 && sudoers_group_rule "$group" && printf '%s\n' "$group"
279 done
280 section shadow
281 # Password hashes never leave the server: only a coarse state is printed.
282 shadow_db | awk -F: '{
283 h=$2; s="set"
284 if (h=="") s="empty"
285 else if (h=="!" || h=="!!" || h=="!*" || h=="*" || h=="*LK*") s="none"
286 else if (substr(h,1,1)=="!") s="locked"
287 print $1 ":" s ":" $3 ":" $8 }'
288 section sudo
289 min=$(uid_min)
290 getent passwd | while IFS=: read -r name _ uid _; do
291 case $uid in ''|*[!0-9]*) continue ;; esac
292 if [ "$uid" -eq 0 ] || { [ "$uid" -ge "$min" ] && [ "$uid" -ne 65534 ]; } || is_member "$name" sudo || is_member "$name" wheel || is_member "$name" admin; then
293 printf '%s:%s\n' "$name" "$(sudo_summary "$name")"
294 fi
295 done | head -n 500
296 fi
297 section end
300op_detail() {
301 [ $# -eq 3 ] || fail ARGUMENTS_INVALID "Invalid arguments."
302 name=$1; addr4=$2; addr6=$3
303 valid_name "$name" || fail NAME_INVALID "Invalid account name."
304 { [ -z "$addr4" ] || valid_addr "$addr4"; } && { [ -z "$addr6" ] || valid_addr "$addr6"; } || fail ARGUMENTS_INVALID "Invalid address."
305 entry=$(passwd_entry "$name"); [ -n "$entry" ] || fail USER_NOT_FOUND "The account does not exist."
306 uid=$(printf '%s' "$entry" | cut -d: -f3); home=$(printf '%s' "$entry" | cut -d: -f6)
307 section account
308 printf 'passwd=%s\n' "$entry"
309 printf 'uidMin=%s\n' "$(uid_min)"
310 printf 'groups=%s\n' "$(id -Gn "$name" 2>/dev/null | tr ' ' ',')"
311 if have pgrep; then printf 'processes=%s\n' "$(pgrep -u "$name" 2>/dev/null | wc -l | tr -d ' ')"; fi
312 printf 'homeMeta=%s\n' "$(file_meta "$home")"
313 if is_root; then
314 printf 'shadow=%s\n' "$(shadow_db | awk -F: -v n="$name" '$1==n' | awk -F: '{
315 h=$2; s="set"; if (h=="") s="empty"; else if (h=="!" || h=="!!" || h=="!*" || h=="*" || h=="*LK*") s="none"; else if (substr(h,1,1)=="!") s="locked"
316 print s ":" $3 ":" $8 }')"
317 printf 'sudo=%s\n' "$(sudo_summary "$name")"
318 fi
319 section ssh
320 SSHD=$(sshd_bin)
321 akf=".ssh/authorized_keys .ssh/authorized_keys2"
322 if [ -z "$SSHD" ]; then printf 'sshd=missing\n'
323 else
324 printf 'sshd=%s\nversion=%s\n' "$SSHD" "$("$SSHD" -V 2>&1 | head -n 1 | oneline)"
325 printf 'layout=%s\nservice=%s\n' "$(layout_state)" "$(service_state)"
326 if is_root; then
327 out=$("$SSHD" -t 2>&1); code=$?
328 printf 'syntax=%s\nsyntaxMessage=%s\n' "$code" "$(printf '%s' "$out" | head -n 3 | oneline)"
329 if [ -n "$addr4" ]; then section effective4; effective "$name" "$addr4"; fi
330 if [ -n "$addr6" ]; then section effective6; effective "$name" "$addr6"; fi
331 configured=$(effective "$name" "${addr4:-${addr6:-192.0.2.10}}" | sed -n 's/^authorizedkeysfile //p' | head -n 1)
332 [ -n "$configured" ] && akf=$configured
333 fi
334 fi
335 print_managed
336 section keys
337 printf 'home=%s\n' "$home"
338 set -f
339 count=0
340 for spec in $akf; do
341 count=$((count + 1)); [ "$count" -le 4 ] || break
342 [ "$spec" != none ] || continue
343 path=$(expand_keys_path "$spec" "$home" "$name" "$uid")
344 printf '\036CHJ_KEYFILE=%s\n' "$path"
345 printf 'spec=%s\n' "$spec"
346 case $path in UNSUPPORTED) printf 'type=unsupported\n'; continue ;; esac
347 printf 'meta=%s\ndirMeta=%s\n' "$(file_meta "$path")" "$(file_meta "${path%/*}")"
348 run_as "$name" sh -c "$READ_KEYS" chj-keys "$path" 2>/dev/null || printf 'type=unreadable\n'
349 done
350 set +f
351 section end
354op_keys_write() {
355 [ $# -eq 3 ] || fail ARGUMENTS_INVALID "Invalid arguments."
356 name=$1; path=$2; expected=$3
357 valid_name "$name" || fail NAME_INVALID "Invalid account name."
358 valid_path "$path" || fail KEYS_PATH_UNSUPPORTED "The authorized_keys path is not supported."
359 case $expected in absent|[0-9a-f]*) ;; *) fail ARGUMENTS_INVALID "Invalid checksum." ;; esac
360 uid=$(user_uid "$name"); [ -n "$uid" ] || fail USER_NOT_FOUND "The account does not exist."
361 home=$(user_home "$name")
362 valid_path "$home" && [ "$home" != / ] || fail KEYS_PATH_UNSUPPORTED "The home directory is not usable."
363 case $path in "$home"/*) ;; *) fail KEYS_PATH_UNSUPPORTED "Only key files inside the home directory are managed." ;; esac
364 read_frame DATA || fail PROTOCOL_ERROR "Key data was not received."
365 work=$(mktemp -d) || fail TEMPORARY_FAILED "Temporary directory could not be created."
366 trap 'rm -rf -- "$work"' EXIT
367 printf '%s' "$FRAME" | b64d > "$work/data" || fail PROTOCOL_ERROR "Key data could not be decoded."
368 run_as "$name" sh -c "$WRITE_KEYS" chj-keys "$path" "$expected" < "$work/data"
369 code=$?
370 case $code in
371 0) ;;
372 21|24) fail KEYS_SYMLINK "The key file or its directory is a symbolic link; it was not changed." ;;
373 22|23) fail KEYS_DIRECTORY_FAILED "The .ssh directory could not be created or is not a directory." ;;
374 25) fail KEYS_NOT_REGULAR "The key file is not a regular file." ;;
375 26) fail KEYS_CHANGED "The key file changed after it was read. Reload and try again." ;;
376 125) fail PRIVILEGE_REQUIRED "Root privileges are required for keys of another account." ;;
377 *) fail KEYS_WRITE_FAILED "The key file could not be written (exit $code)." ;;
378 esac
379 if is_root && have restorecon; then restorecon -F "${path%/*}" "$path" >/dev/null 2>&1 || true; fi
380 printf 'sha256=%s\nmeta=%s\ndirMeta=%s\n' "$(run_as "$name" sh -c 'if command -v sha256sum >/dev/null 2>&1; then sha256sum < "$1" | cut -c1-64; else openssl dgst -sha256 < "$1" | sed "s/^.*= *//"; fi' chj-keys "$path")" "$(file_meta "$path")" "$(file_meta "${path%/*}")"
381 printf '\036CHJ_RESULT=ok\n'
384check_shell() {
385 valid_path "$1" || fail SHELL_INVALID "Invalid shell path."
386 [ -x "$1" ] || fail SHELL_INVALID "The shell does not exist or is not executable."
387 case ${1##*/} in nologin|false) return 0 ;; esac
388 grep -qxF "$1" /etc/shells 2>/dev/null || fail SHELL_INVALID "The shell is not listed in /etc/shells."
390set_password() {
391 have chpasswd || { ERRMSG="chpasswd is not available"; return 127; }
392 read_frame SECRET || { ERRMSG="password was not received"; return 2; }
393 secret=$(printf '%s' "$FRAME" | b64d) || { FRAME=; ERRMSG="password could not be decoded"; return 3; }
394 FRAME=
395 [ -n "$secret" ] || { ERRMSG="empty password"; return 4; }
396 ERRMSG=$(printf '%s:%s\n' "$1" "$secret" | chpasswd 2>&1); code=$?
397 secret=
398 return $code
400grant_sudo() {
401 group=$(admin_group) || { ERRMSG="No sudo, wheel or admin group is referenced by sudoers; sudoers was not changed."; return 2; }
402 ERRMSG=$(usermod -a -G "$group" -- "$1" 2>&1) || return 1
403 ERRMSG="group $group"
405revoke_sudo() {
406 removed=
407 for group in sudo wheel admin; do
408 is_member "$1" "$group" || continue
409 if have gpasswd; then ERRMSG=$(gpasswd -d "$1" "$group" 2>&1) || return 1
410 elif have deluser; then ERRMSG=$(deluser "$1" "$group" 2>&1) || return 1
411 else ERRMSG="gpasswd is not available"; return 127; fi
412 removed="$removed $group"
413 done
414 ERRMSG="removed from:${removed:- none}"
417op_create() {
418 [ $# -eq 9 ] || fail ARGUMENTS_INVALID "Invalid arguments."
419 name=$1; shell=$2; home=$3; mkhome=$4; primary=$5; groups=$6; comment=$7; admin=$8; withpw=$9
420 need_root
421 valid_name "$name" || fail NAME_INVALID "Invalid account name."
422 have useradd || fail TOOL_MISSING "useradd is not available on this server."
423 [ -z "$(passwd_entry "$name")" ] || fail USER_EXISTS "The account already exists."
424 if [ -z "$primary" ] && getent group "$name" >/dev/null 2>&1; then fail GROUP_EXISTS "A group with this name already exists; select a primary group."; fi
425 check_shell "$shell"
426 if [ -n "$home" ]; then valid_path "$home" || fail HOME_INVALID "Invalid home directory."; fi
427 if [ -n "$home" ] && [ "$mkhome" = 1 ] && [ -e "$home" ]; then fail HOME_EXISTS "The home directory already exists."; fi
428 if [ -n "$primary" ]; then valid_name "$primary" && getent group "$primary" >/dev/null 2>&1 || fail GROUP_NOT_FOUND "Primary group does not exist."; fi
429 for group in $(printf '%s' "$groups" | tr ',' ' '); do
430 valid_name "$group" && getent group "$group" >/dev/null 2>&1 || fail GROUP_NOT_FOUND "Group $group does not exist."
431 done
432 set -- -s "$shell"
433 if [ "$mkhome" = 1 ]; then set -- "$@" -m; else set -- "$@" -M; fi
434 [ -n "$home" ] && set -- "$@" -d "$home"
435 if [ -n "$primary" ]; then set -- "$@" -g "$primary"; else set -- "$@" -U; fi
436 [ -n "$groups" ] && set -- "$@" -G "$groups"
437 [ -n "$comment" ] && set -- "$@" -c "$comment"
438 out=$(useradd "$@" -- "$name" 2>&1) || fail USERADD_FAILED "$out"
439 step create ok "$out"
440 result=ok
441 if [ "$withpw" = 1 ]; then
442 if set_password "$name"; then step password ok; else step password failed "$ERRMSG"; result=partial; fi
443 fi
444 if [ "$admin" = 1 ]; then
445 if grant_sudo "$name"; then step sudo ok "$ERRMSG"; else step sudo failed "$ERRMSG"; result=partial; fi
446 fi
447 printf '\036CHJ_RESULT=%s\n' "$result"
450op_account() {
451 [ $# -ge 2 ] || fail ARGUMENTS_INVALID "Invalid arguments."
452 action=$1; name=$2; value=${3:-}; flag=${4:-0}
453 need_root
454 protect "$name"
455 have usermod || fail TOOL_MISSING "usermod is not available on this server."
456 case $action in
457 lock) out=$(usermod -L -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
458 unlock) out=$(usermod -U -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
459 expire) out=$(usermod -e 1 -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
460 unexpire) out=$(usermod -e '' -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
461 password) set_password "$name" || fail PASSWORD_FAILED "$ERRMSG" ;;
462 shell) check_shell "$value"; out=$(usermod -s "$value" -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
463 comment) out=$(usermod -c "$value" -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
464 home)
465 valid_path "$value" && [ "$value" != / ] || fail HOME_INVALID "Invalid home directory."
466 if [ "$flag" = 1 ]; then
467 [ ! -e "$value" ] || fail HOME_EXISTS "The target directory already exists."
468 out=$(usermod -d "$value" -m -- "$name" 2>&1) || fail USERMOD_FAILED "$out"
469 else out=$(usermod -d "$value" -- "$name" 2>&1) || fail USERMOD_FAILED "$out"; fi ;;
470 primary)
471 valid_name "$value" && getent group "$value" >/dev/null 2>&1 || fail GROUP_NOT_FOUND "The group does not exist."
472 out=$(usermod -g "$value" -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
473 group-add)
474 valid_name "$value" && getent group "$value" >/dev/null 2>&1 || fail GROUP_NOT_FOUND "The group does not exist."
475 out=$(usermod -a -G "$value" -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
476 group-remove)
477 valid_name "$value" || fail GROUP_NOT_FOUND "Invalid group."
478 is_member "$name" "$value" || fail GROUP_NOT_MEMBER "The account is not a supplementary member of this group."
479 if have gpasswd; then out=$(gpasswd -d "$name" "$value" 2>&1) || fail GROUP_CHANGE_FAILED "$out"
480 elif have deluser; then out=$(deluser "$name" "$value" 2>&1) || fail GROUP_CHANGE_FAILED "$out"
481 else fail TOOL_MISSING "gpasswd is not available on this server."; fi ;;
482 sudo-grant) grant_sudo "$name" || fail SUDO_GROUP_UNAVAILABLE "$ERRMSG"; step sudo ok "$ERRMSG" ;;
483 sudo-revoke) revoke_sudo "$name" || fail GROUP_CHANGE_FAILED "$ERRMSG"; step sudo ok "$ERRMSG" ;;
484 *) fail ACTION_INVALID "Unsupported account action." ;;
485 esac
486 [ "$action" = sudo-grant ] || [ "$action" = sudo-revoke ] || step "$action" ok
487 if [ "$action" = sudo-grant ] || [ "$action" = sudo-revoke ]; then printf '\036CHJ_SUDO=%s\n' "$(sudo_summary "$name")"; fi
488 printf '\036CHJ_RESULT=ok\n'
491op_delete() {
492 [ $# -eq 2 ] || fail ARGUMENTS_INVALID "Invalid arguments."
493 name=$1; remove_home=$2
494 need_root
495 protect "$name"
496 have userdel || fail TOOL_MISSING "userdel is not available on this server."
497 if have pgrep && pgrep -u "$name" >/dev/null 2>&1; then fail USER_HAS_PROCESSES "The account still has running processes."; fi
498 if [ "$remove_home" = 1 ]; then out=$(userdel -r -- "$name" 2>&1); code=$?; else out=$(userdel -- "$name" 2>&1); code=$?; fi
499 [ -z "$(passwd_entry "$name")" ] || fail USERDEL_FAILED "$out"
500 if [ "$code" -eq 0 ]; then step delete ok; printf '\036CHJ_RESULT=ok\n'
501 else step delete ok; step home failed "$out"; printf '\036CHJ_RESULT=partial\n'; fi
504# Staged and confirmed change of the CH-J managed sshd drop-in. Core decides via stdin:
505# COMMIT reloads sshd, CONFIRM keeps it; ROLLBACK, EOF or the watchdog restores the previous file.
506op_ssh_apply() {
507 [ $# -ge 2 ] || fail ARGUMENTS_INVALID "Invalid arguments."
508 expected=$1; timeout=$2; shift 2
509 case $expected in absent|[0-9a-f]*) ;; *) fail ARGUMENTS_INVALID "Invalid checksum." ;; esac
510 case $timeout in ''|*[!0-9]*) fail ARGUMENTS_INVALID "Invalid timeout." ;; esac
511 for tuple in "$@"; do valid_name "${tuple%%|*}" && valid_addr "${tuple#*|}" || fail ARGUMENTS_INVALID "Invalid verification target."; done
512 need_root
513 SSHD=$(sshd_bin); [ -n "$SSHD" ] || fail SSHD_MISSING "sshd was not found."
514 [ "$(layout_state)" = ok ] || fail SSH_LAYOUT_UNSUPPORTED "sshd_config does not include sshd_config.d before its first Match block."
515 [ -d "$SSHD_DROPIN" ] && [ ! -L "$SSHD_DROPIN" ] || fail SSH_LAYOUT_UNSUPPORTED "sshd_config.d is missing or is a symbolic link."
516 if [ -e "$SSHD_MANAGED" ] || [ -L "$SSHD_MANAGED" ]; then
517 [ -f "$SSHD_MANAGED" ] && [ ! -L "$SSHD_MANAGED" ] || fail SSH_MANAGED_FILE_UNSAFE "The managed file is not a regular file."
518 fi
519 [ "$(sha256_of "$SSHD_MANAGED")" = "$expected" ] || fail SSH_POLICY_CHANGED "The managed SSH policy changed after it was read."
520 out=$("$SSHD" -t 2>&1) || fail SSH_CONFIG_ALREADY_INVALID "$out"
521 read_frame DATA || fail PROTOCOL_ERROR "Policy data was not received."
522 mkdir -p "$STATE_DIR" && chmod 700 "$STATE_DIR" || fail STATE_FAILED "State directory could not be created."
523 work=$(mktemp -d "$STATE_DIR/apply.XXXXXX") || fail STATE_FAILED "Work directory could not be created."
524 printf '%s' "$FRAME" | b64d > "$work/new" || { rm -rf -- "$work"; fail PROTOCOL_ERROR "Policy data could not be decoded."; }
525 stamp=$(date -u +%Y%m%dT%H%M%SZ)
526 if [ -f "$SSHD_MANAGED" ]; then had=1; backup="$STATE_DIR/sshd-managed.$stamp.conf.bak"; cp -p "$SSHD_MANAGED" "$backup" || fail STATE_FAILED "Backup failed."
527 else had=0; backup="$STATE_DIR/sshd-managed.$stamp.absent"; : > "$backup"; fi
528 ls -1t "$STATE_DIR"/sshd-managed.* 2>/dev/null | tail -n +11 | while IFS= read -r old; do rm -f -- "$old"; done
529 restore() {
530 if [ "$had" = 1 ]; then cp -p "$backup" "$SSHD_DROPIN/.chj-users.restore" && mv -f "$SSHD_DROPIN/.chj-users.restore" "$SSHD_MANAGED"
531 else rm -f -- "$SSHD_MANAGED"; fi
532 }
533 claim() { mv "$work/pending" "$work/claimed" 2>/dev/null; }
534 for tuple in "$@"; do printf '\036CHJ_BEFORE=%s\n' "$tuple"; effective "${tuple%%|*}" "${tuple#*|}"; done
535 if [ -s "$work/new" ]; then
536 cp "$work/new" "$SSHD_DROPIN/.chj-users.tmp" && chmod 644 "$SSHD_DROPIN/.chj-users.tmp" && chown 0:0 "$SSHD_DROPIN/.chj-users.tmp" \
537 && mv -f "$SSHD_DROPIN/.chj-users.tmp" "$SSHD_MANAGED" || { rm -f -- "$SSHD_DROPIN/.chj-users.tmp"; restore; fail SSH_WRITE_FAILED "The managed file could not be written."; }
538 else rm -f -- "$SSHD_MANAGED"; fi
539 if ! out=$("$SSHD" -t 2>&1); then restore; rm -rf -- "$work"; fail SSH_CONFIG_INVALID "$out"; fi
540 for tuple in "$@"; do printf '\036CHJ_AFTER=%s\n' "$tuple"; effective "${tuple%%|*}" "${tuple#*|}"; done
541 : > "$work/pending"
542 ( trap '' HUP INT
543 sleep "$timeout"
544 if mv "$work/pending" "$work/claimed" 2>/dev/null; then
545 restore
546 [ -e "$work/reloaded" ] && reload_sshd >/dev/null 2>&1
547 printf 'watchdog rollback %s\n' "$stamp" > "$STATE_DIR/last-watchdog-rollback"
548 fi
549 rm -rf -- "$work" ) </dev/null >/dev/null 2>&1 &
550 printf '\036CHJ_STAGED\n'
551 IFS= read -r decision || decision=EOF
552 if [ "$decision" != COMMIT ]; then
553 claim && restore
554 printf '\036CHJ_ROLLED_BACK=%s\n' "$decision"; exit 0
555 fi
556 if ! out=$(reload_sshd); then
557 claim && restore
558 "$SSHD" -t >/dev/null 2>&1 && reload_sshd >/dev/null 2>&1
559 fail SSH_RELOAD_FAILED "$out"
560 fi
561 : > "$work/reloaded"
562 printf '\036CHJ_RELOADED=%s|%s\n' "$(service_state)" "$(printf '%s' "$out" | oneline)"
563 IFS= read -r decision || decision=EOF
564 if [ "$decision" = CONFIRM ] && claim; then
565 printf '\036CHJ_CONFIRMED=%s\n' "$backup"; exit 0
566 fi
567 if claim; then restore; reload_sshd >/dev/null 2>&1; printf '\036CHJ_ROLLED_BACK=%s\n' "$decision"
568 else printf '\036CHJ_ROLLED_BACK=watchdog\n'; fi
569 exit 0
572case $op in
573 probe) op_probe ;;
574 inventory) op_inventory ;;
575 detail) op_detail "$@" ;;
576 keys-write) op_keys_write "$@" ;;
577 create) op_create "$@" ;;
578 account) op_account "$@" ;;
579 delete) op_delete "$@" ;;
580 ssh-apply) op_ssh_apply "$@" ;;
581 *) fail ACTION_INVALID "Unsupported operation." ;;
582esac
SHA-2566bb7e2b1e50b9200da072610701d55cc804a5e20dab76d44857b611b9e1b1cd8

CH-J Proprietary Software License 1.14 · सोर्स CH-J Proprietary Software License 1.14 के तहत उपलब्ध कराया जाता है। इसकी उपलब्धता लाइसेंस की शर्तें नहीं बदलती और न ही अतिरिक्त अधिकार देती है।

आर्काइव SHA-256cf4455b698f0f28e456a37b1834976a0194d8486acb7101052d59a838745cca3
फ़ाइलें