psusan.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. /*
  2. * 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks
  3. *
  4. * This is a standalone application that speaks on its standard I/O
  5. * (or a listening Unix-domain socket) the server end of the bare
  6. * ssh-connection protocol used by PuTTY's connection sharing.
  7. *
  8. * The idea of this tool is that you can use it to communicate across
  9. * any 8-bit-clean data channel between two inconveniently separated
  10. * domains, provided the channel is already (as the name suggests)
  11. * adequately secured against eavesdropping and modification and
  12. * already authenticated as the right user.
  13. *
  14. * If you're sitting at one end of such a channel and want to type
  15. * commands into the other end, the most obvious thing to do is to run
  16. * a terminal session directly over it. But if you run psusan at one
  17. * end, and a PuTTY (or compatible) client at the other end, then you
  18. * not only get a single terminal session: you get all the other SSH
  19. * amenities, like the ability to spawn extra terminal sessions,
  20. * forward ports or X11 connections, even forward an SSH agent.
  21. *
  22. * There are a surprising number of channels of that kind; see the man
  23. * page for some examples.
  24. */
  25. #include <stdio.h>
  26. #include <stdlib.h>
  27. #include <errno.h>
  28. #include <assert.h>
  29. #include <stdarg.h>
  30. #include <signal.h>
  31. #include <unistd.h>
  32. #include <fcntl.h>
  33. #include <termios.h>
  34. #include <pwd.h>
  35. #include <sys/ioctl.h>
  36. #include <sys/time.h>
  37. #include "putty.h"
  38. #include "mpint.h"
  39. #include "ssh.h"
  40. #include "ssh/server.h"
  41. void modalfatalbox(const char *p, ...)
  42. {
  43. va_list ap;
  44. fprintf(stderr, "FATAL ERROR: ");
  45. va_start(ap, p);
  46. vfprintf(stderr, p, ap);
  47. va_end(ap);
  48. fputc('\n', stderr);
  49. exit(1);
  50. }
  51. void nonfatal(const char *p, ...)
  52. {
  53. va_list ap;
  54. fprintf(stderr, "ERROR: ");
  55. va_start(ap, p);
  56. vfprintf(stderr, p, ap);
  57. va_end(ap);
  58. fputc('\n', stderr);
  59. }
  60. char *platform_default_s(const char *name)
  61. {
  62. return NULL;
  63. }
  64. bool platform_default_b(const char *name, bool def)
  65. {
  66. return def;
  67. }
  68. int platform_default_i(const char *name, int def)
  69. {
  70. return def;
  71. }
  72. FontSpec *platform_default_fontspec(const char *name)
  73. {
  74. return fontspec_new_default();
  75. }
  76. Filename *platform_default_filename(const char *name)
  77. {
  78. return filename_from_str("");
  79. }
  80. char *x_get_default(const char *key)
  81. {
  82. return NULL; /* this is a stub */
  83. }
  84. void old_keyfile_warning(void) { }
  85. void timer_change_notify(unsigned long next)
  86. {
  87. }
  88. char *platform_get_x_display(void) { return NULL; }
  89. void make_unix_sftp_filehandle_key(void *vdata, size_t size)
  90. {
  91. /* psusan runs without a random number generator, so we can't make
  92. * this up by random_read. Fortunately, psusan is also
  93. * non-adversarial, so it's safe to generate this trivially. */
  94. unsigned char *data = (unsigned char *)vdata;
  95. for (size_t i = 0; i < size; i++)
  96. data[i] = (unsigned)rand() / ((unsigned)RAND_MAX / 256);
  97. }
  98. static bool verbose;
  99. struct server_instance {
  100. unsigned id;
  101. LogPolicy logpolicy;
  102. };
  103. static void log_to_stderr(unsigned id, const char *msg)
  104. {
  105. if (!verbose)
  106. return;
  107. if (id != (unsigned)-1)
  108. fprintf(stderr, "#%u: ", id);
  109. fputs(msg, stderr);
  110. fputc('\n', stderr);
  111. fflush(stderr);
  112. }
  113. static void server_eventlog(LogPolicy *lp, const char *event)
  114. {
  115. struct server_instance *inst = container_of(
  116. lp, struct server_instance, logpolicy);
  117. if (verbose)
  118. log_to_stderr(inst->id, event);
  119. }
  120. static void server_logging_error(LogPolicy *lp, const char *event)
  121. {
  122. struct server_instance *inst = container_of(
  123. lp, struct server_instance, logpolicy);
  124. log_to_stderr(inst->id, event); /* unconditional */
  125. }
  126. static int server_askappend(
  127. LogPolicy *lp, Filename *filename,
  128. void (*callback)(void *ctx, int result), void *ctx)
  129. {
  130. return 2; /* always overwrite (FIXME: could make this a cmdline option) */
  131. }
  132. static const LogPolicyVtable server_logpolicy_vt = {
  133. .eventlog = server_eventlog,
  134. .askappend = server_askappend,
  135. .logging_error = server_logging_error,
  136. .verbose = null_lp_verbose_no,
  137. };
  138. static void show_help(FILE *fp)
  139. {
  140. fputs("usage: psusan [options]\n"
  141. "options: --listen SOCKETPATH listen for connections on a Unix-domain socket\n"
  142. " --listen-once (with --listen) stop after one connection\n"
  143. " --verbose print log messages to standard error\n"
  144. " --sessiondir DIR cwd for session subprocess (default $HOME)\n"
  145. " --sshlog FILE write ssh-connection packet log to FILE\n"
  146. " --sshrawlog FILE write packets and raw data log to FILE\n"
  147. "also: psusan --help show this text\n"
  148. " psusan --version show version information\n", fp);
  149. }
  150. static void show_version_and_exit(void)
  151. {
  152. char *buildinfo_text = buildinfo("\n");
  153. printf("%s: %s\n%s\n", appname, ver, buildinfo_text);
  154. sfree(buildinfo_text);
  155. exit(0);
  156. }
  157. const bool buildinfo_gtk_relevant = false;
  158. static bool listening = false, listen_once = false;
  159. static bool finished = false;
  160. void server_instance_terminated(LogPolicy *lp)
  161. {
  162. struct server_instance *inst = container_of(
  163. lp, struct server_instance, logpolicy);
  164. if (listening && !listen_once) {
  165. log_to_stderr(inst->id, "connection terminated");
  166. } else {
  167. finished = true;
  168. }
  169. sfree(inst);
  170. }
  171. bool psusan_continue(void *ctx, bool fd, bool cb)
  172. {
  173. return !finished;
  174. }
  175. static bool longoptarg(const char *arg, const char *expected,
  176. const char **val, int *argcp, char ***argvp)
  177. {
  178. int len = strlen(expected);
  179. if (memcmp(arg, expected, len))
  180. return false;
  181. if (arg[len] == '=') {
  182. *val = arg + len + 1;
  183. return true;
  184. } else if (arg[len] == '\0') {
  185. if (--*argcp > 0) {
  186. *val = *++*argvp;
  187. return true;
  188. } else {
  189. fprintf(stderr, "%s: option %s expects an argument\n",
  190. appname, expected);
  191. exit(1);
  192. }
  193. }
  194. return false;
  195. }
  196. static bool longoptnoarg(const char *arg, const char *expected)
  197. {
  198. int len = strlen(expected);
  199. if (memcmp(arg, expected, len))
  200. return false;
  201. if (arg[len] == '=') {
  202. fprintf(stderr, "%s: option %s expects no argument\n",
  203. appname, expected);
  204. exit(1);
  205. } else if (arg[len] == '\0') {
  206. return true;
  207. }
  208. return false;
  209. }
  210. struct server_config {
  211. Conf *conf;
  212. const SshServerConfig *ssc;
  213. unsigned next_id;
  214. Socket *listening_socket;
  215. Plug listening_plug;
  216. };
  217. static Plug *server_conn_plug(
  218. struct server_config *cfg, struct server_instance **inst_out)
  219. {
  220. struct server_instance *inst = snew(struct server_instance);
  221. memset(inst, 0, sizeof(*inst));
  222. inst->id = cfg->next_id++;
  223. inst->logpolicy.vt = &server_logpolicy_vt;
  224. if (inst_out)
  225. *inst_out = inst;
  226. return ssh_server_plug(
  227. cfg->conf, cfg->ssc, NULL, 0, NULL, NULL,
  228. &inst->logpolicy, &unix_live_sftpserver_vt);
  229. }
  230. static void server_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr,
  231. int port, const char *error_msg, int error_code)
  232. {
  233. log_to_stderr(-1, error_msg);
  234. }
  235. static void server_closing(Plug *plug, PlugCloseType type,
  236. const char *error_msg)
  237. {
  238. if (type != PLUGCLOSE_NORMAL)
  239. log_to_stderr(-1, error_msg);
  240. }
  241. static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
  242. {
  243. struct server_config *cfg = container_of(
  244. p, struct server_config, listening_plug);
  245. Socket *s;
  246. const char *err;
  247. struct server_instance *inst;
  248. if (listen_once) {
  249. if (!cfg->listening_socket) /* in case of rapid double-accept */
  250. return 1;
  251. sk_close(cfg->listening_socket);
  252. cfg->listening_socket = NULL;
  253. }
  254. Plug *plug = server_conn_plug(cfg, &inst);
  255. s = constructor(ctx, plug);
  256. if ((err = sk_socket_error(s)) != NULL)
  257. return 1;
  258. SocketEndpointInfo *pi = sk_peer_info(s);
  259. char *msg = dupprintf("new connection from %s", pi->log_text);
  260. log_to_stderr(inst->id, msg);
  261. sfree(msg);
  262. sk_free_endpoint_info(pi);
  263. sk_set_frozen(s, false);
  264. ssh_server_start(plug, s);
  265. return 0;
  266. }
  267. static const PlugVtable server_plugvt = {
  268. .log = server_log,
  269. .closing = server_closing,
  270. .accepting = server_accepting,
  271. };
  272. unsigned auth_methods(AuthPolicy *ap)
  273. { return 0; }
  274. bool auth_none(AuthPolicy *ap, ptrlen username)
  275. { return false; }
  276. int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password,
  277. ptrlen *new_password_opt)
  278. { return 0; }
  279. bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
  280. { return false; }
  281. RSAKey *auth_publickey_ssh1(
  282. AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus)
  283. { return NULL; }
  284. AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
  285. { return NULL; }
  286. int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
  287. { return -1; }
  288. char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
  289. { return NULL; }
  290. bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
  291. { return false; }
  292. bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
  293. { return false; }
  294. int main(int argc, char **argv)
  295. {
  296. const char *listen_socket = NULL;
  297. SshServerConfig ssc;
  298. Conf *conf = make_ssh_server_conf();
  299. memset(&ssc, 0, sizeof(ssc));
  300. ssc.application_name = "PSUSAN";
  301. ssc.session_starting_dir = getenv("HOME");
  302. ssc.bare_connection = true;
  303. while (--argc > 0) {
  304. const char *arg = *++argv;
  305. const char *val;
  306. if (longoptnoarg(arg, "--help")) {
  307. show_help(stdout);
  308. exit(0);
  309. } else if (longoptnoarg(arg, "--version")) {
  310. show_version_and_exit();
  311. } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) {
  312. verbose = true;
  313. } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) {
  314. ssc.session_starting_dir = val;
  315. } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) ||
  316. longoptarg(arg, "-sshlog", &val, &argc, &argv)) {
  317. Filename *logfile = filename_from_str(val);
  318. conf_set_filename(conf, CONF_logfilename, logfile);
  319. filename_free(logfile);
  320. conf_set_int(conf, CONF_logtype, LGTYP_PACKETS);
  321. conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
  322. } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) ||
  323. longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) {
  324. Filename *logfile = filename_from_str(val);
  325. conf_set_filename(conf, CONF_logfilename, logfile);
  326. filename_free(logfile);
  327. conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW);
  328. conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
  329. } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) {
  330. listen_socket = val;
  331. } else if (!strcmp(arg, "--listen-once")) {
  332. listen_once = true;
  333. } else {
  334. fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
  335. exit(1);
  336. }
  337. }
  338. sk_init();
  339. uxsel_init();
  340. struct server_config scfg;
  341. scfg.conf = conf;
  342. scfg.ssc = &ssc;
  343. scfg.next_id = 0;
  344. if (listen_socket) {
  345. listening = true;
  346. scfg.listening_plug.vt = &server_plugvt;
  347. SockAddr *addr = unix_sock_addr(listen_socket);
  348. scfg.listening_socket = new_unix_listener(addr, &scfg.listening_plug);
  349. char *msg = dupprintf("listening on Unix socket %s", listen_socket);
  350. log_to_stderr(-1, msg);
  351. sfree(msg);
  352. } else {
  353. struct server_instance *inst;
  354. Plug *plug = server_conn_plug(&scfg, &inst);
  355. ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug));
  356. log_to_stderr(inst->id, "running directly on stdio");
  357. }
  358. cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check,
  359. psusan_continue, NULL);
  360. return 0;
  361. }