[prev in list] [next in list] [prev in thread] [next in thread]
List: openssh-unix-dev
Subject: cooked mode sessions
From: Howard Chu <hyc () symas ! com>
Date: 2010-06-14 23:10:10
Message-ID: 4C16B6D2.1060701 () symas ! com
[Download RAW message or body]
Picking up on a couple really old threads (e.g.
http://osdir.com/ml/ietf.secsh/2001-09/msg00003.html ) I've finally gotten
around to this. The EXTPROC support on Linux is missing, but you can find
kernel patches for that here
http://lkml.org/lkml/2010/6/11/403
I've also fixed up the netkit telnet / telnetd code to work with EXTPROC /
LINEMODE on Linux, those patches are here
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=585527
These ssh patches are still not even half-baked, just a proof of concept to
get feedback and guidance on what the right approach actually is. To get an
idea of where it's coming from you should read RFC1184 which gives the Telnet
LINEMODE spec. Since the ssh protocol doesn't have the Do/Dont/Will/Wont
option negotiation of Telnet things are of course a bit different here.
Instead of explicit negotiation, the server assumes the client supports
linemode if it includes an EXTPROC bit in its tty modes. I've added a new
"tty-changed" channel message as well, for the server to send tty mode changes
back to the client. The client will assume the server supports linemode if it
sends tty-changed messages.
If the server reports that the session tty is in cooked mode (ICANON|ECHO)
then the client will use the readline library to process input. This opens the
possibility of doing full local editing with command history on the client.
(Though I haven't enabled history yet in this patch.)
So far this is only working as expected for dumb programs that don't try to
manipulate the tty modes. I'm working on some patches to the readline library
so that it will leave the tty in cooked mode if it detects that EXTPROC is set
on the tty. So a remote bash shell will defer all input processing to the
local client. Will also be able to support command completion, if the tty
session has VEOL set to <TAB>.
Right now the tty mode handling on the client is a mess, it will need to be
rationalized somehow to work cleanly with older raw-mode-only servers along
with the linemode servers.
Feedback and help would be greatly welcomed.
--
-- Howard Chu
CTO, Symas Corp. http://www.symas.com
Director, Highland Sun http://highlandsun.com/hyc/
Chief Architect, OpenLDAP http://www.openldap.org/project/
["dif1.txt" (text/plain)]
diff --git a/clientloop.c b/clientloop.c
index 76de372..2f2424a 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -85,6 +85,7 @@
#include <termios.h>
#include <pwd.h>
#include <unistd.h>
+#include <readline/readline.h>
#include "openbsd-compat/sys-queue.h"
#include "xmalloc.h"
@@ -161,6 +162,8 @@ static int connection_out; /* Connection to server (output). */
static int need_rekeying; /* Set to non-zero if rekeying is requested. */
static int session_closed = 0; /* In SSH2: login session closed. */
+static int got_prompt;
+
static void client_init_dispatch(void);
int session_ident = -1;
@@ -1277,14 +1280,52 @@ client_filter_cleanup(int cid, void *ctx)
xfree(ctx);
}
+static Buffer *rl_buf;
+static void readline_cb(char *line)
+{
+ if (!line) {
+ buffer_put_char(rl_buf, '\004');
+ } else {
+ buffer_append(rl_buf, line, strlen(line));
+ buffer_put_char(rl_buf, '\n');
+ }
+ got_prompt = 0;
+}
+
int
client_simple_escape_filter(Channel *c, char *buf, int len)
{
+ struct termios *tio;
+ int ret, oldlen;
if (c->extended_usage != CHAN_EXTENDED_WRITE)
return 0;
- return process_escapes(c, &c->input, &c->output, &c->extended,
+ oldlen = c->input.end - c->input.offset;
+ ret = process_escapes(c, &c->input, &c->output, &c->extended,
buf, len);
+ if (ret < 0)
+ return ret;
+ tio = get_saved_tio();
+ /* If we're canonical, use readline */
+ if ((tio->c_lflag & (ECHO|ICANON)) == (ECHO|ICANON)) {
+ if (!got_prompt) {
+ char *ptr = c->output.buf + c->output.end;
+ c->output.buf[c->output.end] = 0;
+ while (ptr >= c->output.buf && *ptr != '\n')
+ ptr--;
+ rl_set_prompt(ptr+1);
+ rl_already_prompted = 1;
+ got_prompt = 1;
+ }
+ len = c->input.end - c->input.offset - oldlen;
+ c->input.end -= len;
+ memcpy(buf, c->input.buf + c->input.end, len);
+ while (len) {
+ rl_stuff_char(*buf++);
+ rl_callback_read_char();
+ len--;
+ }
+ }
}
static void
@@ -1863,6 +1904,17 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
__func__, id);
}
packet_check_eom();
+ } else if (strcmp(rtype, "tty-change") == 0 ) {
+ ttyext tx = { fileno(stdin), 0, get_saved_tio() };
+ int nbytes;
+ tty_parse_modes(&tx, &nbytes);
+ /* server supports passthru, just use cooked mode now */
+ cooked_mode();
+ if (!rl_buf) {
+ rl_callback_handler_install(NULL, readline_cb);
+ rl_already_prompted = 1;
+ rl_buf = &c->input;
+ }
}
if (reply) {
packet_start(success ?
@@ -1872,6 +1924,7 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
}
xfree(rtype);
}
+
static void
client_input_global_request(int type, u_int32_t seq, void *ctxt)
{
diff --git a/packet.h b/packet.h
index 33523d7..9a94c95 100644
--- a/packet.h
+++ b/packet.h
@@ -87,8 +87,15 @@ int packet_remaining(void);
void packet_send_ignore(int);
void packet_add_padding(u_char);
+typedef struct ttyext {
+ int ttyfd;
+ int have_extproc;
+ struct termios *tio;
+} ttyext;
+
void tty_make_modes(int, struct termios *);
-void tty_parse_modes(int, int *);
+void tty_parse_modes(ttyext *, int *);
+void tty_new_modes(void *old, char *buf, int len, int all);
void packet_set_alive_timeouts(int);
int packet_inc_alive_timeouts(void);
diff --git a/session.c b/session.c
index b49d08d..2fe48e7 100644
--- a/session.c
+++ b/session.c
@@ -43,6 +43,7 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
+#include <sys/ioctl.h>
#include <arpa/inet.h>
@@ -253,6 +254,40 @@ auth_input_request_forwarding(struct passwd * pw)
return 0;
}
+static int
+pty_pkt_filter(Channel *c, char *buf, int len)
+{
+ if (buf[0] & TIOCPKT_IOCTL) {
+ Session *s = c->filter_ctx;
+ /* read termios struct and send diff */
+ packet_start(SSH2_MSG_CHANNEL_REQUEST);
+ packet_put_int(c->remote_id);
+ packet_put_cstring("tty-change");
+ packet_put_char(0);
+ tty_new_modes(s->termios, buf+1, len-1, !s->set_modes);
+ packet_send();
+ s->set_modes = 1;
+ len = 1;
+ }
+ if (buf[0] & TIOCPKT_FLUSHWRITE) {
+ packet_write_wait();
+ }
+ if (buf[0] & (TIOCPKT_NOSTOP|TIOCPKT_DOSTOP)) {
+ /* send xon-xoff msg */
+ packet_start(SSH2_MSG_CHANNEL_REQUEST);
+ packet_put_int(c->remote_id);
+ packet_put_cstring("xon-xoff");
+ packet_put_char(0);
+ packet_put_char((buf[0] & TIOCPKT_DOSTOP) != 0);
+ packet_send();
+ }
+ buf++;
+ len--;
+ if (len)
+ buffer_append(&c->input, buf, len);
+ return 0;
+}
+
static void
display_loginmsg(void)
{
@@ -767,7 +802,11 @@ do_exec_pty(Session *s, const char *command)
s->ptymaster = ptymaster;
packet_set_interactive(1);
if (compat20) {
+ pty_pkt_mode(ptyfd);
+ if (s->have_extproc)
+ pty_line_mode(ptyfd, 1);
session_set_fds(s, ptyfd, fdout, -1, 1);
+ channel_register_filter(s->chanid, pty_pkt_filter, NULL, NULL, s);
} else {
server_loop(pid, ptyfd, fdout, -1);
/* server_loop _has_ closed ptyfd and fdout. */
@@ -2141,7 +2180,12 @@ session_pty_req(Session *s)
/* for SSH1 the tty modes length is not given */
if (!compat20)
n_bytes = packet_remaining();
- tty_parse_modes(s->ttyfd, &n_bytes);
+ {
+ ttyext tx = { s->ttyfd, 0, NULL };
+ tty_parse_modes(&tx, &n_bytes);
+ s->have_extproc = tx.have_extproc;
+ s->termios = tx.tio;
+ }
if (!use_privsep)
pty_setowner(s->pw, s->tty);
diff --git a/session.h b/session.h
index cbb8e3a..6d72ab2 100644
--- a/session.h
+++ b/session.h
@@ -39,8 +39,11 @@ struct Session {
/* tty */
char *term;
int ptyfd, ttyfd, ptymaster;
+ int have_extproc;
+ int set_modes;
u_int row, col, xpixel, ypixel;
char tty[TTYSZ];
+ void *termios;
/* X11 */
u_int display_number;
diff --git a/sshpty.c b/sshpty.c
index bbbc0fe..c5a081b 100644
--- a/sshpty.c
+++ b/sshpty.c
@@ -256,3 +256,18 @@ pty_setowner(struct passwd *pw, const char *tty)
}
}
}
+
+void pty_line_mode(int fd, int on) {
+ struct termios tio;
+ tcgetattr(fd, &tio);
+ if (on) tio.c_lflag |= EXTPROC;
+ else tio.c_lflag &= ~EXTPROC;
+ tcsetattr(fd, TCSANOW, &tio);
+ debug("pty linemode %d\n", on);
+}
+
+void pty_pkt_mode(int fd) {
+ int on = 1;
+ ioctl(fd, TIOCPKT, (char *)&on);
+ debug("pty pktmode\n");
+}
diff --git a/sshpty.h b/sshpty.h
index cfa3224..0a2fb3e 100644
--- a/sshpty.h
+++ b/sshpty.h
@@ -20,6 +20,9 @@ struct termios *get_saved_tio(void);
void leave_raw_mode(int);
void enter_raw_mode(int);
+void pty_line_mode(int, int);
+void pty_pkt_mode(int);
+
int pty_allocate(int *, int *, char *, size_t);
void pty_release(const char *);
void pty_make_controlling_tty(int *, const char *);
diff --git a/sshtty.c b/sshtty.c
index d214ce3..11580b9 100644
--- a/sshtty.c
+++ b/sshtty.c
@@ -45,7 +45,10 @@
#include "sshpty.h"
static struct termios _saved_tio;
+static struct termios _orig_tio;
static int _in_raw_mode = 0;
+static int _saved_orig = 0;
+static int _cooked = 0;
struct termios *
get_saved_tio(void)
@@ -58,7 +61,7 @@ leave_raw_mode(int quiet)
{
if (!_in_raw_mode)
return;
- if (tcsetattr(fileno(stdin), TCSADRAIN, &_saved_tio) == -1) {
+ if (tcsetattr(fileno(stdin), TCSADRAIN, &_orig_tio) == -1) {
if (!quiet)
perror("tcsetattr");
} else
@@ -70,12 +73,18 @@ enter_raw_mode(int quiet)
{
struct termios tio;
+ if (_cooked) return;
+
if (tcgetattr(fileno(stdin), &tio) == -1) {
if (!quiet)
perror("tcgetattr");
return;
}
_saved_tio = tio;
+ if (!_saved_orig) {
+ _saved_orig = 1;
+ _orig_tio = _saved_tio;
+ }
tio.c_iflag |= IGNPAR;
tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
#ifdef IUCLC
@@ -94,3 +103,9 @@ enter_raw_mode(int quiet)
} else
_in_raw_mode = 1;
}
+
+void
+cooked_mode()
+{
+ _cooked = 1;
+}
diff --git a/ttymodes.c b/ttymodes.c
index 6f51b8a..0659793 100644
--- a/ttymodes.c
+++ b/ttymodes.c
@@ -57,6 +57,7 @@
#include "ssh1.h"
#include "compat.h"
#include "buffer.h"
+#include "xmalloc.h"
#define TTY_OP_END 0
/*
@@ -346,14 +347,15 @@ end:
* manner from a packet being read.
*/
void
-tty_parse_modes(int fd, int *n_bytes_ptr)
+tty_parse_modes(ttyext *tx, int *n_bytes_ptr)
{
- struct termios tio;
+ struct termios *tio;
int opcode, baud;
int n_bytes = 0;
int failure = 0;
u_int (*get_arg)(void);
int arg_size;
+ int fd = tx->ttyfd;
if (compat20) {
*n_bytes_ptr = packet_get_int();
@@ -366,19 +368,30 @@ tty_parse_modes(int fd, int *n_bytes_ptr)
arg_size = 1;
}
+ if (!tx->tio) {
+ tio = xmalloc(sizeof(struct termios));
+ tx->tio = tio;
+
/*
* Get old attributes for the terminal. We will modify these
* flags. I am hoping that if there are any machine-specific
* modes, they will initially have reasonable values.
*/
- if (tcgetattr(fd, &tio) == -1) {
- logit("tcgetattr: %.100s", strerror(errno));
- failure = -1;
+ if (tcgetattr(fd, tio) == -1) {
+ logit("tcgetattr: %.100s", strerror(errno));
+ failure = -1;
+ }
+ } else {
+ tio = tx->tio;
}
for (;;) {
n_bytes += 1;
opcode = packet_get_char();
+#ifdef EXTPROC
+ if (opcode == 63)
+ tx->have_extproc = 1;
+#endif
switch (opcode) {
case TTY_OP_END:
goto set;
@@ -389,7 +402,7 @@ tty_parse_modes(int fd, int *n_bytes_ptr)
n_bytes += 4;
baud = packet_get_int();
if (failure != -1 &&
- cfsetispeed(&tio, baud_to_speed(baud)) == -1)
+ cfsetispeed(tio, baud_to_speed(baud)) == -1)
error("cfsetispeed failed for %d", baud);
break;
@@ -399,22 +412,22 @@ tty_parse_modes(int fd, int *n_bytes_ptr)
n_bytes += 4;
baud = packet_get_int();
if (failure != -1 &&
- cfsetospeed(&tio, baud_to_speed(baud)) == -1)
+ cfsetospeed(tio, baud_to_speed(baud)) == -1)
error("cfsetospeed failed for %d", baud);
break;
#define TTYCHAR(NAME, OP) \
case OP: \
n_bytes += arg_size; \
- tio.c_cc[NAME] = special_char_decode(get_arg()); \
+ tio->c_cc[NAME] = special_char_decode(get_arg()); \
break;
#define TTYMODE(NAME, FIELD, OP) \
case OP: \
n_bytes += arg_size; \
if (get_arg()) \
- tio.FIELD |= NAME; \
+ tio->FIELD |= NAME; \
else \
- tio.FIELD &= ~NAME; \
+ tio->FIELD &= ~NAME; \
break;
#include "ttymodes.h"
@@ -485,6 +498,54 @@ set:
return; /* Packet parsed ok but tcgetattr() failed */
/* Set the new modes for the terminal. */
- if (tcsetattr(fd, TCSANOW, &tio) == -1)
+ if (tcsetattr(fd, TCSANOW, tio) == -1)
logit("Setting tty modes failed: %.100s", strerror(errno));
}
+
+/*
+ * Encodes difference between old and new terminal modes
+ * in a portable manner, and appends the modes to a packet
+ * being constructed.
+ */
+void
+tty_new_modes(void *old, char *cnew, int len, int all)
+{
+ struct termios *told = old, tnew = *told;
+ Buffer buf;
+ void (*put_arg)(Buffer *, u_int);
+
+ buffer_init(&buf);
+ if (compat20) {
+ put_arg = buffer_put_int;
+ } else {
+ put_arg = (void (*)(Buffer *, u_int)) buffer_put_char;
+ }
+
+ memcpy(&tnew, cnew, len);
+
+ /* Store values of changed mode flags. */
+#define TTYCHAR(NAME, OP) \
+ if (all || (told->c_cc[NAME] != tnew.c_cc[NAME])) { \
+ buffer_put_char(&buf, OP); \
+ put_arg(&buf, special_char_encode(tnew.c_cc[NAME])); }
+
+#define TTYMODE(NAME, FIELD, OP) \
+ if (all || (NAME != EXTPROC && (told->FIELD & NAME) != (tnew.FIELD & NAME))) { \
+ buffer_put_char(&buf, OP); \
+ put_arg(&buf, ((tnew.FIELD & NAME) != 0)); }
+
+#include "ttymodes.h"
+
+#undef TTYCHAR
+#undef TTYMODE
+
+end:
+ /* Mark end of mode data. */
+ buffer_put_char(&buf, TTY_OP_END);
+ if (compat20)
+ packet_put_string(buffer_ptr(&buf), buffer_len(&buf));
+ else
+ packet_put_raw(buffer_ptr(&buf), buffer_len(&buf));
+ buffer_free(&buf);
+ memcpy(told, &tnew, sizeof(told));
+}
diff --git a/ttymodes.h b/ttymodes.h
index 4d848fe..aa62c6e 100644
--- a/ttymodes.h
+++ b/ttymodes.h
@@ -151,6 +151,9 @@ TTYMODE(ECHOKE, c_lflag, 61)
#if defined(PENDIN)
TTYMODE(PENDIN, c_lflag, 62)
#endif /* PENDIN */
+#if defined(EXTPROC)
+TTYMODE(EXTPROC, c_lflag, 63)
+#endif /* EXTPROC */
TTYMODE(OPOST, c_oflag, 70)
#if defined(OLCUC)
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic