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