tempseat.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /*
  2. * Implementation of the Seat trait that buffers output and other
  3. * events until it can give them back to a real Seat.
  4. *
  5. * This is used by the SSH proxying code, which temporarily takes over
  6. * the real user-facing Seat so that it can issue host key warnings,
  7. * password prompts etc for the proxy SSH connection. While it's got
  8. * the real Seat, it gives the primary connection's backend one of
  9. * these temporary Seats in the interim, so that if the backend wants
  10. * to send some kind of initial output, or start by reconfiguring the
  11. * trust status, or what have you, then it can do that without having
  12. * to keep careful track of the fact that its Seat is out on loan.
  13. */
  14. #include "putty.h"
  15. struct output_chunk {
  16. struct output_chunk *next;
  17. SeatOutputType type;
  18. size_t size;
  19. };
  20. typedef struct TempSeat TempSeat;
  21. struct TempSeat {
  22. Seat *realseat;
  23. /*
  24. * Single bufchain to hold all the buffered output, regardless of
  25. * its type.
  26. */
  27. bufchain output;
  28. /*
  29. * List of pieces of that bufchain that are intended for one or
  30. * another output destination
  31. */
  32. struct output_chunk *outchunk_head, *outchunk_tail;
  33. bool seen_session_started;
  34. bool seen_remote_exit;
  35. bool seen_remote_disconnect;
  36. bool seen_update_specials_menu;
  37. bool seen_echoedit_update, echoing, editing;
  38. bool seen_trust_status, trusted;
  39. Seat seat;
  40. };
  41. /* ----------------------------------------------------------------------
  42. * Methods we can usefully buffer, and pass their results on to the
  43. * real Seat in tempseat_flush().
  44. */
  45. static size_t tempseat_output(Seat *seat, SeatOutputType type,
  46. const void *data, size_t len)
  47. {
  48. TempSeat *ts = container_of(seat, TempSeat, seat);
  49. bufchain_add(&ts->output, data, len);
  50. if (!(ts->outchunk_tail && ts->outchunk_tail->type == type)) {
  51. struct output_chunk *new_chunk = snew(struct output_chunk);
  52. new_chunk->type = type;
  53. new_chunk->size = 0;
  54. new_chunk->next = NULL;
  55. if (ts->outchunk_tail)
  56. ts->outchunk_tail->next = new_chunk;
  57. else
  58. ts->outchunk_head = new_chunk;
  59. ts->outchunk_tail = new_chunk;
  60. }
  61. ts->outchunk_tail->size += len;
  62. return bufchain_size(&ts->output);
  63. }
  64. static void tempseat_notify_session_started(Seat *seat)
  65. {
  66. TempSeat *ts = container_of(seat, TempSeat, seat);
  67. ts->seen_session_started = true;
  68. }
  69. static void tempseat_notify_remote_exit(Seat *seat)
  70. {
  71. TempSeat *ts = container_of(seat, TempSeat, seat);
  72. ts->seen_remote_exit = true;
  73. }
  74. static void tempseat_notify_remote_disconnect(Seat *seat)
  75. {
  76. TempSeat *ts = container_of(seat, TempSeat, seat);
  77. ts->seen_remote_disconnect = true;
  78. }
  79. static void tempseat_update_specials_menu(Seat *seat)
  80. {
  81. TempSeat *ts = container_of(seat, TempSeat, seat);
  82. ts->seen_update_specials_menu = true;
  83. }
  84. static void tempseat_echoedit_update(Seat *seat, bool echoing, bool editing)
  85. {
  86. TempSeat *ts = container_of(seat, TempSeat, seat);
  87. ts->seen_echoedit_update = true;
  88. ts->echoing = echoing;
  89. ts->editing = editing;
  90. }
  91. static void tempseat_set_trust_status(Seat *seat, bool trusted)
  92. {
  93. TempSeat *ts = container_of(seat, TempSeat, seat);
  94. ts->seen_trust_status = true;
  95. ts->trusted = trusted;
  96. }
  97. /* ----------------------------------------------------------------------
  98. * Methods we can safely pass straight on to the real Seat, usually
  99. * (but not in every case) because they're read-only queries.
  100. */
  101. static char *tempseat_get_ttymode(Seat *seat, const char *mode)
  102. {
  103. TempSeat *ts = container_of(seat, TempSeat, seat);
  104. return seat_get_ttymode(ts->realseat, mode);
  105. }
  106. static void tempseat_set_busy_status(Seat *seat, BusyStatus status)
  107. {
  108. TempSeat *ts = container_of(seat, TempSeat, seat);
  109. /*
  110. * set_busy_status is generally called when something is about to
  111. * do some single-threaded, event-loop blocking computation. This
  112. * _shouldn't_ happen in a backend while it's waiting for a
  113. * network connection to be made, but if for some reason it were
  114. * to, there's no reason we can't just pass this straight to the
  115. * real seat, because we expect that it will mark itself busy,
  116. * compute, and mark itself unbusy, all between yields to the
  117. * event loop that might give whatever else is using the real Seat
  118. * an opportunity to do anything.
  119. */
  120. seat_set_busy_status(ts->realseat, status);
  121. }
  122. static bool tempseat_is_utf8(Seat *seat)
  123. {
  124. TempSeat *ts = container_of(seat, TempSeat, seat);
  125. return seat_is_utf8(ts->realseat);
  126. }
  127. static const char *tempseat_get_x_display(Seat *seat)
  128. {
  129. TempSeat *ts = container_of(seat, TempSeat, seat);
  130. return seat_get_x_display(ts->realseat);
  131. }
  132. static bool tempseat_get_windowid(Seat *seat, long *id_out)
  133. {
  134. TempSeat *ts = container_of(seat, TempSeat, seat);
  135. return seat_get_windowid(ts->realseat, id_out);
  136. }
  137. static bool tempseat_get_window_pixel_size(Seat *seat, int *width, int *height)
  138. {
  139. TempSeat *ts = container_of(seat, TempSeat, seat);
  140. return seat_get_window_pixel_size(ts->realseat, width, height);
  141. }
  142. static StripCtrlChars *tempseat_stripctrl_new(
  143. Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
  144. {
  145. TempSeat *ts = container_of(seat, TempSeat, seat);
  146. return seat_stripctrl_new(ts->realseat, bs_out, sic);
  147. }
  148. static bool tempseat_verbose(Seat *seat)
  149. {
  150. TempSeat *ts = container_of(seat, TempSeat, seat);
  151. return seat_verbose(ts->realseat);
  152. }
  153. static bool tempseat_interactive(Seat *seat)
  154. {
  155. TempSeat *ts = container_of(seat, TempSeat, seat);
  156. return seat_interactive(ts->realseat);
  157. }
  158. static bool tempseat_get_cursor_position(Seat *seat, int *x, int *y)
  159. {
  160. TempSeat *ts = container_of(seat, TempSeat, seat);
  161. return seat_get_cursor_position(ts->realseat, x, y);
  162. }
  163. static bool tempseat_can_set_trust_status(Seat *seat)
  164. {
  165. TempSeat *ts = container_of(seat, TempSeat, seat);
  166. return seat_can_set_trust_status(ts->realseat);
  167. }
  168. static bool tempseat_has_mixed_input_stream(Seat *seat)
  169. {
  170. TempSeat *ts = container_of(seat, TempSeat, seat);
  171. return seat_has_mixed_input_stream(ts->realseat);
  172. }
  173. static const SeatDialogPromptDescriptions *tempseat_prompt_descriptions(
  174. Seat *seat)
  175. {
  176. /* It might be OK to put this in the 'unreachable' category, but I
  177. * think it's equally good to put it here, which allows for
  178. * someone _preparing_ a prompt right now that they intend to
  179. * present once the TempSeat has given way to the real one. */
  180. TempSeat *ts = container_of(seat, TempSeat, seat);
  181. return seat_prompt_descriptions(ts->realseat);
  182. }
  183. /* ----------------------------------------------------------------------
  184. * Methods that should never be called on a TempSeat, so we can put an
  185. * unreachable() in them.
  186. *
  187. * A backend in possession of a TempSeat ought to be sitting and
  188. * patiently waiting for a network connection attempt to either
  189. * succeed or fail. And it should be aware of the possibility that the
  190. * proxy setup code to which it has lent the real Seat might need to
  191. * present interactive prompts - that's the whole point of lending out
  192. * the Seat in the first place - so it absolutely shouldn't get any
  193. * ideas about issuing some kind of prompt of its own while it waits
  194. * for the network connection.
  195. */
  196. static SeatPromptResult tempseat_get_userpass_input(Seat *seat, prompts_t *p)
  197. {
  198. /*
  199. * Interactive prompts of this nature are a thing that a backend
  200. * MUST NOT do while not in possession of the real Seat, because
  201. * the whole point of temporarily lending the real Seat to
  202. * something else is that so it can have a clear field to do
  203. * interactive stuff of its own while making a network connection.
  204. */
  205. unreachable("get_userpass_input should never be called on TempSeat");
  206. }
  207. static size_t tempseat_banner(Seat *seat, const void *data, size_t len)
  208. {
  209. unreachable("banner should never be called on TempSeat");
  210. }
  211. static SeatPromptResult tempseat_confirm_ssh_host_key(
  212. Seat *seat, const char *host, int port, const char *keytype,
  213. char *keystr, SeatDialogText *text, HelpCtx helpctx,
  214. void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
  215. {
  216. unreachable("confirm_ssh_host_key should never be called on TempSeat");
  217. }
  218. static SeatPromptResult tempseat_confirm_weak_crypto_primitive(
  219. Seat *seat, SeatDialogText *text,
  220. void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
  221. {
  222. unreachable("confirm_weak_crypto_primitive "
  223. "should never be called on TempSeat");
  224. }
  225. static SeatPromptResult tempseat_confirm_weak_cached_hostkey(
  226. Seat *seat, SeatDialogText *text,
  227. void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
  228. {
  229. unreachable("confirm_weak_cached_hostkey "
  230. "should never be called on TempSeat");
  231. }
  232. static void tempseat_connection_fatal(Seat *seat, const char *message)
  233. {
  234. /*
  235. * Fatal errors are another thing a backend should not have any
  236. * reason to encounter while waiting to hear back about its
  237. * network connection setup.
  238. *
  239. * Also, if a backend _did_ call this, it would be hellish to
  240. * unpick all the error handling. Just passing on the fatal error
  241. * to the real Seat wouldn't be good enough: what about freeing
  242. * all the various things that are confusingly holding pointers to
  243. * each other? Better to leave this as an assertion-failure level
  244. * issue, so that if it does ever happen by accident, we'll know
  245. * it's a bug.
  246. */
  247. unreachable("connection_fatal should never be called on TempSeat");
  248. }
  249. static void tempseat_nonfatal(Seat *seat, const char *message)
  250. {
  251. /*
  252. * Non-fatal errors specific to a Seat should also not occur,
  253. * because those will be for things like I/O errors writing the
  254. * host key collection, and a backend's not _doing_ that when we
  255. * haven't connected it to the host yet.
  256. */
  257. unreachable("nonfatal should never be called on TempSeat");
  258. }
  259. static bool tempseat_eof(Seat *seat)
  260. {
  261. /*
  262. * EOF is _very nearly_ something that we could buffer, and pass
  263. * on to the real Seat at flush time. The only difficulty is that
  264. * sometimes the front end wants to respond to an incoming EOF by
  265. * instructing the back end to send an outgoing one, which it does
  266. * by returning a bool from its eof method.
  267. *
  268. * So we'd have to arrange that tempseat_flush caught that return
  269. * value and passed it on to the calling backend. And then every
  270. * backend would have to deal with tempseat_flush maybe returning
  271. * it an 'actually, please start closing down now' indication,
  272. * which could only happen _in theory_, if it had for some reason
  273. * called seat_eof on the TempSeat.
  274. *
  275. * But in fact, we don't expect back ends to call seat_eof on the
  276. * TempSeat in the first place, so all of that effort would be a
  277. * total waste. Hence, we'll put EOF in the category of things we
  278. * expect backends never to do while the real Seat is out on loan.
  279. */
  280. unreachable("eof should never be called on TempSeat");
  281. }
  282. /* ----------------------------------------------------------------------
  283. * Done with the TempSeat methods. Here's the vtable definition and
  284. * the main setup/teardown code.
  285. */
  286. static const struct SeatVtable tempseat_vt = {
  287. .output = tempseat_output,
  288. .eof = tempseat_eof,
  289. .sent = nullseat_sent,
  290. .banner = tempseat_banner,
  291. .get_userpass_input = tempseat_get_userpass_input,
  292. .notify_session_started = tempseat_notify_session_started,
  293. .notify_remote_exit = tempseat_notify_remote_exit,
  294. .notify_remote_disconnect = tempseat_notify_remote_disconnect,
  295. .connection_fatal = tempseat_connection_fatal,
  296. .nonfatal = tempseat_nonfatal,
  297. .update_specials_menu = tempseat_update_specials_menu,
  298. .get_ttymode = tempseat_get_ttymode,
  299. .set_busy_status = tempseat_set_busy_status,
  300. .confirm_ssh_host_key = tempseat_confirm_ssh_host_key,
  301. .confirm_weak_crypto_primitive = tempseat_confirm_weak_crypto_primitive,
  302. .confirm_weak_cached_hostkey = tempseat_confirm_weak_cached_hostkey,
  303. .prompt_descriptions = tempseat_prompt_descriptions,
  304. .is_utf8 = tempseat_is_utf8,
  305. .echoedit_update = tempseat_echoedit_update,
  306. .get_x_display = tempseat_get_x_display,
  307. .get_windowid = tempseat_get_windowid,
  308. .get_window_pixel_size = tempseat_get_window_pixel_size,
  309. .stripctrl_new = tempseat_stripctrl_new,
  310. .set_trust_status = tempseat_set_trust_status,
  311. .can_set_trust_status = tempseat_can_set_trust_status,
  312. .has_mixed_input_stream = tempseat_has_mixed_input_stream,
  313. .verbose = tempseat_verbose,
  314. .interactive = tempseat_interactive,
  315. .get_cursor_position = tempseat_get_cursor_position,
  316. };
  317. Seat *tempseat_new(Seat *realseat)
  318. {
  319. TempSeat *ts = snew(TempSeat);
  320. memset(ts, 0, sizeof(*ts));
  321. ts->seat.vt = &tempseat_vt;
  322. ts->realseat = realseat;
  323. bufchain_init(&ts->output);
  324. ts->outchunk_head = ts->outchunk_tail = NULL;
  325. return &ts->seat;
  326. }
  327. bool is_tempseat(Seat *seat)
  328. {
  329. return seat->vt == &tempseat_vt;
  330. }
  331. Seat *tempseat_get_real(Seat *seat)
  332. {
  333. assert(seat->vt == &tempseat_vt);
  334. TempSeat *ts = container_of(seat, TempSeat, seat);
  335. return ts->realseat;
  336. }
  337. void tempseat_free(Seat *seat)
  338. {
  339. assert(seat->vt == &tempseat_vt);
  340. TempSeat *ts = container_of(seat, TempSeat, seat);
  341. bufchain_clear(&ts->output);
  342. while (ts->outchunk_head) {
  343. struct output_chunk *chunk = ts->outchunk_head;
  344. ts->outchunk_head = chunk->next;
  345. sfree(chunk);
  346. }
  347. sfree(ts);
  348. }
  349. void tempseat_flush(Seat *seat)
  350. {
  351. assert(seat->vt == &tempseat_vt);
  352. TempSeat *ts = container_of(seat, TempSeat, seat);
  353. /* Empty the output bufchains into the real seat, taking care to
  354. * preserve both separation and interleaving */
  355. while (bufchain_size(&ts->output)) {
  356. ptrlen pl = bufchain_prefix(&ts->output);
  357. assert(ts->outchunk_head);
  358. struct output_chunk *chunk = ts->outchunk_head;
  359. if (pl.len > chunk->size)
  360. pl.len = chunk->size;
  361. seat_output(ts->realseat, chunk->type, pl.ptr, pl.len);
  362. bufchain_consume(&ts->output, pl.len);
  363. chunk->size -= pl.len;
  364. if (chunk->size == 0) {
  365. ts->outchunk_head = chunk->next;
  366. sfree(chunk);
  367. }
  368. }
  369. /* That should have exactly emptied the output chunk list too */
  370. assert(!ts->outchunk_head);
  371. /* Pass on any other kinds of event we've buffered */
  372. if (ts->seen_session_started)
  373. seat_notify_session_started(ts->realseat);
  374. if (ts->seen_remote_exit)
  375. seat_notify_remote_exit(ts->realseat);
  376. if (ts->seen_remote_disconnect)
  377. seat_notify_remote_disconnect(ts->realseat);
  378. if (ts->seen_update_specials_menu)
  379. seat_update_specials_menu(ts->realseat);
  380. if (ts->seen_echoedit_update)
  381. seat_echoedit_update(ts->realseat, ts->echoing, ts->editing);
  382. if (ts->seen_trust_status)
  383. seat_set_trust_status(ts->realseat, ts->trusted);
  384. }