X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/125105d16c788398562ac03e91ce7a0dc0292492..820ebe3b54a21cfb8d42e43c567d952415c1d05d:/portfwd.c diff --git a/portfwd.c b/portfwd.c index 00c6cfce..03cbd67c 100644 --- a/portfwd.c +++ b/portfwd.c @@ -51,11 +51,6 @@ (cp)[0] = (value) >> 8, \ (cp)[1] = (value) ) -struct pfwd_queue { - struct pfwd_queue *next; - char *buf; -}; - struct PFwdPrivate { const struct plug_function_table *fn; /* the above variable absolutely *must* be the first in this structure */ @@ -63,14 +58,34 @@ struct PFwdPrivate { void *backhandle; /* instance of SSH backend itself */ /* Note that backhandle need not be filled in if c is non-NULL */ Socket s; - char hostname[128]; int throttled, throttle_override; - int port; int ready; - struct pfwd_queue *waiting; + /* + * `dynamic' does double duty. It's set to 0 for an ordinary + * forwarded port, and nonzero for SOCKS-style dynamic port + * forwarding; but it also represents the state of the SOCKS + * exchange. + */ + int dynamic; + /* + * `hostname' and `port' are the real hostname and port, once + * we know what we're connecting to; they're unused for this + * purpose while conducting a local SOCKS exchange, which means + * we can also use them as a buffer and pointer for reading + * data from the SOCKS client. + */ + char hostname[256]; + int port; + /* + * When doing dynamic port forwarding, we can receive + * connection data before we are actually able to send it; so + * we may have to temporarily hold some in a dynamically + * allocated buffer here. + */ + void *buffer; + int buflen; }; - static int pfd_closing(Plug plug, char *error_msg, int error_code, int calling_back) { @@ -81,7 +96,8 @@ static int pfd_closing(Plug plug, char *error_msg, int error_code, * so if an error occurred on the socket, we just ignore it * and treat it like a proper close. */ - sshfwd_close(pr->c); + if (pr->c) + sshfwd_close(pr->c); pfd_close(pr->s); return 1; } @@ -89,6 +105,212 @@ static int pfd_closing(Plug plug, char *error_msg, int error_code, static int pfd_receive(Plug plug, int urgent, char *data, int len) { struct PFwdPrivate *pr = (struct PFwdPrivate *) plug; + if (pr->dynamic) { + while (len--) { + if (pr->port >= lenof(pr->hostname)) { + if ((pr->dynamic >> 12) == 4) { + /* Send back a SOCKS 4 error before closing. */ + char data[8]; + memset(data, 0, sizeof(data)); + data[1] = 91; /* generic `request rejected' */ + sk_write(pr->s, data, 8); + } + pfd_close(pr->s); + return 1; + } + pr->hostname[pr->port++] = *data++; + + /* + * Now check what's in the buffer to see if it's a + * valid and complete message in the SOCKS exchange. + */ + if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 4) && + pr->hostname[0] == 4) { + /* + * SOCKS 4. + */ + if (pr->dynamic == 1) + pr->dynamic = 0x4000; + if (pr->port < 2) continue;/* don't have command code yet */ + if (pr->hostname[1] != 1) { + /* Send back a SOCKS 4 error before closing. */ + char data[8]; + memset(data, 0, sizeof(data)); + data[1] = 91; /* generic `request rejected' */ + sk_write(pr->s, data, 8); + pfd_close(pr->s); + return 1; + } + if (pr->port < 8) continue; /* haven't started username */ + if (pr->hostname[pr->port-1] != 0) + continue; /* haven't _finished_ username */ + /* + * Now we have a full SOCKS 4 request. Check it to + * see if it's a SOCKS 4A request. + */ + if (pr->hostname[4] == 0 && pr->hostname[5] == 0 && + pr->hostname[6] == 0 && pr->hostname[7] != 0) { + /* + * It's SOCKS 4A. So if we haven't yet + * collected the host name, we should continue + * waiting for data in order to do so; if we + * have, we can go ahead. + */ + int len; + if (pr->dynamic == 0x4000) { + pr->dynamic = 0x4001; + continue; + } + pr->hostname[0] = 0; /* reply version code */ + pr->hostname[1] = 90; /* request granted */ + sk_write(pr->s, pr->hostname, 8); + pr->port = GET_16BIT_MSB_FIRST(pr->hostname+2); + len = strlen(pr->hostname+8); + memmove(pr->hostname, pr->hostname + 8 + len + 1, + lenof(pr->hostname) - (8 + len + 1)); + goto connect; + } else { + /* + * It's SOCKS 4, which means we should format + * the IP address into the hostname string and + * then just go. + */ + pr->hostname[0] = 0; /* reply version code */ + pr->hostname[1] = 90; /* request granted */ + sk_write(pr->s, pr->hostname, 8); + pr->port = GET_16BIT_MSB_FIRST(pr->hostname+2); + sprintf(pr->hostname, "%d.%d.%d.%d", + (unsigned char)pr->hostname[4], + (unsigned char)pr->hostname[5], + (unsigned char)pr->hostname[6], + (unsigned char)pr->hostname[7]); + goto connect; + } + } + + if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 5) && + pr->hostname[0] == 5) { + /* + * SOCKS 5. + */ + if (pr->dynamic == 1) + pr->dynamic = 0x5000; + + if (pr->dynamic == 0x5000) { + int i, method; + char data[2]; + /* + * We're receiving a set of method identifiers. + */ + if (pr->port < 2) continue;/* no method count yet */ + if (pr->port < 2 + (unsigned char)pr->hostname[1]) + continue; /* no methods yet */ + method = 0xFF; /* invalid */ + for (i = 0; i < (unsigned char)pr->hostname[1]; i++) + if (pr->hostname[2+i] == 0) { + method = 0;/* no auth */ + break; + } + data[0] = 5; + data[1] = method; + sk_write(pr->s, data, 2); + pr->dynamic = 0x5001; + pr->port = 0; /* re-empty the buffer */ + continue; + } + + if (pr->dynamic == 0x5001) { + int atype, alen; + if (pr->port < 6) continue; + atype = (unsigned char)pr->hostname[3]; + if (atype == 1) /* IPv4 address */ + alen = 4; + if (atype == 4) /* IPv6 address */ + alen = 16; + if (atype == 3) /* domain name has leading length */ + alen = 1 + (unsigned char)pr->hostname[4]; + if (pr->port < 6 + alen) continue; + if (pr->hostname[1] != 1 || pr->hostname[2] != 0) { + pr->hostname[1] = 1; /* generic failure */ + pr->hostname[2] = 0; /* reserved */ + sk_write(pr->s, pr->hostname, pr->port); + pfd_close(pr->s); + return 1; + } + /* + * Now we have a viable connect request. Switch + * on atype. + */ + pr->port = GET_16BIT_MSB_FIRST(pr->hostname+4+alen); + if (atype == 1) { + pr->hostname[1] = 0; /* succeeded */ + sk_write(pr->s, pr->hostname, alen + 6); + sprintf(pr->hostname, "%d.%d.%d.%d", + (unsigned char)pr->hostname[4], + (unsigned char)pr->hostname[5], + (unsigned char)pr->hostname[6], + (unsigned char)pr->hostname[7]); + goto connect; + } else if (atype == 3) { + pr->hostname[1] = 0; /* succeeded */ + sk_write(pr->s, pr->hostname, alen + 6); + memmove(pr->hostname, pr->hostname + 5, alen-1); + pr->hostname[alen-1] = '\0'; + goto connect; + } else { + /* + * Unknown address type. (FIXME: support IPv6!) + */ + pr->hostname[1] = 8; /* atype not supported */ + sk_write(pr->s, pr->hostname, pr->port); + pfd_close(pr->s); + return 1; + } + } + } + + /* + * If we get here without either having done `continue' + * or `goto connect', it must be because there is no + * sensible interpretation of what's in our buffer. So + * close the connection rudely. + */ + pfd_close(pr->s); + return 1; + } + return 1; + + /* + * We come here when we're ready to make an actual + * connection. + */ + connect: + + pr->c = new_sock_channel(pr->backhandle, pr->s); + if (pr->c == NULL) { + pfd_close(pr->s); + return 1; + } else { + /* asks to forward to the specified host/port for this */ + ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding"); + } + pr->dynamic = 0; + + /* + * Now freeze the socket until the SSH server confirms the + * connection. + */ + sk_set_frozen(pr->s, 1); + /* + * If there's any data remaining in our current buffer, + * save it to be sent on pfd_confirm(). + */ + if (len > 0) { + pr->buffer = snewn(len, char); + memcpy(pr->buffer, data, len); + pr->buflen = len; + } + } if (pr->ready) { if (sshfwd_write(pr->c, data, len) > 0) { pr->throttled = 1; @@ -102,7 +324,8 @@ static void pfd_sent(Plug plug, int bufsize) { struct PFwdPrivate *pr = (struct PFwdPrivate *) plug; - sshfwd_unthrottle(pr->c, bufsize); + if (pr->c) + sshfwd_unthrottle(pr->c, bufsize); } /* @@ -133,6 +356,7 @@ char *pfd_newconnect(Socket *s, char *hostname, int port, void *c, * Open socket. */ pr = snew(struct PFwdPrivate); + pr->buffer = NULL; pr->fn = &fn_table; pr->throttled = pr->throttle_override = 0; pr->ready = 1; @@ -169,6 +393,7 @@ static int pfd_accepting(Plug p, void *sock) org = (struct PFwdPrivate *)p; pr = snew(struct PFwdPrivate); + pr->buffer = NULL; pr->fn = &fn_table; pr->c = NULL; @@ -180,22 +405,27 @@ static int pfd_accepting(Plug p, void *sock) return err != NULL; } - pr->c = new_sock_channel(org->backhandle, s); + sk_set_private_ptr(s, pr); - strcpy(pr->hostname, org->hostname); - pr->port = org->port; pr->throttled = pr->throttle_override = 0; pr->ready = 0; - pr->waiting = NULL; - sk_set_private_ptr(s, pr); - - if (pr->c == NULL) { - sfree(pr); - return 1; + if (org->dynamic) { + pr->dynamic = 1; + pr->port = 0; /* hostname buffer is so far empty */ + sk_set_frozen(s, 0); /* we want to receive SOCKS _now_! */ } else { - /* asks to forward to the specified host/port for this */ - ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding"); + strcpy(pr->hostname, org->hostname); + pr->port = org->port; + pr->c = new_sock_channel(org->backhandle, s); + + if (pr->c == NULL) { + sfree(pr); + return 1; + } else { + /* asks to forward to the specified host/port for this */ + ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding"); + } } return 0; @@ -223,13 +453,17 @@ char *pfd_addforward(char *desthost, int destport, char *srcaddr, int port, * Open socket. */ pr = snew(struct PFwdPrivate); + pr->buffer = NULL; pr->fn = &fn_table; pr->c = NULL; - strcpy(pr->hostname, desthost); - pr->port = destport; + if (desthost) { + strcpy(pr->hostname, desthost); + pr->port = destport; + pr->dynamic = 0; + } else + pr->dynamic = 1; pr->throttled = pr->throttle_override = 0; pr->ready = 0; - pr->waiting = NULL; pr->backhandle = backhandle; pr->s = s = new_listener(srcaddr, port, (Plug) pr, @@ -253,6 +487,7 @@ void pfd_close(Socket s) pr = (struct PFwdPrivate *) sk_get_private_ptr(s); + sfree(pr->buffer); sfree(pr); sk_close(s); @@ -302,4 +537,9 @@ void pfd_confirm(Socket s) pr->ready = 1; sk_set_frozen(s, 0); sk_write(s, NULL, 0); + if (pr->buffer) { + sshfwd_write(pr->c, pr->buffer, pr->buflen); + sfree(pr->buffer); + pr->buffer = NULL; + } }