1
0
mirror of https://github.com/openbsd/src.git synced 2026-06-18 07:13:36 +02:00

Allow ACLs to use groups as well as users, GitHub issue 4917.

This commit is contained in:
nicm
2026-06-08 21:38:19 +00:00
parent ae5dadf338
commit fedd2d2215
5 changed files with 236 additions and 154 deletions
+50 -46
View File
@@ -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 <dallasdlyons@gmail.com>
@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
@@ -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);
}
+11 -3
View File
@@ -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 <nicholas.marriott@gmail.com>
@@ -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);
}
+135 -89
View File
@@ -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 <sys/socket.h>
#include <ctype.h>
#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
@@ -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);
}
+31 -9
View File
@@ -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 <nicholas.marriott@gmail.com>
.\"
@@ -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
+9 -7
View File
@@ -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 <nicholas.marriott@gmail.com>
@@ -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 *,