1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00
putty-source/proxy.c
Simon Tatham 3214563d8e Convert a lot of 'int' variables to 'bool'.
My normal habit these days, in new code, is to treat int and bool as
_almost_ completely separate types. I'm still willing to use C's
implicit test for zero on an integer (e.g. 'if (!blob.len)' is fine,
no need to spell it out as blob.len != 0), but generally, if a
variable is going to be conceptually a boolean, I like to declare it
bool and assign to it using 'true' or 'false' rather than 0 or 1.

PuTTY is an exception, because it predates the C99 bool, and I've
stuck to its existing coding style even when adding new code to it.
But it's been annoying me more and more, so now that I've decided C99
bool is an acceptable thing to require from our toolchain in the first
place, here's a quite thorough trawl through the source doing
'boolification'. Many variables and function parameters are now typed
as bool rather than int; many assignments of 0 or 1 to those variables
are now spelled 'true' or 'false'.

I managed this thorough conversion with the help of a custom clang
plugin that I wrote to trawl the AST and apply heuristics to point out
where things might want changing. So I've even managed to do a decent
job on parts of the code I haven't looked at in years!

To make the plugin's work easier, I pushed platform front ends
generally in the direction of using standard 'bool' in preference to
platform-specific boolean types like Windows BOOL or GTK's gboolean;
I've left the platform booleans in places they _have_ to be for the
platform APIs to work right, but variables only used by my own code
have been converted wherever I found them.

In a few places there are int values that look very like booleans in
_most_ of the places they're used, but have a rarely-used third value,
or a distinction between different nonzero values that most users
don't care about. In these cases, I've _removed_ uses of 'true' and
'false' for the return values, to emphasise that there's something
more subtle going on than a simple boolean answer:
 - the 'multisel' field in dialog.h's list box structure, for which
   the GTK front end in particular recognises a difference between 1
   and 2 but nearly everything else treats as boolean
 - the 'urgent' parameter to plug_receive, where 1 vs 2 tells you
   something about the specific location of the urgent pointer, but
   most clients only care about 0 vs 'something nonzero'
 - the return value of wc_match, where -1 indicates a syntax error in
   the wildcard.
 - the return values from SSH-1 RSA-key loading functions, which use
   -1 for 'wrong passphrase' and 0 for all other failures (so any
   caller which already knows it's not loading an _encrypted private_
   key can treat them as boolean)
 - term->esc_query, and the 'query' parameter in toggle_mode in
   terminal.c, which _usually_ hold 0 for ESC[123h or 1 for ESC[?123h,
   but can also hold -1 for some other intervening character that we
   don't support.

In a few places there's an integer that I haven't turned into a bool
even though it really _can_ only take values 0 or 1 (and, as above,
tried to make the call sites consistent in not calling those values
true and false), on the grounds that I thought it would make it more
confusing to imply that the 0 value was in some sense 'negative' or
bad and the 1 positive or good:
 - the return value of plug_accepting uses the POSIXish convention of
   0=success and nonzero=error; I think if I made it bool then I'd
   also want to reverse its sense, and that's a job for a separate
   piece of work.
 - the 'screen' parameter to lineptr() in terminal.c, where 0 and 1
   represent the default and alternate screens. There's no obvious
   reason why one of those should be considered 'true' or 'positive'
   or 'success' - they're just indices - so I've left it as int.

ssh_scp_recv had particularly confusing semantics for its previous int
return value: its call sites used '<= 0' to check for error, but it
never actually returned a negative number, just 0 or 1. Now the
function and its call sites agree that it's a bool.

In a couple of places I've renamed variables called 'ret', because I
don't like that name any more - it's unclear whether it means the
return value (in preparation) for the _containing_ function or the
return value received from a subroutine call, and occasionally I've
accidentally used the same variable for both and introduced a bug. So
where one of those got in my way, I've renamed it to 'toret' or 'retd'
(the latter short for 'returned') in line with my usual modern
practice, but I haven't done a thorough job of finding all of them.

Finally, one amusing side effect of doing this is that I've had to
separate quite a few chained assignments. It used to be perfectly fine
to write 'a = b = c = TRUE' when a,b,c were int and TRUE was just a
the 'true' defined by stdbool.h, that idiom provokes a warning from
gcc: 'suggest parentheses around assignment used as truth value'!
2018-11-03 13:45:00 +00:00

1589 lines
44 KiB
C

/*
* Network proxy abstraction in PuTTY
*
* A proxy layer, if necessary, wedges itself between the network
* code and the higher level backend.
*/
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include "putty.h"
#include "network.h"
#include "proxy.h"
#define do_proxy_dns(conf) \
(conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \
(conf_get_int(conf, CONF_proxy_dns) == AUTO && \
conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4))
/*
* Call this when proxy negotiation is complete, so that this
* socket can begin working normally.
*/
void proxy_activate (ProxySocket *p)
{
void *data;
int len;
long output_before, output_after;
p->state = PROXY_STATE_ACTIVE;
/* we want to ignore new receive events until we have sent
* all of our buffered receive data.
*/
sk_set_frozen(p->sub_socket, true);
/* how many bytes of output have we buffered? */
output_before = bufchain_size(&p->pending_oob_output_data) +
bufchain_size(&p->pending_output_data);
/* and keep track of how many bytes do not get sent. */
output_after = 0;
/* send buffered OOB writes */
while (bufchain_size(&p->pending_oob_output_data) > 0) {
bufchain_prefix(&p->pending_oob_output_data, &data, &len);
output_after += sk_write_oob(p->sub_socket, data, len);
bufchain_consume(&p->pending_oob_output_data, len);
}
/* send buffered normal writes */
while (bufchain_size(&p->pending_output_data) > 0) {
bufchain_prefix(&p->pending_output_data, &data, &len);
output_after += sk_write(p->sub_socket, data, len);
bufchain_consume(&p->pending_output_data, len);
}
/* if we managed to send any data, let the higher levels know. */
if (output_after < output_before)
plug_sent(p->plug, output_after);
/* if we were asked to flush the output during
* the proxy negotiation process, do so now.
*/
if (p->pending_flush) sk_flush(p->sub_socket);
/* if we have a pending EOF to send, send it */
if (p->pending_eof) sk_write_eof(p->sub_socket);
/* if the backend wanted the socket unfrozen, try to unfreeze.
* our set_frozen handler will flush buffered receive data before
* unfreezing the actual underlying socket.
*/
if (!p->freeze)
sk_set_frozen(&p->sock, 0);
}
/* basic proxy socket functions */
static Plug *sk_proxy_plug (Socket *s, Plug *p)
{
ProxySocket *ps = container_of(s, ProxySocket, sock);
Plug *ret = ps->plug;
if (p)
ps->plug = p;
return ret;
}
static void sk_proxy_close (Socket *s)
{
ProxySocket *ps = container_of(s, ProxySocket, sock);
sk_close(ps->sub_socket);
sk_addr_free(ps->remote_addr);
sfree(ps);
}
static int sk_proxy_write (Socket *s, const void *data, int len)
{
ProxySocket *ps = container_of(s, ProxySocket, sock);
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, const void *data, int len)
{
ProxySocket *ps = container_of(s, ProxySocket, sock);
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_write_eof (Socket *s)
{
ProxySocket *ps = container_of(s, ProxySocket, sock);
if (ps->state != PROXY_STATE_ACTIVE) {
ps->pending_eof = true;
return;
}
sk_write_eof(ps->sub_socket);
}
static void sk_proxy_flush (Socket *s)
{
ProxySocket *ps = container_of(s, ProxySocket, sock);
if (ps->state != PROXY_STATE_ACTIVE) {
ps->pending_flush = true;
return;
}
sk_flush(ps->sub_socket);
}
static void sk_proxy_set_frozen (Socket *s, bool is_frozen)
{
ProxySocket *ps = container_of(s, ProxySocket, sock);
if (ps->state != PROXY_STATE_ACTIVE) {
ps->freeze = is_frozen;
return;
}
/* handle any remaining buffered recv data first */
if (bufchain_size(&ps->pending_input_data) > 0) {
ps->freeze = is_frozen;
/* loop while we still have buffered data, and while we are
* unfrozen. the plug_receive call in the loop could result
* in a call back into this function refreezing the socket,
* so we have to check each time.
*/
while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) {
void *data;
char databuf[512];
int len;
bufchain_prefix(&ps->pending_input_data, &data, &len);
if (len > lenof(databuf))
len = lenof(databuf);
memcpy(databuf, data, len);
bufchain_consume(&ps->pending_input_data, len);
plug_receive(ps->plug, 0, databuf, len);
}
/* if we're still frozen, we'll have to wait for another
* call from the backend to finish unbuffering the data.
*/
if (ps->freeze) return;
}
sk_set_frozen(ps->sub_socket, is_frozen);
}
static const char * sk_proxy_socket_error (Socket *s)
{
ProxySocket *ps = container_of(s, ProxySocket, sock);
if (ps->error != NULL || ps->sub_socket == NULL) {
return ps->error;
}
return sk_socket_error(ps->sub_socket);
}
/* basic proxy plug functions */
static void plug_proxy_log(Plug *plug, int type, SockAddr *addr, int port,
const char *error_msg, int error_code)
{
ProxySocket *ps = container_of(plug, ProxySocket, plugimpl);
plug_log(ps->plug, type, addr, port, error_msg, error_code);
}
static void plug_proxy_closing (Plug *p, const char *error_msg,
int error_code, bool calling_back)
{
ProxySocket *ps = container_of(p, ProxySocket, plugimpl);
if (ps->state != PROXY_STATE_ACTIVE) {
ps->closing_error_msg = error_msg;
ps->closing_error_code = error_code;
ps->closing_calling_back = calling_back;
ps->negotiate(ps, PROXY_CHANGE_CLOSING);
} else {
plug_closing(ps->plug, error_msg, error_code, calling_back);
}
}
static void plug_proxy_receive (Plug *p, int urgent, char *data, int len)
{
ProxySocket *ps = container_of(p, ProxySocket, plugimpl);
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 != 0);
ps->receive_data = data;
ps->receive_len = len;
ps->negotiate(ps, PROXY_CHANGE_RECEIVE);
} else {
plug_receive(ps->plug, urgent, data, len);
}
}
static void plug_proxy_sent (Plug *p, int bufsize)
{
ProxySocket *ps = container_of(p, ProxySocket, plugimpl);
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,
accept_fn_t constructor, accept_ctx_t ctx)
{
ProxySocket *ps = container_of(p, ProxySocket, plugimpl);
if (ps->state != PROXY_STATE_ACTIVE) {
ps->accepting_constructor = constructor;
ps->accepting_ctx = ctx;
return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING);
}
return plug_accepting(ps->plug, constructor, ctx);
}
/*
* This function can accept a NULL pointer as `addr', in which case
* it will only check the host name.
*/
bool proxy_for_destination (SockAddr *addr, const char *hostname,
int port, Conf *conf)
{
int s = 0, e = 0;
char hostip[64];
int hostip_len, hostname_len;
const char *exclude_list;
/*
* Special local connections such as Unix-domain sockets
* unconditionally cannot be proxied, even in proxy-localhost
* mode. There just isn't any way to ask any known proxy type for
* them.
*/
if (addr && sk_address_is_special_local(addr))
return false; /* do not proxy */
/*
* Check the host name and IP against the hard-coded
* representations of `localhost'.
*/
if (!conf_get_bool(conf, CONF_even_proxy_localhost) &&
(sk_hostname_is_local(hostname) ||
(addr && sk_address_is_local(addr))))
return false; /* do not proxy */
/* we want a string representation of the IP address for comparisons */
if (addr) {
sk_getaddr(addr, hostip, 64);
hostip_len = strlen(hostip);
} else
hostip_len = 0; /* placate gcc; shouldn't be required */
hostname_len = strlen(hostname);
exclude_list = conf_get_str(conf, CONF_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((unsigned char)exclude_list[s]) ||
exclude_list[s] == ',')) s++;
if (!exclude_list[s]) break;
e = s;
while (exclude_list[e] &&
(isalnum((unsigned char)exclude_list[e]) ||
exclude_list[e] == '-' ||
exclude_list[e] == '.' ||
exclude_list[e] == '*')) e++;
if (exclude_list[s] == '*') {
/* wildcard at beginning of entry */
if ((addr && 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) {
/* IP/hostname range excluded. do not use proxy. */
return false;
}
} else if (exclude_list[e-1] == '*') {
/* wildcard at end of entry */
if ((addr && strnicmp(hostip, exclude_list + s, e - s - 1) == 0) ||
strnicmp(hostname, exclude_list + s, e - s - 1) == 0) {
/* IP/hostname range excluded. do not use proxy. */
return false;
}
} else {
/* no wildcard at either end, so let's try an absolute
* match (ie. a specific IP)
*/
if (addr && strnicmp(hostip, exclude_list + s, e - s) == 0)
return false; /* IP/hostname excluded. do not use proxy. */
if (strnicmp(hostname, exclude_list + s, e - s) == 0)
return false; /* IP/hostname excluded. do not use proxy. */
}
s = e;
/* Make sure we really have reached the next comma or end-of-string */
while (exclude_list[s] &&
!isspace((unsigned char)exclude_list[s]) &&
exclude_list[s] != ',') s++;
}
/* no matches in the exclude list, so use the proxy */
return true;
}
static char *dns_log_msg(const char *host, int addressfamily,
const char *reason)
{
return dupprintf("Looking up host \"%s\"%s for %s", host,
(addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
""), reason);
}
SockAddr *name_lookup(const char *host, int port, char **canonicalname,
Conf *conf, int addressfamily, LogContext *logctx,
const char *reason)
{
if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
do_proxy_dns(conf) &&
proxy_for_destination(NULL, host, port, conf)) {
if (logctx)
logeventf(logctx, "Leaving host lookup to proxy of \"%s\""
" (for %s)", host, reason);
*canonicalname = dupstr(host);
return sk_nonamelookup(host);
} else {
if (logctx)
logevent_and_free(
logctx, dns_log_msg(host, addressfamily, reason));
return sk_namelookup(host, canonicalname, addressfamily);
}
}
static const struct SocketVtable ProxySocket_sockvt = {
sk_proxy_plug,
sk_proxy_close,
sk_proxy_write,
sk_proxy_write_oob,
sk_proxy_write_eof,
sk_proxy_flush,
sk_proxy_set_frozen,
sk_proxy_socket_error,
NULL, /* peer_info */
};
static const struct PlugVtable ProxySocket_plugvt = {
plug_proxy_log,
plug_proxy_closing,
plug_proxy_receive,
plug_proxy_sent,
plug_proxy_accepting
};
Socket *new_connection(SockAddr *addr, const char *hostname,
int port, bool privport,
bool oobinline, bool nodelay, bool keepalive,
Plug *plug, Conf *conf)
{
if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
proxy_for_destination(addr, hostname, port, conf))
{
ProxySocket *ret;
SockAddr *proxy_addr;
char *proxy_canonical_name;
const char *proxy_type;
Socket *sret;
int type;
if ((sret = platform_new_connection(addr, hostname, port, privport,
oobinline, nodelay, keepalive,
plug, conf)) !=
NULL)
return sret;
ret = snew(ProxySocket);
ret->sock.vt = &ProxySocket_sockvt;
ret->plugimpl.vt = &ProxySocket_plugvt;
ret->conf = conf_copy(conf);
ret->plug = plug;
ret->remote_addr = addr; /* will need to be freed on close */
ret->remote_port = port;
ret->error = NULL;
ret->pending_flush = false;
ret->pending_eof = false;
ret->freeze = false;
bufchain_init(&ret->pending_input_data);
bufchain_init(&ret->pending_output_data);
bufchain_init(&ret->pending_oob_output_data);
ret->sub_socket = NULL;
ret->state = PROXY_STATE_NEW;
ret->negotiate = NULL;
type = conf_get_int(conf, CONF_proxy_type);
if (type == PROXY_HTTP) {
ret->negotiate = proxy_http_negotiate;
proxy_type = "HTTP";
} else if (type == PROXY_SOCKS4) {
ret->negotiate = proxy_socks4_negotiate;
proxy_type = "SOCKS 4";
} else if (type == PROXY_SOCKS5) {
ret->negotiate = proxy_socks5_negotiate;
proxy_type = "SOCKS 5";
} else if (type == PROXY_TELNET) {
ret->negotiate = proxy_telnet_negotiate;
proxy_type = "Telnet";
} else {
ret->error = "Proxy error: Unknown proxy method";
return &ret->sock;
}
{
char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect"
" to %s:%d", proxy_type,
conf_get_str(conf, CONF_proxy_host),
conf_get_int(conf, CONF_proxy_port),
hostname, port);
plug_log(plug, 2, NULL, 0, logmsg, 0);
sfree(logmsg);
}
{
char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host),
conf_get_int(conf, CONF_addressfamily),
"proxy");
plug_log(plug, 2, NULL, 0, logmsg, 0);
sfree(logmsg);
}
/* look-up proxy */
proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host),
&proxy_canonical_name,
conf_get_int(conf, CONF_addressfamily));
if (sk_addr_error(proxy_addr) != NULL) {
ret->error = "Proxy error: Unable to resolve proxy host name";
sk_addr_free(proxy_addr);
return &ret->sock;
}
sfree(proxy_canonical_name);
{
char addrbuf[256], *logmsg;
sk_getaddr(proxy_addr, addrbuf, lenof(addrbuf));
logmsg = dupprintf("Connecting to %s proxy at %s port %d",
proxy_type, addrbuf,
conf_get_int(conf, CONF_proxy_port));
plug_log(plug, 2, NULL, 0, logmsg, 0);
sfree(logmsg);
}
/* create the actual socket we will be using,
* connected to our proxy server and port.
*/
ret->sub_socket = sk_new(proxy_addr,
conf_get_int(conf, CONF_proxy_port),
privport, oobinline,
nodelay, keepalive, &ret->plugimpl);
if (sk_socket_error(ret->sub_socket) != NULL)
return &ret->sock;
/* start the proxy negotiation process... */
sk_set_frozen(ret->sub_socket, 0);
ret->negotiate(ret, PROXY_CHANGE_NEW);
return &ret->sock;
}
/* no proxy, so just return the direct socket */
return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug);
}
Socket *new_listener(const char *srcaddr, int port, Plug *plug,
bool local_host_only, Conf *conf, int addressfamily)
{
/* TODO: SOCKS (and potentially others) support inbound
* TODO: connections via the proxy. support them.
*/
return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily);
}
/* ----------------------------------------------------------------------
* 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 (ProxySocket *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, dest[512];
char *username, *password;
sk_getaddr(p->remote_addr, dest, lenof(dest));
buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n",
dest, p->remote_port, dest, p->remote_port);
sk_write(p->sub_socket, buf, strlen(buf));
sfree(buf);
username = conf_get_str(p->conf, CONF_proxy_username);
password = conf_get_str(p->conf, CONF_proxy_password);
if (username[0] || password[0]) {
char *buf, *buf2;
int i, j, len;
buf = dupprintf("%s:%s", username, password);
len = strlen(buf);
buf2 = snewn(len * 4 / 3 + 100, char);
sprintf(buf2, "Proxy-Authorization: Basic ");
for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4)
base64_encode_atom((unsigned char *)(buf+i),
(len-i > 3 ? 3 : len-i), buf2+j);
strcpy(buf2+j, "\r\n");
sk_write(p->sub_socket, buf2, strlen(buf2));
sfree(buf);
sfree(buf2);
}
sk_write(p->sub_socket, "\r\n", 2);
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.
*/
plug_closing(p->plug, p->closing_error_msg, p->closing_error_code,
p->closing_calling_back);
return 0; /* ignored */
}
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_constructor, p->accepting_ctx);
}
if (change == PROXY_CHANGE_RECEIVE) {
/* we have received data from the underlying socket, which
* we'll need to parse, process, and respond to appropriately.
*/
char *data, *datap;
int len;
int eol;
if (p->state == 1) {
int min_ver, maj_ver, status;
/* get the status line */
len = bufchain_size(&p->pending_input_data);
assert(len > 0); /* or we wouldn't be here */
data = snewn(len+1, char);
bufchain_fetch(&p->pending_input_data, data, len);
/*
* We must NUL-terminate this data, because Windows
* sscanf appears to require a NUL at the end of the
* string because it strlens it _first_. Sigh.
*/
data[len] = '\0';
eol = get_line_end(data, len);
if (eol < 0) {
sfree(data);
return 1;
}
status = -1;
/* We can't rely on whether the %n incremented the sscanf return */
if (sscanf((char *)data, "HTTP/%i.%i %n",
&maj_ver, &min_ver, &status) < 2 || status == -1) {
plug_closing(p->plug, "Proxy error: HTTP response was absent",
PROXY_ERROR_GENERAL, 0);
sfree(data);
return 1;
}
/* remove the status line from the input buffer. */
bufchain_consume(&p->pending_input_data, eol);
if (data[status] != '2') {
/* error */
char *buf;
data[eol] = '\0';
while (eol > status &&
(data[eol-1] == '\r' || data[eol-1] == '\n'))
data[--eol] = '\0';
buf = dupprintf("Proxy error: %s", data+status);
plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0);
sfree(buf);
sfree(data);
return 1;
}
sfree(data);
p->state = 2;
}
if (p->state == 2) {
/* get headers. we're done when we get a
* header of length 2, (ie. just "\r\n")
*/
len = bufchain_size(&p->pending_input_data);
assert(len > 0); /* or we wouldn't be here */
data = snewn(len, char);
datap = data;
bufchain_fetch(&p->pending_input_data, data, len);
eol = get_line_end(datap, len);
if (eol < 0) {
sfree(data);
return 1;
}
while (eol > 2)
{
bufchain_consume(&p->pending_input_data, eol);
datap += eol;
len -= eol;
eol = get_line_end(datap, 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 */
sfree(data);
return 1;
}
sfree(data);
return 1;
}
}
plug_closing(p->plug, "Proxy error: unexpected proxy error",
PROXY_ERROR_UNEXPECTED, 0);
return 1;
}
/* ----------------------------------------------------------------------
* SOCKS proxy type.
*/
/* SOCKS version 4 */
int proxy_socks4_negotiate (ProxySocket *p, int change)
{
if (p->state == PROXY_CHANGE_NEW) {
/* request format:
* version number (1 byte) = 4
* command code (1 byte)
* 1 = CONNECT
* 2 = BIND
* dest. port (2 bytes) [network order]
* dest. address (4 bytes)
* user ID (variable length, null terminated string)
*/
strbuf *command = strbuf_new();
char hostname[512];
bool write_hostname = false;
put_byte(command, 4); /* SOCKS version 4 */
put_byte(command, 1); /* CONNECT command */
put_uint16(command, p->remote_port);
switch (sk_addrtype(p->remote_addr)) {
case ADDRTYPE_IPV4:
{
char addr[4];
sk_addrcopy(p->remote_addr, addr);
put_data(command, addr, 4);
break;
}
case ADDRTYPE_NAME:
sk_getaddr(p->remote_addr, hostname, lenof(hostname));
put_uint32(command, 1);
write_hostname = true;
break;
case ADDRTYPE_IPV6:
p->error = "Proxy error: SOCKS version 4 does not support IPv6";
strbuf_free(command);
return 1;
}
put_asciz(command, conf_get_str(p->conf, CONF_proxy_username));
if (write_hostname)
put_asciz(command, hostname);
sk_write(p->sub_socket, command->s, command->len);
strbuf_free(command);
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.
*/
plug_closing(p->plug, p->closing_error_msg, p->closing_error_code,
p->closing_calling_back);
return 0; /* ignored */
}
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_constructor, p->accepting_ctx);
}
if (change == PROXY_CHANGE_RECEIVE) {
/* we have received data from the underlying socket, which
* we'll need to parse, process, and respond to appropriately.
*/
if (p->state == 1) {
/* response format:
* version number (1 byte) = 4
* reply code (1 byte)
* 90 = request granted
* 91 = request rejected or failed
* 92 = request rejected due to lack of IDENTD on client
* 93 = request rejected due to difference in user ID
* (what we sent vs. what IDENTD said)
* dest. port (2 bytes)
* dest. address (4 bytes)
*/
char data[8];
if (bufchain_size(&p->pending_input_data) < 8)
return 1; /* not got anything yet */
/* get the response */
bufchain_fetch(&p->pending_input_data, data, 8);
if (data[0] != 0) {
plug_closing(p->plug, "Proxy error: SOCKS proxy responded with "
"unexpected reply code version",
PROXY_ERROR_GENERAL, 0);
return 1;
}
if (data[1] != 90) {
switch (data[1]) {
case 92:
plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client",
PROXY_ERROR_GENERAL, 0);
break;
case 93:
plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree",
PROXY_ERROR_GENERAL, 0);
break;
case 91:
default:
plug_closing(p->plug, "Proxy error: Error while communicating with proxy",
PROXY_ERROR_GENERAL, 0);
break;
}
return 1;
}
bufchain_consume(&p->pending_input_data, 8);
/* we're done */
proxy_activate(p);
/* proxy activate will have dealt with
* whatever is left of the buffer */
return 1;
}
}
plug_closing(p->plug, "Proxy error: unexpected proxy error",
PROXY_ERROR_UNEXPECTED, 0);
return 1;
}
/* SOCKS version 5 */
int proxy_socks5_negotiate (ProxySocket *p, int change)
{
if (p->state == PROXY_CHANGE_NEW) {
/* initial command:
* version number (1 byte) = 5
* number of available authentication methods (1 byte)
* available authentication methods (1 byte * previous value)
* authentication methods:
* 0x00 = no authentication
* 0x01 = GSSAPI
* 0x02 = username/password
* 0x03 = CHAP
*/
strbuf *command;
char *username, *password;
int method_count_offset, methods_start;
command = strbuf_new();
put_byte(command, 5); /* SOCKS version 5 */
username = conf_get_str(p->conf, CONF_proxy_username);
password = conf_get_str(p->conf, CONF_proxy_password);
method_count_offset = command->len;
put_byte(command, 0);
methods_start = command->len;
put_byte(command, 0x00); /* no authentication */
if (username[0] || password[0]) {
proxy_socks5_offerencryptedauth(BinarySink_UPCAST(command));
put_byte(command, 0x02); /* username/password */
}
command->u[method_count_offset] = command->len - methods_start;
sk_write(p->sub_socket, command->s, command->len);
strbuf_free(command);
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.
*/
plug_closing(p->plug, p->closing_error_msg, p->closing_error_code,
p->closing_calling_back);
return 0; /* ignored */
}
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_constructor, p->accepting_ctx);
}
if (change == PROXY_CHANGE_RECEIVE) {
/* we have received data from the underlying socket, which
* we'll need to parse, process, and respond to appropriately.
*/
if (p->state == 1) {
/* initial response:
* version number (1 byte) = 5
* authentication method (1 byte)
* authentication methods:
* 0x00 = no authentication
* 0x01 = GSSAPI
* 0x02 = username/password
* 0x03 = CHAP
* 0xff = no acceptable methods
*/
char data[2];
if (bufchain_size(&p->pending_input_data) < 2)
return 1; /* not got anything yet */
/* get the response */
bufchain_fetch(&p->pending_input_data, data, 2);
if (data[0] != 5) {
plug_closing(p->plug, "Proxy error: SOCKS proxy returned unexpected version",
PROXY_ERROR_GENERAL, 0);
return 1;
}
if (data[1] == 0x00) p->state = 2; /* no authentication needed */
else if (data[1] == 0x01) p->state = 4; /* GSSAPI authentication */
else if (data[1] == 0x02) p->state = 5; /* username/password authentication */
else if (data[1] == 0x03) p->state = 6; /* CHAP authentication */
else {
plug_closing(p->plug, "Proxy error: SOCKS proxy did not accept our authentication",
PROXY_ERROR_GENERAL, 0);
return 1;
}
bufchain_consume(&p->pending_input_data, 2);
}
if (p->state == 7) {
/* password authentication reply format:
* version number (1 bytes) = 1
* reply code (1 byte)
* 0 = succeeded
* >0 = failed
*/
char data[2];
if (bufchain_size(&p->pending_input_data) < 2)
return 1; /* not got anything yet */
/* get the response */
bufchain_fetch(&p->pending_input_data, data, 2);
if (data[0] != 1) {
plug_closing(p->plug, "Proxy error: SOCKS password "
"subnegotiation contained wrong version number",
PROXY_ERROR_GENERAL, 0);
return 1;
}
if (data[1] != 0) {
plug_closing(p->plug, "Proxy error: SOCKS proxy refused"
" password authentication",
PROXY_ERROR_GENERAL, 0);
return 1;
}
bufchain_consume(&p->pending_input_data, 2);
p->state = 2; /* now proceed as authenticated */
}
if (p->state == 8) {
int ret;
ret = proxy_socks5_handlechap(p);
if (ret) return ret;
}
if (p->state == 2) {
/* request format:
* version number (1 byte) = 5
* command code (1 byte)
* 1 = CONNECT
* 2 = BIND
* 3 = UDP ASSOCIATE
* reserved (1 byte) = 0x00
* address type (1 byte)
* 1 = IPv4
* 3 = domainname (first byte has length, no terminating null)
* 4 = IPv6
* dest. address (variable)
* dest. port (2 bytes) [network order]
*/
strbuf *command = strbuf_new();
put_byte(command, 5); /* SOCKS version 5 */
put_byte(command, 1); /* CONNECT command */
put_byte(command, 0x00); /* reserved byte */
switch (sk_addrtype(p->remote_addr)) {
case ADDRTYPE_IPV4:
put_byte(command, 1); /* IPv4 */
sk_addrcopy(p->remote_addr, strbuf_append(command, 4));
break;
case ADDRTYPE_IPV6:
put_byte(command, 4); /* IPv6 */
sk_addrcopy(p->remote_addr, strbuf_append(command, 16));
break;
case ADDRTYPE_NAME:
{
char hostname[512];
put_byte(command, 3); /* domain name */
sk_getaddr(p->remote_addr, hostname, lenof(hostname));
if (!put_pstring(command, hostname)) {
p->error = "Proxy error: SOCKS 5 cannot "
"support host names longer than 255 chars";
strbuf_free(command);
return 1;
}
}
break;
}
put_uint16(command, p->remote_port);
sk_write(p->sub_socket, command->s, command->len);
strbuf_free(command);
p->state = 3;
return 1;
}
if (p->state == 3) {
/* reply format:
* version number (1 bytes) = 5
* reply code (1 byte)
* 0 = succeeded
* 1 = general SOCKS server failure
* 2 = connection not allowed by ruleset
* 3 = network unreachable
* 4 = host unreachable
* 5 = connection refused
* 6 = TTL expired
* 7 = command not supported
* 8 = address type not supported
* reserved (1 byte) = x00
* address type (1 byte)
* 1 = IPv4
* 3 = domainname (first byte has length, no terminating null)
* 4 = IPv6
* server bound address (variable)
* server bound port (2 bytes) [network order]
*/
char data[5];
int len;
/* First 5 bytes of packet are enough to tell its length. */
if (bufchain_size(&p->pending_input_data) < 5)
return 1; /* not got anything yet */
/* get the response */
bufchain_fetch(&p->pending_input_data, data, 5);
if (data[0] != 5) {
plug_closing(p->plug, "Proxy error: SOCKS proxy returned wrong version number",
PROXY_ERROR_GENERAL, 0);
return 1;
}
if (data[1] != 0) {
char buf[256];
strcpy(buf, "Proxy error: ");
switch (data[1]) {
case 1: strcat(buf, "General SOCKS server failure"); break;
case 2: strcat(buf, "Connection not allowed by ruleset"); break;
case 3: strcat(buf, "Network unreachable"); break;
case 4: strcat(buf, "Host unreachable"); break;
case 5: strcat(buf, "Connection refused"); break;
case 6: strcat(buf, "TTL expired"); break;
case 7: strcat(buf, "Command not supported"); break;
case 8: strcat(buf, "Address type not supported"); break;
default: sprintf(buf+strlen(buf),
"Unrecognised SOCKS error code %d",
data[1]);
break;
}
plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0);
return 1;
}
/*
* Eat the rest of the reply packet.
*/
len = 6; /* first 4 bytes, last 2 */
switch (data[3]) {
case 1: len += 4; break; /* IPv4 address */
case 4: len += 16; break;/* IPv6 address */
case 3: len += (unsigned char)data[4]; break; /* domain name */
default:
plug_closing(p->plug, "Proxy error: SOCKS proxy returned "
"unrecognised address format",
PROXY_ERROR_GENERAL, 0);
return 1;
}
if (bufchain_size(&p->pending_input_data) < len)
return 1; /* not got whole reply yet */
bufchain_consume(&p->pending_input_data, len);
/* we're done */
proxy_activate(p);
return 1;
}
if (p->state == 4) {
/* TODO: Handle GSSAPI authentication */
plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication",
PROXY_ERROR_GENERAL, 0);
return 1;
}
if (p->state == 5) {
const char *username = conf_get_str(p->conf, CONF_proxy_username);
const char *password = conf_get_str(p->conf, CONF_proxy_password);
if (username[0] || password[0]) {
strbuf *auth = strbuf_new();
put_byte(auth, 1); /* version number of subnegotiation */
if (!put_pstring(auth, username)) {
p->error = "Proxy error: SOCKS 5 authentication cannot "
"support usernames longer than 255 chars";
strbuf_free(auth);
return 1;
}
if (!put_pstring(auth, password)) {
p->error = "Proxy error: SOCKS 5 authentication cannot "
"support passwords longer than 255 chars";
strbuf_free(auth);
return 1;
}
sk_write(p->sub_socket, auth->s, auth->len);
strbuf_free(auth);
p->state = 7;
} else
plug_closing(p->plug, "Proxy error: Server chose "
"username/password authentication but we "
"didn't offer it!",
PROXY_ERROR_GENERAL, 0);
return 1;
}
if (p->state == 6) {
int ret;
ret = proxy_socks5_selectchap(p);
if (ret) return ret;
}
}
plug_closing(p->plug, "Proxy error: Unexpected proxy error",
PROXY_ERROR_UNEXPECTED, 0);
return 1;
}
/* ----------------------------------------------------------------------
* `Telnet' proxy type.
*
* (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.)
*/
char *format_telnet_command(SockAddr *addr, int port, Conf *conf)
{
char *fmt = conf_get_str(conf, CONF_proxy_telnet_command);
char *ret = NULL;
int retlen = 0, retsize = 0;
int so = 0, eo = 0;
#define ENSURE(n) do { \
if (retsize < retlen + n) { \
retsize = retlen + n + 512; \
ret = sresize(ret, retsize, char); \
} \
} while (0)
/* we need to escape \\, \%, \r, \n, \t, \x??, \0???,
* %%, %host, %port, %user, and %pass
*/
while (fmt[eo] != 0) {
/* scan forward until we hit end-of-line,
* or an escape character (\ or %) */
while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\')
eo++;
/* if we hit eol, break out of our escaping loop */
if (fmt[eo] == 0) break;
/* if there was any unescaped text before the escape
* character, send that now */
if (eo != so) {
ENSURE(eo - so);
memcpy(ret + retlen, fmt + so, eo - so);
retlen += eo - so;
}
so = eo++;
/* if the escape character was the last character of
* the line, we'll just stop and send it. */
if (fmt[eo] == 0) break;
if (fmt[so] == '\\') {
/* we recognize \\, \%, \r, \n, \t, \x??.
* anything else, we just send unescaped (including the \).
*/
switch (fmt[eo]) {
case '\\':
ENSURE(1);
ret[retlen++] = '\\';
eo++;
break;
case '%':
ENSURE(1);
ret[retlen++] = '%';
eo++;
break;
case 'r':
ENSURE(1);
ret[retlen++] = '\r';
eo++;
break;
case 'n':
ENSURE(1);
ret[retlen++] = '\n';
eo++;
break;
case 't':
ENSURE(1);
ret[retlen++] = '\t';
eo++;
break;
case 'x':
case 'X':
{
/* escaped hexadecimal value (ie. \xff) */
unsigned char v = 0;
int i = 0;
for (;;) {
eo++;
if (fmt[eo] >= '0' && fmt[eo] <= '9')
v += fmt[eo] - '0';
else if (fmt[eo] >= 'a' && fmt[eo] <= 'f')
v += fmt[eo] - 'a' + 10;
else if (fmt[eo] >= 'A' && fmt[eo] <= 'F')
v += fmt[eo] - 'A' + 10;
else {
/* non hex character, so we abort and just
* send the whole thing unescaped (including \x)
*/
ENSURE(1);
ret[retlen++] = '\\';
eo = so + 1;
break;
}
/* we only extract two hex characters */
if (i == 1) {
ENSURE(1);
ret[retlen++] = v;
eo++;
break;
}
i++;
v <<= 4;
}
}
break;
default:
ENSURE(2);
memcpy(ret+retlen, fmt + so, 2);
retlen += 2;
eo++;
break;
}
} else {
/* % escape. we recognize %%, %host, %port, %user, %pass.
* %proxyhost, %proxyport. Anything else we just send
* unescaped (including the %).
*/
if (fmt[eo] == '%') {
ENSURE(1);
ret[retlen++] = '%';
eo++;
}
else if (strnicmp(fmt + eo, "host", 4) == 0) {
char dest[512];
int destlen;
sk_getaddr(addr, dest, lenof(dest));
destlen = strlen(dest);
ENSURE(destlen);
memcpy(ret+retlen, dest, destlen);
retlen += destlen;
eo += 4;
}
else if (strnicmp(fmt + eo, "port", 4) == 0) {
char portstr[8], portlen;
portlen = sprintf(portstr, "%i", port);
ENSURE(portlen);
memcpy(ret + retlen, portstr, portlen);
retlen += portlen;
eo += 4;
}
else if (strnicmp(fmt + eo, "user", 4) == 0) {
char *username = conf_get_str(conf, CONF_proxy_username);
int userlen = strlen(username);
ENSURE(userlen);
memcpy(ret+retlen, username, userlen);
retlen += userlen;
eo += 4;
}
else if (strnicmp(fmt + eo, "pass", 4) == 0) {
char *password = conf_get_str(conf, CONF_proxy_password);
int passlen = strlen(password);
ENSURE(passlen);
memcpy(ret+retlen, password, passlen);
retlen += passlen;
eo += 4;
}
else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) {
char *host = conf_get_str(conf, CONF_proxy_host);
int phlen = strlen(host);
ENSURE(phlen);
memcpy(ret+retlen, host, phlen);
retlen += phlen;
eo += 9;
}
else if (strnicmp(fmt + eo, "proxyport", 9) == 0) {
int port = conf_get_int(conf, CONF_proxy_port);
char pport[50];
int pplen;
sprintf(pport, "%d", port);
pplen = strlen(pport);
ENSURE(pplen);
memcpy(ret+retlen, pport, pplen);
retlen += pplen;
eo += 9;
}
else {
/* we don't escape this, so send the % now, and
* don't advance eo, so that we'll consider the
* text immediately following the % as unescaped.
*/
ENSURE(1);
ret[retlen++] = '%';
}
}
/* resume scanning for additional escapes after this one. */
so = eo;
}
/* if there is any unescaped text at the end of the line, send it */
if (eo != so) {
ENSURE(eo - so);
memcpy(ret + retlen, fmt + so, eo - so);
retlen += eo - so;
}
ENSURE(1);
ret[retlen] = '\0';
return ret;
#undef ENSURE
}
int proxy_telnet_negotiate (ProxySocket *p, int change)
{
if (p->state == PROXY_CHANGE_NEW) {
char *formatted_cmd;
formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port,
p->conf);
{
/*
* Re-escape control chars in the command, for logging.
*/
char *reescaped = snewn(4*strlen(formatted_cmd) + 1, char);
const char *in;
char *out;
char *logmsg;
for (in = formatted_cmd, out = reescaped; *in; in++) {
if (*in == '\n') {
*out++ = '\\'; *out++ = 'n';
} else if (*in == '\r') {
*out++ = '\\'; *out++ = 'r';
} else if (*in == '\t') {
*out++ = '\\'; *out++ = 't';
} else if (*in == '\\') {
*out++ = '\\'; *out++ = '\\';
} else if ((unsigned)(((unsigned char)*in) - 0x20) <
(0x7F-0x20)) {
*out++ = *in;
} else {
out += sprintf(out, "\\x%02X", (unsigned)*in & 0xFF);
}
}
*out = '\0';
logmsg = dupprintf("Sending Telnet proxy command: %s", reescaped);
plug_log(p->plug, 2, NULL, 0, logmsg, 0);
sfree(logmsg);
sfree(reescaped);
}
sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd));
sfree(formatted_cmd);
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.
*/
plug_closing(p->plug, p->closing_error_msg, p->closing_error_code,
p->closing_calling_back);
return 0; /* ignored */
}
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_constructor, p->accepting_ctx);
}
if (change == PROXY_CHANGE_RECEIVE) {
/* we have received data from the underlying socket, which
* we'll need to parse, process, and respond to appropriately.
*/
/* we're done */
proxy_activate(p);
/* proxy activate will have dealt with
* whatever is left of the buffer */
return 1;
}
plug_closing(p->plug, "Proxy error: Unexpected proxy error",
PROXY_ERROR_UNEXPECTED, 0);
return 1;
}