Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'LDISC.C')
-rw-r--r--LDISC.C277
1 files changed, 256 insertions, 21 deletions
diff --git a/LDISC.C b/LDISC.C
index f097c040..caff52d0 100644
--- a/LDISC.C
+++ b/LDISC.C
@@ -11,7 +11,62 @@
#include "putty.h"
#include "terminal.h"
-#include "ldisc.h"
+
+struct Ldisc_tag {
+ Terminal *term;
+ Backend *backend;
+ Seat *seat;
+
+ /*
+ * When the backend is not reporting true from sendok(), terminal
+ * input that comes here is stored in this bufchain instead. When
+ * the backend later decides it wants session input, we empty the
+ * queue in ldisc_check_sendok_callback(), passing its contents on
+ * to the backend. Before then, we also provide data from this
+ * queue to term_get_userpass_input() via ldisc_get_input_token(),
+ * to be interpreted as user responses to username and password
+ * prompts during authentication.
+ *
+ * Unfortunately, the data stored in this queue is not all of the
+ * same type: our output to the backend consists of both raw bytes
+ * sent to backend_send(), and also session specials such as
+ * SS_EOL and SS_EC. So we have to encode our queued data in a way
+ * that can represent both.
+ *
+ * The encoding is private to this source file, so we can change
+ * it if necessary and only have to worry about the encode and
+ * decode functions here. Currently, it is:
+ *
+ * - Bytes other than 0xFF are stored literally.
+ * - The byte 0xFF itself is stored as 0xFF 0xFF.
+ * - A session special (code, arg) is stored as 0xFF, followed by
+ * a big-endian 4-byte integer containing code, followed by
+ * another big-endian 4-byte integer containing arg.
+ *
+ * (This representation relies on session special codes being at
+ * most 0xFEFFFFFF when represented in 32 bits, so that the first
+ * byte of the 'code' integer can't be confused with the 0xFF
+ * followup byte indicating a literal 0xFF, But since session
+ * special codes are defined by an enum counting up from zero, and
+ * there are only a couple of dozen of them, that shouldn't be a
+ * problem! Even so, just in case, an assertion checks that at
+ * encode time.)
+ */
+ bufchain input_queue;
+
+ IdempotentCallback input_queue_callback;
+ prompts_t *prompts;
+
+ /*
+ * Values cached out of conf.
+ */
+ bool telnet_keyboard, telnet_newline;
+ int protocol, localecho, localedit;
+
+ char *buf;
+ size_t buflen, bufsiz;
+ bool quotenext;
+};
#define ECHOING (ldisc->localecho == FORCE_ON || \
(ldisc->localecho == AUTO && \
@@ -72,6 +127,8 @@ static void bsb(Ldisc *ldisc, int n)
c_write(ldisc, "\010 \010", 3);
}
+static void ldisc_input_queue_callback(void *ctx);
+
#define CTRL(x) (x^'@')
#define KCTRL(x) ((x^'@') | 0x100)
@@ -88,6 +145,14 @@ Ldisc *ldisc_create(Conf *conf, Terminal *term, Backend *backend, Seat *seat)
ldisc->term = term;
ldisc->seat = seat;
+ bufchain_init(&ldisc->input_queue);
+
+ ldisc->prompts = NULL;
+ ldisc->input_queue_callback.fn = ldisc_input_queue_callback;
+ ldisc->input_queue_callback.ctx = ldisc;
+ ldisc->input_queue_callback.queued = false;
+ bufchain_set_callback(&ldisc->input_queue, &ldisc->input_queue_callback);
+
ldisc_configure(ldisc, conf);
/* Link ourselves into the backend and the terminal */
@@ -110,12 +175,16 @@ void ldisc_configure(Ldisc *ldisc, Conf *conf)
void ldisc_free(Ldisc *ldisc)
{
+ bufchain_clear(&ldisc->input_queue);
if (ldisc->term)
ldisc->term->ldisc = NULL;
if (ldisc->backend)
backend_provide_ldisc(ldisc->backend, NULL);
if (ldisc->buf)
sfree(ldisc->buf);
+ if (ldisc->prompts && ldisc->prompts->ldisc_ptr_to_us == &ldisc->prompts)
+ ldisc->prompts->ldisc_ptr_to_us = NULL;
+ delete_callbacks_for_context(ldisc);
sfree(ldisc);
}
@@ -124,6 +193,173 @@ void ldisc_echoedit_update(Ldisc *ldisc)
seat_echoedit_update(ldisc->seat, ECHOING, EDITING);
}
+void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *prompts)
+{
+ /*
+ * Called by the terminal to indicate that there's a prompts_t
+ * currently in flight, or to indicate that one has just finished
+ * (by passing NULL). When ldisc->prompts is not null, we notify
+ * the terminal whenever new data arrives in our input queue, so
+ * that it can continue the interactive prompting process.
+ */
+ ldisc->prompts = prompts;
+ if (prompts)
+ ldisc->prompts->ldisc_ptr_to_us = &ldisc->prompts;
+}
+
+static void ldisc_input_queue_callback(void *ctx)
+{
+ /*
+ * Toplevel callback that is triggered whenever the input queue
+ * lengthens. If we're currently processing an interactive prompt,
+ * we call back the Terminal to tell it to do some more stuff with
+ * that prompt based on the new input.
+ */
+ Ldisc *ldisc = (Ldisc *)ctx;
+ if (ldisc->term && ldisc->prompts) {
+ /*
+ * The integer return value from this call is discarded,
+ * because we have no channel to pass it on to the backend
+ * that originally wanted it. But that's OK, because if the
+ * return value is >= 0 (that is, the prompts are either
+ * completely filled in, or aborted by the user), then the
+ * terminal will notify the callback in the prompts_t, and
+ * when that calls term_get_userpass_input again, it will
+ * return the same answer again.
+ */
+ term_get_userpass_input(ldisc->term, ldisc->prompts);
+ }
+}
+
+static void ldisc_to_backend_raw(
+ Ldisc *ldisc, const void *vbuf, size_t len)
+{
+ if (backend_sendok(ldisc->backend)) {
+ backend_send(ldisc->backend, vbuf, len);
+ } else {
+ const char *buf = (const char *)vbuf;
+ while (len > 0) {
+ /*
+ * Encode raw data in input_queue, by storing large chunks
+ * as long as they don't include 0xFF, and pausing every
+ * time they do to escape it.
+ */
+ const char *ff = memchr(buf, '\xFF', len);
+ size_t this_len = ff ? ff - buf : len;
+ if (this_len > 0) {
+ bufchain_add(&ldisc->input_queue, buf, len);
+ } else {
+ bufchain_add(&ldisc->input_queue, "\xFF\xFF", 2);
+ this_len = 1;
+ }
+ buf += this_len;
+ len -= this_len;
+ }
+ }
+}
+
+static void ldisc_to_backend_special(
+ Ldisc *ldisc, SessionSpecialCode code, int arg)
+{
+ if (backend_sendok(ldisc->backend)) {
+ backend_special(ldisc->backend, code, arg);
+ } else {
+ /*
+ * Encode a session special in input_queue.
+ */
+ unsigned char data[9];
+ data[0] = 0xFF;
+ PUT_32BIT_MSB_FIRST(data+1, code);
+ PUT_32BIT_MSB_FIRST(data+5, arg);
+ assert(data[1] != 0xFF &&
+ "SessionSpecialCode encoding collides with FF FF escape");
+ bufchain_add(&ldisc->input_queue, data, 9);
+ }
+}
+
+bool ldisc_has_input_buffered(Ldisc *ldisc)
+{
+ return bufchain_size(&ldisc->input_queue) > 0;
+}
+
+LdiscInputToken ldisc_get_input_token(Ldisc *ldisc)
+{
+ assert(bufchain_size(&ldisc->input_queue) > 0 &&
+ "You're not supposed to call this unless there is buffered input!");
+
+ LdiscInputToken tok;
+
+ char c;
+ bufchain_fetch_consume(&ldisc->input_queue, &c, 1);
+ if (c != '\xFF') {
+ /* A literal non-FF byte */
+ tok.is_special = false;
+ tok.chr = c;
+ return tok;
+ } else {
+ char data[8];
+
+ /* See if the byte after the FF is also FF, indicating a literal FF */
+ bufchain_fetch_consume(&ldisc->input_queue, data, 1);
+ if (data[0] == '\xFF') {
+ tok.is_special = false;
+ tok.chr = '\xFF';
+ return tok;
+ }
+
+ /* If not, get the rest of an 8-byte chunk and decode a special */
+ bufchain_fetch_consume(&ldisc->input_queue, data+1, 7);
+ tok.is_special = true;
+ tok.code = GET_32BIT_MSB_FIRST(data);
+ tok.arg = toint(GET_32BIT_MSB_FIRST(data+4));
+ return tok;
+ }
+}
+
+static void ldisc_check_sendok_callback(void *ctx)
+{
+ Ldisc *ldisc = (Ldisc *)ctx;
+
+ if (!(ldisc->backend && backend_sendok(ldisc->backend)))
+ return;
+
+ /*
+ * Flush the ldisc input queue into the backend, which is now
+ * willing to receive the data.
+ */
+ while (bufchain_size(&ldisc->input_queue) > 0) {
+ /*
+ * Process either a chunk of non-special data, or an FF
+ * escape, depending on whether the first thing we see is an
+ * FF byte.
+ */
+ ptrlen data = bufchain_prefix(&ldisc->input_queue);
+ const char *ff = memchr(data.ptr, '\xFF', data.len);
+ if (ff != data.ptr) {
+ /* Send a maximal block of data not containing any
+ * difficult bytes. */
+ if (ff)
+ data.len = ff - (const char *)data.ptr;
+ backend_send(ldisc->backend, data.ptr, data.len);
+ bufchain_consume(&ldisc->input_queue, data.len);
+ } else {
+ /* Decode either a special or an escaped FF byte. The
+ * easiest way to do this is to reuse the decoding code
+ * already in ldisc_get_input_token. */
+ LdiscInputToken tok = ldisc_get_input_token(ldisc);
+ if (tok.is_special)
+ backend_special(ldisc->backend, tok.code, tok.arg);
+ else
+ backend_send(ldisc->backend, &tok.chr, 1);
+ }
+ }
+}
+
+void ldisc_check_sendok(Ldisc *ldisc)
+{
+ queue_toplevel_callback(ldisc_check_sendok_callback, ldisc);
+}
+
void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
{
const char *buf = (const char *)vbuf;
@@ -206,7 +442,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
ldisc->buflen--;
}
- backend_special(ldisc->backend, SS_EL, 0);
+ ldisc_to_backend_special(ldisc, SS_EL, 0);
/*
* We don't send IP, SUSP or ABORT if the user has
* configured telnet specials off! This breaks
@@ -215,11 +451,11 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
if (!ldisc->telnet_keyboard)
goto default_case;
if (c == CTRL('C'))
- backend_special(ldisc->backend, SS_IP, 0);
+ ldisc_to_backend_special(ldisc, SS_IP, 0);
if (c == CTRL('Z'))
- backend_special(ldisc->backend, SS_SUSP, 0);
+ ldisc_to_backend_special(ldisc, SS_SUSP, 0);
if (c == CTRL('\\'))
- backend_special(ldisc->backend, SS_ABORT, 0);
+ ldisc_to_backend_special(ldisc, SS_ABORT, 0);
break;
case CTRL('R'): /* redraw line */
if (ECHOING) {
@@ -234,9 +470,9 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
break;
case CTRL('D'): /* logout or send */
if (ldisc->buflen == 0) {
- backend_special(ldisc->backend, SS_EOF, 0);
+ ldisc_to_backend_special(ldisc, SS_EOF, 0);
} else {
- backend_send(ldisc->backend, ldisc->buf, ldisc->buflen);
+ ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen);
ldisc->buflen = 0;
}
break;
@@ -272,14 +508,13 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
/* FALLTHROUGH */
case KCTRL('M'): /* send with newline */
if (ldisc->buflen > 0)
- backend_send(ldisc->backend,
- ldisc->buf, ldisc->buflen);
+ ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen);
if (ldisc->protocol == PROT_RAW)
- backend_send(ldisc->backend, "\r\n", 2);
+ ldisc_to_backend_raw(ldisc, "\r\n", 2);
else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
- backend_special(ldisc->backend, SS_EOL, 0);
+ ldisc_to_backend_special(ldisc, SS_EOL, 0);
else
- backend_send(ldisc->backend, "\r", 1);
+ ldisc_to_backend_raw(ldisc, "\r", 1);
if (ECHOING)
c_write(ldisc, "\r\n", 2);
ldisc->buflen = 0;
@@ -287,7 +522,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
}
/* FALLTHROUGH */
default: /* get to this label from ^V handler */
- default_case:
+ default_case:
sgrowarray(ldisc->buf, ldisc->bufsiz, ldisc->buflen);
ldisc->buf[ldisc->buflen++] = c;
if (ECHOING)
@@ -298,7 +533,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
}
} else {
if (ldisc->buflen != 0) {
- backend_send(ldisc->backend, ldisc->buf, ldisc->buflen);
+ ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen);
while (ldisc->buflen > 0) {
bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
ldisc->buflen--;
@@ -311,33 +546,33 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
switch (buf[0]) {
case CTRL('M'):
if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
- backend_special(ldisc->backend, SS_EOL, 0);
+ ldisc_to_backend_special(ldisc, SS_EOL, 0);
else
- backend_send(ldisc->backend, "\r", 1);
+ ldisc_to_backend_raw(ldisc, "\r", 1);
break;
case CTRL('?'):
case CTRL('H'):
if (ldisc->telnet_keyboard) {
- backend_special(ldisc->backend, SS_EC, 0);
+ ldisc_to_backend_special(ldisc, SS_EC, 0);
break;
}
case CTRL('C'):
if (ldisc->telnet_keyboard) {
- backend_special(ldisc->backend, SS_IP, 0);
+ ldisc_to_backend_special(ldisc, SS_IP, 0);
break;
}
case CTRL('Z'):
if (ldisc->telnet_keyboard) {
- backend_special(ldisc->backend, SS_SUSP, 0);
+ ldisc_to_backend_special(ldisc, SS_SUSP, 0);
break;
}
default:
- backend_send(ldisc->backend, buf, len);
+ ldisc_to_backend_raw(ldisc, buf, len);
break;
}
} else
- backend_send(ldisc->backend, buf, len);
+ ldisc_to_backend_raw(ldisc, buf, len);
}
}
}