From cd692b5a68be0eb95252380db97fbbec587d6350 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 12:48:57 +0000 Subject: [PATCH 01/13] 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. --- Makefile | 2 + client.c | 1 + cmd-attach-session.c | 3 +- cmd-server-access.c | 147 +++++++++++++++++++++++++++++++++++++++++++ cmd.c | 2 + proc.c | 6 ++ server.c | 16 +++-- tmux.1 | 40 +++++++++++- 8 files changed, 211 insertions(+), 6 deletions(-) create mode 100644 cmd-server-access.c diff --git a/Makefile b/Makefile index 4cbbd34a..fc7ed262 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/client.c b/client.c index 8a0a0673..ef7dea69 100644 --- a/client.c +++ b/client.c @@ -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) { diff --git a/cmd-attach-session.c b/cmd-attach-session.c index cc795b22..b92a7f2b 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -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"); diff --git a/cmd-server-access.c b/cmd-server-access.c new file mode 100644 index 00000000..4fd1dfbf --- /dev/null +++ b/cmd-server-access.c @@ -0,0 +1,147 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 Dallas Lyons + * + * 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 +#include + +#include +#include +#include +#include +#include + +#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); +} diff --git a/cmd.c b/cmd.c index 123fd366..cbdb7c15 100644 --- a/cmd.c +++ b/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_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, diff --git a/proc.c b/proc.c index 9dcb042e..330d73f3 100644 --- a/proc.c +++ b/proc.c @@ -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) { diff --git a/server.c b/server.c index f46dd056..3a2580a9 100644 --- a/server.c +++ b/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_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; + } } /* diff --git a/tmux.1 b/tmux.1 index f6b498e9..3f7ed889 100644 --- a/tmux.1 +++ b/tmux.1 @@ -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" From 4ae2c646576cc4e9f91c730c80bf75fd5122e4b3 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 12:52:02 +0000 Subject: [PATCH 02/13] Better error reporting when applying custom layouts. --- cmd-select-layout.c | 7 ++++--- layout-custom.c | 23 +++++++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/cmd-select-layout.c b/cmd-select-layout.c index c857a0e1..6dfe2b6a 100644 --- a/cmd-select-layout.c +++ b/cmd-select-layout.c @@ -77,7 +77,7 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) struct window *w = wl->window; struct window_pane *wp = target->wp; const char *layoutname; - char *oldlayout; + char *oldlayout, *cause; int next, previous, layout; server_unzoom_window(w); @@ -124,8 +124,9 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) } if (layoutname != NULL) { - if (layout_parse(w, layoutname) == -1) { - cmdq_error(item, "can't set layout: %s", layoutname); + if (layout_parse(w, layoutname, &cause) == -1) { + cmdq_error(item, "%s: %s", cause, layoutname); + free(cause); goto error; } goto changed; diff --git a/layout-custom.c b/layout-custom.c index e7fb4253..932b30e7 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -154,7 +154,7 @@ layout_check(struct layout_cell *lc) /* Parse a layout string and arrange window as layout. */ int -layout_parse(struct window *w, const char *layout) +layout_parse(struct window *w, const char *layout, char **cause) { struct layout_cell *lc, *lcchild; struct window_pane *wp; @@ -165,22 +165,31 @@ layout_parse(struct window *w, const char *layout) if (sscanf(layout, "%hx,", &csum) != 1) return (-1); layout += 5; - if (csum != layout_checksum(layout)) + if (csum != layout_checksum(layout)) { + *cause = xstrdup("invalid layout"); return (-1); + } /* Build the layout. */ lc = layout_construct(NULL, &layout); - if (lc == NULL) + if (lc == NULL) { + *cause = xstrdup("invalid layout"); return (-1); - if (*layout != '\0') + } + if (*layout != '\0') { + *cause = xstrdup("invalid layout"); goto fail; + } /* Check this window will fit into the layout. */ for (;;) { npanes = window_count_panes(w); ncells = layout_count_cells(lc); - if (npanes > ncells) + if (npanes > ncells) { + xasprintf(cause, "have %u panes but need %u", npanes, + ncells); goto fail; + } if (npanes == ncells) break; @@ -217,8 +226,10 @@ layout_parse(struct window *w, const char *layout) } /* Check the new layout. */ - if (!layout_check(lc)) + if (!layout_check(lc)) { + *cause = xstrdup("size mismatch after applying layout"); return (-1); + } /* Resize to the layout size. */ window_resize(w, lc->sx, lc->sy, -1, -1); From af611815ea70d687a15e20426ed1b88017d3d248 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 12:54:09 +0000 Subject: [PATCH 03/13] Emit window-layout-changed on swap-pane, from George Nachman. --- cmd-swap-pane.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 7d477739..4191b894 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -135,6 +135,9 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) } server_redraw_window(src_w); server_redraw_window(dst_w); + notify_window("window-layout-changed", src_w); + if (src_w != dst_w) + notify_window("window-layout-changed", dst_w); out: if (window_pop_zoom(src_w)) From 0a8f356c7278dba34a526adea03561f2063df359 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 12:55:25 +0000 Subject: [PATCH 04/13] Spacing/style nits. --- cmd-command-prompt.c | 2 +- cmd-refresh-client.c | 2 +- format.c | 2 +- log.c | 2 +- notify.c | 2 +- screen.c | 4 +- server-acl.c | 186 +++++++++++++++++++++++++++++++++++++++++++ server-client.c | 24 +++++- window-buffer.c | 4 +- window-client.c | 2 +- window-tree.c | 2 +- window.c | 2 +- 12 files changed, 218 insertions(+), 16 deletions(-) create mode 100644 server-acl.c diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index a7a02702..4455856b 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -112,7 +112,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) } next_prompt = prompts; } else - next_prompt = prompts = xstrdup (s); + next_prompt = prompts = xstrdup(s); if ((s = args_get(args, 'I')) != NULL) next_input = inputs = xstrdup(s); else diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index b2665ad9..6b947280 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -185,7 +185,7 @@ cmd_refresh_client_clipboard(struct cmd *self, struct cmdq_item *item) } if (i != tc->clipboard_npanes) return (CMD_RETURN_NORMAL); - tc->clipboard_panes = xreallocarray (tc->clipboard_panes, + tc->clipboard_panes = xreallocarray(tc->clipboard_panes, tc->clipboard_npanes + 1, sizeof *tc->clipboard_panes); tc->clipboard_panes[tc->clipboard_npanes++] = fs.wp->id; } diff --git a/format.c b/format.c index 981161b3..24d03af0 100644 --- a/format.c +++ b/format.c @@ -2597,7 +2597,7 @@ format_cb_user(__unused struct format_tree *ft) if ((pw = getpwuid(getuid())) != NULL) return (xstrdup(pw->pw_name)); - return NULL; + return (NULL); } /* Format table type. */ diff --git a/log.c b/log.c index abc097dc..ede6e257 100644 --- a/log.c +++ b/log.c @@ -144,7 +144,7 @@ fatal(const char *msg, ...) va_list ap; if (snprintf(tmp, sizeof tmp, "fatal: %s: ", strerror(errno)) < 0) - exit (1); + exit(1); va_start(ap, msg); log_vwrite(msg, ap, tmp); diff --git a/notify.c b/notify.c index f5342710..619bd933 100644 --- a/notify.c +++ b/notify.c @@ -47,7 +47,7 @@ notify_insert_one_hook(struct cmdq_item *item, struct notify_entry *ne, if (log_get_level() != 0) { s = cmd_list_print(cmdlist, 0); log_debug("%s: hook %s is: %s", __func__, ne->name, s); - free (s); + free(s); } new_item = cmdq_get_command(cmdlist, state); return (cmdq_insert_after(item, new_item)); diff --git a/screen.c b/screen.c index 912ab126..87b3330a 100644 --- a/screen.c +++ b/screen.c @@ -662,9 +662,9 @@ screen_mode_to_string(int mode) static char tmp[1024]; if (mode == 0) - return "NONE"; + return ("NONE"); if (mode == ALL_MODES) - return "ALL"; + return ("ALL"); *tmp = '\0'; if (mode & MODE_CURSOR) diff --git a/server-acl.c b/server-acl.c new file mode 100644 index 00000000..26f2490d --- /dev/null +++ b/server-acl.c @@ -0,0 +1,186 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 Holland Schutte, Jayson Morberg + * Copyright (c) 2021 Dallas Lyons + * + * 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 +#include +#include + +#include +#include +#include +#include +#include + +#include "tmux.h" + +struct server_acl_user { + uid_t uid; + + int flags; +#define SERVER_ACL_READONLY 0x1 + + RB_ENTRY(server_acl_user) entry; +}; + +static int +server_acl_cmp(struct server_acl_user *user1, struct server_acl_user *user2) +{ + if (user1->uid < user2->uid) + return (-1); + return (user1->uid > user2->uid); +} + +RB_HEAD(server_acl_entries, server_acl_user) server_acl_entries; +RB_GENERATE_STATIC(server_acl_entries, server_acl_user, entry, server_acl_cmp); + +/* Initialize server_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()); +} + +/* Find user entry. */ +struct server_acl_user* +server_acl_user_find(uid_t uid) +{ + struct server_acl_user find = { .uid = uid }; + + return (RB_FIND(server_acl_entries, &server_acl_entries, &find)); +} + +/* Display the tree. */ +void +server_acl_display(struct cmdq_item *item) +{ + struct server_acl_user *loop; + struct passwd *pw; + const char *name; + + RB_FOREACH(loop, server_acl_entries, &server_acl_entries) { + if (loop->uid == 0) + continue; + if ((pw = getpwuid(loop->uid)) != NULL) + name = pw->pw_name; + else + name = "unknown"; + if (loop->flags == SERVER_ACL_READONLY) + cmdq_print(item, "%s (R)", name); + else + cmdq_print(item, "%s (W)", name); + } +} + +/* Allow a user. */ +void +server_acl_user_allow(uid_t uid) +{ + struct server_acl_user *user; + + 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); + } +} + +/* Deny a user (remove from the tree). */ +void +server_acl_user_deny(uid_t uid) +{ + struct server_acl_user *user; + + user = server_acl_user_find(uid); + if (user != NULL) { + RB_REMOVE(server_acl_entries, &server_acl_entries, user); + free(user); + } +} + +/* Allow this user write access. */ +void +server_acl_user_allow_write(uid_t uid) +{ + struct server_acl_user *user; + struct client *c; + + user = server_acl_user_find(uid); + if (user == 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; + } +} + +/* Deny this user write access. */ +void +server_acl_user_deny_write(uid_t uid) +{ + struct server_acl_user *user; + struct client *c; + + user = server_acl_user_find(uid); + if (user == 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; + } +} + +/* + * 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. + */ +int +server_acl_join(struct client *c) +{ + struct server_acl_user *user; + uid_t uid; + + uid = proc_get_peer_uid(c->peer); + if (uid == (uid_t)-1) + return (0); + + user = server_acl_user_find(uid); + if (user == NULL) + return (0); + if (user->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/server-client.c b/server-client.c index a2a367be..7c4c2fdd 100644 --- a/server-client.c +++ b/server-client.c @@ -2775,6 +2775,14 @@ server_client_dispatch(struct imsg *imsg, void *arg) } } +/* Callback when command is not allowed. */ +static enum cmd_retval +server_client_read_only(struct cmdq_item *item, __unused void *data) +{ + cmdq_error(item, "client is read-only"); + return (CMD_RETURN_ERROR); +} + /* Callback when command is done. */ static enum cmd_retval server_client_command_done(struct cmdq_item *item, __unused void *data) @@ -2799,6 +2807,7 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg) char **argv, *cause; struct cmd_parse_result *pr; struct args_value *values; + struct cmdq_item *new_item; if (c->flags & CLIENT_EXIT) return; @@ -2837,7 +2846,12 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg) free(values); cmd_free_argv(argc, argv); - cmdq_append(c, cmdq_get_command(pr->cmdlist, NULL)); + if ((c->flags & CLIENT_READONLY) && + !cmd_list_all_have(pr->cmdlist, CMD_READONLY)) + new_item = cmdq_get_callback(server_client_read_only, NULL); + else + new_item = cmdq_get_command(pr->cmdlist, NULL); + cmdq_append(c, new_item); cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL)); cmd_list_free(pr->cmdlist); @@ -3071,9 +3085,11 @@ server_client_set_flags(struct client *c, const char *flags) continue; log_debug("client %s set flag %s", c->name, next); - if (not) + if (not) { + if (c->flags & CLIENT_READONLY) + flag &= ~CLIENT_READONLY; c->flags &= ~flag; - else + } else c->flags |= flag; if (flag == CLIENT_CONTROL_NOOUTPUT) control_reset_offsets(c); @@ -3141,7 +3157,7 @@ server_client_add_client_window(struct client *c, u_int id) cw->window = id; RB_INSERT(client_windows, &c->windows, cw); } - return cw; + return (cw); } /* Get client active pane. */ diff --git a/window-buffer.c b/window-buffer.c index a2fa08ad..544a1155 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -308,7 +308,7 @@ window_buffer_get_key(void *modedata, void *itemdata, u_int line) } pb = paste_get_name(item->name); if (pb == NULL) - return KEYC_NONE; + return (KEYC_NONE); ft = format_create(NULL, NULL, FORMAT_NONE, 0); format_defaults(ft, NULL, NULL, 0, NULL); @@ -320,7 +320,7 @@ window_buffer_get_key(void *modedata, void *itemdata, u_int line) key = key_string_lookup_string(expanded); free(expanded); format_free(ft); - return key; + return (key); } static struct screen * diff --git a/window-client.c b/window-client.c index 00f36c7c..8d501b0d 100644 --- a/window-client.c +++ b/window-client.c @@ -281,7 +281,7 @@ window_client_get_key(void *modedata, void *itemdata, u_int line) key = key_string_lookup_string(expanded); free(expanded); format_free(ft); - return key; + return (key); } static struct screen * diff --git a/window-tree.c b/window-tree.c index b594edd9..fc21af43 100644 --- a/window-tree.c +++ b/window-tree.c @@ -895,7 +895,7 @@ window_tree_get_key(void *modedata, void *itemdata, u_int line) key = key_string_lookup_string(expanded); free(expanded); format_free(ft); - return key; + return (key); } static struct screen * diff --git a/window.c b/window.c index b14a9c60..f14c3bc7 100644 --- a/window.c +++ b/window.c @@ -1046,7 +1046,7 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) if (sx == wp->sx && sy == wp->sy) return; - r = xmalloc (sizeof *r); + r = xmalloc(sizeof *r); r->sx = sx; r->sy = sy; r->osx = wp->sx; From 20b0b38cf47112c0219b5bd041d61c5a28fae0fd Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 12:57:31 +0000 Subject: [PATCH 05/13] iTerm2 has OSC 7, from Gregory Anders. --- tty-features.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tty-features.c b/tty-features.c index 4d83a465..2848b4d6 100644 --- a/tty-features.c +++ b/tty-features.c @@ -376,7 +376,7 @@ tty_default_features(int *feat, const char *name, u_int version) }, { .name = "iTerm2", .features = TTY_FEATURES_BASE_MODERN_XTERM - ",cstyle,extkeys,margins,usstyle,sync" + ",cstyle,extkeys,margins,usstyle,sync,osc7" }, { .name = "XTerm", /* From cd89000c1d75d0cfec28cf7e81b06f80a43ea093 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 13:00:18 +0000 Subject: [PATCH 06/13] Add a way for lines added to copy mode to be passed through the parser to handle escape sequences and use it for run-shell, GitHub issue 3156. --- cfg.c | 2 +- cmd-queue.c | 30 +++++++++++++++++------ cmd-run-shell.c | 20 +++++++--------- input.c | 5 ++++ tmux.h | 19 ++++++++++++--- window-copy.c | 63 +++++++++++++++++++++++++++++++++++++++---------- 6 files changed, 103 insertions(+), 36 deletions(-) diff --git a/cfg.c b/cfg.c index 3130e323..d32e5ff1 100644 --- a/cfg.c +++ b/cfg.c @@ -251,7 +251,7 @@ cfg_show_causes(struct session *s) if (wme == NULL || wme->mode != &window_view_mode) window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); for (i = 0; i < cfg_ncauses; i++) { - window_copy_add(wp, "%s", cfg_causes[i]); + window_copy_add(wp, 0, "%s", cfg_causes[i]); free(cfg_causes[i]); } diff --git a/cmd-queue.c b/cmd-queue.c index 4fbdc4e7..8325e2e8 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -19,9 +19,11 @@ #include #include +#include #include #include #include +#include #include "tmux.h" @@ -124,7 +126,7 @@ cmdq_new(void) { struct cmdq_list *queue; - queue = xcalloc (1, sizeof *queue); + queue = xcalloc(1, sizeof *queue); TAILQ_INIT (&queue->list); return (queue); } @@ -558,17 +560,31 @@ cmdq_add_message(struct cmdq_item *item) { struct client *c = item->client; struct cmdq_state *state = item->state; - const char *name, *key; + const char *key; char *tmp; + uid_t uid; + struct passwd *pw; + char *user = NULL; tmp = cmd_print(item->cmd); if (c != NULL) { - name = c->name; + uid = proc_get_peer_uid(c->peer); + if (uid != (uid_t)-1 && uid != getuid()) { + if ((pw = getpwuid(uid)) != NULL) + xasprintf(&user, "[%s]", pw->pw_name); + else + user = xstrdup("[unknown]"); + } else + user = xstrdup(""); if (c->session != NULL && state->event.key != KEYC_NONE) { key = key_string_lookup_key(state->event.key, 0); - server_add_message("%s key %s: %s", name, key, tmp); - } else - server_add_message("%s command: %s", name, tmp); + server_add_message("%s%s key %s: %s", c->name, user, + key, tmp); + } else { + server_add_message("%s%s command: %s", c->name, user, + tmp); + } + free(user); } else server_add_message("command: %s", tmp); free(tmp); @@ -840,7 +856,7 @@ cmdq_print(struct cmdq_item *item, const char *fmt, ...) window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); } - window_copy_add(wp, "%s", msg); + window_copy_add(wp, 0, "%s", msg); } free(msg); diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 5e914e65..db5774d2 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -84,22 +84,17 @@ cmd_run_shell_print(struct job *job, const char *msg) if (cdata->wp_id != -1) wp = window_pane_find_by_id(cdata->wp_id); - if (wp == NULL) { - if (cdata->item != NULL) { - cmdq_print(cdata->item, "%s", msg); - return; - } - if (cmd_find_from_nothing(&fs, 0) != 0) - return; + if (wp == NULL && cdata->item != NULL) + wp = server_client_get_pane(cdata->client); + if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0) wp = fs.wp; - if (wp == NULL) - return; - } + if (wp == NULL) + return; wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode != &window_view_mode) window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); - window_copy_add(wp, "%s", msg); + window_copy_add(wp, 1, "%s", msg); } static enum cmd_retval @@ -227,7 +222,8 @@ cmd_run_shell_callback(struct job *job) int retcode, status; do { - if ((line = evbuffer_readline(event->input)) != NULL) { + line = evbuffer_readln(event->input, NULL, EVBUFFER_EOL_LF); + if (line != NULL) { cmd_run_shell_print(job, line); free(line); } diff --git a/input.c b/input.c index b1856538..693b6f32 100644 --- a/input.c +++ b/input.c @@ -1078,6 +1078,9 @@ input_reply(struct input_ctx *ictx, const char *fmt, ...) va_list ap; char *reply; + if (bev == NULL) + return; + va_start(ap, fmt); xvasprintf(&reply, fmt, ap); va_end(ap); @@ -1798,6 +1801,8 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) screen_write_mode_set(sctx, MODE_FOCUSON); if (wp == NULL) break; + if (!options_get_number(global_options, "focus-events")) + break; if (wp->flags & PANE_FOCUSED) bufferevent_write(wp->event, "\033[I", 3); else diff --git a/tmux.h b/tmux.h index 3bde14a2..089b45fb 100644 --- a/tmux.h +++ b/tmux.h @@ -2024,6 +2024,7 @@ struct tmuxpeer *proc_add_peer(struct tmuxproc *, int, void (*)(struct imsg *, void *), void *); void proc_remove_peer(struct tmuxpeer *); void proc_kill_peer(struct tmuxpeer *); +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 *); @@ -3017,7 +3018,7 @@ void layout_spread_out(struct window_pane *); /* layout-custom.c */ char *layout_dump(struct layout_cell *); -int layout_parse(struct window *, const char *); +int layout_parse(struct window *, const char *, char **); /* layout-set.c */ int layout_set_lookup(const char *); @@ -3082,8 +3083,9 @@ extern const struct window_mode window_client_mode; /* window-copy.c */ extern const struct window_mode window_copy_mode; extern const struct window_mode window_view_mode; -void printflike(2, 3) window_copy_add(struct window_pane *, const char *, ...); -void printflike(2, 0) window_copy_vadd(struct window_pane *, const char *, +void printflike(3, 4) window_copy_add(struct window_pane *, int, const char *, + ...); +void printflike(3, 0) window_copy_vadd(struct window_pane *, int, const char *, va_list); void window_copy_pageup(struct window_pane *, int); void window_copy_start_drag(struct client *, struct mouse_event *); @@ -3266,4 +3268,15 @@ struct window_pane *spawn_pane(struct spawn_context *, char **); /* regsub.c */ char *regsub(const char *, const char *, const char *, int); +/* server-acl.c */ +void server_acl_init(void); +struct server_acl_user *server_acl_user_find(uid_t); +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); +int server_acl_join(struct client *); +uid_t server_acl_get_uid(struct server_acl_user *); + #endif /* TMUX_H */ diff --git a/window-copy.c b/window-copy.c index 7c55b0f2..03070556 100644 --- a/window-copy.c +++ b/window-copy.c @@ -222,6 +222,8 @@ struct window_copy_mode_data { struct screen *backing; int backing_written; /* backing display started */ + struct screen *writing; + struct input_ctx *ictx; int viewmode; /* view mode entered */ @@ -467,13 +469,16 @@ window_copy_view_init(struct window_mode_entry *wme, struct window_pane *wp = wme->wp; struct window_copy_mode_data *data; struct screen *base = &wp->base; - struct screen *s; + u_int sx = screen_size_x(base); data = window_copy_common_init(wme); data->viewmode = 1; - data->backing = s = xmalloc(sizeof *data->backing); - screen_init(s, screen_size_x(base), screen_size_y(base), UINT_MAX); + data->backing = xmalloc(sizeof *data->backing); + screen_init(data->backing, sx, screen_size_y(base), UINT_MAX); + data->writing = xmalloc(sizeof *data->writing); + screen_init(data->writing, sx, screen_size_y(base), 0); + data->ictx = input_init(NULL, NULL, NULL); data->mx = data->cx; data->my = screen_hsize(data->backing) + data->cy - data->oy; data->showmark = 0; @@ -492,6 +497,12 @@ window_copy_free(struct window_mode_entry *wme) free(data->searchstr); free(data->jumpchar); + if (data->writing != NULL) { + screen_free(data->writing); + free(data->writing); + } + if (data->ictx != NULL) + input_free(data->ictx); screen_free(data->backing); free(data->backing); @@ -500,41 +511,67 @@ window_copy_free(struct window_mode_entry *wme) } void -window_copy_add(struct window_pane *wp, const char *fmt, ...) +window_copy_add(struct window_pane *wp, int parse, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - window_copy_vadd(wp, fmt, ap); + window_copy_vadd(wp, parse, fmt, ap); va_end(ap); } +static void +window_copy_init_ctx_cb(__unused struct screen_write_ctx *ctx, + struct tty_ctx *ttyctx) +{ + memcpy(&ttyctx->defaults, &grid_default_cell, sizeof ttyctx->defaults); + ttyctx->palette = NULL; + ttyctx->redraw_cb = NULL; + ttyctx->set_client_cb = NULL; + ttyctx->arg = NULL; +} + void -window_copy_vadd(struct window_pane *wp, const char *fmt, va_list ap) +window_copy_vadd(struct window_pane *wp, int parse, const char *fmt, va_list ap) { struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); struct window_copy_mode_data *data = wme->data; struct screen *backing = data->backing; - struct screen_write_ctx back_ctx, ctx; + struct screen *writing = data->writing; + struct screen_write_ctx writing_ctx, backing_ctx, ctx; struct grid_cell gc; u_int old_hsize, old_cy; + u_int sx = screen_size_x(backing); + char *text; - memcpy(&gc, &grid_default_cell, sizeof gc); + if (parse) { + vasprintf(&text, fmt, ap); + screen_write_start(&writing_ctx, writing); + screen_write_reset(&writing_ctx); + input_parse_screen(data->ictx, writing, window_copy_init_ctx_cb, + data, text, strlen(text)); + free(text); + } old_hsize = screen_hsize(data->backing); - screen_write_start(&back_ctx, backing); + screen_write_start(&backing_ctx, backing); if (data->backing_written) { /* * On the second or later line, do a CRLF before writing * (so it's on a new line). */ - screen_write_carriagereturn(&back_ctx); - screen_write_linefeed(&back_ctx, 0, 8); + screen_write_carriagereturn(&backing_ctx); + screen_write_linefeed(&backing_ctx, 0, 8); } else data->backing_written = 1; old_cy = backing->cy; - screen_write_vnputs(&back_ctx, 0, &gc, fmt, ap); - screen_write_stop(&back_ctx); + if (parse) + screen_write_fast_copy(&backing_ctx, writing, 0, 0, sx, 1); + else { + memcpy(&gc, &grid_default_cell, sizeof gc); + screen_write_vnputs(&backing_ctx, 0, &gc, fmt, ap); + } + screen_write_stop(&backing_ctx); data->oy += screen_hsize(data->backing) - old_hsize; From 2b60ff588ebc26258848fa9d89a6e32e46eeba58 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 13:02:55 +0000 Subject: [PATCH 07/13] If a mouse position was above the maximum supported by the normal mouse protocol (223), tmux was allowing it to wrap around. However, since tmux was not correctly handling this on input, other programs also do not handle it correctly, and the alternative SGR mouse mode is now widespread, this seems unnecessary, so remove this feature. Also define some constants to make it clearer what the numbers mean. Mostly from Leonid S Usov in GitHub issue 3165. --- input-keys.c | 31 +++++++++++++++++++++++-------- tmux.h | 6 ++++++ tty-keys.c | 16 ++++++---------- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/input-keys.c b/input-keys.c index 78ae91da..038003df 100644 --- a/input-keys.c +++ b/input-keys.c @@ -607,19 +607,34 @@ input_key_get_mouse(struct screen *s, struct mouse_event *m, u_int x, u_int y, len = xsnprintf(buf, sizeof buf, "\033[<%u;%u;%u%c", m->sgr_b, x + 1, y + 1, m->sgr_type); } else if (s->mode & MODE_MOUSE_UTF8) { - if (m->b > 0x7ff - 32 || x > 0x7ff - 33 || y > 0x7ff - 33) + if (m->b > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_BTN_OFF || + x > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_POS_OFF || + y > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_POS_OFF) return (0); len = xsnprintf(buf, sizeof buf, "\033[M"); - len += input_key_split2(m->b + 32, &buf[len]); - len += input_key_split2(x + 33, &buf[len]); - len += input_key_split2(y + 33, &buf[len]); + len += input_key_split2(m->b + MOUSE_PARAM_BTN_OFF, &buf[len]); + len += input_key_split2(x + MOUSE_PARAM_POS_OFF, &buf[len]); + len += input_key_split2(y + MOUSE_PARAM_POS_OFF, &buf[len]); } else { - if (m->b > 223) + if (m->b + MOUSE_PARAM_BTN_OFF > MOUSE_PARAM_MAX) return (0); + len = xsnprintf(buf, sizeof buf, "\033[M"); - buf[len++] = m->b + 32; - buf[len++] = x + 33; - buf[len++] = y + 33; + buf[len++] = m->b + MOUSE_PARAM_BTN_OFF; + + /* + * The incoming x and y may be out of the range which can be + * supported by the "normal" mouse protocol. Clamp the + * coordinates to the supported range. + */ + if (x + MOUSE_PARAM_POS_OFF > MOUSE_PARAM_MAX) + buf[len++] = MOUSE_PARAM_MAX; + else + buf[len++] = x + MOUSE_PARAM_POS_OFF; + if (y + MOUSE_PARAM_POS_OFF > MOUSE_PARAM_MAX) + buf[len++] = MOUSE_PARAM_MAX; + else + buf[len++] = y + MOUSE_PARAM_POS_OFF; } *rbuf = buf; diff --git a/tmux.h b/tmux.h index 089b45fb..dd177550 100644 --- a/tmux.h +++ b/tmux.h @@ -580,6 +580,12 @@ enum tty_code_code { #define MOTION_MOUSE_MODES (MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) #define CURSOR_MODES (MODE_CURSOR|MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE) +/* Mouse protocol constants. */ +#define MOUSE_PARAM_MAX 0xff +#define MOUSE_PARAM_UTF8_MAX 0x7ff +#define MOUSE_PARAM_BTN_OFF 0x20 +#define MOUSE_PARAM_POS_OFF 0x21 + /* A single UTF-8 character. */ typedef u_int utf8_char; diff --git a/tty-keys.c b/tty-keys.c index 8538e74b..64dd91bb 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1061,17 +1061,13 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size, log_debug("%s: mouse input: %.*s", c->name, (int)*size, buf); /* Check and return the mouse input. */ - if (b < 32) + if (b < MOUSE_PARAM_BTN_OFF || + x < MOUSE_PARAM_POS_OFF || + y < MOUSE_PARAM_POS_OFF) return (-1); - b -= 32; - if (x >= 33) - x -= 33; - else - x = 256 - x; - if (y >= 33) - y -= 33; - else - y = 256 - y; + b -= MOUSE_PARAM_BTN_OFF; + x -= MOUSE_PARAM_POS_OFF; + y -= MOUSE_PARAM_POS_OFF; } else if (buf[2] == '<') { /* Read the three inputs. */ *size = 3; From af1496b300cd755dcffd514ed0a329943f633cd4 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 13:03:46 +0000 Subject: [PATCH 08/13] Do not allow pipe-pane on dead panes, from Anindya Mukherjee, GitHub issue 3174. --- cmd-pipe-pane.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd-pipe-pane.c b/cmd-pipe-pane.c index 1a8c851b..bedc8f24 100644 --- a/cmd-pipe-pane.c +++ b/cmd-pipe-pane.c @@ -68,6 +68,12 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) struct format_tree *ft; sigset_t set, oldset; + /* Do nothing if pane is dead. */ + if (wp->fd == -1 || (wp->flags & PANE_EXITED)) { + cmdq_error(item, "target pane has exited"); + return (CMD_RETURN_ERROR); + } + /* Destroy the old pipe. */ old_fd = wp->pipe_fd; if (wp->pipe_fd != -1) { From 006a529db11bb7050e0c925f7c76b2ff5023655a Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 13:04:24 +0000 Subject: [PATCH 09/13] Check if args_strtonum argument is NULL or not a string, from Anindya Mukherjee. --- arguments.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arguments.c b/arguments.c index 37cd0236..c94a3a29 100644 --- a/arguments.c +++ b/arguments.c @@ -831,6 +831,12 @@ args_strtonum(struct args *args, u_char flag, long long minval, return (0); } value = TAILQ_LAST(&entry->values, args_values); + if (value == NULL || + value->type != ARGS_STRING || + value->string == NULL) { + *cause = xstrdup("missing"); + return (0); + } ll = strtonum(value->string, minval, maxval, &errstr); if (errstr != NULL) { From 384f0ee269a49b8a139f8090264420df3a93ff78 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 13:06:10 +0000 Subject: [PATCH 10/13] Fix property name from Sergei Dyshel, and a typo from imcusg at gmail dot com. --- cmd-server-access.c | 2 +- tmux.1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd-server-access.c b/cmd-server-access.c index 4fd1dfbf..45442a62 100644 --- a/cmd-server-access.c +++ b/cmd-server-access.c @@ -81,7 +81,7 @@ cmd_server_access_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } if (args_count(args) == 0) { - cmdq_error(item, "missing user arguement"); + cmdq_error(item, "missing user argument"); return (CMD_RETURN_ERROR); } diff --git a/tmux.1 b/tmux.1 index 3f7ed889..da13b1e9 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5876,7 +5876,7 @@ milliseconds. If .Ar delay is not given, the -.Ic message-time +.Ic display-time option is used; a delay of zero waits for a key press. .Ql N ignores key presses and closes only after the delay expires. From 6a5d210e558ffe9d8ec9b84f06da07e69629db7b Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 13:06:41 +0000 Subject: [PATCH 11/13] Set PWD so shells have a hint about the real path (this was done before but lost in a merge). GitHub issue 3186. --- spawn.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/spawn.c b/spawn.c index 294eb50f..2349f1b3 100644 --- a/spawn.c +++ b/spawn.c @@ -211,7 +211,7 @@ spawn_pane(struct spawn_context *sc, char **cause) struct window_pane *new_wp; struct environ *child; struct environ_entry *ee; - char **argv, *cp, **argvp, *argv0, *cwd; + char **argv, *cp, **argvp, *argv0, *cwd, *new_cwd; const char *cmd, *tmp; int argc; u_int idx; @@ -227,9 +227,15 @@ spawn_pane(struct spawn_context *sc, char **cause) * Work out the current working directory. If respawning, use * the pane's stored one unless specified. */ - if (sc->cwd != NULL) + if (sc->cwd != NULL) { cwd = format_single(item, sc->cwd, c, target->s, NULL, NULL); - else if (~sc->flags & SPAWN_RESPAWN) + if (*cwd != '/') { + xasprintf(&new_cwd, "%s/%s", server_client_get_cwd(c, + target->s), cwd); + free(cwd); + cwd = new_cwd; + } + } else if (~sc->flags & SPAWN_RESPAWN) cwd = xstrdup(server_client_get_cwd(c, target->s)); else cwd = NULL; @@ -337,8 +343,7 @@ spawn_pane(struct spawn_context *sc, char **cause) log_debug("%s: cmd=%s", __func__, cp); free(cp); } - if (cwd != NULL) - log_debug("%s: cwd=%s", __func__, cwd); + log_debug("%s: cwd=%s", __func__, new_wp->cwd); cmd_log_argv(new_wp->argc, new_wp->argv, "%s", __func__); environ_log(child, "%s: environment ", __func__); @@ -384,9 +389,13 @@ spawn_pane(struct spawn_context *sc, char **cause) * Child process. Change to the working directory or home if that * fails. */ - if (chdir(new_wp->cwd) != 0 && - ((tmp = find_home()) == NULL || chdir(tmp) != 0) && - chdir("/") != 0) + if (chdir(new_wp->cwd) == 0) + environ_set(child, "PWD", 0, "%s", new_wp->cwd); + else if ((tmp = find_home()) != NULL || chdir(tmp) == 0) + environ_set(child, "PWD", 0, "%s", tmp); + else if (chdir("/") == 0) + environ_set(child, "PWD", 0, "/"); + else fatal("chdir failed"); /* From 2f2bb82f5f9c7ba995e8c21a217926efbbb4c5e5 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 13:07:06 +0000 Subject: [PATCH 12/13] Add pane_start_path to match start_command. --- format.c | 17 +++++++++++++++++ tmux.1 | 1 + 2 files changed, 18 insertions(+) diff --git a/format.c b/format.c index 24d03af0..d085e348 100644 --- a/format.c +++ b/format.c @@ -801,6 +801,20 @@ format_cb_start_command(struct format_tree *ft) return (cmd_stringify_argv(wp->argc, wp->argv)); } +/* Callback for pane_start_path. */ +static void * +format_cb_start_path(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp == NULL) + return (NULL); + + if (wp->cwd == NULL) + return (xstrdup("")); + return (xstrdup(wp->cwd)); +} + /* Callback for pane_current_command. */ static void * format_cb_current_command(struct format_tree *ft) @@ -2898,6 +2912,9 @@ static const struct format_table_entry format_table[] = { { "pane_start_command", FORMAT_TABLE_STRING, format_cb_start_command }, + { "pane_start_path", FORMAT_TABLE_STRING, + format_cb_start_path + }, { "pane_synchronized", FORMAT_TABLE_STRING, format_cb_pane_synchronized }, diff --git a/tmux.1 b/tmux.1 index da13b1e9..c58597c6 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5192,6 +5192,7 @@ The following variables are available, where appropriate: .It Li "pane_right" Ta "" Ta "Right of pane" .It Li "pane_search_string" Ta "" Ta "Last search string in copy mode" .It Li "pane_start_command" Ta "" Ta "Command pane started with" +.It Li "pane_start_path" Ta "" Ta "Path pane started with" .It Li "pane_synchronized" Ta "" Ta "1 if pane is synchronized" .It Li "pane_tabs" Ta "" Ta "Pane tab positions" .It Li "pane_title" Ta "#T" Ta "Title of pane (can be set by application)" From 58c8ea120943232887b2d4be53b77009c504af06 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 May 2022 13:07:46 +0000 Subject: [PATCH 13/13] Remove duplicates from completion list, GitHub issue 3178. --- status.c | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/status.c b/status.c index 8d702de1..929276d2 100644 --- a/status.c +++ b/status.c @@ -1576,11 +1576,25 @@ status_prompt_add_history(const char *line, u_int type) status_prompt_hsize[type] = newsize; } +/* Add to completion list. */ +static void +status_prompt_add_list(char ***list, u_int *size, const char *s) +{ + u_int i; + + for (i = 0; i < *size; i++) { + if (strcmp((*list)[i], s) == 0) + return; + } + *list = xreallocarray(*list, (*size) + 1, sizeof **list); + (*list)[(*size)++] = xstrdup(s); +} + /* Build completion list. */ static char ** status_prompt_complete_list(u_int *size, const char *s, int at_start) { - char **list = NULL; + char **list = NULL, *tmp; const char **layout, *value, *cp; const struct cmd_entry **cmdent; const struct options_table_entry *oe; @@ -1594,15 +1608,11 @@ status_prompt_complete_list(u_int *size, const char *s, int at_start) *size = 0; for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { - if (strncmp((*cmdent)->name, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup((*cmdent)->name); - } + if (strncmp((*cmdent)->name, s, slen) == 0) + status_prompt_add_list(&list, size, (*cmdent)->name); if ((*cmdent)->alias != NULL && - strncmp((*cmdent)->alias, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup((*cmdent)->alias); - } + strncmp((*cmdent)->alias, s, slen) == 0) + status_prompt_add_list(&list, size, (*cmdent)->alias); } o = options_get_only(global_options, "command-alias"); if (o != NULL) { @@ -1615,8 +1625,9 @@ status_prompt_complete_list(u_int *size, const char *s, int at_start) if (slen > valuelen || strncmp(value, s, slen) != 0) goto next; - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrndup(value, valuelen); + xasprintf(&tmp, "%.*s", (int)valuelen, value); + status_prompt_add_list(&list, size, tmp); + free(tmp); next: a = options_array_next(a); @@ -1624,18 +1635,13 @@ status_prompt_complete_list(u_int *size, const char *s, int at_start) } if (at_start) return (list); - for (oe = options_table; oe->name != NULL; oe++) { - if (strncmp(oe->name, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup(oe->name); - } + if (strncmp(oe->name, s, slen) == 0) + status_prompt_add_list(&list, size, oe->name); } for (layout = layouts; *layout != NULL; layout++) { - if (strncmp(*layout, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup(*layout); - } + if (strncmp(*layout, s, slen) == 0) + status_prompt_add_list(&list, size, *layout); } return (list); }