diff --git a/usr.bin/tmux/cmd-server-access.c b/usr.bin/tmux/cmd-server-access.c index bc5e4140434..ba2c285de3c 100644 --- a/usr.bin/tmux/cmd-server-access.c +++ b/usr.bin/tmux/cmd-server-access.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-server-access.c,v 1.4 2026/02/23 08:54:56 nicm Exp $ */ +/* $OpenBSD: cmd-server-access.c,v 1.5 2026/06/08 21:38:19 nicm Exp $ */ /* * Copyright (c) 2021 Dallas Lyons @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -37,67 +38,71 @@ const struct cmd_entry cmd_server_access_entry = { .name = "server-access", .alias = NULL, - .args = { "adlrw", 0, 1, NULL }, - .usage = "[-adlrw] " CMD_TARGET_PANE_USAGE " [user]", + .args = { "adglrw", 0, 1, NULL }, + .usage = "[-adglrw] " CMD_TARGET_PANE_USAGE " [user|group]", .flags = CMD_CLIENT_CANFAIL, .exec = cmd_server_access_exec }; static enum cmd_retval -cmd_server_access_deny(struct cmdq_item *item, struct passwd *pw) +cmd_server_access_deny(struct cmdq_item *item, id_t id, int flags, + const char *type, const char *name) { - struct client *loop; - struct server_acl_user *user; - uid_t uid; - - if ((user = server_acl_user_find(pw->pw_uid)) == NULL) { - cmdq_error(item, "user %s not found", pw->pw_name); + if (!server_acl_find(id, flags)) { + cmdq_error(item, "%s %s not found", type, name); return (CMD_RETURN_ERROR); } - TAILQ_FOREACH(loop, &clients, entry) { - uid = proc_get_peer_uid(loop->peer); - if (uid == server_acl_get_uid(user)) { - loop->exit_message = xstrdup("access not allowed"); - loop->flags |= CLIENT_EXIT; - } - } - server_acl_user_deny(pw->pw_uid); - + server_acl_deny(id, flags); return (CMD_RETURN_NORMAL); } static enum cmd_retval cmd_server_access_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = cmd_get_args(self); struct client *c = cmdq_get_target_client(item); - char *name; - struct passwd *pw = NULL; + char *arg; + const char *name, *type = NULL; + struct passwd *pw; + struct group *gr; + id_t id; + int flags = 0; if (args_has(args, 'l')) { server_acl_display(item); return (CMD_RETURN_NORMAL); } if (args_count(args) == 0) { - cmdq_error(item, "missing user argument"); + cmdq_error(item, "missing user or group argument"); return (CMD_RETURN_ERROR); } - name = format_single(item, args_string(args, 0), c, NULL, NULL, NULL); - if (*name != '\0') - pw = getpwnam(name); - if (pw == NULL) { - cmdq_error(item, "unknown user: %s", name); - free(name); + arg = format_single(item, args_string(args, 0), c, NULL, NULL, NULL); + if (args_has(args, 'g')) { + if ((gr = getgrnam(arg)) != NULL) { + type = "group"; + id = gr->gr_gid; + name = gr->gr_name; + flags |= SERVER_ACL_IS_GROUP; + } + } else { + if ((pw = getpwnam(arg)) != NULL) { + type = "user"; + id = pw->pw_uid; + name = pw->pw_name; + } + } + if (type == NULL) { + cmdq_error(item, "unknown %s: %s", type, arg); + free(arg); return (CMD_RETURN_ERROR); } - free(name); + free(arg); - if (pw->pw_uid == 0 || pw->pw_uid == getuid()) { + if ((~flags & SERVER_ACL_IS_GROUP) && (id == 0 || id == getuid())) { cmdq_error(item, "%s owns the server, can't change access", - pw->pw_name); + name); return (CMD_RETURN_ERROR); } @@ -111,36 +116,35 @@ cmd_server_access_exec(struct cmd *self, struct cmdq_item *item) } if (args_has(args, 'd')) - return (cmd_server_access_deny(item, pw)); + return (cmd_server_access_deny(item, id, flags, type, name)); if (args_has(args, 'a')) { - if (server_acl_user_find(pw->pw_uid) != NULL) { - cmdq_error(item, "user %s is already added", - pw->pw_name); + if (server_acl_find(id, flags)) { + cmdq_error(item, "%s %s is already added", type, name); return (CMD_RETURN_ERROR); } - server_acl_user_allow(pw->pw_uid); + server_acl_allow(id, flags); /* Do not return - allow -r or -w with -a. */ } else if (args_has(args, 'r') || args_has(args, 'w')) { - /* -r or -w implies -a if user does not exist. */ - if (server_acl_user_find(pw->pw_uid) == NULL) - server_acl_user_allow(pw->pw_uid); + /* -r or -w implies -a if the entry does not exist. */ + if (!server_acl_find(id, flags)) + server_acl_allow(id, flags); } if (args_has(args, 'w')) { - if (server_acl_user_find(pw->pw_uid) == NULL) { - cmdq_error(item, "user %s not found", pw->pw_name); + if (!server_acl_find(id, flags)) { + cmdq_error(item, "%s %s not found", type, name); return (CMD_RETURN_ERROR); } - server_acl_user_allow_write(pw->pw_uid); + server_acl_allow_write(id, flags); return (CMD_RETURN_NORMAL); } if (args_has(args, 'r')) { - if (server_acl_user_find(pw->pw_uid) == NULL) { - cmdq_error(item, "user %s not found", pw->pw_name); + if (!server_acl_find(id, flags)) { + cmdq_error(item, "%s %s not found", type, name); return (CMD_RETURN_ERROR); } - server_acl_user_deny_write(pw->pw_uid); + server_acl_deny_write(id, flags); return (CMD_RETURN_NORMAL); } diff --git a/usr.bin/tmux/proc.c b/usr.bin/tmux/proc.c index 5c759a58166..19437123647 100644 --- a/usr.bin/tmux/proc.c +++ b/usr.bin/tmux/proc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: proc.c,v 1.30 2024/11/21 13:35:20 claudio Exp $ */ +/* $OpenBSD: proc.c,v 1.31 2026/06/08 21:38:19 nicm Exp $ */ /* * Copyright (c) 2015 Nicholas Marriott @@ -56,6 +56,7 @@ struct tmuxpeer { struct imsgbuf ibuf; struct event event; uid_t uid; + gid_t gid; int flags; #define PEER_BAD 0x1 @@ -297,7 +298,6 @@ proc_add_peer(struct tmuxproc *tp, int fd, void (*dispatchcb)(struct imsg *, void *), void *arg) { struct tmuxpeer *peer; - gid_t gid; peer = xcalloc(1, sizeof *peer); peer->parent = tp; @@ -310,8 +310,10 @@ proc_add_peer(struct tmuxproc *tp, int fd, imsgbuf_allow_fdpass(&peer->ibuf); event_set(&peer->event, fd, EV_READ, proc_event_cb, peer); - if (getpeereid(fd, &peer->uid, &gid) != 0) + if (getpeereid(fd, &peer->uid, &peer->gid) != 0) { peer->uid = (uid_t)-1; + peer->gid = (gid_t)-1; + } log_debug("add peer %p: %d (%p)", peer, fd, arg); TAILQ_INSERT_TAIL(&tp->peers, peer, entry); @@ -380,3 +382,9 @@ proc_get_peer_uid(struct tmuxpeer *peer) { return (peer->uid); } + +gid_t +proc_get_peer_gid(struct tmuxpeer *peer) +{ + return (peer->gid); +} diff --git a/usr.bin/tmux/server-acl.c b/usr.bin/tmux/server-acl.c index 52851806136..03e758af56c 100644 --- a/usr.bin/tmux/server-acl.c +++ b/usr.bin/tmux/server-acl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-acl.c,v 1.2 2022/05/30 12:55:25 nicm Exp $ */ +/* $OpenBSD: server-acl.c,v 1.3 2026/06/08 21:38:19 nicm Exp $ */ /* * Copyright (c) 2021 Holland Schutte, Jayson Morberg @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -29,158 +30,203 @@ #include "tmux.h" -struct server_acl_user { - uid_t uid; +struct server_acl_entry { + id_t id; - int flags; -#define SERVER_ACL_READONLY 0x1 + int flags; - RB_ENTRY(server_acl_user) entry; + RB_ENTRY(server_acl_entry) entry; }; static int -server_acl_cmp(struct server_acl_user *user1, struct server_acl_user *user2) +server_acl_cmp(struct server_acl_entry *entry1, + struct server_acl_entry *entry2) { - if (user1->uid < user2->uid) + if ((entry1->flags ^ entry2->flags) & SERVER_ACL_IS_GROUP) { + if (entry1->flags & SERVER_ACL_IS_GROUP) + return (1); return (-1); - return (user1->uid > user2->uid); + } + + if (entry1->id < entry2->id) + return (-1); + return (entry1->id > entry2->id); } -RB_HEAD(server_acl_entries, server_acl_user) server_acl_entries; -RB_GENERATE_STATIC(server_acl_entries, server_acl_user, entry, server_acl_cmp); +RB_HEAD(server_acl_entries, server_acl_entry) server_acl_entries; +RB_GENERATE_STATIC(server_acl_entries, server_acl_entry, entry, server_acl_cmp); -/* Initialize server_acl tree. */ +static struct server_acl_entry * +server_acl_entry_find(id_t id, int flags) +{ + struct server_acl_entry find = { + .id = id, + .flags = flags & SERVER_ACL_IS_GROUP + }; + + return (RB_FIND(server_acl_entries, &server_acl_entries, &find)); +} + +static struct server_acl_entry * +server_acl_check(struct client *c) +{ + struct server_acl_entry *entry; + uid_t uid; + gid_t gid; + + uid = proc_get_peer_uid(c->peer); + if (uid == (uid_t)-1) + return (NULL); + + entry = server_acl_entry_find(uid, 0); + if (entry != NULL) + return (entry); + + gid = proc_get_peer_gid(c->peer); + if (gid == (gid_t)-1) + return (NULL); + + return (server_acl_entry_find(gid, SERVER_ACL_IS_GROUP)); +} + +static void +server_acl_update(void) +{ + struct server_acl_entry *entry; + struct client *c; + + TAILQ_FOREACH(c, &clients, entry) { + entry = server_acl_check(c); + if (entry == NULL) { + c->exit_message = xstrdup("access not allowed"); + c->flags |= CLIENT_EXIT; + } else if (entry->flags & SERVER_ACL_READONLY) + c->flags |= CLIENT_READONLY; + else + c->flags &= ~CLIENT_READONLY; + } +} + +/* Initialize ACL tree. */ void server_acl_init(void) { RB_INIT(&server_acl_entries); if (getuid() != 0) - server_acl_user_allow(0); - server_acl_user_allow(getuid()); + server_acl_allow(0, 0); + server_acl_allow(getuid(), 0); } -/* Find user entry. */ -struct server_acl_user* -server_acl_user_find(uid_t uid) +/* Check if an ACL entry exists. */ +int +server_acl_find(id_t id, int flags) { - struct server_acl_user find = { .uid = uid }; - - return (RB_FIND(server_acl_entries, &server_acl_entries, &find)); + return (server_acl_entry_find(id, flags) != NULL); } /* Display the tree. */ void server_acl_display(struct cmdq_item *item) { - struct server_acl_user *loop; + struct server_acl_entry *loop; struct passwd *pw; + struct group *gr; const char *name; + char type; RB_FOREACH(loop, server_acl_entries, &server_acl_entries) { - if (loop->uid == 0) - continue; - if ((pw = getpwuid(loop->uid)) != NULL) - name = pw->pw_name; + if (~loop->flags & SERVER_ACL_IS_GROUP) { + if (loop->id == 0) + continue; + if ((pw = getpwuid(loop->id)) != NULL) + name = pw->pw_name; + else + name = "unknown"; + type = 'U'; + } else { + if ((gr = getgrgid(loop->id)) != NULL) + name = gr->gr_name; + else + name = "unknown"; + type = 'G'; + } + if (loop->flags & SERVER_ACL_READONLY) + cmdq_print(item, "%s (%c,R)", name, type); else - name = "unknown"; - if (loop->flags == SERVER_ACL_READONLY) - cmdq_print(item, "%s (R)", name); - else - cmdq_print(item, "%s (W)", name); + cmdq_print(item, "%s (%c,W)", name, type); } } -/* Allow a user. */ +/* Allow an ACL entry. */ void -server_acl_user_allow(uid_t uid) +server_acl_allow(id_t id, int flags) { - struct server_acl_user *user; + struct server_acl_entry *entry; - user = server_acl_user_find(uid); - if (user == NULL) { - user = xcalloc(1, sizeof *user); - user->uid = uid; - RB_INSERT(server_acl_entries, &server_acl_entries, user); + entry = server_acl_entry_find(id, flags); + if (entry == NULL) { + entry = xcalloc(1, sizeof *entry); + entry->id = id; + entry->flags = flags & SERVER_ACL_IS_GROUP; + RB_INSERT(server_acl_entries, &server_acl_entries, entry); } } -/* Deny a user (remove from the tree). */ +/* Deny an ACL entry (remove it from the tree). */ void -server_acl_user_deny(uid_t uid) +server_acl_deny(id_t id, int flags) { - struct server_acl_user *user; + struct server_acl_entry *entry; - user = server_acl_user_find(uid); - if (user != NULL) { - RB_REMOVE(server_acl_entries, &server_acl_entries, user); - free(user); + entry = server_acl_entry_find(id, flags); + if (entry != NULL) { + RB_REMOVE(server_acl_entries, &server_acl_entries, entry); + free(entry); + server_acl_update(); } } -/* Allow this user write access. */ +/* Allow this ACL entry write access. */ void -server_acl_user_allow_write(uid_t uid) +server_acl_allow_write(id_t id, int flags) { - struct server_acl_user *user; - struct client *c; + struct server_acl_entry *entry; - user = server_acl_user_find(uid); - if (user == NULL) + entry = server_acl_entry_find(id, flags); + if (entry == NULL) return; - user->flags &= ~SERVER_ACL_READONLY; - - TAILQ_FOREACH(c, &clients, entry) { - uid = proc_get_peer_uid(c->peer); - if (uid != (uid_t)-1 && uid == user->uid) - c->flags &= ~CLIENT_READONLY; - } + entry->flags &= ~SERVER_ACL_READONLY; + server_acl_update(); } -/* Deny this user write access. */ +/* Deny this ACL entry write access. */ void -server_acl_user_deny_write(uid_t uid) +server_acl_deny_write(id_t id, int flags) { - struct server_acl_user *user; - struct client *c; + struct server_acl_entry *entry; - user = server_acl_user_find(uid); - if (user == NULL) + entry = server_acl_entry_find(id, flags); + if (entry == NULL) return; - user->flags |= SERVER_ACL_READONLY; - - TAILQ_FOREACH(c, &clients, entry) { - uid = proc_get_peer_uid(c->peer); - if (uid != (uid_t)-1 && uid == user->uid) - c->flags |= CLIENT_READONLY; - } + entry->flags |= SERVER_ACL_READONLY; + server_acl_update(); } /* - * Check if the client's UID exists in the ACL list and if so, set as read only - * if needed. Return false if the user does not exist. + * Check if the client's UID or GID exists in the ACL list and if so, set as + * read only if needed. UID entries take precedence over GID entries. Return + * false if no entry exists. */ int server_acl_join(struct client *c) { - struct server_acl_user *user; - uid_t uid; + struct server_acl_entry *entry; - uid = proc_get_peer_uid(c->peer); - if (uid == (uid_t)-1) + entry = server_acl_check(c); + if (entry == NULL) return (0); - - user = server_acl_user_find(uid); - if (user == NULL) - return (0); - if (user->flags & SERVER_ACL_READONLY) + if (entry->flags & SERVER_ACL_READONLY) c->flags |= CLIENT_READONLY; return (1); } - -/* Get UID for user entry. */ -uid_t -server_acl_get_uid(struct server_acl_user *user) -{ - return (user->uid); -} diff --git a/usr.bin/tmux/tmux.1 b/usr.bin/tmux/tmux.1 index 30d17a331d0..1d70e4c996f 100644 --- a/usr.bin/tmux/tmux.1 +++ b/usr.bin/tmux/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1073 2026/06/08 21:19:52 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1074 2026/06/08 21:38:19 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -1574,35 +1574,57 @@ option. Rename the session to .Ar new\-name . .It Xo Ic server\-access -.Op Fl adlrw -.Op Ar user +.Op Fl adglrw +.Op Ar user | group .Xc Change the access or read/write permission of -.Ar user . +.Ar user +or +.Ar group . The user running the .Nm server (its owner) and the root user cannot be changed and are always permitted access. +.Fl g +changes a group rather than a user. .Pp .Fl a and .Fl d -are used to give or revoke access for the specified user. -If the user is already attached, the +are used to give or revoke access for the specified user or group. +If a client is already attached, the .Fl d -flag causes their clients to be detached. +flag causes it to be detached unless it is still permitted by another entry. .Pp .Fl r and .Fl w change the permissions for -.Ar user : +.Ar user +or +.Ar group : .Fl r -makes their clients read-only and +makes matching clients read-only and .Fl w writable. .Fl l lists current access permissions. +User entries are shown with +.Ql U , +group entries with +.Ql G , +and read-only or writable entries with +.Ql R +or +.Ql W , +for example +.Ql user1 (U,W) +or +.Ql testgroup (G,R) . +If both a user and group entry match a client, the user entry takes +precedence. +Only the effective group ID of the client is used, not its supplementary +groups. .Pp By default, the access list is empty and .Nm diff --git a/usr.bin/tmux/tmux.h b/usr.bin/tmux/tmux.h index dcd2f023c04..568f6ef4cb6 100644 --- a/usr.bin/tmux/tmux.h +++ b/usr.bin/tmux/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1338 2026/06/08 20:41:21 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1339 2026/06/08 21:38:19 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -2386,6 +2386,7 @@ void proc_flush_peer(struct tmuxpeer *); void proc_toggle_log(struct tmuxproc *); pid_t proc_fork_and_daemon(int *); uid_t proc_get_peer_uid(struct tmuxpeer *); +gid_t proc_get_peer_gid(struct tmuxpeer *); /* cfg.c */ extern int cfg_finished; @@ -3787,15 +3788,16 @@ struct window_pane *spawn_pane(struct spawn_context *, char **); char *regsub(const char *, const char *, const char *, int); /* server-acl.c */ +#define SERVER_ACL_READONLY 0x1 +#define SERVER_ACL_IS_GROUP 0x2 void server_acl_init(void); -struct server_acl_user *server_acl_user_find(uid_t); +int server_acl_find(id_t, int); void server_acl_display(struct cmdq_item *); -void server_acl_user_allow(uid_t); -void server_acl_user_deny(uid_t); -void server_acl_user_allow_write(uid_t); -void server_acl_user_deny_write(uid_t); +void server_acl_allow(id_t, int); +void server_acl_deny(id_t, int); +void server_acl_allow_write(id_t, int); +void server_acl_deny_write(id_t, int); int server_acl_join(struct client *); -uid_t server_acl_get_uid(struct server_acl_user *); /* hyperlink.c */ u_int hyperlinks_put(struct hyperlinks *, const char *,