This quick and dirty patch against OpenSSH 3.8p1 implements FTP data connection tunnelling. You can invoke ssh with a command line like this: ssh -L ftp/2121:remotehost:21 remotehost (the syntax is the same as the commercial SSH Tectia client) This will tunnel your local port 2121 to remotehost's port 21 in FTP mode. You can then connect to the local port: ftp localhost 2121 Log in AND SWITCH TO PASSIVE MODE ftp> pass Proceed normally. My patch intercepts the 227 response to the PASV command and creates a new tunnel to remotehost. It is very crude and has many limitations: - Only works for forwards set on the unix command line, not from the ~C command line or the config file - Doesn't work with -g option - Doesn't work in the reverse direction with -R - Doesn't work for FTP/IPv6 - Requires passive mode Maybe it will nevertheless be useful to some. I developed it for one specific application, hence the limitations. -Anthony Uk uk & dataway # ch http://www.dataway.ch/chip/patches/ --- channels.c.orig Fri Apr 9 11:31:57 2004 +++ channels.c Sat Apr 10 21:24:05 2004 @@ -55,6 +55,7 @@ #include "authfd.h" #include "pathnames.h" #include "bufaux.h" +#include "readconf.h" /* -- channel core */ @@ -388,6 +389,8 @@ case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_X11_LISTENER: + case SSH_CHANNEL_FTP_LISTENER: + case SSH_CHANNEL_FTP_DATA_LISTENER: channel_close_fd(&c->sock); channel_free(c); break; @@ -450,6 +453,8 @@ case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_ZOMBIE: + case SSH_CHANNEL_FTP_LISTENER: + case SSH_CHANNEL_FTP_DATA_LISTENER: continue; case SSH_CHANNEL_LARVAL: if (!compat20) @@ -493,6 +498,8 @@ case SSH_CHANNEL_OPENING: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_ZOMBIE: + case SSH_CHANNEL_FTP_LISTENER: + case SSH_CHANNEL_FTP_DATA_LISTENER: continue; case SSH_CHANNEL_LARVAL: case SSH_CHANNEL_AUTH_SOCKET: @@ -541,6 +548,8 @@ case SSH_CHANNEL_CLOSED: case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_ZOMBIE: + case SSH_CHANNEL_FTP_LISTENER: + case SSH_CHANNEL_FTP_DATA_LISTENER: continue; case SSH_CHANNEL_LARVAL: case SSH_CHANNEL_OPENING: @@ -550,9 +559,9 @@ case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: - snprintf(buf, sizeof buf, " #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d)\r\n", + snprintf(buf, sizeof buf, " #%d %.300s (%s t%d r%d i%d/%d o%d/%d fd %d/%d)\r\n", c->self, c->remote_name, - c->type, c->remote_id, + c->ctype, c->type, c->remote_id, c->istate, buffer_len(&c->input), c->ostate, buffer_len(&c->output), c->rfd, c->wfd); @@ -1215,7 +1224,7 @@ struct sockaddr addr; int newsock, nextstate; socklen_t addrlen; - char *rtype; + char *rtype, *ctype; if (FD_ISSET(c->sock, readset)) { debug("Connection to port %d forwarding " @@ -1224,14 +1233,22 @@ if (c->type == SSH_CHANNEL_RPORT_LISTENER) { nextstate = SSH_CHANNEL_OPENING; - rtype = "forwarded-tcpip"; + ctype = rtype = "forwarded-tcpip"; + } else if (c->type == SSH_CHANNEL_FTP_LISTENER) { + nextstate = SSH_CHANNEL_OPENING; + ctype = "ftp-ctl"; + rtype = "direct-tcpip"; + } else if (c->type == SSH_CHANNEL_FTP_DATA_LISTENER) { + nextstate = SSH_CHANNEL_OPENING; + ctype = "ftp-data"; + rtype = "direct-tcpip"; } else { if (c->host_port == 0) { nextstate = SSH_CHANNEL_DYNAMIC; - rtype = "dynamic-tcpip"; + ctype = rtype = "dynamic-tcpip"; } else { nextstate = SSH_CHANNEL_OPENING; - rtype = "direct-tcpip"; + ctype = rtype = "direct-tcpip"; } } @@ -1242,7 +1259,7 @@ return; } set_nodelay(newsock); - nc = channel_new(rtype, nextstate, newsock, newsock, -1, + nc = channel_new(ctype, nextstate, newsock, newsock, -1, c->local_window_max, c->local_maxpacket, 0, rtype, 1); nc->listening_port = c->listening_port; nc->host_port = c->host_port; @@ -1258,6 +1275,11 @@ } else { port_open_helper(nc, rtype); } + + if (c->type == SSH_CHANNEL_FTP_DATA_LISTENER) { + debug("Closing FTP data listener %d", c->self); + chan_mark_dead(c); + } } } @@ -1389,6 +1411,10 @@ u_char *data; u_int dlen; int len; + unsigned int h1, h2, h3, h4, p1, p2; + char hbuf[64]; + u_char *endl; + int fwd_port; /* Send buffered output data to the socket. */ if (c->wfd != -1 && @@ -1401,6 +1427,34 @@ if (compat20 && c->wfd_isatty) dlen = MIN(dlen, 8*1024); #endif + if (!strcmp(c->ctype, "ftp-ctl") && data[0] == '2') { + if ((endl = memchr(data, '\n', dlen)) == NULL) { + debug2("channel %d: incomplete FTP server response", c->self); + return 1; + } + if (sscanf(data, "227 %*[^(](%u,%u,%u,%u,%u,%u)", + &h1, &h2, &h3, &h4, &p1, &p2) == 6) { + snprintf(hbuf, sizeof(hbuf), "%u.%u.%u.%u", h1, h2, h3, h4); + debug("FTP channel %d: found 227, %s:%d", c->self, hbuf, p1 * 256 + p2); + for (len = 0; len < 10; len++) { + fwd_port = (arc4random() & 32767) + 10240; + if (channel_setup_local_fwd_listener(fwd_port, hbuf, (p1 << 8) | p2, 0, FWD_TYPE_FTP_DATA) >= 0) + break; + fwd_port = 0; + } + if (fwd_port == 0) { + logit("FTP data port forwarding failed."); + strcpy(hbuf, "425 Can't open data connection.\r\n"); + } + else { + snprintf(hbuf, sizeof(hbuf), "227 Entering Passive Mode (127,0,0,1,%u,%u).\r\n", fwd_port >> 8, fwd_port & 255); + } + buffer_clear(&c->output); + buffer_append(&c->output, hbuf, strlen(hbuf)); + data = buffer_ptr(&c->output); + dlen = buffer_len(&c->output); + } + } len = write(c->wfd, data, dlen); if (len < 0 && (errno == EINTR || errno == EAGAIN)) return 1; @@ -1541,6 +1595,8 @@ channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; + channel_pre[SSH_CHANNEL_FTP_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_FTP_DATA_LISTENER] = &channel_pre_listener; channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; @@ -1549,6 +1605,8 @@ channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; + channel_post[SSH_CHANNEL_FTP_LISTENER] = &channel_post_port_listener; + channel_post[SSH_CHANNEL_FTP_DATA_LISTENER] = &channel_post_port_listener; } static void @@ -1563,6 +1621,8 @@ channel_pre[SSH_CHANNEL_OUTPUT_DRAINING] = &channel_pre_output_draining; channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; + channel_pre[SSH_CHANNEL_FTP_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_FTP_DATA_LISTENER] = &channel_pre_listener; channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; @@ -1571,6 +1631,8 @@ channel_post[SSH_CHANNEL_OUTPUT_DRAINING] = &channel_post_output_drain_13; channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; + channel_post[SSH_CHANNEL_FTP_LISTENER] = &channel_post_port_listener; + channel_post[SSH_CHANNEL_FTP_DATA_LISTENER] = &channel_post_port_listener; } static void @@ -1583,6 +1645,8 @@ channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; + channel_pre[SSH_CHANNEL_FTP_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_FTP_DATA_LISTENER] = &channel_pre_listener; channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; @@ -1590,6 +1654,8 @@ channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; + channel_post[SSH_CHANNEL_FTP_LISTENER] = &channel_post_port_listener; + channel_post[SSH_CHANNEL_FTP_DATA_LISTENER] = &channel_post_port_listener; } static void @@ -2173,6 +2239,10 @@ for (ai = aitop; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; + if (type == SSH_CHANNEL_FTP_LISTENER && ai->ai_family != AF_INET) + continue; /* FTP spoofing doesn't work with IPv6 yet */ + if (type == SSH_CHANNEL_FTP_DATA_LISTENER && ai->ai_family != AF_INET) + continue; /* FTP spoofing doesn't work with IPv6 yet */ if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { error("channel_setup_fwd_listener: getnameinfo failed"); @@ -2231,9 +2301,17 @@ /* protocol local port fwd, used by ssh (and sshd in v1) */ int channel_setup_local_fwd_listener(u_short listen_port, - const char *host_to_connect, u_short port_to_connect, int gateway_ports) + const char *host_to_connect, u_short port_to_connect, int gateway_ports, + int fwd_type) { - return channel_setup_fwd_listener(SSH_CHANNEL_PORT_LISTENER, + int chan_type; + if (fwd_type == FWD_TYPE_FTP) + chan_type = SSH_CHANNEL_FTP_LISTENER; + else if (fwd_type == FWD_TYPE_FTP_DATA) + chan_type = SSH_CHANNEL_FTP_DATA_LISTENER; + else + chan_type = SSH_CHANNEL_PORT_LISTENER; + return channel_setup_fwd_listener(chan_type, NULL, listen_port, host_to_connect, port_to_connect, gateway_ports); } @@ -2335,7 +2413,7 @@ #endif /* Initiate forwarding */ - channel_setup_local_fwd_listener(port, hostname, host_port, gateway_ports); + channel_setup_local_fwd_listener(port, hostname, host_port, gateway_ports, FWD_TYPE_NORMAL); /* Free the argument string. */ xfree(hostname); --- channels.h.orig Fri Apr 9 11:39:18 2004 +++ channels.h Sat Apr 10 19:57:43 2004 @@ -55,7 +55,9 @@ #define SSH_CHANNEL_CONNECTING 12 #define SSH_CHANNEL_DYNAMIC 13 #define SSH_CHANNEL_ZOMBIE 14 /* Almost dead. */ -#define SSH_CHANNEL_MAX_TYPE 15 +#define SSH_CHANNEL_FTP_LISTENER 15 +#define SSH_CHANNEL_FTP_DATA_LISTENER 16 +#define SSH_CHANNEL_MAX_TYPE 17 #define SSH_CHANNEL_PATH_LEN 256 @@ -200,7 +202,7 @@ int channel_connect_to(const char *, u_short); int channel_connect_by_listen_address(u_short); void channel_request_remote_forwarding(u_short, const char *, u_short); -int channel_setup_local_fwd_listener(u_short, const char *, u_short, int); +int channel_setup_local_fwd_listener(u_short, const char *, u_short, int, int); int channel_setup_remote_fwd_listener(const char *, u_short, int); /* x11 forwarding */ --- readconf.c.orig Fri Apr 9 11:26:53 2004 +++ readconf.c Sat Apr 10 21:07:58 2004 @@ -202,7 +202,7 @@ void add_local_forward(Options *options, u_short port, const char *host, - u_short host_port) + u_short host_port, int fwd_type) { Forward *fwd; #ifndef NO_IPPORT_RESERVED_CONCEPT @@ -216,6 +216,7 @@ fwd->port = port; fwd->host = xstrdup(host); fwd->host_port = host_port; + fwd->fwd_type = fwd_type; } /* @@ -660,7 +661,7 @@ if (*activep) { if (opcode == oLocalForward) add_local_forward(options, fwd_port, buf, - fwd_host_port); + fwd_host_port, FWD_TYPE_NORMAL); else if (opcode == oRemoteForward) add_remote_forward(options, fwd_port, buf, fwd_host_port); @@ -677,7 +678,7 @@ fatal("%.200s line %d: Badly formatted port number.", filename, linenum); if (*activep) - add_local_forward(options, fwd_port, "socks", 0); + add_local_forward(options, fwd_port, "socks", 0, FWD_TYPE_NORMAL); break; case oClearAllForwardings: --- readconf.h.orig Fri Apr 9 11:23:22 2004 +++ readconf.h Sat Apr 10 21:07:44 2004 @@ -24,7 +24,11 @@ u_short port; /* Port to forward. */ char *host; /* Host to connect. */ u_short host_port; /* Port to connect on host. */ + int fwd_type; /* Type of forward (see FWD_TYPE_* values) */ } Forward; +#define FWD_TYPE_NORMAL 0 +#define FWD_TYPE_FTP 10 +#define FWD_TYPE_FTP_DATA 11 /* Data structure for representing option data. */ typedef struct { @@ -112,7 +116,7 @@ int process_config_line(Options *, const char *, char *, const char *, int, int *); -void add_local_forward(Options *, u_short, const char *, u_short); +void add_local_forward(Options *, u_short, const char *, u_short, int); void add_remote_forward(Options *, u_short, const char *, u_short); #endif /* READCONF_H */ --- ssh.c.orig Fri Apr 9 11:05:26 2004 +++ ssh.c Sat Apr 10 21:09:23 2004 @@ -418,7 +418,14 @@ case 'L': case 'R': - if (sscanf(optarg, "%5[0123456789]:%255[^:]:%5[0123456789]", + dummy = FWD_TYPE_NORMAL; + if (sscanf(optarg, "ftp/%5[0123456789]:%255[^:]:%5[0123456789]", + sfwd_port, buf, sfwd_host_port) == 3 || + sscanf(optarg, "ftp/%5[0123456789]/%255[^/]/%5[0123456789]", + sfwd_port, buf, sfwd_host_port) == 3) { + dummy = FWD_TYPE_FTP; + } + else if (sscanf(optarg, "%5[0123456789]:%255[^:]:%5[0123456789]", sfwd_port, buf, sfwd_host_port) != 3 && sscanf(optarg, "%5[0123456789]/%255[^/]/%5[0123456789]", sfwd_port, buf, sfwd_host_port) != 3) { @@ -436,7 +443,7 @@ } if (opt == 'L') add_local_forward(&options, fwd_port, buf, - fwd_host_port); + fwd_host_port, dummy); else if (opt == 'R') add_remote_forward(&options, fwd_port, buf, fwd_host_port); @@ -449,7 +456,7 @@ optarg); exit(1); } - add_local_forward(&options, fwd_port, "socks", 0); + add_local_forward(&options, fwd_port, "socks", 0, FWD_TYPE_NORMAL); break; case 'C': @@ -840,25 +847,28 @@ /* Initiate local TCP/IP port forwardings. */ for (i = 0; i < options.num_local_forwards; i++) { - debug("Connections to local port %d forwarded to remote address %.200s:%d", + debug("Connections to local port %d forwarded to remote address %.200s:%d type %d", options.local_forwards[i].port, options.local_forwards[i].host, - options.local_forwards[i].host_port); + options.local_forwards[i].host_port, + options.local_forwards[i].fwd_type); success += channel_setup_local_fwd_listener( options.local_forwards[i].port, options.local_forwards[i].host, options.local_forwards[i].host_port, - options.gateway_ports); + options.gateway_ports, + options.local_forwards[i].fwd_type); } if (i > 0 && success == 0) error("Could not request local forwarding."); /* Initiate remote TCP/IP port forwardings. */ for (i = 0; i < options.num_remote_forwards; i++) { - debug("Connections to remote port %d forwarded to local address %.200s:%d", + debug("Connections to remote port %d forwarded to local address %.200s:%d type %d", options.remote_forwards[i].port, options.remote_forwards[i].host, - options.remote_forwards[i].host_port); + options.remote_forwards[i].host_port, + options.remote_forwards[i].fwd_type); channel_request_remote_forwarding( options.remote_forwards[i].port, options.remote_forwards[i].host,