diff --git a/party b/party index 7542704..65f1f20 100755 --- a/party +++ b/party @@ -83,13 +83,6 @@ Info: EOF } -# Library mode -# ============ - -# Sourced from tests with __PARTY_LIB_ONLY=1 to expose helpers without -# triggering dispatch. The marker MUST be checked before the dispatch -# block at the bottom of the file. - # Helpers # ======= @@ -104,9 +97,7 @@ EOF validate_party_name() { name="$1" # Length cap: 63 keeps the full socket path comfortably below the - # 108-char sun_path limit for typical USERs. Runs before the empty/ - # shape checks below; an empty name has length 0, so it always - # passes this check and falls through to the '' case there. + # 108-char sun_path limit for typical USERs. if [ "${#name}" -gt 63 ]; then echo "party: name '$name' too long (max 63 chars)" >&2 return 2 @@ -287,9 +278,6 @@ roster_read() { # validate_party_name rejects them (dots are banned: tmux rewrites # them in session names). Run the canonical validator so the # invariant cmd_host enforces on write is also checked on read. - # Side effect: pre-existing dotted-party dirs from older builds - # become undiscoverable, which is the pre-release migration policy - # (README §Status). validate_party_name "$RR_PARTY_NAME" 2>/dev/null || return 1 # Both the dir and its sock must be owned by the user named in the @@ -324,9 +312,8 @@ roster_read() { '' | \#*) ;; esac done < "$rec" - # A roster written before --group existed (or otherwise missing - # GROUP=) gates on the default group; callers can rely on RR_GROUP - # always naming the party's actual perimeter. + # No GROUP= in the record: assume the default, so callers can use + # RR_GROUP unconditionally. [ -n "$RR_GROUP" ] || RR_GROUP=$TMUX_PARTY_GROUP return 0 } @@ -379,15 +366,11 @@ roster_list() { # list-clients` exits 0 only against a real tmux server bound to that # exact socket path, which rules out stale roster pointers, AF_UNIX # impostors (a same-group user planting a hand-rolled listener via -# `nc -lU`), and PID-reuse leftovers in one shot. Earlier revisions had -# a separate is_party_alive() that also format-checked the roster's -# SERVER_PID, a vestige of a `kill -0` pre-filter that broke cross-user -# discovery on every non-Linux POSIX target: per POSIX, kill(2) signal -# 0 may return EPERM when the caller lacks send-permission, and -# illumos, all the BSDs, and macOS honor that. Linux (and LX-branded -# zones) return 0 for "exists, even if not signalable", which is why -# the bug hid in the test matrix until a native illumos run surfaced -# it. SERVER_PID stays in the roster as display-only metadata. +# `nc -lU`), and PID-reuse leftovers in one shot. Do not add a +# `kill -0 $SERVER_PID` pre-filter: per POSIX, kill(2) signal 0 may +# return EPERM when the caller lacks send-permission, and illumos, the +# BSDs, and macOS honor that, which breaks cross-user discovery +# everywhere but Linux (and LX zones). SERVER_PID is display-only. party_conn_state() { _cs_err=$("$PARTY_TMUX" -S "$1" list-clients 2>&1 >/dev/null) \ && _cs_rc=0 || _cs_rc=$? @@ -403,9 +386,7 @@ party_conn_state() { # Guest sessions # -------------- -# Print the caller's guest session name for a given user: the fixed -# session __party_guest_ that join/leave/status/role/kick/detach -# all target. +# Print the guest session name of user $1. guest_session_name() { printf '__party_guest_%s\n' "$1" } @@ -419,7 +400,7 @@ guest_session_name() { # __party_guest_$USER (and even group it to the party), so on a shared # server no session name is unforgeable. We trust invitees by design, # that's the whole model. A read-only invitee (-r) can't create -# sessions, so it's not exposed. See README §Trust model. +# sessions, so it's not exposed. See README §Security. ensure_guest_session() { _eg_sock="$1" _eg_host_sess="$2" guest=$(guest_session_name "$USER") @@ -441,7 +422,7 @@ caller_present_on() { # # The OS group is the perimeter, tmux's server-access allowlist is the # per-user filter. Both are universal across the supported platforms -# (Linux, FreeBSD, NetBSD, OpenBSD, illumos, Solaris, macOS), so the +# (Linux, FreeBSD, DragonFly, NetBSD, OpenBSD, illumos, Solaris, macOS), so the # mechanism is one liner: chgrp + chmod. No ACL syscalls invoked. # # ACL-shadowing on filesystems with NFSv4-style extended ACLs (macOS @@ -470,7 +451,7 @@ caller_present_on() { # ACLs that chmod 0750 can't override, document it in your deployment # notes, server-access still keeps strangers out, but advertised # group members may need ACL-level access to traverse PARTY_SOCKET_DIR. -# See README §Trust model for the user-facing version of this caveat. +# See README §Security for the user-facing version of this caveat. # WONTFIX, by design. apply_party_perms_file() { @@ -923,9 +904,7 @@ EOF trap - EXIT INT TERM HUP # Roster: chgrp only. roster_write already left mode 0640 (g+r, no g+w). - # Guests read records to discover/join; only the host writes them. Keeping - # them non-group-writable means a tampered SOCKET=... can't redirect - # cmd_close's destructive cleanup at peer-supplied paths. + # Guests read records to discover/join; only the host writes them. chgrp "$TMUX_PARTY_GROUP" "$rec" if [ -z "${TMUX:-}" ] && [ -t 1 ]; then @@ -1309,9 +1288,8 @@ EOF echo "$PARTY_TMUX -S $RR_SOCKET $*" return 0 fi - # exec with discrete, quoted args: the old flat-string + unquoted - # `exec $attach_cmd` word-split on any whitespace in PARTY_TMUX or - # the socket path. + # exec with discrete, quoted args: a flat command string would + # word-split on whitespace in PARTY_TMUX or the socket path. exec "$PARTY_TMUX" -S "$RR_SOCKET" "$@" } @@ -1586,9 +1564,9 @@ cmd_list() { "$RR_HOST_USER" "$RR_PARTY_NAME" "$attendees" "$group_tag") else # Live server, connection refused: we can't count attendees - # (list-clients is behind the auth gate: its empty stdout - # used to render here as a bogus "0 attendee(s)"), but the - # party is real and the caller can ask for an invite. + # (list-clients is behind the auth gate and would answer + # with empty stdout, a bogus "0 attendee(s)"), but the party + # is real and the caller can ask for an invite. line=$(printf '%-12s %-30s invite-only (ask %s)%s' \ "$RR_HOST_USER" "$RR_PARTY_NAME" "$RR_HOST_USER" "$group_tag") fi @@ -1704,8 +1682,6 @@ cmd_status() { if [ -n "$joined" ]; then echo "joined: $joined"; fi } -# Locate the party socket where the calling user has at least one -# attached client. Sets RR_* on success. Used by cmd_role. cmd_role() { sub="${1:-}" case "$sub" in @@ -1818,6 +1794,9 @@ dispatch() { # Entrypoint # ========== +# Tests source this file with __PARTY_LIB_ONLY=1 to reach the helpers +# without triggering dispatch. + if [ "${__PARTY_LIB_ONLY:-0}" != "1" ]; then dispatch "$@" fi