conpty.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. /*
  2. * Backend to run a Windows console session using ConPTY.
  3. */
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <limits.h>
  7. #include "putty.h"
  8. #include <windows.h>
  9. #include <consoleapi.h>
  10. typedef struct ConPTY ConPTY;
  11. struct ConPTY {
  12. HPCON pseudoconsole;
  13. HANDLE outpipe, inpipe, hprocess;
  14. struct handle *out, *in;
  15. HandleWait *subprocess;
  16. bool exited;
  17. DWORD exitstatus;
  18. Seat *seat;
  19. LogContext *logctx;
  20. int bufsize;
  21. Backend backend;
  22. };
  23. DECL_WINDOWS_FUNCTION(static, HRESULT, CreatePseudoConsole,
  24. (COORD, HANDLE, HANDLE, DWORD, HPCON *));
  25. DECL_WINDOWS_FUNCTION(static, void, ClosePseudoConsole, (HPCON));
  26. DECL_WINDOWS_FUNCTION(static, HRESULT, ResizePseudoConsole, (HPCON, COORD));
  27. static bool init_conpty_api(void)
  28. {
  29. static bool tried = false;
  30. if (!tried) {
  31. tried = true;
  32. HMODULE kernel32_module = load_system32_dll("kernel32.dll");
  33. GET_WINDOWS_FUNCTION(kernel32_module, CreatePseudoConsole);
  34. GET_WINDOWS_FUNCTION(kernel32_module, ClosePseudoConsole);
  35. GET_WINDOWS_FUNCTION(kernel32_module, ResizePseudoConsole);
  36. }
  37. return (p_CreatePseudoConsole != NULL &&
  38. p_ClosePseudoConsole != NULL &&
  39. p_ResizePseudoConsole != NULL);
  40. }
  41. static void conpty_terminate(ConPTY *conpty)
  42. {
  43. if (conpty->out) {
  44. handle_free(conpty->out);
  45. conpty->out = NULL;
  46. }
  47. if (conpty->outpipe != INVALID_HANDLE_VALUE) {
  48. CloseHandle(conpty->outpipe);
  49. conpty->outpipe = INVALID_HANDLE_VALUE;
  50. }
  51. if (conpty->in) {
  52. handle_free(conpty->in);
  53. conpty->in = NULL;
  54. }
  55. if (conpty->inpipe != INVALID_HANDLE_VALUE) {
  56. CloseHandle(conpty->inpipe);
  57. conpty->inpipe = INVALID_HANDLE_VALUE;
  58. }
  59. if (conpty->subprocess) {
  60. delete_handle_wait(conpty->subprocess);
  61. conpty->subprocess = NULL;
  62. conpty->hprocess = INVALID_HANDLE_VALUE;
  63. }
  64. if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) {
  65. p_ClosePseudoConsole(conpty->pseudoconsole);
  66. conpty->pseudoconsole = INVALID_HANDLE_VALUE;
  67. }
  68. }
  69. static void conpty_process_wait_callback(void *vctx)
  70. {
  71. ConPTY *conpty = (ConPTY *)vctx;
  72. if (!GetExitCodeProcess(conpty->hprocess, &conpty->exitstatus))
  73. return;
  74. conpty->exited = true;
  75. /*
  76. * We can stop waiting for the process now.
  77. */
  78. if (conpty->subprocess) {
  79. delete_handle_wait(conpty->subprocess);
  80. conpty->subprocess = NULL;
  81. conpty->hprocess = INVALID_HANDLE_VALUE;
  82. }
  83. /*
  84. * Once the contained process exits, close the pseudo-console as
  85. * well. But don't close the pipes yet, since apparently
  86. * ClosePseudoConsole can trigger a final bout of terminal output
  87. * as things clean themselves up.
  88. */
  89. if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) {
  90. p_ClosePseudoConsole(conpty->pseudoconsole);
  91. conpty->pseudoconsole = INVALID_HANDLE_VALUE;
  92. }
  93. }
  94. static size_t conpty_gotdata(
  95. struct handle *h, const void *data, size_t len, int err)
  96. {
  97. ConPTY *conpty = (ConPTY *)handle_get_privdata(h);
  98. if (err || len == 0) {
  99. char *error_msg;
  100. conpty_terminate(conpty);
  101. seat_notify_remote_exit(conpty->seat);
  102. if (!err && conpty->exited) {
  103. /*
  104. * The clean-exit case: our subprocess terminated, we
  105. * deleted the PseudoConsole ourself, and now we got the
  106. * expected EOF on the pipe.
  107. */
  108. return 0;
  109. }
  110. if (err)
  111. error_msg = dupprintf("Error reading from console pty: %s",
  112. win_strerror(err));
  113. else
  114. error_msg = dupprintf(
  115. "Unexpected end of file reading from console pty");
  116. logevent(conpty->logctx, error_msg);
  117. seat_connection_fatal(conpty->seat, "%s", error_msg);
  118. sfree(error_msg);
  119. return 0;
  120. } else {
  121. return seat_stdout(conpty->seat, data, len);
  122. }
  123. }
  124. static void conpty_sentdata(struct handle *h, size_t new_backlog, int err,
  125. bool close)
  126. {
  127. ConPTY *conpty = (ConPTY *)handle_get_privdata(h);
  128. if (err) {
  129. const char *error_msg = "Error writing to conpty device";
  130. conpty_terminate(conpty);
  131. seat_notify_remote_exit(conpty->seat);
  132. logevent(conpty->logctx, error_msg);
  133. seat_connection_fatal(conpty->seat, "%s", error_msg);
  134. } else {
  135. conpty->bufsize = new_backlog;
  136. }
  137. }
  138. static char *conpty_init(const BackendVtable *vt, Seat *seat,
  139. Backend **backend_handle, LogContext *logctx,
  140. Conf *conf, const char *host, int port,
  141. char **realhost, bool nodelay, bool keepalive)
  142. {
  143. ConPTY *conpty;
  144. char *err = NULL;
  145. HANDLE in_r = INVALID_HANDLE_VALUE;
  146. HANDLE in_w = INVALID_HANDLE_VALUE;
  147. HANDLE out_r = INVALID_HANDLE_VALUE;
  148. HANDLE out_w = INVALID_HANDLE_VALUE;
  149. HPCON pcon;
  150. bool pcon_needs_cleanup = false;
  151. STARTUPINFOEX si;
  152. memset(&si, 0, sizeof(si));
  153. if (!init_conpty_api()) {
  154. err = dupprintf("Pseudo-console API is not available on this "
  155. "Windows system");
  156. goto out;
  157. }
  158. if (!CreatePipe(&in_r, &in_w, NULL, 0)) {
  159. err = dupprintf("CreatePipe: %s", win_strerror(GetLastError()));
  160. goto out;
  161. }
  162. if (!CreatePipe(&out_r, &out_w, NULL, 0)) {
  163. err = dupprintf("CreatePipe: %s", win_strerror(GetLastError()));
  164. goto out;
  165. }
  166. COORD size;
  167. size.X = conf_get_int(conf, CONF_width);
  168. size.Y = conf_get_int(conf, CONF_height);
  169. HRESULT result = p_CreatePseudoConsole(size, in_r, out_w, 0, &pcon);
  170. if (FAILED(result)) {
  171. if (HRESULT_FACILITY(result) == FACILITY_WIN32)
  172. err = dupprintf("CreatePseudoConsole: %s",
  173. win_strerror(HRESULT_CODE(result)));
  174. else
  175. err = dupprintf("CreatePseudoConsole failed: HRESULT=0x%08x",
  176. (unsigned)result);
  177. goto out;
  178. }
  179. pcon_needs_cleanup = true;
  180. CloseHandle(in_r);
  181. in_r = INVALID_HANDLE_VALUE;
  182. CloseHandle(out_w);
  183. out_w = INVALID_HANDLE_VALUE;
  184. si.StartupInfo.cb = sizeof(si);
  185. SIZE_T attrsize = 0;
  186. InitializeProcThreadAttributeList(NULL, 1, 0, &attrsize);
  187. si.lpAttributeList = smalloc(attrsize);
  188. if (!InitializeProcThreadAttributeList(
  189. si.lpAttributeList, 1, 0, &attrsize)) {
  190. err = dupprintf("InitializeProcThreadAttributeList: %s",
  191. win_strerror(GetLastError()));
  192. goto out;
  193. }
  194. if (!UpdateProcThreadAttribute(
  195. si.lpAttributeList,
  196. 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
  197. pcon, sizeof(pcon), NULL, NULL)) {
  198. err = dupprintf("UpdateProcThreadAttribute: %s",
  199. win_strerror(GetLastError()));
  200. goto out;
  201. }
  202. PROCESS_INFORMATION pi;
  203. memset(&pi, 0, sizeof(pi));
  204. char *command;
  205. const char *conf_cmd = conf_get_str(conf, CONF_remote_cmd);
  206. if (*conf_cmd) {
  207. command = dupstr(conf_cmd);
  208. } else {
  209. command = dupcat(get_system_dir(), "\\cmd.exe");
  210. }
  211. bool created_ok = CreateProcess(NULL, command, NULL, NULL,
  212. false, EXTENDED_STARTUPINFO_PRESENT,
  213. NULL, NULL, &si.StartupInfo, &pi);
  214. sfree(command);
  215. if (!created_ok) {
  216. err = dupprintf("CreateProcess: %s",
  217. win_strerror(GetLastError()));
  218. goto out;
  219. }
  220. /* No local authentication phase in this protocol */
  221. seat_set_trust_status(seat, false);
  222. conpty = snew(ConPTY);
  223. memset(conpty, 0, sizeof(ConPTY));
  224. conpty->pseudoconsole = pcon;
  225. pcon_needs_cleanup = false;
  226. conpty->outpipe = in_w;
  227. conpty->out = handle_output_new(in_w, conpty_sentdata, conpty, 0);
  228. in_w = INVALID_HANDLE_VALUE;
  229. conpty->inpipe = out_r;
  230. conpty->in = handle_input_new(out_r, conpty_gotdata, conpty, 0);
  231. out_r = INVALID_HANDLE_VALUE;
  232. conpty->subprocess = add_handle_wait(
  233. pi.hProcess, conpty_process_wait_callback, conpty);
  234. conpty->hprocess = pi.hProcess;
  235. CloseHandle(pi.hThread);
  236. conpty->exited = false;
  237. conpty->exitstatus = 0;
  238. conpty->bufsize = 0;
  239. conpty->backend.vt = vt;
  240. *backend_handle = &conpty->backend;
  241. conpty->seat = seat;
  242. conpty->logctx = logctx;
  243. *realhost = dupstr("");
  244. /*
  245. * Specials are always available.
  246. */
  247. seat_update_specials_menu(conpty->seat);
  248. out:
  249. if (in_r != INVALID_HANDLE_VALUE)
  250. CloseHandle(in_r);
  251. if (in_w != INVALID_HANDLE_VALUE)
  252. CloseHandle(in_w);
  253. if (out_r != INVALID_HANDLE_VALUE)
  254. CloseHandle(out_r);
  255. if (out_w != INVALID_HANDLE_VALUE)
  256. CloseHandle(out_w);
  257. if (pcon_needs_cleanup)
  258. p_ClosePseudoConsole(pcon);
  259. sfree(si.lpAttributeList);
  260. return err;
  261. }
  262. static void conpty_free(Backend *be)
  263. {
  264. ConPTY *conpty = container_of(be, ConPTY, backend);
  265. conpty_terminate(conpty);
  266. expire_timer_context(conpty);
  267. sfree(conpty);
  268. }
  269. static void conpty_reconfig(Backend *be, Conf *conf)
  270. {
  271. }
  272. static void conpty_send(Backend *be, const char *buf, size_t len)
  273. {
  274. ConPTY *conpty = container_of(be, ConPTY, backend);
  275. if (conpty->out == NULL)
  276. return;
  277. conpty->bufsize = handle_write(conpty->out, buf, len);
  278. }
  279. static size_t conpty_sendbuffer(Backend *be)
  280. {
  281. ConPTY *conpty = container_of(be, ConPTY, backend);
  282. return conpty->bufsize;
  283. }
  284. static void conpty_size(Backend *be, int width, int height)
  285. {
  286. ConPTY *conpty = container_of(be, ConPTY, backend);
  287. COORD size;
  288. size.X = width;
  289. size.Y = height;
  290. p_ResizePseudoConsole(conpty->pseudoconsole, size);
  291. }
  292. static void conpty_special(Backend *be, SessionSpecialCode code, int arg)
  293. {
  294. }
  295. static const SessionSpecial *conpty_get_specials(Backend *be)
  296. {
  297. static const SessionSpecial specials[] = {
  298. {NULL, SS_EXITMENU}
  299. };
  300. return specials;
  301. }
  302. static bool conpty_connected(Backend *be)
  303. {
  304. return true; /* always connected */
  305. }
  306. static bool conpty_sendok(Backend *be)
  307. {
  308. return true;
  309. }
  310. static void conpty_unthrottle(Backend *be, size_t backlog)
  311. {
  312. ConPTY *conpty = container_of(be, ConPTY, backend);
  313. if (conpty->in)
  314. handle_unthrottle(conpty->in, backlog);
  315. }
  316. static bool conpty_ldisc(Backend *be, int option)
  317. {
  318. return false;
  319. }
  320. static void conpty_provide_ldisc(Backend *be, Ldisc *ldisc)
  321. {
  322. }
  323. static int conpty_exitcode(Backend *be)
  324. {
  325. ConPTY *conpty = container_of(be, ConPTY, backend);
  326. if (conpty->exited) {
  327. /*
  328. * PuTTY's representation of exit statuses expects them to be
  329. * non-negative 'int' values. But Windows exit statuses can
  330. * include all those exception codes like 0xC000001D which
  331. * convert to negative 32-bit ints.
  332. *
  333. * I don't think there's a great deal of use for returning
  334. * those in full detail, right now. (Though if we ever
  335. * connected this system up to a Windows version of psusan or
  336. * Uppity, perhaps there might be?)
  337. *
  338. * So we clip them at INT_MAX-1, since INT_MAX is reserved for
  339. * 'exit so unclean as to inhibit Close On Clean Exit'.
  340. */
  341. return (0 <= conpty->exitstatus && conpty->exitstatus < INT_MAX) ?
  342. conpty->exitstatus : INT_MAX-1;
  343. } else {
  344. return -1;
  345. }
  346. }
  347. static int conpty_cfg_info(Backend *be)
  348. {
  349. return 0;
  350. }
  351. const BackendVtable conpty_backend = {
  352. .init = conpty_init,
  353. .free = conpty_free,
  354. .reconfig = conpty_reconfig,
  355. .send = conpty_send,
  356. .sendbuffer = conpty_sendbuffer,
  357. .size = conpty_size,
  358. .special = conpty_special,
  359. .get_specials = conpty_get_specials,
  360. .connected = conpty_connected,
  361. .exitcode = conpty_exitcode,
  362. .sendok = conpty_sendok,
  363. .ldisc_option_state = conpty_ldisc,
  364. .provide_ldisc = conpty_provide_ldisc,
  365. .unthrottle = conpty_unthrottle,
  366. .cfg_info = conpty_cfg_info,
  367. .id = "conpty",
  368. .displayname_tc = "ConPTY",
  369. .displayname_lc = "ConPTY", /* proper name, so capitalise it anyway */
  370. .protocol = -1,
  371. };