diff --git a/fuzzterm.c b/fuzzterm.c index 2e680342..b1efeac7 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -119,6 +119,11 @@ static const TermWinVtable fuzz_termwin_vt = { void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} void ldisc_echoedit_update(Ldisc *ldisc) {} +bool ldisc_has_input_buffered(Ldisc *ldisc) { return false; } +LdiscInputToken ldisc_get_input_token(Ldisc *ldisc) +{ unreachable("This fake ldisc never has any buffered input"); } +void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *p) +{ unreachable("This fake ldisc should never be used for user/pass prompts"); } void modalfatalbox(const char *fmt, ...) { exit(0); } void nonfatal(const char *fmt, ...) { } diff --git a/ldisc.c b/ldisc.c index 8c985f06..b549c14a 100644 --- a/ldisc.c +++ b/ldisc.c @@ -17,6 +17,46 @@ struct Ldisc_tag { 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. */ @@ -87,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) @@ -103,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 */ @@ -125,12 +175,14 @@ 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); + delete_callbacks_for_context(ldisc); sfree(ldisc); } @@ -139,8 +191,169 @@ 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; +} + +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) @@ -225,7 +438,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 @@ -234,11 +447,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) { @@ -253,9 +466,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; @@ -291,14 +504,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; @@ -317,7 +529,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--; @@ -330,33 +542,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); } } } diff --git a/noterm.c b/noterm.c index 4ca99fa2..b5329aec 100644 --- a/noterm.c +++ b/noterm.c @@ -9,3 +9,8 @@ void term_nopaste(Terminal *term) { } + +int term_get_userpass_input(Terminal *term, prompts_t *p) +{ + return 0; +} diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 11deb89c..09f1ab1d 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -35,6 +35,7 @@ struct Rlogin { static void rlogin_startup(Rlogin *rlogin, int prompt_result, const char *ruser); +static void rlogin_try_username_prompt(void *ctx); static void c_write(Rlogin *rlogin, const void *buf, size_t len) { @@ -80,18 +81,11 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, rlogin->prompt->to_server = true; rlogin->prompt->from_server = false; rlogin->prompt->name = dupstr("Rlogin login name"); + rlogin->prompt->callback = rlogin_try_username_prompt; + rlogin->prompt->callback_ctx = rlogin; add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); - - int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, - NULL); - if (ret >= 0) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, ret, prompt_get_result_ref( - rlogin->prompt->prompts[0])); - } + rlogin_try_username_prompt(rlogin); } - } } @@ -300,45 +294,36 @@ static void rlogin_reconfig(Backend *be, Conf *conf) { } +static void rlogin_try_username_prompt(void *ctx) +{ + Rlogin *rlogin = (Rlogin *)ctx; + + int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt); + if (ret < 0) + return; + + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + + /* Send the rlogin setup protocol data, and then we're ready to + * start receiving normal input to send down the wire, which + * rlogin_startup will signal to rlogin_sendok by nulling out + * rlogin->prompt. */ + rlogin_startup( + rlogin, ret, prompt_get_result_ref(rlogin->prompt->prompts[0])); +} + /* * Called to send data down the rlogin connection. */ static void rlogin_send(Backend *be, const char *buf, size_t len) { Rlogin *rlogin = container_of(be, Rlogin, backend); - bufchain bc; if (rlogin->s == NULL) return; - bufchain_init(&bc); - bufchain_add(&bc, buf, len); - - if (rlogin->prompt) { - /* - * We're still prompting for a username, and aren't talking - * directly to the network connection yet. - */ - int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, &bc); - if (ret >= 0) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, ret, prompt_get_result_ref( - rlogin->prompt->prompts[0])); - /* that nulls out rlogin->prompt, so then we'll start sending - * data down the wire in the obvious way */ - } - } - - if (!rlogin->prompt) { - while (bufchain_size(&bc) > 0) { - ptrlen data = bufchain_prefix(&bc); - rlogin->bufsize = sk_write(rlogin->s, data.ptr, data.len); - bufchain_consume(&bc, len); - } - } - - bufchain_clear(&bc); + rlogin->bufsize = sk_write(rlogin->s, buf, len); } /* @@ -398,8 +383,12 @@ static bool rlogin_connected(Backend *be) static bool rlogin_sendok(Backend *be) { + /* + * We only want to receive input data if the socket is connected + * and we're not still at the username prompt stage. + */ Rlogin *rlogin = container_of(be, Rlogin, backend); - return rlogin->socket_connected; + return rlogin->socket_connected && !rlogin->prompt; } static void rlogin_unthrottle(Backend *be, size_t backlog) diff --git a/putty.h b/putty.h index fcfff4b6..417579e4 100644 --- a/putty.h +++ b/putty.h @@ -741,6 +741,11 @@ extern const int be_default_protocol; */ extern const char *const appname; +/* + * Used by callback.c; declared up here so that prompts_t can use it + */ +typedef void (*toplevel_callback_fn_t)(void *ctx); + /* * Mechanism for getting text strings such as usernames and passwords * from the front-end. @@ -790,6 +795,17 @@ typedef struct { prompt_t **prompts; void *data; /* slot for housekeeping data, managed by * seat_get_userpass_input(); initially NULL */ + int idata; /* another slot private to the implementation */ + + /* + * Callback you can fill in to be notified when all the prompts' + * responses are available. After you receive this notification, a + * further call to the get_userpass_input function will return the + * final state of the prompts system, which is guaranteed not to + * be negative for 'still ongoing'. + */ + toplevel_callback_fn_t callback; + void *callback_ctx; } prompts_t; prompts_t *new_prompts(void); void add_prompt(prompts_t *p, char *promptstr, bool echo); @@ -907,13 +923,7 @@ struct SeatVtable { /* * Try to get answers from a set of interactive login prompts. The - * prompts are provided in 'p'; the bufchain 'input' holds the - * data currently outstanding in the session's normal standard- - * input channel. Seats may implement this function by consuming - * data from 'input' (e.g. password prompts in GUI PuTTY, - * displayed in the same terminal as the subsequent session), or - * by doing something entirely different (e.g. directly - * interacting with standard I/O, or putting up a dialog box). + * prompts are provided in 'p'. * * A positive return value means that all prompts have had answers * filled in. A zero return means that the user performed a @@ -939,7 +949,7 @@ struct SeatVtable { * ever do want to move password prompts into a dialog box, I'll * want a backend method for sending that notification.) */ - int (*get_userpass_input)(Seat *seat, prompts_t *p, bufchain *input); + int (*get_userpass_input)(Seat *seat, prompts_t *p); /* * Notify the seat that the main session channel has been @@ -1155,9 +1165,8 @@ static inline bool seat_eof(Seat *seat) { return seat->vt->eof(seat); } static inline void seat_sent(Seat *seat, size_t bufsize) { seat->vt->sent(seat, bufsize); } -static inline int seat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input) -{ return seat->vt->get_userpass_input(seat, p, input); } +static inline int seat_get_userpass_input(Seat *seat, prompts_t *p) +{ return seat->vt->get_userpass_input(seat, p); } static inline void seat_notify_session_started(Seat *seat) { seat->vt->notify_session_started(seat); } static inline void seat_notify_remote_exit(Seat *seat) @@ -1233,7 +1242,7 @@ size_t nullseat_output( Seat *seat, bool is_stderr, const void *data, size_t len); bool nullseat_eof(Seat *seat); void nullseat_sent(Seat *seat, size_t bufsize); -int nullseat_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); +int nullseat_get_userpass_input(Seat *seat, prompts_t *p); void nullseat_notify_session_started(Seat *seat); void nullseat_notify_remote_exit(Seat *seat); void nullseat_notify_remote_disconnect(Seat *seat); @@ -1292,7 +1301,7 @@ bool console_can_set_trust_status(Seat *seat); /* * Other centralised seat functions. */ -int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); +int filexfer_get_userpass_input(Seat *seat, prompts_t *p); bool cmdline_seat_verbose(Seat *seat); /* @@ -1888,7 +1897,7 @@ void term_provide_backend(Terminal *term, Backend *backend); void term_provide_logctx(Terminal *term, LogContext *logctx); void term_set_focus(Terminal *term, bool has_focus); char *term_get_ttymode(Terminal *term, const char *mode); -int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input); +int term_get_userpass_input(Terminal *term, prompts_t *p); void term_set_trust_status(Terminal *term, bool trusted); void term_keyinput(Terminal *, int codepage, const void *buf, int len); void term_keyinputw(Terminal *, const wchar_t * widebuf, int len); @@ -2055,6 +2064,28 @@ void ldisc_configure(Ldisc *, Conf *); void ldisc_free(Ldisc *); void ldisc_send(Ldisc *, const void *buf, int len, bool interactive); void ldisc_echoedit_update(Ldisc *); +typedef struct LdiscInputToken { + /* + * Structure that encodes any single item of data that Ldisc can + * buffer: either a single character of raw data, or a session + * special. + */ + bool is_special; + union { + struct { + /* if is_special == false */ + char chr; + }; + struct { + /* if is_special == true */ + SessionSpecialCode code; + int arg; + }; + }; +} LdiscInputToken; +bool ldisc_has_input_buffered(Ldisc *); +LdiscInputToken ldisc_get_input_token(Ldisc *); /* asserts there is input */ +void ldisc_enable_prompt_callback(Ldisc *, prompts_t *); void ldisc_check_sendok(Ldisc *); /* @@ -2472,7 +2503,6 @@ unsigned long timing_last_clock(void); * loop, as in PSFTP, for example - if a callback has run then perhaps * it might have done whatever the loop's caller was waiting for. */ -typedef void (*toplevel_callback_fn_t)(void *ctx); void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx); bool run_toplevel_callbacks(void); bool toplevel_callback_pending(void); diff --git a/ssh/common.c b/ssh/common.c index e421d90e..b6391729 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -737,6 +737,19 @@ size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl) return ppl->out_pq->pqb.total_size; } +static void ssh_ppl_prompts_callback(void *ctx) +{ + ssh_ppl_process_queue((PacketProtocolLayer *)ctx); +} + +prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl) +{ + prompts_t *p = new_prompts(); + p->callback = ssh_ppl_prompts_callback; + p->callback_ctx = ppl; + return p; +} + /* ---------------------------------------------------------------------- * Common helper functions for clients and implementations of * BinaryPacketProtocol. diff --git a/ssh/connection1.c b/ssh/connection1.c index 071e0139..bff09f51 100644 --- a/ssh/connection1.c +++ b/ssh/connection1.c @@ -370,7 +370,7 @@ static void ssh1_connection_process_queue(PacketProtocolLayer *ppl) * connection-sharing downstream). */ if (ssh1_connection_need_antispoof_prompt(s)) { - s->antispoof_prompt = new_prompts(); + s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl); s->antispoof_prompt->to_server = true; s->antispoof_prompt->from_server = false; s->antispoof_prompt->name = dupstr("Authentication successful"); @@ -378,19 +378,11 @@ static void ssh1_connection_process_queue(PacketProtocolLayer *ppl) s->antispoof_prompt, dupstr("Access granted. Press Return to begin session. "), false); s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, NULL); - while (1) { - while (s->antispoof_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); - - if (s->antispoof_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->antispoof_prompt); + while (s->antispoof_ret < 0) { crReturnV; - s->want_user_input = false; + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt); } free_prompts(s->antispoof_prompt); s->antispoof_prompt = NULL; diff --git a/ssh/connection2.c b/ssh/connection2.c index 86e26f4b..df542f63 100644 --- a/ssh/connection2.c +++ b/ssh/connection2.c @@ -982,7 +982,7 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) * connection-sharing downstream). */ if (ssh2_connection_need_antispoof_prompt(s)) { - s->antispoof_prompt = new_prompts(); + s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl); s->antispoof_prompt->to_server = true; s->antispoof_prompt->from_server = false; s->antispoof_prompt->name = dupstr("Authentication successful"); @@ -990,19 +990,11 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) s->antispoof_prompt, dupstr("Access granted. Press Return to begin session. "), false); s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, NULL); - while (1) { - while (s->antispoof_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); - - if (s->antispoof_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->antispoof_prompt); + while (s->antispoof_ret < 0) { crReturnV; - s->want_user_input = false; + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt); } free_prompts(s->antispoof_prompt); s->antispoof_prompt = NULL; diff --git a/ssh/login1.c b/ssh/login1.c index e0230d81..a700e02a 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -404,25 +404,16 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) ppl_logevent("Successfully started encryption"); if ((s->username = get_remote_username(s->conf)) == NULL) { - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH login name"); add_prompt(s->cur_prompt, dupstr("login as: "), true); - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->userpass_ret = seat_get_userpass_input(s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* @@ -707,7 +698,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) ppl_printf("No passphrase required.\r\n"); passphrase = NULL; } else { - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = false; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH key passphrase"); @@ -715,19 +706,11 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) dupprintf("Passphrase for key \"%s\": ", s->publickey_comment), false); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* Failed to get a passphrase. Terminate. */ @@ -846,7 +829,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) /* * Otherwise, try various forms of password-like authentication. */ - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); if (conf_get_bool(s->conf, CONF_try_tis_auth) && (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && @@ -977,20 +960,11 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) * or CryptoCard exchange if we're doing TIS or CryptoCard * authentication. */ - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->userpass_ret = seat_get_userpass_input(s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* diff --git a/ssh/ppl.h b/ssh/ppl.h index cd3e4694..5ba1e9d3 100644 --- a/ssh/ppl.h +++ b/ssh/ppl.h @@ -154,6 +154,11 @@ void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr); * be handled by ssh2connection. */ bool ssh2_common_filter_queue(PacketProtocolLayer *ppl); +/* Method for making a prompts_t in such a way that it will install a + * callback that causes this PPL's process_queue method to be called + * when asynchronous prompt input completes. */ +prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl); + /* Methods for ssh1login to pass protocol flags to ssh1connection */ void ssh1_connection_set_protoflags( PacketProtocolLayer *ppl, int local, int remote); diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 0fa1df06..51872ebf 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -442,25 +442,17 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * it again. */ } else if ((s->username = s->default_username) == NULL) { - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH login name"); add_prompt(s->cur_prompt, dupstr("login as: "), true); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* @@ -913,7 +905,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) /* * Get a passphrase from the user. */ - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = false; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH key passphrase"); @@ -922,20 +914,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->publickey_comment), false); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, - s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* Failed to get a passphrase. Terminate. */ @@ -1304,7 +1287,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) name = get_string(pktin); inst = get_string(pktin); get_string(pktin); /* skip language tag */ - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = true; @@ -1408,19 +1391,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * user's response(s). */ s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* @@ -1486,7 +1461,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD; - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH password"); @@ -1495,19 +1470,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) false); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* @@ -1581,7 +1548,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) prompt = get_string(pktin); - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("New SSH password"); @@ -1613,20 +1580,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ while (!got_new) { s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, - s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* diff --git a/sshproxy.c b/sshproxy.c index 433585ad..3d9665eb 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -276,8 +276,7 @@ static void sshproxy_notify_remote_disconnect(Seat *seat) sshproxy_eof(seat); } -static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p, - bufchain *input) +static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) { /* * TODO: if we had access to the outer Seat, we could pass on this diff --git a/terminal.c b/terminal.c index 663e0f02..e94e3634 100644 --- a/terminal.c +++ b/terminal.c @@ -7519,18 +7519,45 @@ static inline void term_write(Terminal *term, ptrlen data) term_data(term, false, data.ptr, data.len); } +/* + * Signal that a prompts_t is done. This involves sending a + * notification to the caller, and also turning off our own callback + * that listens for more data arriving in the ldisc's input queue. + */ +static inline int signal_prompts_t(Terminal *term, prompts_t *p, int result) +{ + assert(p->callback && "Asynchronous userpass input requires a callback"); + queue_toplevel_callback(p->callback, p->callback_ctx); + ldisc_enable_prompt_callback(term->ldisc, NULL); + p->idata = result; + return result; +} + /* * Process some terminal data in the course of username/password * input. */ -int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) +int term_get_userpass_input(Terminal *term, prompts_t *p) { + if (!term->ldisc) { + /* Can't handle interactive prompts without an ldisc */ + return signal_prompts_t(term, p, 0); + } + + if (p->idata >= 0) { + /* We've already finished these prompts, so return the same + * result again */ + return p->idata; + } + struct term_userpass_state *s = (struct term_userpass_state *)p->data; + if (!s) { /* * First call. Set some stuff up. */ p->data = s = snew(struct term_userpass_state); + p->idata = -1; s->curr_prompt = 0; s->done_prompt = false; /* We only print the `name' caption if we have to... */ @@ -7569,12 +7596,26 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) /* Breaking out here ensures that the prompt is printed even * if we're now waiting for user data. */ - if (!input || !bufchain_size(input)) break; + if (!ldisc_has_input_buffered(term->ldisc)) + break; /* FIXME: should we be using local-line-editing code instead? */ - while (!finished_prompt && bufchain_size(input) > 0) { + while (!finished_prompt && ldisc_has_input_buffered(term->ldisc)) { + LdiscInputToken tok = ldisc_get_input_token(term->ldisc); + char c; - bufchain_fetch_consume(input, &c, 1); + if (tok.is_special) { + switch (tok.code) { + case SS_EOL: c = 13; break; + case SS_EC: c = 8; break; + case SS_IP: c = 3; break; + case SS_EOF: c = 3; break; + default: continue; + } + } else { + c = tok.chr; + } + switch (c) { case 10: case 13: @@ -7606,7 +7647,7 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) term_write(term, PTRLEN_LITERAL("\r\n")); sfree(s); p->data = NULL; - return 0; /* user abort */ + return signal_prompts_t(term, p, 0); /* user abort */ default: /* * This simplistic check for printability is disabled @@ -7626,11 +7667,12 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) } if (s->curr_prompt < p->n_prompts) { + ldisc_enable_prompt_callback(term->ldisc, p); return -1; /* more data required */ } else { sfree(s); p->data = NULL; - return +1; /* all done */ + return signal_prompts_t(term, p, +1); /* all done */ } } diff --git a/unix/plink.c b/unix/plink.c index f8acd5ec..46f2da85 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -370,7 +370,7 @@ static bool plink_eof(Seat *seat) return false; /* do not respond to incoming EOF with outgoing */ } -static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +static int plink_get_userpass_input(Seat *seat, prompts_t *p) { int ret; ret = cmdline_get_passwd_input(p); diff --git a/unix/sftp.c b/unix/sftp.c index 89a81c92..331cbc70 100644 --- a/unix/sftp.c +++ b/unix/sftp.c @@ -63,7 +63,7 @@ Filename *platform_default_filename(const char *name) return filename_from_str(""); } -int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +int filexfer_get_userpass_input(Seat *seat, prompts_t *p) { int ret; ret = cmdline_get_passwd_input(p); diff --git a/unix/window.c b/unix/window.c index cb6e831d..e1e36fa5 100644 --- a/unix/window.c +++ b/unix/window.c @@ -331,14 +331,13 @@ static bool gtk_seat_eof(Seat *seat) return true; /* do respond to incoming EOF with outgoing */ } -static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p, - bufchain *input) +static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p) { GtkFrontend *inst = container_of(seat, GtkFrontend, seat); int ret; ret = cmdline_get_passwd_input(p); if (ret == -1) - ret = term_get_userpass_input(inst->term, p, input); + ret = term_get_userpass_input(inst->term, p); return ret; } diff --git a/utils/nullseat.c b/utils/nullseat.c index 907d9176..564225e5 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -8,8 +8,7 @@ size_t nullseat_output( Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } bool nullseat_eof(Seat *seat) { return true; } void nullseat_sent(Seat *seat, size_t bufsize) {} -int nullseat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input) { return 0; } +int nullseat_get_userpass_input(Seat *seat, prompts_t *p) { return 0; } void nullseat_notify_session_started(Seat *seat) {} void nullseat_notify_remote_exit(Seat *seat) {} void nullseat_notify_remote_disconnect(Seat *seat) {} diff --git a/utils/prompts.c b/utils/prompts.c index f37bde59..8784130d 100644 --- a/utils/prompts.c +++ b/utils/prompts.c @@ -14,6 +14,8 @@ prompts_t *new_prompts(void) p->to_server = true; /* to be on the safe side */ p->name = p->instruction = NULL; p->name_reqd = p->instr_reqd = false; + p->callback = NULL; + p->callback_ctx = NULL; return p; } diff --git a/utils/tempseat.c b/utils/tempseat.c index 22cf642c..aa3fbe5a 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -177,8 +177,7 @@ static bool tempseat_can_set_trust_status(Seat *seat) * for the network connection. */ -static int tempseat_get_userpass_input(Seat *seat, prompts_t *p, - bufchain *input) +static int tempseat_get_userpass_input(Seat *seat, prompts_t *p) { /* * Interactive prompts of this nature are a thing that a backend diff --git a/windows/plink.c b/windows/plink.c index 1587cbd3..bbed994d 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -64,7 +64,7 @@ static bool plink_eof(Seat *seat) return false; /* do not respond to incoming EOF with outgoing */ } -static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +static int plink_get_userpass_input(Seat *seat, prompts_t *p) { int ret; ret = cmdline_get_passwd_input(p); diff --git a/windows/sftp.c b/windows/sftp.c index e316f8f8..b2e90faa 100644 --- a/windows/sftp.c +++ b/windows/sftp.c @@ -12,7 +12,7 @@ #include "ssh.h" #include "security-api.h" -int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +int filexfer_get_userpass_input(Seat *seat, prompts_t *p) { int ret; ret = cmdline_get_passwd_input(p); diff --git a/windows/window.c b/windows/window.c index 6c288600..5aed396c 100644 --- a/windows/window.c +++ b/windows/window.c @@ -317,8 +317,7 @@ static StripCtrlChars *win_seat_stripctrl_new( static size_t win_seat_output( Seat *seat, bool is_stderr, const void *, size_t); static bool win_seat_eof(Seat *seat); -static int win_seat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input); +static int win_seat_get_userpass_input(Seat *seat, prompts_t *p); static void win_seat_notify_remote_exit(Seat *seat); static void win_seat_connection_fatal(Seat *seat, const char *msg); static void win_seat_update_specials_menu(Seat *seat); @@ -5746,13 +5745,12 @@ static bool win_seat_eof(Seat *seat) return true; /* do respond to incoming EOF with outgoing */ } -static int win_seat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input) +static int win_seat_get_userpass_input(Seat *seat, prompts_t *p) { int ret; ret = cmdline_get_passwd_input(p); if (ret == -1) - ret = term_get_userpass_input(term, p, input); + ret = term_get_userpass_input(term, p); return ret; }