From 8eebd22198133e95ce25af1dd15dead0a5389371 Mon Sep 17 00:00:00 2001 From: simon Date: Sat, 23 Mar 2002 17:47:21 +0000 Subject: [PATCH] Justin Bradford's proxy support patch. Currently supports only HTTP CONNECT, but contains an extensible framework to allow other proxies. Apparently SOCKS and ad-hoc-telnet-proxy are already planned (the GUI mentions them already even though they don't work yet). GUI includes full configurability and allows definition of exclusion zones. Rock and roll. git-svn-id: svn://svn.tartarus.org/sgt/putty@1598 cda61777-01e9-0310-a592-d414129be87e --- Makefile | 9 +- Recipe | 2 +- network.h | 16 +- portfwd.c | 4 +- proxy.c | 594 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ proxy.h | 119 +++++++++++++ putty.h | 9 + raw.c | 2 +- rlogin.c | 2 +- settings.c | 27 +++ ssh.c | 2 +- telnet.c | 2 +- windlg.c | 145 +++++++++++++++ winnet.c | 18 +- x11fwd.c | 2 +- 15 files changed, 935 insertions(+), 18 deletions(-) create mode 100644 proxy.c create mode 100644 proxy.h diff --git a/Makefile b/Makefile index 3cc94849..d095aee0 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ GOBJS1 = window.$(OBJ) windlg.$(OBJ) winctrls.$(OBJ) terminal.$(OBJ) GOBJS2 = sizetip.$(OBJ) wcwidth.$(OBJ) unicode.$(OBJ) logging.$(OBJ) GOBJS3 = printing.$(OBJ) ##-- objects putty puttytel plink -LOBJS1 = telnet.$(OBJ) raw.$(OBJ) rlogin.$(OBJ) ldisc.$(OBJ) winnet.$(OBJ) +LOBJS1 = telnet.$(OBJ) raw.$(OBJ) rlogin.$(OBJ) ldisc.$(OBJ) winnet.$(OBJ) proxy.$(OBJ) ##-- objects putty plink POBJS = be_all.$(OBJ) ##-- objects puttytel @@ -104,9 +104,9 @@ TOBJS = be_nossh.$(OBJ) ##-- objects plink PLOBJS = plink.$(OBJ) logging.$(OBJ) ##-- objects pscp -SOBJS = scp.$(OBJ) winnet.$(OBJ) be_none.$(OBJ) wildcard.$(OBJ) +SOBJS = scp.$(OBJ) winnet.$(OBJ) proxy.$(OBJ) be_none.$(OBJ) wildcard.$(OBJ) ##-- objects psftp -FOBJS = psftp.$(OBJ) winnet.$(OBJ) be_none.$(OBJ) +FOBJS = psftp.$(OBJ) winnet.$(OBJ) proxy.$(OBJ) be_none.$(OBJ) ##-- objects pscp psftp SFOBJS = sftp.$(OBJ) int64.$(OBJ) logging.$(OBJ) ##-- objects putty puttytel pscp psftp plink @@ -303,7 +303,8 @@ pageantc.$(OBJ): pageantc.c puttymem.h plink.$(OBJ): plink.c network.h misc.h puttymem.h storage.h putty.h tree234.h portfwd.$(OBJ): portfwd.c network.h misc.h puttymem.h int64.h ssh.h putty.h printing.$(OBJ): printing.c network.h misc.h puttymem.h putty.h -psftp.$(OBJ): psftp.c network.h misc.h sftp.h ssh.h storage.h int64.h puttymem.h putty.h +proxy.$(OBJ): proxy.c proxy.h network.h +psftp.$(OBJ): psftp.c network.h misc.h sftp.h ssh.h storage.h int64.h puttymem.h putty.h puttygen.$(OBJ): puttygen.c network.h misc.h puttymem.h int64.h winstuff.h ssh.h putty.h raw.$(OBJ): raw.c network.h misc.h puttymem.h putty.h rlogin.$(OBJ): rlogin.c network.h misc.h puttymem.h putty.h diff --git a/Recipe b/Recipe index 4452cbf3..d032be85 100644 --- a/Recipe +++ b/Recipe @@ -105,7 +105,7 @@ SFTP = sftp int64 logging # Miscellaneous objects appearing in all the network utilities (not # Pageant or PuTTYgen). -MISC = misc version winstore settings tree234 winnet +MISC = misc version winstore settings tree234 winnet proxy # Standard libraries, and the same with WinSocks 1 and 2. LIBS = advapi32.lib user32.lib gdi32.lib comctl32.lib comdlg32.lib diff --git a/network.h b/network.h index 2cab4388..8f1b2bed 100644 --- a/network.h +++ b/network.h @@ -27,6 +27,9 @@ struct socket_function_table { int (*write) (Socket s, char *data, int len); int (*write_oob) (Socket s, char *data, int len); void (*flush) (Socket s); + void (*set_private_ptr) (Socket s, void *ptr); + void *(*get_private_ptr) (Socket s); + void (*set_frozen) (Socket s, int is_frozen); /* ignored by tcp, but vital for ssl */ char *(*socket_error) (Socket s); }; @@ -60,6 +63,13 @@ struct plug_function_table { */ }; +/* proxy indirection layer */ +Socket new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, Plug plug); +Socket new_listener(int port, Plug plug, int local_host_only); + +/* socket functions */ void sk_init(void); /* called once at program startup */ void sk_cleanup(void); /* called just before program exit */ @@ -95,8 +105,8 @@ Socket sk_register(void *sock, Plug plug); * This is perhaps unnecessary now that we have the notion of a plug, * but there is some existing code that uses it, so it stays. */ -void sk_set_private_ptr(Socket s, void *ptr); -void *sk_get_private_ptr(Socket s); +#define sk_set_private_ptr(s, ptr) (((*s)->set_private_ptr) (s, ptr)) +#define sk_get_private_ptr(s) (((*s)->get_private_ptr) (s)) /* * Special error values are returned from sk_namelookup and sk_new @@ -123,7 +133,7 @@ char *sk_addr_error(SockAddr addr); * associated local socket in order to avoid unbounded buffer * growth. */ -void sk_set_frozen(Socket sock, int is_frozen); +#define sk_set_frozen(s, is_frozen) (((*s)->set_frozen) (s, is_frozen)) /* * Call this after an operation that might have tried to send on a diff --git a/portfwd.c b/portfwd.c index 732bfc5b..919d27d8 100644 --- a/portfwd.c +++ b/portfwd.c @@ -138,7 +138,7 @@ char *pfd_newconnect(Socket *s, char *hostname, int port, void *c) pr->ready = 1; pr->c = c; - pr->s = *s = sk_new(addr, port, 0, 1, 0, (Plug) pr); + pr->s = *s = new_connection(addr, dummy_realhost, port, 0, 1, 0, (Plug) pr); if ((err = sk_socket_error(*s))) { sfree(pr); return err; @@ -227,7 +227,7 @@ char *pfd_addforward(char *desthost, int destport, int port) pr->ready = 0; pr->waiting = NULL; - pr->s = s = sk_newlistener(port, (Plug) pr, !cfg.lport_acceptall); + pr->s = s = new_listener(port, (Plug) pr, !cfg.lport_acceptall); if ((err = sk_socket_error(s))) { sfree(pr); return err; diff --git a/proxy.c b/proxy.c new file mode 100644 index 00000000..b773f251 --- /dev/null +++ b/proxy.c @@ -0,0 +1,594 @@ +/* + * Network proxy abstraction in PuTTY + * + * A proxy layer, if necessary, wedges itself between the network + * code and the higher level backend. + */ + +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "network.h" +#include "proxy.h" + +/* + * Call this when proxy negotiation is complete, so that this + * socket can begin working normally. + */ +void proxy_activate (Proxy_Socket p) +{ + void *data; + int len; + + p->lock_close = + p->lock_write = + p->lock_write_oob = + p->lock_receive = + p->lock_flush = + p->lock_closing = + p->lock_sent = + p->lock_accepting = + p->lock_freeze = 1; + + p->state = PROXY_STATE_ACTIVE; + + /* let's try to keep extra receive events from coming through */ + sk_set_frozen(p->sub_socket, 1); + + while (bufchain_size(&p->pending_oob_output_data) > 0) { + bufchain_prefix(&p->pending_oob_output_data, &data, &len); + sk_write_oob(p->sub_socket, data, len); + bufchain_consume(&p->pending_oob_output_data, len); + } + bufchain_clear(&p->pending_oob_output_data); + + while (bufchain_size(&p->pending_output_data) > 0) { + bufchain_prefix(&p->pending_output_data, &data, &len); + sk_write(p->sub_socket, data, len); + bufchain_consume(&p->pending_output_data, len); + } + bufchain_clear(&p->pending_output_data); + + p->lock_write_oob = 0; + p->lock_write = 0; + + if (p->pending_flush) sk_flush(p->sub_socket); + p->lock_flush = 0; + + while (bufchain_size(&p->pending_input_data) > 0) { + bufchain_prefix(&p->pending_input_data, &data, &len); + plug_receive(p->plug, 0, data, len); + bufchain_consume(&p->pending_input_data, len); + } + bufchain_clear(&p->pending_input_data); + p->lock_receive = 0; + + /* now set the underlying socket to whatever freeze state they wanted */ + sk_set_frozen(p->sub_socket, p->freeze); + p->lock_freeze = 0; + + p->lock_sent = 0; + p->lock_accepting = 0; + p->lock_closing = 0; + p->lock_close = 0; +} + +/* basic proxy socket functions */ + +static Plug sk_proxy_plug (Socket s, Plug p) +{ + Proxy_Socket ps = (Proxy_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_proxy_close (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + while (ps->lock_close) ; + sk_close(ps->sub_socket); + sfree(ps); +} + +static int sk_proxy_write (Socket s, char *data, int len) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + while (ps->lock_write) ; + if (ps->state != PROXY_STATE_ACTIVE) { + bufchain_add(&ps->pending_output_data, data, len); + return bufchain_size(&ps->pending_output_data); + } + return sk_write(ps->sub_socket, data, len); +} + +static int sk_proxy_write_oob (Socket s, char *data, int len) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + while (ps->lock_write_oob) ; + if (ps->state != PROXY_STATE_ACTIVE) { + bufchain_clear(&ps->pending_output_data); + bufchain_clear(&ps->pending_oob_output_data); + bufchain_add(&ps->pending_oob_output_data, data, len); + return len; + } + return sk_write_oob(ps->sub_socket, data, len); +} + +static void sk_proxy_flush (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + while (ps->lock_flush) ; + if (ps->state != PROXY_STATE_ACTIVE) { + ps->pending_flush = 1; + return; + } + sk_flush(ps->sub_socket); +} + +static void sk_proxy_set_private_ptr (Socket s, void *ptr) +{ + Proxy_Socket ps = (Proxy_Socket) s; + sk_set_private_ptr(ps->sub_socket, ptr); +} + +static void * sk_proxy_get_private_ptr (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + return sk_get_private_ptr(ps->sub_socket); +} + +static void sk_proxy_set_frozen (Socket s, int is_frozen) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + while (ps->lock_freeze) ; + if (ps->state != PROXY_STATE_ACTIVE) { + ps->freeze = is_frozen; + return; + } + sk_set_frozen(ps->sub_socket, is_frozen); +} + +static char * sk_proxy_socket_error (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + if (ps->error != NULL || ps->sub_socket == NULL) { + return ps->error; + } + return sk_socket_error(ps->sub_socket); +} + +/* basic proxy plug functions */ + +static int plug_proxy_closing (Plug p, char *error_msg, + int error_code, int calling_back) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + while (ps->lock_closing) ; + if (ps->state != PROXY_STATE_ACTIVE) { + ps->closing_error_msg = error_msg; + ps->closing_error_code = error_code; + ps->closing_calling_back = calling_back; + return ps->negotiate(ps, PROXY_CHANGE_CLOSING); + } + return plug_closing(ps->plug, error_msg, + error_code, calling_back); +} + +static int plug_proxy_receive (Plug p, int urgent, char *data, int len) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + while (ps->lock_receive) ; + if (ps->state != PROXY_STATE_ACTIVE) { + /* we will lose the urgentness of this data, but since most, + * if not all, of this data will be consumed by the negotiation + * process, hopefully it won't affect the protocol above us + */ + bufchain_add(&ps->pending_input_data, data, len); + ps->receive_urgent = urgent; + ps->receive_data = data; + ps->receive_len = len; + return ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + } + return plug_receive(ps->plug, urgent, data, len); +} + +static void plug_proxy_sent (Plug p, int bufsize) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + while (ps->lock_sent) ; + if (ps->state != PROXY_STATE_ACTIVE) { + ps->sent_bufsize = bufsize; + ps->negotiate(ps, PROXY_CHANGE_SENT); + return; + } + plug_sent(ps->plug, bufsize); +} + +static int plug_proxy_accepting (Plug p, void *sock) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + while (ps->lock_accepting) ; + if (ps->state != PROXY_STATE_ACTIVE) { + ps->accepting_sock = sock; + return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING); + } + return plug_accepting(ps->plug, sock); +} + +static int proxy_for_destination (SockAddr addr, char * hostname, int port) +{ + int s = 0, e = 0; + char hostip[64]; + int hostip_len, hostname_len; + char * exclude_list; + + /* we want a string representation of the IP address for comparisons */ + sk_getaddr(addr, hostip, 64); + + hostip_len = strlen(hostip); + hostname_len = strlen(hostname); + + exclude_list = cfg.proxy_exclude_list; + + /* now parse the exclude list, and see if either our IP + * or hostname matches anything in it. + */ + + while (exclude_list[s]) { + while (exclude_list[s] && + (isspace(exclude_list[s]) || + exclude_list[s] == ',')) s++; + + if (!exclude_list[s]) break; + + e = s; + + while (exclude_list[e] && + (isalnum(exclude_list[e]) || + exclude_list[e] == '-' || + exclude_list[e] == '.' || + exclude_list[e] == '*')) e++; + + if (exclude_list[s] == '*') { + /* wildcard at beginning of entry */ + + if (strnicmp(hostip + hostip_len - (e - s - 1), + exclude_list + s + 1, e - s - 1) == 0 || + strnicmp(hostname + hostname_len - (e - s - 1), + exclude_list + s + 1, e - s - 1) == 0) + return 0; /* IP/hostname range excluded. do not use proxy. */ + + } else if (exclude_list[e-1] == '*') { + /* wildcard at end of entry */ + + if (strnicmp(hostip, exclude_list + s, e - s - 1) == 0 || + strnicmp(hostname, exclude_list + s, e - s - 1) == 0) + return 0; /* IP/hostname range excluded. do not use proxy. */ + + } else { + /* no wildcard at either end, so let's try an absolute + * match (ie. a specific IP) + */ + + if (stricmp(hostip, exclude_list + s) == 0) + return 0; /* IP/hostname excluded. do not use proxy. */ + if (stricmp(hostname, exclude_list + s) == 0) + return 0; /* IP/hostname excluded. do not use proxy. */ + } + + s = e; + } + + /* no matches in the exclude list, so use the proxy */ + return 1; +} + +Socket new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, Plug plug) +{ + static struct socket_function_table socket_fn_table = { + sk_proxy_plug, + sk_proxy_close, + sk_proxy_write, + sk_proxy_write_oob, + sk_proxy_flush, + sk_proxy_set_private_ptr, + sk_proxy_get_private_ptr, + sk_proxy_set_frozen, + sk_proxy_socket_error + }; + + static struct plug_function_table plug_fn_table = { + plug_proxy_closing, + plug_proxy_receive, + plug_proxy_sent, + plug_proxy_accepting + }; + + if (cfg.proxy_type != PROXY_NONE && + proxy_for_destination(addr, hostname, port)) + { + Proxy_Socket ret; + Proxy_Plug pplug; + SockAddr proxy_addr; + char * proxy_canonical_name; + + ret = smalloc(sizeof(struct Socket_proxy_tag)); + ret->fn = &socket_fn_table; + ret->plug = plug; + ret->remote_addr = addr; + ret->remote_port = port; + + bufchain_init(&ret->pending_input_data); + bufchain_init(&ret->pending_output_data); + bufchain_init(&ret->pending_oob_output_data); + + ret->lock_close = + ret->lock_write = + ret->lock_write_oob = + ret->lock_receive = + ret->lock_flush = + ret->lock_closing = + ret->lock_sent = + ret->lock_accepting = 0; + + ret->sub_socket = NULL; + ret->state = PROXY_STATE_NEW; + + if (cfg.proxy_type == PROXY_HTTP) { + ret->negotiate = proxy_http_negotiate; + } else if (cfg.proxy_type == PROXY_SOCKS) { + ret->negotiate = proxy_socks_negotiate; + } else if (cfg.proxy_type == PROXY_TELNET) { + ret->negotiate = proxy_telnet_negotiate; + } else { + ret->error = "Network error: Unknown proxy method"; + return (Socket) ret; + } + + /* create the proxy plug to map calls from the actual + * socket into our proxy socket layer */ + pplug = smalloc(sizeof(struct Plug_proxy_tag)); + pplug->fn = &plug_fn_table; + pplug->proxy_socket = ret; + + /* look-up proxy */ + proxy_addr = sk_namelookup(cfg.proxy_host, + &proxy_canonical_name); + sfree(proxy_canonical_name); + + /* create the actual socket we will be using, + * connected to our proxy server and port. + */ + ret->sub_socket = sk_new(proxy_addr, cfg.proxy_port, + privport, oobinline, + nodelay, (Plug) pplug); + if (sk_socket_error(ret->sub_socket) != NULL) + return (Socket) ret; + + sk_addr_free(proxy_addr); + + /* start the proxy negotiation process... */ + sk_set_frozen(ret->sub_socket, 0); + ret->negotiate(ret, PROXY_CHANGE_NEW); + + return (Socket) ret; + } + + /* no proxy, so just return the direct socket */ + return sk_new(addr, port, privport, oobinline, nodelay, plug); +} + +Socket new_listener(int port, Plug plug, int local_host_only) +{ + /* TODO: SOCKS (and potentially others) support inbound + * TODO: connections via the proxy. support them. + */ + + return sk_newlistener(port, plug, local_host_only); +} + +/* ---------------------------------------------------------------------- + * HTTP CONNECT proxy type. + */ + +static int get_line_end (char * data, int len) +{ + int off = 0; + + while (off < len) + { + if (data[off] == '\n') { + /* we have a newline */ + off++; + + /* is that the only thing on this line? */ + if (off <= 2) return off; + + /* if not, then there is the possibility that this header + * continues onto the next line, if it starts with a space + * or a tab. + */ + + if (off + 1 < len && + data[off+1] != ' ' && + data[off+1] != '\t') return off; + + /* the line does continue, so we have to keep going + * until we see an the header's "real" end of line. + */ + off++; + } + + off++; + } + + return -1; +} + +int proxy_http_negotiate (Proxy_Socket p, int change) +{ + if (p->state == PROXY_STATE_NEW) { + /* we are just beginning the proxy negotiate process, + * so we'll send off the initial bits of the request. + * for this proxy method, it's just a simple HTTP + * request + */ + char buf[1024], dest[21]; + + sk_getaddr(p->remote_addr, dest, 20); + + sprintf(buf, "CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n\r\n", + dest, p->remote_port, dest, p->remote_port); + sk_write(p->sub_socket, buf, strlen(buf)); + + p->state = 1; + + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + return plug_closing(p->plug, p->closing_error_msg, + p->closing_error_code, + p->closing_calling_back); + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, p->accepting_sock); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + void *data; + int len; + int eol; + + if (p->state == 1) { + + int min_ver, maj_ver, status; + + /* get the status line */ + bufchain_prefix(&p->pending_input_data, &data, &len); + eol = get_line_end(data, len); + if (eol < 0) return 1; + + sscanf((char *)data, "HTTP/%i.%i %i", &maj_ver, &min_ver, &status); + + /* remove the status line from the input buffer. */ + bufchain_consume(&p->pending_input_data, eol); + + /* TODO: we need to support Proxy-Auth headers */ + + if (status < 200 || status > 299) { + /* error */ + /* TODO: return a more specific error message, + * TODO: based on the status code. + */ + plug_closing(p->plug, "Network error: Error while communicating with proxy", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + p->state = 2; + } + + if (p->state == 2) { + + /* get headers. we're done when we get a + * header of length 2, (ie. just "\r\n") + */ + + bufchain_prefix(&p->pending_input_data, &data, &len); + eol = get_line_end(data, len); + while (eol > 2) + { + /* TODO: Proxy-Auth stuff. in some cases, we will + * TODO: need to extract information from headers. + */ + bufchain_consume(&p->pending_input_data, eol); + bufchain_prefix(&p->pending_input_data, &data, &len); + eol = get_line_end(data, len); + } + + if (eol == 2) { + /* we're done */ + bufchain_consume(&p->pending_input_data, 2); + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + return 1; + } + + return 1; + } + } + + plug_closing(p->plug, "Network error: Unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 0; +} + +/* ---------------------------------------------------------------------- + * SOCKS proxy type (as yet unimplemented). + */ + +int proxy_socks_negotiate (Proxy_Socket p, int change) +{ + p->error = "Network error: SOCKS proxy implementation is incomplete"; + return 0; +} + +/* ---------------------------------------------------------------------- + * `Telnet' proxy type (as yet unimplemented). + * + * (This is for ad-hoc proxies where you connect to the proxy's + * telnet port and send a command such as `connect host port'. The + * command is configurable, since this proxy type is typically not + * standardised or at all well-defined.) + */ + +int proxy_telnet_negotiate (Proxy_Socket p, int change) +{ + p->error = "Network error: Telnet proxy implementation is incomplete"; + return 0; +} diff --git a/proxy.h b/proxy.h new file mode 100644 index 00000000..43d92cb0 --- /dev/null +++ b/proxy.h @@ -0,0 +1,119 @@ +/* + * Network proxy abstraction in PuTTY + * + * A proxy layer, if necessary, wedges itself between the + * network code and the higher level backend. + * + * Supported proxies: HTTP CONNECT, generic telnet + * In progress: SOCKS + */ + +#ifndef PUTTY_PROXY_H +#define PUTTY_PROXY_H + +#define PROXY_ERROR_GENERAL 8000 +#define PROXY_ERROR_UNEXPECTED 8001 + +typedef struct Socket_proxy_tag * Proxy_Socket; + +struct Socket_proxy_tag { + struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + char * error; + + Socket sub_socket; + Plug plug; + SockAddr remote_addr; + int remote_port; + + bufchain pending_output_data; + bufchain pending_oob_output_data; + int pending_flush; + bufchain pending_input_data; + +#define PROXY_STATE_NEW -1 +#define PROXY_STATE_ACTIVE 0 + + int state; /* proxy states greater than 0 are implementation + * dependent, but represent various stages/states + * of the initialization/setup/negotiation with the + * proxy server. + */ + int freeze; /* should we freeze the underlying socket when + * we are done with the proxy negotiation? this + * simply caches the value of sk_set_frozen calls. + */ + +#define PROXY_CHANGE_NEW -1 +#define PROXY_CHANGE_CLOSING 0 +#define PROXY_CHANGE_SENT 1 +#define PROXY_CHANGE_RECEIVE 2 +#define PROXY_CHANGE_ACCEPTING 3 + + /* something has changed (a call from the sub socket + * layer into our Proxy Plug layer, or we were just + * created, etc), so the proxy layer needs to handle + * this change (the type of which is the second argument) + * and further the proxy negotiation process. + */ + + int (*negotiate) (Proxy_Socket /* this */, int /* change type */); + + /* current arguments of plug handlers + * (for use by proxy's negotiate function) + */ + + /* closing */ + char *closing_error_msg; + int closing_error_code; + int closing_calling_back; + + /* receive */ + int receive_urgent; + char *receive_data; + int receive_len; + + /* sent */ + int sent_bufsize; + + /* accepting */ + void *accepting_sock; + + /* spin locks, for the critical switch from negotiating + * to active state. we have to dump all of our pending + * buffers without new events (read, writes, etc) corrupting + * things. we should not have built up a large amount of + * pending data during negotiation, so hopefully this will + * not have a large effect on performance. + */ + + char lock_close; + char lock_write; + char lock_write_oob; + char lock_receive; + char lock_flush; + char lock_closing; + char lock_sent; + char lock_accepting; + char lock_freeze; + +}; + +typedef struct Plug_proxy_tag * Proxy_Plug; + +struct Plug_proxy_tag { + struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + Proxy_Socket proxy_socket; + +}; + +extern void proxy_activate (Proxy_Socket); + +extern int proxy_http_negotiate (Proxy_Socket, int); +extern int proxy_telnet_negotiate (Proxy_Socket, int); +extern int proxy_socks_negotiate (Proxy_Socket, int); + +#endif diff --git a/putty.h b/putty.h index 79f0b8d9..e2e4d0d4 100644 --- a/putty.h +++ b/putty.h @@ -242,6 +242,15 @@ typedef struct { int warn_on_close; int ping_interval; /* in seconds */ int tcp_nodelay; + /* Proxy options */ + char proxy_exclude_list[512]; + enum { PROXY_NONE, PROXY_HTTP, PROXY_SOCKS, PROXY_TELNET } proxy_type; + char proxy_host[512]; + int proxy_port; + char proxy_username[32]; + char proxy_password[32]; + char proxy_telnet_command[512]; + int proxy_socks_version; /* SSH options */ char remote_cmd[512]; char remote_cmd2[512]; /* fallback if the first fails diff --git a/raw.c b/raw.c index 0d27bb77..58b060c5 100644 --- a/raw.c +++ b/raw.c @@ -92,7 +92,7 @@ static char *raw_init(char *host, int port, char **realhost, int nodelay) sprintf(buf, "Connecting to %.100s port %d", addrbuf, port); logevent(buf); } - s = sk_new(addr, port, 0, 1, nodelay, &fn_table_ptr); + s = new_connection(addr, *realhost, port, 0, 1, nodelay, &fn_table_ptr); if ((err = sk_socket_error(s))) return err; diff --git a/rlogin.c b/rlogin.c index a80d12e3..4038a2ff 100644 --- a/rlogin.c +++ b/rlogin.c @@ -122,7 +122,7 @@ static char *rlogin_init(char *host, int port, char **realhost, int nodelay) sprintf(buf, "Connecting to %.100s port %d", addrbuf, port); logevent(buf); } - s = sk_new(addr, port, 1, 0, nodelay, &fn_table_ptr); + s = new_connection(addr, *realhost, port, 1, 0, nodelay, &fn_table_ptr); if ((err = sk_socket_error(s))) return err; diff --git a/settings.c b/settings.c index 55955acc..8764bb32 100644 --- a/settings.c +++ b/settings.c @@ -148,6 +148,17 @@ void save_settings(char *section, int do_host, Config * cfg) write_setting_i(sesskey, "TCPNoDelay", cfg->tcp_nodelay); write_setting_s(sesskey, "TerminalType", cfg->termtype); write_setting_s(sesskey, "TerminalSpeed", cfg->termspeed); + + /* proxy settings */ + write_setting_s(sesskey, "ProxyExcludeList", cfg->proxy_exclude_list); + write_setting_i(sesskey, "ProxyType", cfg->proxy_type); + write_setting_s(sesskey, "ProxyHost", cfg->proxy_host); + write_setting_i(sesskey, "ProxyPort", cfg->proxy_port); + write_setting_s(sesskey, "ProxyUsername", cfg->proxy_username); + write_setting_s(sesskey, "ProxyPassword", cfg->proxy_password); + write_setting_s(sesskey, "ProxyTelnetCommand", cfg->proxy_telnet_command); + write_setting_i(sesskey, "ProxySOCKSVersion", cfg->proxy_socks_version); + { char buf[2 * sizeof(cfg->environmt)], *p, *q; p = buf; @@ -343,6 +354,22 @@ void load_settings(char *section, int do_host, Config * cfg) sizeof(cfg->termtype)); gpps(sesskey, "TerminalSpeed", "38400,38400", cfg->termspeed, sizeof(cfg->termspeed)); + + /* proxy settings */ + gpps(sesskey, "ProxyExcludeList", "", cfg->proxy_exclude_list, + sizeof(cfg->proxy_exclude_list)); + gppi(sesskey, "ProxyType", PROXY_NONE, &cfg->proxy_type); + gpps(sesskey, "ProxyHost", "proxy", cfg->proxy_host, + sizeof(cfg->proxy_host)); + gppi(sesskey, "ProxyPort", 80, &cfg->proxy_port); + gpps(sesskey, "ProxyUsername", "", cfg->proxy_username, + sizeof(cfg->proxy_username)); + gpps(sesskey, "ProxyPassword", "", cfg->proxy_password, + sizeof(cfg->proxy_password)); + gpps(sesskey, "ProxyTelnetCommand", "connect %host %port", + cfg->proxy_telnet_command, sizeof(cfg->proxy_telnet_command)); + gppi(sesskey, "ProxySOCKSVersion", 5, &cfg->proxy_socks_version); + { char buf[2 * sizeof(cfg->environmt)], *p, *q; gpps(sesskey, "Environment", "", buf, sizeof(buf)); diff --git a/ssh.c b/ssh.c index 94da21d4..6cc79bff 100644 --- a/ssh.c +++ b/ssh.c @@ -1872,7 +1872,7 @@ static char *connect_to_host(char *host, int port, char **realhost, int nodelay) sprintf(buf, "Connecting to %.100s port %d", addrbuf, port); logevent(buf); } - s = sk_new(addr, port, 0, 1, nodelay, &fn_table_ptr); + s = new_connection(addr, *realhost, port, 0, 1, nodelay, &fn_table_ptr); if ((err = sk_socket_error(s))) { s = NULL; return err; diff --git a/telnet.c b/telnet.c index 5e5fb247..8b3de1e5 100644 --- a/telnet.c +++ b/telnet.c @@ -636,7 +636,7 @@ static char *telnet_init(char *host, int port, char **realhost, int nodelay) sprintf(buf, "Connecting to %.100s port %d", addrbuf, port); logevent(buf); } - s = sk_new(addr, port, 0, 1, nodelay, &fn_table_ptr); + s = new_connection(addr, *realhost, port, 0, 1, nodelay, &fn_table_ptr); if ((err = sk_socket_error(s))) return err; diff --git a/windlg.c b/windlg.c index 316115e5..f4a63de3 100644 --- a/windlg.c +++ b/windlg.c @@ -438,6 +438,32 @@ enum { IDCX_ABOUT = IDC_NODELAY, connectionpanelend, + proxypanelstart, + IDC_TITLE_PROXY, + IDC_BOX_PROXY1, + IDC_PROXYTYPESTATIC, + IDC_PROXYTYPENONE, + IDC_PROXYTYPEHTTP, + IDC_PROXYTYPESOCKS, + IDC_PROXYTYPETELNET, + IDC_PROXYHOSTSTATIC, + IDC_PROXYHOSTEDIT, + IDC_PROXYPORTSTATIC, + IDC_PROXYPORTEDIT, + IDC_PROXYEXCLUDESTATIC, + IDC_PROXYEXCLUDEEDIT, + IDC_PROXYUSERSTATIC, + IDC_PROXYUSEREDIT, + IDC_PROXYPASSSTATIC, + IDC_PROXYPASSEDIT, + IDC_BOX_PROXY2, + IDC_PROXYTELNETCMDSTATIC, + IDC_PROXYTELNETCMDEDIT, + IDC_PROXYSOCKSVERSTATIC, + IDC_PROXYSOCKSVER5, + IDC_PROXYSOCKSVER4, + proxypanelend, + telnetpanelstart, IDC_TITLE_TELNET, IDC_BOX_TELNET1, @@ -1243,6 +1269,20 @@ static void init_dlg_ctrls(HWND hwnd, int keepsess) CheckDlgButton(hwnd, IDC_LPORT_ALL, cfg.lport_acceptall); CheckDlgButton(hwnd, IDC_RPORT_ALL, cfg.rport_acceptall); CheckRadioButton(hwnd, IDC_PFWDLOCAL, IDC_PFWDREMOTE, IDC_PFWDLOCAL); + + /* proxy config */ + CheckRadioButton(hwnd, IDC_PROXYTYPENONE, IDC_PROXYTYPETELNET, + cfg.proxy_type == PROXY_HTTP ? IDC_PROXYTYPEHTTP : + cfg.proxy_type == PROXY_SOCKS ? IDC_PROXYTYPESOCKS : + cfg.proxy_type == PROXY_TELNET ? IDC_PROXYTYPETELNET : IDC_PROXYTYPENONE); + SetDlgItemText(hwnd, IDC_PROXYHOSTEDIT, cfg.proxy_host); + SetDlgItemInt(hwnd, IDC_PROXYPORTEDIT, cfg.proxy_port, FALSE); + SetDlgItemText(hwnd, IDC_PROXYEXCLUDEEDIT, cfg.proxy_exclude_list); + SetDlgItemText(hwnd, IDC_PROXYTELNETCMDEDIT, cfg.proxy_telnet_command); + SetDlgItemText(hwnd, IDC_PROXYUSEREDIT, cfg.proxy_username); + SetDlgItemText(hwnd, IDC_PROXYPASSEDIT, cfg.proxy_password); + CheckRadioButton(hwnd, IDC_PROXYSOCKSVER5, IDC_PROXYSOCKSVER4, + cfg.proxy_socks_version == 4 ? IDC_PROXYSOCKSVER4 : IDC_PROXYSOCKSVER5); } struct treeview_faff { @@ -1687,6 +1727,41 @@ static void create_controls(HWND hwnd, int dlgtype, int panel) } } + if (panel == proxypanelstart) { + /* The Proxy panel. Accelerators used: [acgoh] ntslypeuwmv */ + struct ctlpos cp; + ctlposinit(&cp, hwnd, 80, 3, 13); + if (dlgtype == 0) { + bartitle(&cp, "Options controlling proxy usage", + IDC_TITLE_PROXY); + beginbox(&cp, "Proxy basics", IDC_BOX_PROXY1); + radioline(&cp, "Proxy type:", IDC_PROXYTYPESTATIC, 4, + "&None", IDC_PROXYTYPENONE, + "H&TTP", IDC_PROXYTYPEHTTP, + "&SOCKS", IDC_PROXYTYPESOCKS, + "Te&lnet", IDC_PROXYTYPETELNET, NULL); + multiedit(&cp, + "Prox&y Host", IDC_PROXYHOSTSTATIC, IDC_PROXYHOSTEDIT, 80, + "&Port", IDC_PROXYPORTSTATIC, IDC_PROXYPORTEDIT, 20, NULL); + multiedit(&cp, + "&Exclude Hosts/IPs", IDC_PROXYEXCLUDESTATIC, + IDC_PROXYEXCLUDEEDIT, 100, NULL); + staticedit(&cp, "&Username", IDC_PROXYUSERSTATIC, + IDC_PROXYUSEREDIT, 60); + staticedit(&cp, "Pass&word", IDC_PROXYPASSSTATIC, + IDC_PROXYPASSEDIT, 60); + endbox(&cp); + beginbox(&cp, "Misc. proxy settings", IDC_BOX_PROXY2); + multiedit(&cp, + "Telnet co&mmand", IDC_PROXYTELNETCMDSTATIC, + IDC_PROXYTELNETCMDEDIT, 100, NULL); + radioline(&cp, "SOCKS &Version", IDC_PROXYSOCKSVERSTATIC, + 2, "Version 5", IDC_PROXYSOCKSVER5, "Version 4", + IDC_PROXYSOCKSVER4, NULL); + endbox(&cp); + } + } + if (panel == telnetpanelstart) { /* The Telnet panel. Accelerators used: [acgoh] svldr bftk */ struct ctlpos cp; @@ -1957,6 +2032,7 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg, treeview_insert(&tvfaff, 1, "Colours"); treeview_insert(&tvfaff, 0, "Connection"); if (dlgtype == 0) { + treeview_insert(&tvfaff, 1, "Proxy"); treeview_insert(&tvfaff, 1, "Telnet"); treeview_insert(&tvfaff, 1, "Rlogin"); if (backends[3].backend != NULL) { @@ -2040,6 +2116,8 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg, create_controls(hwnd, dlgtype, tunnelspanelstart); if (!strcmp(buffer, "Connection")) create_controls(hwnd, dlgtype, connectionpanelstart); + if (!strcmp(buffer, "Proxy")) + create_controls(hwnd, dlgtype, proxypanelstart); if (!strcmp(buffer, "Telnet")) create_controls(hwnd, dlgtype, telnetpanelstart); if (!strcmp(buffer, "Rlogin")) @@ -2741,6 +2819,73 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg, GetDlgItemText(hwnd, IDC_TTEDIT, cfg.termtype, sizeof(cfg.termtype) - 1); break; + + /* proxy config */ + case IDC_PROXYHOSTEDIT: + if (HIWORD(wParam) == EN_CHANGE) + GetDlgItemText(hwnd, IDC_PROXYHOSTEDIT, cfg.proxy_host, + sizeof(cfg.proxy_host) - 1); + break; + case IDC_PROXYPORTEDIT: + if (HIWORD(wParam) == EN_CHANGE) { + GetDlgItemText(hwnd, IDC_PROXYPORTEDIT, portname, 31); + if (isdigit(portname[0])) + MyGetDlgItemInt(hwnd, IDC_PROXYPORTEDIT, &cfg.proxy_port); + else { + service = getservbyname(portname, NULL); + if (service) + cfg.proxy_port = ntohs(service->s_port); + else + cfg.proxy_port = 0; + } + } + break; + case IDC_PROXYEXCLUDEEDIT: + if (HIWORD(wParam) == EN_CHANGE) + GetDlgItemText(hwnd, IDC_PROXYEXCLUDEEDIT, + cfg.proxy_exclude_list, + sizeof(cfg.proxy_exclude_list) - 1); + break; + case IDC_PROXYUSEREDIT: + if (HIWORD(wParam) == EN_CHANGE) + GetDlgItemText(hwnd, IDC_PROXYUSEREDIT, + cfg.proxy_username, + sizeof(cfg.proxy_username) - 1); + break; + case IDC_PROXYPASSEDIT: + if (HIWORD(wParam) == EN_CHANGE) + GetDlgItemText(hwnd, IDC_PROXYPASSEDIT, + cfg.proxy_password, + sizeof(cfg.proxy_password) - 1); + break; + case IDC_PROXYTELNETCMDEDIT: + if (HIWORD(wParam) == EN_CHANGE) + GetDlgItemText(hwnd, IDC_PROXYTELNETCMDEDIT, + cfg.proxy_telnet_command, + sizeof(cfg.proxy_telnet_command) - 1); + break; + case IDC_PROXYSOCKSVER5: + case IDC_PROXYSOCKSVER4: + if (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED) { + cfg.proxy_socks_version = + IsDlgButtonChecked(hwnd, IDC_PROXYSOCKSVER4) ? 4 : 5; + } + break; + case IDC_PROXYTYPENONE: + case IDC_PROXYTYPEHTTP: + case IDC_PROXYTYPESOCKS: + case IDC_PROXYTYPETELNET: + if (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED) { + cfg.proxy_type = + IsDlgButtonChecked(hwnd, IDC_PROXYTYPEHTTP) ? PROXY_HTTP : + IsDlgButtonChecked(hwnd, IDC_PROXYTYPESOCKS) ? PROXY_SOCKS : + IsDlgButtonChecked(hwnd, IDC_PROXYTYPETELNET) ? PROXY_TELNET : + PROXY_NONE; + } + break; + case IDC_LGFEDIT: if (HIWORD(wParam) == EN_CHANGE) GetDlgItemText(hwnd, IDC_LGFEDIT, cfg.logfilename, diff --git a/winnet.c b/winnet.c index 0dac18e2..068b9b49 100644 --- a/winnet.c +++ b/winnet.c @@ -393,6 +393,9 @@ static void sk_tcp_flush(Socket s) static void sk_tcp_close(Socket s); static int sk_tcp_write(Socket s, char *data, int len); static int sk_tcp_write_oob(Socket s, char *data, int len); +static void sk_tcp_set_private_ptr(Socket s, void *ptr); +static void *sk_tcp_get_private_ptr(Socket s); +static void sk_tcp_set_frozen(Socket s, int is_frozen); static char *sk_tcp_socket_error(Socket s); extern char *do_select(SOCKET skt, int startup); @@ -405,6 +408,9 @@ Socket sk_register(void *sock, Plug plug) sk_tcp_write, sk_tcp_write_oob, sk_tcp_flush, + sk_tcp_set_private_ptr, + sk_tcp_get_private_ptr, + sk_tcp_set_frozen, sk_tcp_socket_error }; @@ -459,6 +465,9 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline, sk_tcp_write, sk_tcp_write_oob, sk_tcp_flush, + sk_tcp_set_private_ptr, + sk_tcp_get_private_ptr, + sk_tcp_set_frozen, sk_tcp_socket_error }; @@ -633,6 +642,9 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only) sk_tcp_write, sk_tcp_write_oob, sk_tcp_flush, + sk_tcp_set_private_ptr, + sk_tcp_get_private_ptr, + sk_tcp_set_frozen, sk_tcp_socket_error }; @@ -1043,13 +1055,13 @@ void net_pending_errors(void) * Each socket abstraction contains a `void *' private field in * which the client can keep state. */ -void sk_set_private_ptr(Socket sock, void *ptr) +static void sk_tcp_set_private_ptr(Socket sock, void *ptr) { Actual_Socket s = (Actual_Socket) sock; s->private_ptr = ptr; } -void *sk_get_private_ptr(Socket sock) +static void *sk_tcp_get_private_ptr(Socket sock) { Actual_Socket s = (Actual_Socket) sock; return s->private_ptr; @@ -1070,7 +1082,7 @@ static char *sk_tcp_socket_error(Socket sock) return s->error; } -void sk_set_frozen(Socket sock, int is_frozen) +static void sk_tcp_set_frozen(Socket sock, int is_frozen) { Actual_Socket s = (Actual_Socket) sock; if (s->frozen == is_frozen) diff --git a/x11fwd.c b/x11fwd.c index 480d8560..6f7be40a 100644 --- a/x11fwd.c +++ b/x11fwd.c @@ -188,7 +188,7 @@ char *x11_init(Socket * s, char *display, void *c) pr->throttled = pr->throttle_override = 0; pr->c = c; - pr->s = *s = sk_new(addr, port, 0, 1, 0, (Plug) pr); + pr->s = *s = new_connection(addr, dummy_realhost, port, 0, 1, 0, (Plug) pr); if ((err = sk_socket_error(*s))) { sfree(pr); return err; -- 2.11.0