telnet.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. /*
  2. * "Telnet" proxy negotiation.
  3. *
  4. * (This is for ad-hoc proxies where you connect to the proxy's
  5. * telnet port and send a command such as `connect host port'. The
  6. * command is configurable, since this proxy type is typically not
  7. * standardised or at all well-defined.)
  8. */
  9. #include "putty.h"
  10. #include "network.h"
  11. #include "proxy.h"
  12. #include "sshcr.h"
  13. char *format_telnet_command(SockAddr *addr, int port, Conf *conf,
  14. unsigned *flags_out)
  15. {
  16. char *fmt = conf_get_str(conf, CONF_proxy_telnet_command);
  17. int so = 0, eo = 0;
  18. strbuf *buf = strbuf_new();
  19. unsigned flags = 0;
  20. /* we need to escape \\, \%, \r, \n, \t, \x??, \0???,
  21. * %%, %host, %port, %user, and %pass
  22. */
  23. while (fmt[eo] != 0) {
  24. /* scan forward until we hit end-of-line,
  25. * or an escape character (\ or %) */
  26. while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\')
  27. eo++;
  28. /* if we hit eol, break out of our escaping loop */
  29. if (fmt[eo] == 0) break;
  30. /* if there was any unescaped text before the escape
  31. * character, send that now */
  32. if (eo != so)
  33. put_data(buf, fmt + so, eo - so);
  34. so = eo++;
  35. /* if the escape character was the last character of
  36. * the line, we'll just stop and send it. */
  37. if (fmt[eo] == 0) break;
  38. if (fmt[so] == '\\') {
  39. /* we recognize \\, \%, \r, \n, \t, \x??.
  40. * anything else, we just send unescaped (including the \).
  41. */
  42. switch (fmt[eo]) {
  43. case '\\':
  44. put_byte(buf, '\\');
  45. eo++;
  46. break;
  47. case '%':
  48. put_byte(buf, '%');
  49. eo++;
  50. break;
  51. case 'r':
  52. put_byte(buf, '\r');
  53. eo++;
  54. break;
  55. case 'n':
  56. put_byte(buf, '\n');
  57. eo++;
  58. break;
  59. case 't':
  60. put_byte(buf, '\t');
  61. eo++;
  62. break;
  63. case 'x':
  64. case 'X': {
  65. /* escaped hexadecimal value (ie. \xff) */
  66. unsigned char v = 0;
  67. int i = 0;
  68. for (;;) {
  69. eo++;
  70. if (fmt[eo] >= '0' && fmt[eo] <= '9')
  71. v += fmt[eo] - '0';
  72. else if (fmt[eo] >= 'a' && fmt[eo] <= 'f')
  73. v += fmt[eo] - 'a' + 10;
  74. else if (fmt[eo] >= 'A' && fmt[eo] <= 'F')
  75. v += fmt[eo] - 'A' + 10;
  76. else {
  77. /* non hex character, so we abort and just
  78. * send the whole thing unescaped (including \x)
  79. */
  80. put_byte(buf, '\\');
  81. eo = so + 1;
  82. break;
  83. }
  84. /* we only extract two hex characters */
  85. if (i == 1) {
  86. put_byte(buf, v);
  87. eo++;
  88. break;
  89. }
  90. i++;
  91. v <<= 4;
  92. }
  93. break;
  94. }
  95. default:
  96. put_data(buf, fmt + so, 2);
  97. eo++;
  98. break;
  99. }
  100. } else {
  101. /* % escape. we recognize %%, %host, %port, %user, %pass.
  102. * %proxyhost, %proxyport. Anything else we just send
  103. * unescaped (including the %).
  104. */
  105. if (fmt[eo] == '%') {
  106. put_byte(buf, '%');
  107. eo++;
  108. }
  109. else if (strnicmp(fmt + eo, "host", 4) == 0) {
  110. char dest[512];
  111. sk_getaddr(addr, dest, lenof(dest));
  112. put_data(buf, dest, strlen(dest));
  113. eo += 4;
  114. }
  115. else if (strnicmp(fmt + eo, "port", 4) == 0) {
  116. put_fmt(buf, "%d", port);
  117. eo += 4;
  118. }
  119. else if (strnicmp(fmt + eo, "user", 4) == 0) {
  120. const char *username = conf_get_str(conf, CONF_proxy_username);
  121. put_data(buf, username, strlen(username));
  122. eo += 4;
  123. if (!*username)
  124. flags |= TELNET_CMD_MISSING_USERNAME;
  125. }
  126. else if (strnicmp(fmt + eo, "pass", 4) == 0) {
  127. const char *password = conf_get_str(conf, CONF_proxy_password);
  128. put_data(buf, password, strlen(password));
  129. eo += 4;
  130. if (!*password)
  131. flags |= TELNET_CMD_MISSING_PASSWORD;
  132. }
  133. else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) {
  134. const char *host = conf_get_str(conf, CONF_proxy_host);
  135. put_data(buf, host, strlen(host));
  136. eo += 9;
  137. }
  138. else if (strnicmp(fmt + eo, "proxyport", 9) == 0) {
  139. int port = conf_get_int(conf, CONF_proxy_port);
  140. put_fmt(buf, "%d", port);
  141. eo += 9;
  142. }
  143. else {
  144. /* we don't escape this, so send the % now, and
  145. * don't advance eo, so that we'll consider the
  146. * text immediately following the % as unescaped.
  147. */
  148. put_byte(buf, '%');
  149. }
  150. }
  151. /* resume scanning for additional escapes after this one. */
  152. so = eo;
  153. }
  154. /* if there is any unescaped text at the end of the line, send it */
  155. if (eo != so) {
  156. put_data(buf, fmt + so, eo - so);
  157. }
  158. if (flags_out)
  159. *flags_out = flags;
  160. return strbuf_to_str(buf);
  161. }
  162. typedef struct TelnetProxyNegotiator {
  163. int crLine;
  164. Conf *conf;
  165. char *formatted_cmd;
  166. prompts_t *prompts;
  167. int username_prompt_index, password_prompt_index;
  168. ProxyNegotiator pn;
  169. } TelnetProxyNegotiator;
  170. static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt)
  171. {
  172. TelnetProxyNegotiator *s = snew(TelnetProxyNegotiator);
  173. memset(s, 0, sizeof(*s));
  174. s->pn.vt = vt;
  175. return &s->pn;
  176. }
  177. static void proxy_telnet_free(ProxyNegotiator *pn)
  178. {
  179. TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn);
  180. if (s->conf)
  181. conf_free(s->conf);
  182. if (s->prompts)
  183. free_prompts(s->prompts);
  184. burnstr(s->formatted_cmd);
  185. delete_callbacks_for_context(s);
  186. sfree(s);
  187. }
  188. static void proxy_telnet_process_queue_callback(void *vctx)
  189. {
  190. TelnetProxyNegotiator *s = (TelnetProxyNegotiator *)vctx;
  191. proxy_negotiator_process_queue(&s->pn);
  192. }
  193. static void proxy_telnet_process_queue(ProxyNegotiator *pn)
  194. {
  195. TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn);
  196. crBegin(s->crLine);
  197. s->conf = conf_copy(pn->ps->conf);
  198. /*
  199. * Make an initial attempt to figure out the command we want, and
  200. * see if it tried to include a username or password that we don't
  201. * have.
  202. */
  203. {
  204. unsigned flags;
  205. s->formatted_cmd = format_telnet_command(
  206. pn->ps->remote_addr, pn->ps->remote_port, s->conf, &flags);
  207. if (pn->itr && (flags & (TELNET_CMD_MISSING_USERNAME |
  208. TELNET_CMD_MISSING_PASSWORD))) {
  209. burnstr(s->formatted_cmd);
  210. s->formatted_cmd = NULL;
  211. /*
  212. * We're missing at least one of the two parts, and we
  213. * have an Interactor we can use to prompt for them, so
  214. * try it.
  215. */
  216. s->prompts = proxy_new_prompts(pn->ps);
  217. s->prompts->to_server = true;
  218. s->prompts->from_server = false;
  219. s->prompts->name = dupstr("Telnet proxy authentication");
  220. if (flags & TELNET_CMD_MISSING_USERNAME) {
  221. s->username_prompt_index = s->prompts->n_prompts;
  222. add_prompt(s->prompts, dupstr("Proxy username: "), true);
  223. } else {
  224. s->username_prompt_index = -1;
  225. }
  226. if (flags & TELNET_CMD_MISSING_PASSWORD) {
  227. s->password_prompt_index = s->prompts->n_prompts;
  228. add_prompt(s->prompts, dupstr("Proxy password: "), false);
  229. } else {
  230. s->password_prompt_index = -1;
  231. }
  232. /*
  233. * This prompt is presented extremely early in PuTTY's
  234. * setup. (Very promptly, you might say.)
  235. *
  236. * In particular, we can get here through a chain of
  237. * synchronous calls from backend_init, which means (in
  238. * GUI PuTTY) that the terminal we'll be sending this
  239. * prompt to may not have its Ldisc set up yet (due to
  240. * cyclic dependencies among all the things that have to
  241. * be initialised).
  242. *
  243. * So we'll start by having ourself called back via a
  244. * toplevel callback, to make sure we don't call
  245. * seat_get_userpass_input until we've returned from
  246. * backend_init and the frontend has finished getting
  247. * everything ready.
  248. */
  249. queue_toplevel_callback(proxy_telnet_process_queue_callback, s);
  250. crReturnV;
  251. while (true) {
  252. SeatPromptResult spr = seat_get_userpass_input(
  253. interactor_announce(pn->itr), s->prompts);
  254. if (spr.kind == SPRK_OK) {
  255. break;
  256. } else if (spr_is_abort(spr)) {
  257. proxy_spr_abort(pn, spr);
  258. crStopV;
  259. }
  260. crReturnV;
  261. }
  262. if (s->username_prompt_index != -1) {
  263. conf_set_str(
  264. s->conf, CONF_proxy_username,
  265. prompt_get_result_ref(
  266. s->prompts->prompts[s->username_prompt_index]));
  267. }
  268. if (s->password_prompt_index != -1) {
  269. conf_set_str(
  270. s->conf, CONF_proxy_password,
  271. prompt_get_result_ref(
  272. s->prompts->prompts[s->password_prompt_index]));
  273. }
  274. free_prompts(s->prompts);
  275. s->prompts = NULL;
  276. }
  277. /*
  278. * Now format the command a second time, with the results of
  279. * those prompts written into s->conf.
  280. */
  281. s->formatted_cmd = format_telnet_command(
  282. pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL);
  283. }
  284. /*
  285. * Log the command, with some changes. Firstly, we regenerate it
  286. * with the password masked; secondly, we escape control
  287. * characters so that the log message is printable.
  288. */
  289. conf_set_str(s->conf, CONF_proxy_password, "*password*");
  290. {
  291. char *censored_cmd = format_telnet_command(
  292. pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL);
  293. strbuf *logmsg = strbuf_new();
  294. put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: "));
  295. put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd));
  296. plug_log(pn->ps->plug, &pn->ps->sock, PLUGLOG_PROXY_MSG, NULL, 0,
  297. logmsg->s, 0);
  298. strbuf_free(logmsg);
  299. sfree(censored_cmd);
  300. }
  301. /*
  302. * Actually send the command.
  303. */
  304. put_dataz(pn->output, s->formatted_cmd);
  305. /*
  306. * Unconditionally report success. We don't hang around waiting
  307. * for error messages from the proxy, because this proxy type is
  308. * so ad-hoc that we wouldn't know how to even recognise an error
  309. * message if we saw one, let alone what to do about it.
  310. */
  311. pn->done = true;
  312. crFinishV;
  313. }
  314. const struct ProxyNegotiatorVT telnet_proxy_negotiator_vt = {
  315. .new = proxy_telnet_new,
  316. .free = proxy_telnet_free,
  317. .process_queue = proxy_telnet_process_queue,
  318. .type = "Telnet",
  319. };