mirror of https://github.com/tmux/tmux.git
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
parent
d4423dca19
commit
cd692b5a68
2
Makefile
2
Makefile
|
@ -55,6 +55,7 @@ SRCS= alerts.c \
|
||||||
cmd-select-pane.c \
|
cmd-select-pane.c \
|
||||||
cmd-select-window.c \
|
cmd-select-window.c \
|
||||||
cmd-send-keys.c \
|
cmd-send-keys.c \
|
||||||
|
cmd-server-access.c \
|
||||||
cmd-set-buffer.c \
|
cmd-set-buffer.c \
|
||||||
cmd-set-environment.c \
|
cmd-set-environment.c \
|
||||||
cmd-set-option.c \
|
cmd-set-option.c \
|
||||||
|
@ -104,6 +105,7 @@ SRCS= alerts.c \
|
||||||
screen-redraw.c \
|
screen-redraw.c \
|
||||||
screen-write.c \
|
screen-write.c \
|
||||||
screen.c \
|
screen.c \
|
||||||
|
server-acl.c \
|
||||||
server-client.c \
|
server-client.c \
|
||||||
server-fn.c \
|
server-fn.c \
|
||||||
server.c \
|
server.c \
|
||||||
|
|
1
client.c
1
client.c
|
@ -360,6 +360,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
||||||
/* Send identify messages. */
|
/* Send identify messages. */
|
||||||
client_send_identify(ttynam, termname, caps, ncaps, cwd, feat);
|
client_send_identify(ttynam, termname, caps, ncaps, cwd, feat);
|
||||||
tty_term_free_list(caps, ncaps);
|
tty_term_free_list(caps, ncaps);
|
||||||
|
proc_flush_peer(client_peer);
|
||||||
|
|
||||||
/* Send first command. */
|
/* Send first command. */
|
||||||
if (msg == MSG_COMMAND) {
|
if (msg == MSG_COMMAND) {
|
||||||
|
|
|
@ -43,7 +43,7 @@ const struct cmd_entry cmd_attach_session_entry = {
|
||||||
|
|
||||||
/* -t is special */
|
/* -t is special */
|
||||||
|
|
||||||
.flags = CMD_STARTSERVER,
|
.flags = CMD_STARTSERVER|CMD_READONLY,
|
||||||
.exec = cmd_attach_session_exec
|
.exec = cmd_attach_session_exec
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
|
||||||
|
|
||||||
if (c == NULL)
|
if (c == NULL)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
|
|
||||||
if (server_client_check_nested(c)) {
|
if (server_client_check_nested(c)) {
|
||||||
cmdq_error(item, "sessions should be nested with care, "
|
cmdq_error(item, "sessions should be nested with care, "
|
||||||
"unset $TMUX to force");
|
"unset $TMUX to force");
|
||||||
|
|
|
@ -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
2
cmd.c
|
@ -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_select_window_entry;
|
||||||
extern const struct cmd_entry cmd_send_keys_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_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_buffer_entry;
|
||||||
extern const struct cmd_entry cmd_set_environment_entry;
|
extern const struct cmd_entry cmd_set_environment_entry;
|
||||||
extern const struct cmd_entry cmd_set_hook_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_select_window_entry,
|
||||||
&cmd_send_keys_entry,
|
&cmd_send_keys_entry,
|
||||||
&cmd_send_prefix_entry,
|
&cmd_send_prefix_entry,
|
||||||
|
&cmd_server_access_entry,
|
||||||
&cmd_set_buffer_entry,
|
&cmd_set_buffer_entry,
|
||||||
&cmd_set_environment_entry,
|
&cmd_set_environment_entry,
|
||||||
&cmd_set_hook_entry,
|
&cmd_set_hook_entry,
|
||||||
|
|
6
proc.c
6
proc.c
|
@ -337,6 +337,12 @@ proc_kill_peer(struct tmuxpeer *peer)
|
||||||
peer->flags |= PEER_BAD;
|
peer->flags |= PEER_BAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
proc_flush_peer(struct tmuxpeer *peer)
|
||||||
|
{
|
||||||
|
imsg_flush(&peer->ibuf);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
proc_toggle_log(struct tmuxproc *tp)
|
proc_toggle_log(struct tmuxproc *tp)
|
||||||
{
|
{
|
||||||
|
|
16
server.c
16
server.c
|
@ -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_set(&server_ev_tidy, server_tidy_event, NULL);
|
||||||
evtimer_add(&server_ev_tidy, &tv);
|
evtimer_add(&server_ev_tidy, &tv);
|
||||||
|
|
||||||
|
server_acl_init();
|
||||||
|
|
||||||
server_add_accept(0);
|
server_add_accept(0);
|
||||||
proc_loop(server_proc, server_loop);
|
proc_loop(server_proc, server_loop);
|
||||||
|
|
||||||
|
@ -355,9 +357,10 @@ server_update_socket(void)
|
||||||
static void
|
static void
|
||||||
server_accept(int fd, short events, __unused void *data)
|
server_accept(int fd, short events, __unused void *data)
|
||||||
{
|
{
|
||||||
struct sockaddr_storage sa;
|
struct sockaddr_storage sa;
|
||||||
socklen_t slen = sizeof sa;
|
socklen_t slen = sizeof sa;
|
||||||
int newfd;
|
int newfd;
|
||||||
|
struct client *c;
|
||||||
|
|
||||||
server_add_accept(0);
|
server_add_accept(0);
|
||||||
if (!(events & EV_READ))
|
if (!(events & EV_READ))
|
||||||
|
@ -374,11 +377,16 @@ server_accept(int fd, short events, __unused void *data)
|
||||||
}
|
}
|
||||||
fatal("accept failed");
|
fatal("accept failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server_exit) {
|
if (server_exit) {
|
||||||
close(newfd);
|
close(newfd);
|
||||||
return;
|
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
40
tmux.1
|
@ -1485,6 +1485,44 @@ option.
|
||||||
.D1 Pq alias: Ic rename
|
.D1 Pq alias: Ic rename
|
||||||
Rename the session to
|
Rename the session to
|
||||||
.Ar new-name .
|
.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
|
.Tg showmsgs
|
||||||
.It Xo Ic show-messages
|
.It Xo Ic show-messages
|
||||||
.Op Fl JT
|
.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_name" Ta "" Ta "Name of client"
|
||||||
.It Li "client_pid" Ta "" Ta "PID of client process"
|
.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_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_session" Ta "" Ta "Name of the client's session"
|
||||||
.It Li "client_termfeatures" Ta "" Ta "Terminal features of client, if any"
|
.It Li "client_termfeatures" Ta "" Ta "Terminal features of client, if any"
|
||||||
.It Li "client_termname" Ta "" Ta "Terminal name of client"
|
.It Li "client_termname" Ta "" Ta "Terminal name of client"
|
||||||
|
|
Loading…
Reference in New Issue