Add an ACL list for users connecting to the tmux socket. Users may be

forbidden from attaching, forced to attach read-only, or allowed to
attach read-write. A new command, server-access, configures the list.
tmux gets the user using getpeereid(3) of the client socket. Users must
still configure file system permissions manually. From Dallas Lyons and
others.
pull/3201/head
nicm 2022-05-30 12:48:57 +00:00
parent d4423dca19
commit cd692b5a68
8 changed files with 211 additions and 6 deletions

View File

@ -55,6 +55,7 @@ SRCS= alerts.c \
cmd-select-pane.c \
cmd-select-window.c \
cmd-send-keys.c \
cmd-server-access.c \
cmd-set-buffer.c \
cmd-set-environment.c \
cmd-set-option.c \
@ -104,6 +105,7 @@ SRCS= alerts.c \
screen-redraw.c \
screen-write.c \
screen.c \
server-acl.c \
server-client.c \
server-fn.c \
server.c \

View File

@ -360,6 +360,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
/* Send identify messages. */
client_send_identify(ttynam, termname, caps, ncaps, cwd, feat);
tty_term_free_list(caps, ncaps);
proc_flush_peer(client_peer);
/* Send first command. */
if (msg == MSG_COMMAND) {

View File

@ -43,7 +43,7 @@ const struct cmd_entry cmd_attach_session_entry = {
/* -t is special */
.flags = CMD_STARTSERVER,
.flags = CMD_STARTSERVER|CMD_READONLY,
.exec = cmd_attach_session_exec
};
@ -69,6 +69,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
if (c == NULL)
return (CMD_RETURN_NORMAL);
if (server_client_check_nested(c)) {
cmdq_error(item, "sessions should be nested with care, "
"unset $TMUX to force");

147
cmd-server-access.c Normal file
View File

@ -0,0 +1,147 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2021 Dallas Lyons <dallasdlyons@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "tmux.h"
/*
* Controls access to session.
*/
static enum cmd_retval cmd_server_access_exec(struct cmd *, struct cmdq_item *);
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]",
.flags = CMD_CLIENT_CANFAIL,
.exec = cmd_server_access_exec
};
static enum cmd_retval
cmd_server_access_deny(struct cmdq_item *item, struct passwd *pw)
{
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);
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);
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;
if (args_has(args, 'l')) {
server_acl_display(item);
return (CMD_RETURN_NORMAL);
}
if (args_count(args) == 0) {
cmdq_error(item, "missing user arguement");
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);
return (CMD_RETURN_ERROR);
}
free(name);
if (pw->pw_uid == 0 || pw->pw_uid == getuid()) {
cmdq_error(item, "%s owns the server, can't change access",
pw->pw_name);
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'a') && args_has(args, 'd')) {
cmdq_error(item, "-a and -d cannot be used together");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'w') && args_has(args, 'r')) {
cmdq_error(item, "-r and -w cannot be used together");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'd'))
return (cmd_server_access_deny(item, pw));
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);
return (CMD_RETURN_ERROR);
}
server_acl_user_allow(pw->pw_uid);
/* 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);
}
if (args_has(args, 'w')) {
if (server_acl_user_find(pw->pw_uid) == NULL) {
cmdq_error(item, "user %s not found", pw->pw_name);
return (CMD_RETURN_ERROR);
}
server_acl_user_allow_write(pw->pw_uid);
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);
return (CMD_RETURN_ERROR);
}
server_acl_user_deny_write(pw->pw_uid);
return (CMD_RETURN_NORMAL);
}
return (CMD_RETURN_NORMAL);
}

2
cmd.c
View File

@ -96,6 +96,7 @@ extern const struct cmd_entry cmd_select_pane_entry;
extern const struct cmd_entry cmd_select_window_entry;
extern const struct cmd_entry cmd_send_keys_entry;
extern const struct cmd_entry cmd_send_prefix_entry;
extern const struct cmd_entry cmd_server_access_entry;
extern const struct cmd_entry cmd_set_buffer_entry;
extern const struct cmd_entry cmd_set_environment_entry;
extern const struct cmd_entry cmd_set_hook_entry;
@ -188,6 +189,7 @@ const struct cmd_entry *cmd_table[] = {
&cmd_select_window_entry,
&cmd_send_keys_entry,
&cmd_send_prefix_entry,
&cmd_server_access_entry,
&cmd_set_buffer_entry,
&cmd_set_environment_entry,
&cmd_set_hook_entry,

6
proc.c
View File

@ -337,6 +337,12 @@ proc_kill_peer(struct tmuxpeer *peer)
peer->flags |= PEER_BAD;
}
void
proc_flush_peer(struct tmuxpeer *peer)
{
imsg_flush(&peer->ibuf);
}
void
proc_toggle_log(struct tmuxproc *tp)
{

View File

@ -239,6 +239,8 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base,
evtimer_set(&server_ev_tidy, server_tidy_event, NULL);
evtimer_add(&server_ev_tidy, &tv);
server_acl_init();
server_add_accept(0);
proc_loop(server_proc, server_loop);
@ -355,9 +357,10 @@ server_update_socket(void)
static void
server_accept(int fd, short events, __unused void *data)
{
struct sockaddr_storage sa;
socklen_t slen = sizeof sa;
int newfd;
struct sockaddr_storage sa;
socklen_t slen = sizeof sa;
int newfd;
struct client *c;
server_add_accept(0);
if (!(events & EV_READ))
@ -374,11 +377,16 @@ server_accept(int fd, short events, __unused void *data)
}
fatal("accept failed");
}
if (server_exit) {
close(newfd);
return;
}
server_client_create(newfd);
c = server_client_create(newfd);
if (!server_acl_join(c)) {
c->exit_message = xstrdup("access not allowed");
c->flags |= CLIENT_EXIT;
}
}
/*

40
tmux.1
View File

@ -1485,6 +1485,44 @@ option.
.D1 Pq alias: Ic rename
Rename the session to
.Ar new-name .
.It Xo Ic server-access
.Op Fl adlrw
.Op Ar user
.Xc
Change the access or read/write permission of
.Ar user .
The user running the
.Nm
server (its owner) and the root user cannot be changed and are always
permitted access.
.Pp
.Fl a
and
.Fl d
are used to give or revoke access for the specified user.
If the user is already attached, the
.Fl d
flag causes their clients to be detached.
.Pp
.Fl r
and
.Fl w
change the permissions for
.Ar user :
.Fl r
makes their clients read-only and
.Fl w
writable.
.Fl l
lists current access permissions.
.Pp
By default, the access list is empty and
.Nm
creates sockets with file system permissions preventing access by any user
other than the owner (and root).
These permissions must be changed manually.
Great care should be taken not to allow access to untrusted users even
read-only.
.Tg showmsgs
.It Xo Ic show-messages
.Op Fl JT
@ -5069,7 +5107,7 @@ The following variables are available, where appropriate:
.It Li "client_name" Ta "" Ta "Name of client"
.It Li "client_pid" Ta "" Ta "PID of client process"
.It Li "client_prefix" Ta "" Ta "1 if prefix key has been pressed"
.It Li "client_readonly" Ta "" Ta "1 if client is readonly"
.It Li "client_readonly" Ta "" Ta "1 if client is read-only"
.It Li "client_session" Ta "" Ta "Name of the client's session"
.It Li "client_termfeatures" Ta "" Ta "Terminal features of client, if any"
.It Li "client_termname" Ta "" Ta "Terminal name of client"