Merge branch 'obsd-master'

This commit is contained in:
Thomas Adam
2026-06-08 23:00:06 +01:00
6 changed files with 235 additions and 153 deletions

View File

@@ -19,6 +19,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <grp.h>
#include <pwd.h> #include <pwd.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -37,67 +38,71 @@ const struct cmd_entry cmd_server_access_entry = {
.name = "server-access", .name = "server-access",
.alias = NULL, .alias = NULL,
.args = { "adlrw", 0, 1, NULL }, .args = { "adglrw", 0, 1, NULL },
.usage = "[-adlrw] " CMD_TARGET_PANE_USAGE " [user]", .usage = "[-adglrw] " CMD_TARGET_PANE_USAGE " [user|group]",
.flags = CMD_CLIENT_CANFAIL, .flags = CMD_CLIENT_CANFAIL,
.exec = cmd_server_access_exec .exec = cmd_server_access_exec
}; };
static enum cmd_retval 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; if (!server_acl_find(id, flags)) {
struct server_acl_user *user; cmdq_error(item, "%s %s not found", type, name);
uid_t uid;
if ((user = server_acl_user_find(pw->pw_uid)) == NULL) {
cmdq_error(item, "user %s not found", pw->pw_name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
TAILQ_FOREACH(loop, &clients, entry) { server_acl_deny(id, flags);
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);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
static enum cmd_retval static enum cmd_retval
cmd_server_access_exec(struct cmd *self, struct cmdq_item *item) cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_target_client(item); struct client *c = cmdq_get_target_client(item);
char *name; char *arg;
struct passwd *pw = NULL; const char *name, *type = NULL;
struct passwd *pw;
struct group *gr;
id_t id;
int flags = 0;
if (args_has(args, 'l')) { if (args_has(args, 'l')) {
server_acl_display(item); server_acl_display(item);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_count(args) == 0) { if (args_count(args) == 0) {
cmdq_error(item, "missing user argument"); cmdq_error(item, "missing user or group argument");
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
name = format_single(item, args_string(args, 0), c, NULL, NULL, NULL); arg = format_single(item, args_string(args, 0), c, NULL, NULL, NULL);
if (*name != '\0') if (args_has(args, 'g')) {
pw = getpwnam(name); if ((gr = getgrnam(arg)) != NULL) {
if (pw == NULL) { type = "group";
cmdq_error(item, "unknown user: %s", name); id = gr->gr_gid;
free(name); 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); 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", cmdq_error(item, "%s owns the server, can't change access",
pw->pw_name); name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
@@ -111,36 +116,35 @@ cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
} }
if (args_has(args, 'd')) 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 (args_has(args, 'a')) {
if (server_acl_user_find(pw->pw_uid) != NULL) { if (server_acl_find(id, flags)) {
cmdq_error(item, "user %s is already added", cmdq_error(item, "%s %s is already added", type, name);
pw->pw_name);
return (CMD_RETURN_ERROR); 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. */ /* Do not return - allow -r or -w with -a. */
} else if (args_has(args, 'r') || args_has(args, 'w')) { } else if (args_has(args, 'r') || args_has(args, 'w')) {
/* -r or -w implies -a if user does not exist. */ /* -r or -w implies -a if the entry does not exist. */
if (server_acl_user_find(pw->pw_uid) == NULL) if (!server_acl_find(id, flags))
server_acl_user_allow(pw->pw_uid); server_acl_allow(id, flags);
} }
if (args_has(args, 'w')) { if (args_has(args, 'w')) {
if (server_acl_user_find(pw->pw_uid) == NULL) { if (!server_acl_find(id, flags)) {
cmdq_error(item, "user %s not found", pw->pw_name); cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
server_acl_user_allow_write(pw->pw_uid); server_acl_allow_write(id, flags);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_has(args, 'r')) { if (args_has(args, 'r')) {
if (server_acl_user_find(pw->pw_uid) == NULL) { if (!server_acl_find(id, flags)) {
cmdq_error(item, "user %s not found", pw->pw_name); cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
server_acl_user_deny_write(pw->pw_uid); server_acl_deny_write(id, flags);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

12
proc.c
View File

@@ -57,6 +57,7 @@ struct tmuxpeer {
struct imsgbuf ibuf; struct imsgbuf ibuf;
struct event event; struct event event;
uid_t uid; uid_t uid;
gid_t gid;
int flags; int flags;
#define PEER_BAD 0x1 #define PEER_BAD 0x1
@@ -301,7 +302,6 @@ proc_add_peer(struct tmuxproc *tp, int fd,
void (*dispatchcb)(struct imsg *, void *), void *arg) void (*dispatchcb)(struct imsg *, void *), void *arg)
{ {
struct tmuxpeer *peer; struct tmuxpeer *peer;
gid_t gid;
peer = xcalloc(1, sizeof *peer); peer = xcalloc(1, sizeof *peer);
peer->parent = tp; peer->parent = tp;
@@ -314,8 +314,10 @@ proc_add_peer(struct tmuxproc *tp, int fd,
imsgbuf_allow_fdpass(&peer->ibuf); imsgbuf_allow_fdpass(&peer->ibuf);
event_set(&peer->event, fd, EV_READ, proc_event_cb, peer); 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->uid = (uid_t)-1;
peer->gid = (gid_t)-1;
}
log_debug("add peer %p: %d (%p)", peer, fd, arg); log_debug("add peer %p: %d (%p)", peer, fd, arg);
TAILQ_INSERT_TAIL(&tp->peers, peer, entry); TAILQ_INSERT_TAIL(&tp->peers, peer, entry);
@@ -384,3 +386,9 @@ proc_get_peer_uid(struct tmuxpeer *peer)
{ {
return (peer->uid); return (peer->uid);
} }
gid_t
proc_get_peer_gid(struct tmuxpeer *peer)
{
return (peer->gid);
}

View File

@@ -22,6 +22,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <ctype.h> #include <ctype.h>
#include <grp.h>
#include <pwd.h> #include <pwd.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -29,158 +30,203 @@
#include "tmux.h" #include "tmux.h"
struct server_acl_user { struct server_acl_entry {
uid_t uid; id_t id;
int flags; int flags;
#define SERVER_ACL_READONLY 0x1
RB_ENTRY(server_acl_user) entry; RB_ENTRY(server_acl_entry) entry;
}; };
static int 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 (-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_HEAD(server_acl_entries, server_acl_entry) server_acl_entries;
RB_GENERATE_STATIC(server_acl_entries, server_acl_user, entry, server_acl_cmp); 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 void
server_acl_init(void) server_acl_init(void)
{ {
RB_INIT(&server_acl_entries); RB_INIT(&server_acl_entries);
if (getuid() != 0) if (getuid() != 0)
server_acl_user_allow(0); server_acl_allow(0, 0);
server_acl_user_allow(getuid()); server_acl_allow(getuid(), 0);
} }
/* Find user entry. */ /* Check if an ACL entry exists. */
struct server_acl_user* int
server_acl_user_find(uid_t uid) server_acl_find(id_t id, int flags)
{ {
struct server_acl_user find = { .uid = uid }; return (server_acl_entry_find(id, flags) != NULL);
return (RB_FIND(server_acl_entries, &server_acl_entries, &find));
} }
/* Display the tree. */ /* Display the tree. */
void void
server_acl_display(struct cmdq_item *item) server_acl_display(struct cmdq_item *item)
{ {
struct server_acl_user *loop; struct server_acl_entry *loop;
struct passwd *pw; struct passwd *pw;
struct group *gr;
const char *name; const char *name;
char type;
RB_FOREACH(loop, server_acl_entries, &server_acl_entries) { RB_FOREACH(loop, server_acl_entries, &server_acl_entries) {
if (loop->uid == 0) if (~loop->flags & SERVER_ACL_IS_GROUP) {
continue; if (loop->id == 0)
if ((pw = getpwuid(loop->uid)) != NULL) continue;
name = pw->pw_name; 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 else
name = "unknown"; cmdq_print(item, "%s (%c,W)", name, type);
if (loop->flags == SERVER_ACL_READONLY)
cmdq_print(item, "%s (R)", name);
else
cmdq_print(item, "%s (W)", name);
} }
} }
/* Allow a user. */ /* Allow an ACL entry. */
void 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); entry = server_acl_entry_find(id, flags);
if (user == NULL) { if (entry == NULL) {
user = xcalloc(1, sizeof *user); entry = xcalloc(1, sizeof *entry);
user->uid = uid; entry->id = id;
RB_INSERT(server_acl_entries, &server_acl_entries, user); 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 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); entry = server_acl_entry_find(id, flags);
if (user != NULL) { if (entry != NULL) {
RB_REMOVE(server_acl_entries, &server_acl_entries, user); RB_REMOVE(server_acl_entries, &server_acl_entries, entry);
free(user); free(entry);
server_acl_update();
} }
} }
/* Allow this user write access. */ /* Allow this ACL entry write access. */
void void
server_acl_user_allow_write(uid_t uid) server_acl_allow_write(id_t id, int flags)
{ {
struct server_acl_user *user; struct server_acl_entry *entry;
struct client *c;
user = server_acl_user_find(uid); entry = server_acl_entry_find(id, flags);
if (user == NULL) if (entry == NULL)
return; return;
user->flags &= ~SERVER_ACL_READONLY; entry->flags &= ~SERVER_ACL_READONLY;
server_acl_update();
TAILQ_FOREACH(c, &clients, entry) {
uid = proc_get_peer_uid(c->peer);
if (uid != (uid_t)-1 && uid == user->uid)
c->flags &= ~CLIENT_READONLY;
}
} }
/* Deny this user write access. */ /* Deny this ACL entry write access. */
void void
server_acl_user_deny_write(uid_t uid) server_acl_deny_write(id_t id, int flags)
{ {
struct server_acl_user *user; struct server_acl_entry *entry;
struct client *c;
user = server_acl_user_find(uid); entry = server_acl_entry_find(id, flags);
if (user == NULL) if (entry == NULL)
return; return;
user->flags |= SERVER_ACL_READONLY; entry->flags |= SERVER_ACL_READONLY;
server_acl_update();
TAILQ_FOREACH(c, &clients, entry) {
uid = proc_get_peer_uid(c->peer);
if (uid != (uid_t)-1 && uid == user->uid)
c->flags |= CLIENT_READONLY;
}
} }
/* /*
* Check if the client's UID exists in the ACL list and if so, set as read only * Check if the client's UID or GID exists in the ACL list and if so, set as
* if needed. Return false if the user does not exist. * read only if needed. UID entries take precedence over GID entries. Return
* false if no entry exists.
*/ */
int int
server_acl_join(struct client *c) server_acl_join(struct client *c)
{ {
struct server_acl_user *user; struct server_acl_entry *entry;
uid_t uid;
uid = proc_get_peer_uid(c->peer); entry = server_acl_check(c);
if (uid == (uid_t)-1) if (entry == NULL)
return (0); return (0);
if (entry->flags & SERVER_ACL_READONLY)
user = server_acl_user_find(uid);
if (user == NULL)
return (0);
if (user->flags & SERVER_ACL_READONLY)
c->flags |= CLIENT_READONLY; c->flags |= CLIENT_READONLY;
return (1); return (1);
} }
/* Get UID for user entry. */
uid_t
server_acl_get_uid(struct server_acl_user *user)
{
return (user->uid);
}

38
tmux.1
View File

@@ -1576,35 +1576,57 @@ option.
Rename the session to Rename the session to
.Ar new\-name . .Ar new\-name .
.It Xo Ic server\-access .It Xo Ic server\-access
.Op Fl adlrw .Op Fl adglrw
.Op Ar user .Op Ar user | group
.Xc .Xc
Change the access or read/write permission of Change the access or read/write permission of
.Ar user . .Ar user
or
.Ar group .
The user running the The user running the
.Nm .Nm
server (its owner) and the root user cannot be changed and are always server (its owner) and the root user cannot be changed and are always
permitted access. permitted access.
.Fl g
changes a group rather than a user.
.Pp .Pp
.Fl a .Fl a
and and
.Fl d .Fl d
are used to give or revoke access for the specified user. are used to give or revoke access for the specified user or group.
If the user is already attached, the If a client is already attached, the
.Fl d .Fl d
flag causes their clients to be detached. flag causes it to be detached unless it is still permitted by another entry.
.Pp .Pp
.Fl r .Fl r
and and
.Fl w .Fl w
change the permissions for change the permissions for
.Ar user : .Ar user
or
.Ar group :
.Fl r .Fl r
makes their clients read-only and makes matching clients read-only and
.Fl w .Fl w
writable. writable.
.Fl l .Fl l
lists current access permissions. 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 .Pp
By default, the access list is empty and By default, the access list is empty and
.Nm .Nm

14
tmux.h
View File

@@ -2427,6 +2427,7 @@ void proc_flush_peer(struct tmuxpeer *);
void proc_toggle_log(struct tmuxproc *); void proc_toggle_log(struct tmuxproc *);
pid_t proc_fork_and_daemon(int *); pid_t proc_fork_and_daemon(int *);
uid_t proc_get_peer_uid(struct tmuxpeer *); uid_t proc_get_peer_uid(struct tmuxpeer *);
gid_t proc_get_peer_gid(struct tmuxpeer *);
/* cfg.c */ /* cfg.c */
extern int cfg_finished; extern int cfg_finished;
@@ -3862,15 +3863,16 @@ struct screen *sixel_to_screen(struct sixel_image *);
#endif #endif
/* server-acl.c */ /* server-acl.c */
#define SERVER_ACL_READONLY 0x1
#define SERVER_ACL_IS_GROUP 0x2
void server_acl_init(void); 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_display(struct cmdq_item *);
void server_acl_user_allow(uid_t); void server_acl_allow(id_t, int);
void server_acl_user_deny(uid_t); void server_acl_deny(id_t, int);
void server_acl_user_allow_write(uid_t); void server_acl_allow_write(id_t, int);
void server_acl_user_deny_write(uid_t); void server_acl_deny_write(id_t, int);
int server_acl_join(struct client *); int server_acl_join(struct client *);
uid_t server_acl_get_uid(struct server_acl_user *);
/* hyperlink.c */ /* hyperlink.c */
u_int hyperlinks_put(struct hyperlinks *, const char *, u_int hyperlinks_put(struct hyperlinks *, const char *,

View File

@@ -355,6 +355,7 @@ window_tree_build(void *modedata, struct sort_criteria *sort_crit,
uint64_t *tag, const char *filter) uint64_t *tag, const char *filter)
{ {
struct window_tree_modedata *data = modedata; struct window_tree_modedata *data = modedata;
int squash_groups = data->squash_groups;
struct session *s, **l; struct session *s, **l;
struct session_group *sg, *current; struct session_group *sg, *current;
u_int n, i; u_int n, i;
@@ -370,15 +371,14 @@ window_tree_build(void *modedata, struct sort_criteria *sort_crit,
l = sort_get_sessions(&n, sort_crit); l = sort_get_sessions(&n, sort_crit);
if (n == 0) if (n == 0)
return; return;
s = l[n - 1];
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
if (data->squash_groups && s = l[i];
(sg = session_group_contains(s)) != NULL) { if (squash_groups && (sg = session_group_contains(s)) != NULL) {
if ((sg == current && s != data->fs.s) || if ((sg == current && s != data->fs.s) ||
(sg != current && s != TAILQ_FIRST(&sg->sessions))) (sg != current && s != TAILQ_FIRST(&sg->sessions)))
continue; continue;
} }
window_tree_build_session(l[i], modedata, sort_crit, filter); window_tree_build_session(s, modedata, sort_crit, filter);
} }
switch (data->type) { switch (data->type) {