1 /*
2 * Platform-independent parts of a standalone SOCKS server program
3 * based on the PuTTY SOCKS code.
4 */
5
6 #include <string.h>
7 #include <errno.h>
8
9 #include "putty.h"
10 #include "misc.h"
11 #include "ssh.h"
12 #include "sshchan.h"
13 #include "psocks.h"
14
15 /*
16 * Possible later TODOs:
17 *
18 * - verbosity setting for log messages
19 *
20 * - could import proxy.c and use name_lookup rather than
21 * sk_namelookup, to allow forwarding via some other proxy type
22 */
23
24 #define BUFLIMIT 16384
25
26 #define LOGBITS(X) \
27 X(CONNSTATUS) \
28 X(DIALOGUE) \
29 /* end of list */
30
31 #define BITINDEX_ENUM(x) LOG_##x##_bitindex,
32 enum { LOGBITS(BITINDEX_ENUM) };
33 #define BITFLAG_ENUM(x) LOG_##x = 1 << LOG_##x##_bitindex,
34 enum { LOGBITS(BITFLAG_ENUM) };
35
36 typedef struct psocks_connection psocks_connection;
37
38 typedef enum RecordDestination {
39 REC_NONE, REC_FILE, REC_PIPE
40 } RecordDestination;
41
42 struct psocks_state {
43 const PsocksPlatform *platform;
44 int listen_port;
45 bool acceptall;
46 PortFwdManager *portfwdmgr;
47 uint64_t next_conn_index;
48 FILE *logging_fp;
49 unsigned log_flags;
50 RecordDestination rec_dest;
51 char *rec_cmd;
52 strbuf *subcmd;
53
54 ConnectionLayer cl;
55 };
56
57 struct psocks_connection {
58 psocks_state *ps;
59 Channel *chan;
60 char *host, *realhost;
61 int port;
62 SockAddr *addr;
63 Socket *socket;
64 bool connecting, eof_pfmgr_to_socket, eof_socket_to_pfmgr;
65 uint64_t index;
66 PsocksDataSink *rec_sink;
67
68 Plug plug;
69 SshChannel sc;
70 };
71
72 static SshChannel *psocks_lportfwd_open(
73 ConnectionLayer *cl, const char *hostname, int port,
74 const char *description, const SocketPeerInfo *pi, Channel *chan);
75
76 static const ConnectionLayerVtable psocks_clvt = {
77 .lportfwd_open = psocks_lportfwd_open,
78 /* everything else is NULL */
79 };
80
81 static size_t psocks_sc_write(SshChannel *sc, bool is_stderr, const void *,
82 size_t);
83 static void psocks_sc_write_eof(SshChannel *sc);
84 static void psocks_sc_initiate_close(SshChannel *sc, const char *err);
85 static void psocks_sc_unthrottle(SshChannel *sc, size_t bufsize);
86
87 static const SshChannelVtable psocks_scvt = {
88 .write = psocks_sc_write,
89 .write_eof = psocks_sc_write_eof,
90 .initiate_close = psocks_sc_initiate_close,
91 .unthrottle = psocks_sc_unthrottle,
92 /* all the rest are NULL */
93 };
94
95 static void psocks_plug_log(Plug *p, PlugLogType type, SockAddr *addr,
96 int port, const char *error_msg, int error_code);
97 static void psocks_plug_closing(Plug *p, const char *error_msg,
98 int error_code, bool calling_back);
99 static void psocks_plug_receive(Plug *p, int urgent,
100 const char *data, size_t len);
101 static void psocks_plug_sent(Plug *p, size_t bufsize);
102
103 static const PlugVtable psocks_plugvt = {
104 .log = psocks_plug_log,
105 .closing = psocks_plug_closing,
106 .receive = psocks_plug_receive,
107 .sent = psocks_plug_sent,
108 };
109
psocks_conn_log(psocks_connection * conn,const char * fmt,...)110 static void psocks_conn_log(psocks_connection *conn, const char *fmt, ...)
111 {
112 if (!conn->ps->logging_fp)
113 return;
114
115 va_list ap;
116 va_start(ap, fmt);
117 char *msg = dupvprintf(fmt, ap);
118 va_end(ap);
119 fprintf(conn->ps->logging_fp, "c#%"PRIu64": %s\n", conn->index, msg);
120 sfree(msg);
121 fflush(conn->ps->logging_fp);
122 }
123
psocks_conn_log_data(psocks_connection * conn,PsocksDirection dir,const void * vdata,size_t len)124 static void psocks_conn_log_data(psocks_connection *conn, PsocksDirection dir,
125 const void *vdata, size_t len)
126 {
127 if ((conn->ps->log_flags & LOG_DIALOGUE) && conn->ps->logging_fp) {
128 const char *data = vdata;
129 while (len > 0) {
130 const char *nl = memchr(data, '\n', len);
131 size_t thislen = nl ? (nl+1) - data : len;
132 const char *thisdata = data;
133 data += thislen;
134 len -= thislen;
135
136 static const char *const direction_names[2] = {
137 [UP] = "send", [DN] = "recv" };
138
139 fprintf(conn->ps->logging_fp, "c#%"PRIu64": %s \"", conn->index,
140 direction_names[dir]);
141 write_c_string_literal(conn->ps->logging_fp,
142 make_ptrlen(thisdata, thislen));
143 fprintf(conn->ps->logging_fp, "\"\n");
144 }
145
146 fflush(conn->ps->logging_fp);
147 }
148
149 if (conn->rec_sink)
150 put_data(conn->rec_sink->s[dir], vdata, len);
151 }
152
153 static void psocks_connection_establish(void *vctx);
154
psocks_lportfwd_open(ConnectionLayer * cl,const char * hostname,int port,const char * description,const SocketPeerInfo * pi,Channel * chan)155 static SshChannel *psocks_lportfwd_open(
156 ConnectionLayer *cl, const char *hostname, int port,
157 const char *description, const SocketPeerInfo *pi, Channel *chan)
158 {
159 psocks_state *ps = container_of(cl, psocks_state, cl);
160 psocks_connection *conn = snew(psocks_connection);
161 memset(conn, 0, sizeof(*conn));
162 conn->ps = ps;
163 conn->sc.vt = &psocks_scvt;
164 conn->plug.vt = &psocks_plugvt;
165 conn->chan = chan;
166 conn->host = dupstr(hostname);
167 conn->port = port;
168 conn->index = ps->next_conn_index++;
169 if (conn->ps->log_flags & LOG_CONNSTATUS)
170 psocks_conn_log(conn, "request from %s for %s port %d",
171 pi->log_text, hostname, port);
172 switch (conn->ps->rec_dest) {
173 case REC_FILE:
174 {
175 char *fnames[2];
176 FILE *fp[2];
177 bool ok = true;
178
179 static const char *const direction_names[2] = {
180 [UP] = "sockout", [DN] = "sockin" };
181
182 for (size_t i = 0; i < 2; i++) {
183 fnames[i] = dupprintf("%s.%"PRIu64, direction_names[i],
184 conn->index);
185 fp[i] = fopen(fnames[i], "wb");
186 if (!fp[i]) {
187 psocks_conn_log(conn, "cannot log this connection: "
188 "creating file '%s': %s",
189 fnames[i], strerror(errno));
190 ok = false;
191 }
192 }
193 if (ok) {
194 if (conn->ps->log_flags & LOG_CONNSTATUS)
195 psocks_conn_log(conn, "logging to '%s' / '%s'",
196 fnames[0], fnames[1]);
197 conn->rec_sink = pds_stdio(fp);
198 } else {
199 for (size_t i = 0; i < 2; i++) {
200 if (fp[i]) {
201 remove(fnames[i]);
202 fclose(fp[i]);
203 }
204 }
205 }
206 for (size_t i = 0; i < 2; i++)
207 sfree(fnames[i]);
208 }
209 break;
210 case REC_PIPE:
211 {
212 static const char *const direction_args[2] = {
213 [UP] = "out", [DN] = "in" };
214 char *index_arg = dupprintf("%"PRIu64, conn->index);
215 char *err;
216 conn->rec_sink = conn->ps->platform->open_pipes(
217 conn->ps->rec_cmd, direction_args, index_arg, &err);
218 if (!conn->rec_sink) {
219 psocks_conn_log(conn, "cannot log this connection: "
220 "creating pipes: %s", err);
221 sfree(err);
222 }
223 sfree(index_arg);
224 }
225 break;
226 default:
227 break;
228 }
229 queue_toplevel_callback(psocks_connection_establish, conn);
230 return &conn->sc;
231 }
232
psocks_conn_free(psocks_connection * conn)233 static void psocks_conn_free(psocks_connection *conn)
234 {
235 if (conn->ps->log_flags & LOG_CONNSTATUS)
236 psocks_conn_log(conn, "closed");
237
238 sfree(conn->host);
239 sfree(conn->realhost);
240 if (conn->socket)
241 sk_close(conn->socket);
242 if (conn->chan)
243 chan_free(conn->chan);
244 if (conn->rec_sink)
245 pds_free(conn->rec_sink);
246 delete_callbacks_for_context(conn);
247 sfree(conn);
248 }
249
psocks_connection_establish(void * vctx)250 static void psocks_connection_establish(void *vctx)
251 {
252 psocks_connection *conn = (psocks_connection *)vctx;
253
254 /*
255 * Look up destination host name.
256 */
257 conn->addr = sk_namelookup(conn->host, &conn->realhost, ADDRTYPE_UNSPEC);
258
259 const char *err = sk_addr_error(conn->addr);
260 if (err) {
261 char *msg = dupprintf("name lookup failed: %s", err);
262 chan_open_failed(conn->chan, msg);
263 sfree(msg);
264
265 psocks_conn_free(conn);
266 return;
267 }
268
269 /*
270 * Make the connection.
271 */
272 conn->connecting = true;
273 conn->socket = sk_new(conn->addr, conn->port, false, false, false, false,
274 &conn->plug);
275 }
276
psocks_sc_write(SshChannel * sc,bool is_stderr,const void * data,size_t len)277 static size_t psocks_sc_write(SshChannel *sc, bool is_stderr,
278 const void *data, size_t len)
279 {
280 psocks_connection *conn = container_of(sc, psocks_connection, sc);
281 if (!conn->socket) return 0;
282
283 psocks_conn_log_data(conn, UP, data, len);
284
285 return sk_write(conn->socket, data, len);
286 }
287
psocks_check_close(void * vctx)288 static void psocks_check_close(void *vctx)
289 {
290 psocks_connection *conn = (psocks_connection *)vctx;
291 if (chan_want_close(conn->chan, conn->eof_pfmgr_to_socket,
292 conn->eof_socket_to_pfmgr))
293 psocks_conn_free(conn);
294 }
295
psocks_sc_write_eof(SshChannel * sc)296 static void psocks_sc_write_eof(SshChannel *sc)
297 {
298 psocks_connection *conn = container_of(sc, psocks_connection, sc);
299 if (!conn->socket) return;
300 sk_write_eof(conn->socket);
301 conn->eof_pfmgr_to_socket = true;
302
303 if (conn->ps->log_flags & LOG_DIALOGUE)
304 psocks_conn_log(conn, "send eof");
305
306 queue_toplevel_callback(psocks_check_close, conn);
307 }
308
psocks_sc_initiate_close(SshChannel * sc,const char * err)309 static void psocks_sc_initiate_close(SshChannel *sc, const char *err)
310 {
311 psocks_connection *conn = container_of(sc, psocks_connection, sc);
312 sk_close(conn->socket);
313 conn->socket = NULL;
314 }
315
psocks_sc_unthrottle(SshChannel * sc,size_t bufsize)316 static void psocks_sc_unthrottle(SshChannel *sc, size_t bufsize)
317 {
318 psocks_connection *conn = container_of(sc, psocks_connection, sc);
319 if (bufsize < BUFLIMIT)
320 sk_set_frozen(conn->socket, false);
321 }
322
psocks_plug_log(Plug * plug,PlugLogType type,SockAddr * addr,int port,const char * error_msg,int error_code)323 static void psocks_plug_log(Plug *plug, PlugLogType type, SockAddr *addr,
324 int port, const char *error_msg, int error_code)
325 {
326 psocks_connection *conn = container_of(plug, psocks_connection, plug);
327 char addrbuf[256];
328
329 if (!(conn->ps->log_flags & LOG_CONNSTATUS))
330 return;
331
332 switch (type) {
333 case PLUGLOG_CONNECT_TRYING:
334 sk_getaddr(addr, addrbuf, sizeof(addrbuf));
335 if (sk_addr_needs_port(addr))
336 psocks_conn_log(conn, "trying to connect to %s port %d",
337 addrbuf, port);
338 else
339 psocks_conn_log(conn, "trying to connect to %s", addrbuf);
340 break;
341 case PLUGLOG_CONNECT_FAILED:
342 psocks_conn_log(conn, "connection attempt failed: %s", error_msg);
343 break;
344 case PLUGLOG_CONNECT_SUCCESS:
345 psocks_conn_log(conn, "connection established", error_msg);
346 if (conn->connecting) {
347 chan_open_confirmation(conn->chan);
348 conn->connecting = false;
349 }
350 break;
351 case PLUGLOG_PROXY_MSG:
352 psocks_conn_log(conn, "connection setup: %s", error_msg);
353 break;
354 };
355 }
356
psocks_plug_closing(Plug * plug,const char * error_msg,int error_code,bool calling_back)357 static void psocks_plug_closing(Plug *plug, const char *error_msg,
358 int error_code, bool calling_back)
359 {
360 psocks_connection *conn = container_of(plug, psocks_connection, plug);
361 if (conn->connecting) {
362 if (conn->ps->log_flags & LOG_CONNSTATUS)
363 psocks_conn_log(conn, "unable to connect: %s", error_msg);
364
365 chan_open_failed(conn->chan, error_msg);
366 conn->eof_socket_to_pfmgr = true;
367 conn->eof_pfmgr_to_socket = true;
368 conn->connecting = false;
369 } else {
370 if (conn->ps->log_flags & LOG_DIALOGUE)
371 psocks_conn_log(conn, "recv eof");
372
373 chan_send_eof(conn->chan);
374 conn->eof_socket_to_pfmgr = true;
375 }
376 queue_toplevel_callback(psocks_check_close, conn);
377 }
378
psocks_plug_receive(Plug * plug,int urgent,const char * data,size_t len)379 static void psocks_plug_receive(Plug *plug, int urgent,
380 const char *data, size_t len)
381 {
382 psocks_connection *conn = container_of(plug, psocks_connection, plug);
383 size_t bufsize = chan_send(conn->chan, false, data, len);
384 sk_set_frozen(conn->socket, bufsize > BUFLIMIT);
385
386 psocks_conn_log_data(conn, DN, data, len);
387 }
388
psocks_plug_sent(Plug * plug,size_t bufsize)389 static void psocks_plug_sent(Plug *plug, size_t bufsize)
390 {
391 psocks_connection *conn = container_of(plug, psocks_connection, plug);
392 sk_set_frozen(conn->socket, bufsize > BUFLIMIT);
393 }
394
psocks_new(const PsocksPlatform * platform)395 psocks_state *psocks_new(const PsocksPlatform *platform)
396 {
397 psocks_state *ps = snew(psocks_state);
398 memset(ps, 0, sizeof(*ps));
399
400 ps->listen_port = 1080;
401 ps->acceptall = false;
402
403 ps->cl.vt = &psocks_clvt;
404 ps->portfwdmgr = portfwdmgr_new(&ps->cl);
405
406 ps->logging_fp = stderr; /* could make this configurable later */
407 ps->log_flags = LOG_CONNSTATUS;
408 ps->rec_dest = REC_NONE;
409 ps->platform = platform;
410 ps->subcmd = strbuf_new();
411
412 return ps;
413 }
414
psocks_free(psocks_state * ps)415 void psocks_free(psocks_state *ps)
416 {
417 portfwdmgr_free(ps->portfwdmgr);
418 strbuf_free(ps->subcmd);
419 sfree(ps->rec_cmd);
420 sfree(ps);
421 }
422
psocks_cmdline(psocks_state * ps,int argc,char ** argv)423 void psocks_cmdline(psocks_state *ps, int argc, char **argv)
424 {
425 bool doing_opts = true;
426 bool accumulating_exec_args = false;
427 size_t args_seen = 0;
428
429 while (--argc > 0) {
430 const char *p = *++argv;
431
432 if (doing_opts && p[0] == '-' && p[1]) {
433 if (!strcmp(p, "--")) {
434 doing_opts = false;
435 } else if (!strcmp(p, "-g")) {
436 ps->acceptall = true;
437 } else if (!strcmp(p, "-d")) {
438 ps->log_flags |= LOG_DIALOGUE;
439 } else if (!strcmp(p, "-f")) {
440 ps->rec_dest = REC_FILE;
441 } else if (!strcmp(p, "-p")) {
442 if (!ps->platform->open_pipes) {
443 fprintf(stderr, "psocks: '-p' is not supported on this "
444 "platform\n");
445 exit(1);
446 }
447 if (--argc > 0) {
448 ps->rec_cmd = dupstr(*++argv);
449 } else {
450 fprintf(stderr, "psocks: expected an argument to '-p'\n");
451 exit(1);
452 }
453 ps->rec_dest = REC_PIPE;
454 } else if (!strcmp(p, "--exec")) {
455 if (!ps->platform->start_subcommand) {
456 fprintf(stderr, "psocks: running a subcommand is not "
457 "supported on this platform\n");
458 exit(1);
459 }
460 accumulating_exec_args = true;
461 /* Now consume all further argv words for the
462 * subcommand, even if they look like options */
463 doing_opts = false;
464 } else if (!strcmp(p, "--help")) {
465 printf("usage: psocks [ -d ] [ -f");
466 if (ps->platform->open_pipes)
467 printf(" | -p pipe-cmd");
468 printf(" ] [ -g ] port-number");
469 printf("\n");
470 printf("where: -d log all connection contents to"
471 " standard output\n");
472 printf(" -f record each half-connection to "
473 "a file sockin.N/sockout.N\n");
474 if (ps->platform->open_pipes)
475 printf(" -p pipe-cmd pipe each half-connection"
476 " to 'pipe-cmd [in|out] N'\n");
477 printf(" -g accept connections from anywhere,"
478 " not just localhost\n");
479 if (ps->platform->start_subcommand)
480 printf(" --exec subcmd [args...] run command, and "
481 "terminate when it exits\n");
482 printf(" port-number listen on this port"
483 " (default 1080)\n");
484 printf("also: psocks --help display this help text\n");
485 exit(0);
486 } else {
487 fprintf(stderr, "psocks: unrecognised option '%s'\n", p);
488 exit(1);
489 }
490 } else {
491 if (accumulating_exec_args) {
492 put_asciz(ps->subcmd, p);
493 } else switch (args_seen++) {
494 case 0:
495 ps->listen_port = atoi(p);
496 break;
497 default:
498 fprintf(stderr, "psocks: unexpected extra argument '%s'\n", p);
499 exit(1);
500 break;
501 }
502 }
503 }
504 }
505
psocks_start(psocks_state * ps)506 void psocks_start(psocks_state *ps)
507 {
508 Conf *conf = conf_new();
509 conf_set_bool(conf, CONF_lport_acceptall, ps->acceptall);
510 char *key = dupprintf("AL%d", ps->listen_port);
511 conf_set_str_str(conf, CONF_portfwd, key, "D");
512 sfree(key);
513
514 portfwdmgr_config(ps->portfwdmgr, conf);
515
516 if (ps->subcmd->len)
517 ps->platform->start_subcommand(ps->subcmd);
518
519 conf_free(conf);
520 }
521
522 /*
523 * Some stubs that are needed to link against PuTTY modules.
524 */
525
verify_host_key(const char * hostname,int port,const char * keytype,const char * key)526 int verify_host_key(const char *hostname, int port,
527 const char *keytype, const char *key)
528 {
529 unreachable("host keys not handled in this tool");
530 }
531
store_host_key(const char * hostname,int port,const char * keytype,const char * key)532 void store_host_key(const char *hostname, int port,
533 const char *keytype, const char *key)
534 {
535 unreachable("host keys not handled in this tool");
536 }
537
538 /*
539 * stdio-targeted PsocksDataSink.
540 */
541 typedef struct PsocksDataSinkStdio {
542 stdio_sink sink[2];
543 PsocksDataSink pds;
544 } PsocksDataSinkStdio;
545
stdio_free(PsocksDataSink * pds)546 static void stdio_free(PsocksDataSink *pds)
547 {
548 PsocksDataSinkStdio *pdss = container_of(pds, PsocksDataSinkStdio, pds);
549
550 for (size_t i = 0; i < 2; i++)
551 fclose(pdss->sink[i].fp);
552
553 sfree(pdss);
554 }
555
pds_stdio(FILE * fp[2])556 PsocksDataSink *pds_stdio(FILE *fp[2])
557 {
558 PsocksDataSinkStdio *pdss = snew(PsocksDataSinkStdio);
559
560 for (size_t i = 0; i < 2; i++) {
561 setvbuf(fp[i], NULL, _IONBF, 0);
562 stdio_sink_init(&pdss->sink[i], fp[i]);
563 pdss->pds.s[i] = BinarySink_UPCAST(&pdss->sink[i]);
564 }
565
566 pdss->pds.free = stdio_free;
567
568 return &pdss->pds;
569 }
570