diff --git a/clientloop.c b/clientloop.c index 76de372..2f2424a 100644 --- a/clientloop.c +++ b/clientloop.c @@ -85,6 +85,7 @@ #include #include #include +#include #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 #include #include +#include #include @@ -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)