mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
Reorganise proxy system into coroutines.
Previously, the proxy negotiation functions were written as explicit state machines, with ps->state being manually set to a sequence of positive integer values which would be tested by if statements in the next call to the same negotiation function. That's not how this code base likes to do things! We have a coroutine system to allow those state machines to be implicit rather than explicit, so that we can use ordinary control flow statements like while loops. Reorganised each proxy negotiation function into a coroutine-based system like that. While I'm at it, I've also moved each proxy negotiator out into its own source file, to make proxy.c less overcrowded and monolithic. And _that_ gave me the opportunity to define each negotiator as an implementation of a trait rather than as a single function - which means now each one can define its own local variables and have its own cleanup function, instead of all of them having to share the variables inside the main ProxySocket struct. In the new coroutine system, negotiators don't have to worry about the mechanics of actually sending data down the underlying Socket any more. The negotiator coroutine just appends to a bufchain (via a provided bufchain_sink), and after every call to the coroutine, central code in proxy.c transfers the data to the Socket itself. This avoids a lot of intermediate allocations within the negotiators, which previously kept having to make temporary strbufs or arrays in order to have something to point an sk_write() at; now they can just put formatted data directly into the output bufchain via the marshal.h interface. In this version of the code, I've also moved most of the SOCKS5 CHAP implementation from cproxy.c into socks5.c, so that it can sit in the same coroutine as the rest of the proxy negotiation control flow. That's because calling a sub-coroutine (co-subroutine?) is awkward to set up (though it is _possible_ - we do SSH-2 kex that way), and there's no real need to bother in this case, since the only thing that really needs to go in cproxy.c is the actual cryptography plus a flag to tell socks5.c whether to offer CHAP authentication in the first place.
This commit is contained in:
parent
23c64fa00e
commit
b7bf2aec74
@ -34,7 +34,12 @@ add_subdirectory(crypto)
|
||||
|
||||
add_library(network STATIC
|
||||
be_misc.c nullplug.c errsock.c logging.c x11disp.c
|
||||
proxy/proxy.c proxy/interactor.c)
|
||||
proxy/proxy.c
|
||||
proxy/http.c
|
||||
proxy/socks4.c
|
||||
proxy/socks5.c
|
||||
proxy/telnet.c
|
||||
proxy/interactor.c)
|
||||
|
||||
add_library(keygen STATIC
|
||||
import.c)
|
||||
|
163
proxy/cproxy.c
163
proxy/cproxy.c
@ -13,163 +13,14 @@
|
||||
#include "ssh.h" /* For MD5 support */
|
||||
#include "network.h"
|
||||
#include "proxy.h"
|
||||
#include "socks.h"
|
||||
#include "marshal.h"
|
||||
|
||||
static void hmacmd5_chap(const unsigned char *challenge, int challen,
|
||||
const char *passwd, unsigned char *response)
|
||||
const bool socks5_chap_available = true;
|
||||
|
||||
strbuf *chap_response(ptrlen challenge, ptrlen password)
|
||||
{
|
||||
mac_simple(&ssh_hmac_md5, ptrlen_from_asciz(passwd),
|
||||
make_ptrlen(challenge, challen), response);
|
||||
}
|
||||
|
||||
void proxy_socks5_offerencryptedauth(BinarySink *bs)
|
||||
{
|
||||
put_byte(bs, SOCKS5_AUTH_CHAP);
|
||||
}
|
||||
|
||||
int proxy_socks5_handlechap (ProxySocket *ps)
|
||||
{
|
||||
|
||||
/* CHAP authentication reply format:
|
||||
* version number (1 bytes) = 1
|
||||
* number of commands (1 byte)
|
||||
*
|
||||
* For each command:
|
||||
* command identifier (1 byte)
|
||||
* data length (1 byte)
|
||||
*/
|
||||
unsigned char data[260];
|
||||
unsigned char outbuf[20];
|
||||
|
||||
while(ps->chap_num_attributes == 0 ||
|
||||
ps->chap_num_attributes_processed < ps->chap_num_attributes) {
|
||||
if (ps->chap_num_attributes == 0 ||
|
||||
ps->chap_current_attribute == -1) {
|
||||
/* CHAP normally reads in two bytes, either at the
|
||||
* beginning or for each attribute/value pair. But if
|
||||
* we're waiting for the value's data, we might not want
|
||||
* to read 2 bytes.
|
||||
*/
|
||||
|
||||
if (bufchain_size(&ps->pending_input_data) < 2)
|
||||
return 1; /* not got anything yet */
|
||||
|
||||
/* get the response */
|
||||
bufchain_fetch(&ps->pending_input_data, data, 2);
|
||||
bufchain_consume(&ps->pending_input_data, 2);
|
||||
}
|
||||
|
||||
if (ps->chap_num_attributes == 0) {
|
||||
/* If there are no attributes, this is our first msg
|
||||
* with the server, where we negotiate version and
|
||||
* number of attributes
|
||||
*/
|
||||
if (data[0] != SOCKS5_AUTH_CHAP_VERSION) {
|
||||
plug_closing_error(ps->plug, "Proxy error: SOCKS proxy wants "
|
||||
"a different CHAP version");
|
||||
return 1;
|
||||
}
|
||||
if (data[1] == 0x00) {
|
||||
plug_closing_error(ps->plug, "Proxy error: SOCKS proxy won't "
|
||||
"negotiate CHAP with us");
|
||||
return 1;
|
||||
}
|
||||
ps->chap_num_attributes = data[1];
|
||||
} else {
|
||||
if (ps->chap_current_attribute == -1) {
|
||||
/* We have to read in each attribute/value pair -
|
||||
* those we don't understand can be ignored, but
|
||||
* there are a few we'll need to handle.
|
||||
*/
|
||||
ps->chap_current_attribute = data[0];
|
||||
ps->chap_current_datalen = data[1];
|
||||
}
|
||||
if (bufchain_size(&ps->pending_input_data) <
|
||||
ps->chap_current_datalen)
|
||||
return 1; /* not got everything yet */
|
||||
|
||||
/* get the response */
|
||||
bufchain_fetch(&ps->pending_input_data, data,
|
||||
ps->chap_current_datalen);
|
||||
|
||||
bufchain_consume(&ps->pending_input_data,
|
||||
ps->chap_current_datalen);
|
||||
|
||||
switch (ps->chap_current_attribute) {
|
||||
case SOCKS5_AUTH_CHAP_ATTR_STATUS:
|
||||
/* Successful authentication */
|
||||
if (data[0] == 0x00)
|
||||
ps->state = 2;
|
||||
else {
|
||||
plug_closing_error(ps->plug, "Proxy error: SOCKS proxy "
|
||||
"refused CHAP authentication");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case SOCKS5_AUTH_CHAP_ATTR_CHALLENGE:
|
||||
outbuf[0] = SOCKS5_AUTH_CHAP_VERSION;
|
||||
outbuf[1] = 0x01; /* One attribute */
|
||||
outbuf[2] = SOCKS5_AUTH_CHAP_ATTR_RESPONSE;
|
||||
outbuf[3] = 0x10; /* Length */
|
||||
hmacmd5_chap(data, ps->chap_current_datalen,
|
||||
conf_get_str(ps->conf, CONF_proxy_password),
|
||||
&outbuf[4]);
|
||||
sk_write(ps->sub_socket, outbuf, 20);
|
||||
break;
|
||||
case SOCKS5_AUTH_CHAP_ATTR_ALGLIST:
|
||||
/* Chose a protocol */
|
||||
if (data[0] != SOCKS5_AUTH_CHAP_ALG_HMACMD5) {
|
||||
plug_closing_error(ps->plug, "Proxy error: Server chose "
|
||||
"CHAP of other than HMAC-MD5 but we "
|
||||
"didn't offer it!");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
ps->chap_current_attribute = -1;
|
||||
ps->chap_num_attributes_processed++;
|
||||
}
|
||||
if (ps->state == 8 &&
|
||||
ps->chap_num_attributes_processed >= ps->chap_num_attributes) {
|
||||
ps->chap_num_attributes = 0;
|
||||
ps->chap_num_attributes_processed = 0;
|
||||
ps->chap_current_datalen = 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int proxy_socks5_selectchap(ProxySocket *ps)
|
||||
{
|
||||
char *username = conf_get_str(ps->conf, CONF_proxy_username);
|
||||
char *password = conf_get_str(ps->conf, CONF_proxy_password);
|
||||
if (username[0] || password[0]) {
|
||||
char chapbuf[514];
|
||||
int ulen;
|
||||
chapbuf[0] = SOCKS5_AUTH_CHAP_VERSION;
|
||||
chapbuf[1] = '\x02'; /* Number of attributes sent */
|
||||
chapbuf[2] = SOCKS5_AUTH_CHAP_ATTR_ALGLIST;
|
||||
chapbuf[3] = '\x01'; /* Only one CHAP algorithm */
|
||||
chapbuf[4] = SOCKS5_AUTH_CHAP_ALG_HMACMD5;
|
||||
chapbuf[5] = SOCKS5_AUTH_CHAP_ATTR_USERNAME;
|
||||
|
||||
ulen = strlen(username);
|
||||
if (ulen > 255) ulen = 255;
|
||||
if (ulen < 1) ulen = 1;
|
||||
|
||||
chapbuf[6] = ulen;
|
||||
memcpy(chapbuf+7, username, ulen);
|
||||
|
||||
sk_write(ps->sub_socket, chapbuf, ulen + 7);
|
||||
ps->chap_num_attributes = 0;
|
||||
ps->chap_num_attributes_processed = 0;
|
||||
ps->chap_current_attribute = -1;
|
||||
ps->chap_current_datalen = 0;
|
||||
|
||||
ps->state = 8;
|
||||
} else
|
||||
plug_closing_error(ps->plug, "Proxy error: Server chose "
|
||||
"CHAP authentication but we didn't offer it!");
|
||||
return 1;
|
||||
strbuf *sb = strbuf_new_nm();
|
||||
const ssh2_macalg *alg = &ssh_hmac_md5;
|
||||
mac_simple(alg, password, challenge, strbuf_append(sb, alg->len));
|
||||
return sb;
|
||||
}
|
||||
|
158
proxy/http.c
Normal file
158
proxy/http.c
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* HTTP CONNECT proxy negotiation.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
#include "network.h"
|
||||
#include "proxy.h"
|
||||
#include "sshcr.h"
|
||||
|
||||
static bool read_line(bufchain *input, strbuf *output, bool is_header)
|
||||
{
|
||||
char c;
|
||||
|
||||
while (bufchain_try_fetch(input, &c, 1)) {
|
||||
if (is_header && output->len > 0 &&
|
||||
output->s[output->len - 1] == '\n') {
|
||||
/*
|
||||
* A newline terminates the header, provided we're sure it
|
||||
* is _not_ followed by a space or a tab.
|
||||
*/
|
||||
if (c != ' ' && c != '\t')
|
||||
goto done; /* we have a complete header line */
|
||||
} else {
|
||||
put_byte(output, c);
|
||||
bufchain_consume(input, 1);
|
||||
|
||||
if (!is_header && output->len > 0 &&
|
||||
output->s[output->len - 1] == '\n') {
|
||||
/* If we're looking for just a line, not an HTTP
|
||||
* header, then any newline terminates it. */
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
done:
|
||||
strbuf_chomp(output, '\n');
|
||||
strbuf_chomp(output, '\r');
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct HttpProxyNegotiator {
|
||||
int crLine;
|
||||
strbuf *line;
|
||||
ProxyNegotiator pn;
|
||||
} HttpProxyNegotiator;
|
||||
|
||||
static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt)
|
||||
{
|
||||
HttpProxyNegotiator *s = snew(HttpProxyNegotiator);
|
||||
s->pn.vt = vt;
|
||||
s->crLine = 0;
|
||||
s->line = strbuf_new();
|
||||
return &s->pn;
|
||||
}
|
||||
|
||||
static void proxy_http_free(ProxyNegotiator *pn)
|
||||
{
|
||||
HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn);
|
||||
strbuf_free(s->line);
|
||||
sfree(s);
|
||||
}
|
||||
|
||||
static void proxy_http_process_queue(ProxyNegotiator *pn)
|
||||
{
|
||||
HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn);
|
||||
|
||||
crBegin(s->crLine);
|
||||
|
||||
/*
|
||||
* Standard prefix for the HTTP CONNECT request.
|
||||
*/
|
||||
{
|
||||
char dest[512];
|
||||
sk_getaddr(pn->ps->remote_addr, dest, lenof(dest));
|
||||
put_fmt(pn->output,
|
||||
"CONNECT %s:%d HTTP/1.1\r\n"
|
||||
"Host: %s:%d\r\n",
|
||||
dest, pn->ps->remote_port, dest, pn->ps->remote_port);
|
||||
}
|
||||
|
||||
/*
|
||||
* Optionally send an HTTP Basic auth header with the username and
|
||||
* password.
|
||||
*/
|
||||
{
|
||||
const char *username = conf_get_str(pn->ps->conf, CONF_proxy_username);
|
||||
const char *password = conf_get_str(pn->ps->conf, CONF_proxy_password);
|
||||
if (username[0] || password[0]) {
|
||||
put_datalit(pn->output, "Proxy-Authorization: Basic ");
|
||||
|
||||
char *base64_input = dupcat(username, ":", password);
|
||||
char base64_output[4];
|
||||
for (size_t i = 0, e = strlen(base64_input); i < e; i += 3) {
|
||||
base64_encode_atom((const unsigned char *)base64_input + i,
|
||||
e-i > 3 ? 3 : e-i, base64_output);
|
||||
put_data(pn->output, base64_output, 4);
|
||||
}
|
||||
burnstr(base64_input);
|
||||
smemclr(base64_output, sizeof(base64_output));
|
||||
put_datalit(pn->output, "\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Blank line to terminate the HTTP request.
|
||||
*/
|
||||
put_datalit(pn->output, "\r\n");
|
||||
crReturnV;
|
||||
|
||||
/*
|
||||
* Read and parse the HTTP status line, and check if it's a 2xx
|
||||
* for success.
|
||||
*/
|
||||
strbuf_clear(s->line);
|
||||
crMaybeWaitUntilV(read_line(pn->input, s->line, false));
|
||||
{
|
||||
int maj_ver, min_ver, status_pos = -1;
|
||||
sscanf(s->line->s, "HTTP/%d.%d %n", &maj_ver, &min_ver, &status_pos);
|
||||
|
||||
/* If status_pos is still -1 then the sscanf didn't get right
|
||||
* to the end of the string */
|
||||
if (status_pos == -1) {
|
||||
pn->error = dupstr("HTTP response was absent or malformed");
|
||||
crStopV;
|
||||
}
|
||||
|
||||
if (s->line->s[status_pos] != '2') {
|
||||
pn->error = dupprintf("HTTP response %s", s->line->s + status_pos);
|
||||
crStopV;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read and skip the rest of the HTTP response headers, terminated
|
||||
* by a blank line.
|
||||
*/
|
||||
do {
|
||||
strbuf_clear(s->line);
|
||||
crMaybeWaitUntilV(read_line(pn->input, s->line, true));
|
||||
} while (s->line->len > 0);
|
||||
|
||||
/*
|
||||
* Success! Hand over to the main connection.
|
||||
*/
|
||||
pn->done = true;
|
||||
|
||||
crFinishV;
|
||||
}
|
||||
|
||||
const struct ProxyNegotiatorVT http_proxy_negotiator_vt = {
|
||||
.new = proxy_http_new,
|
||||
.free = proxy_http_free,
|
||||
.process_queue = proxy_http_process_queue,
|
||||
.type = "HTTP",
|
||||
};
|
@ -12,21 +12,9 @@
|
||||
#include "network.h"
|
||||
#include "proxy.h"
|
||||
|
||||
void proxy_socks5_offerencryptedauth(BinarySink *bs)
|
||||
{
|
||||
/* For telnet, don't add any new encrypted authentication routines */
|
||||
}
|
||||
const bool socks5_chap_available = false;
|
||||
|
||||
int proxy_socks5_handlechap(ProxySocket *p)
|
||||
strbuf *chap_response(ptrlen challenge, ptrlen password)
|
||||
{
|
||||
plug_closing_error(p->plug, "Proxy error: Trying to handle a "
|
||||
"SOCKS5 CHAP request in telnet-only build");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int proxy_socks5_selectchap(ProxySocket *p)
|
||||
{
|
||||
plug_closing_error(p->plug, "Proxy error: Trying to handle a "
|
||||
"SOCKS5 CHAP request in telnet-only build");
|
||||
return 1;
|
||||
unreachable("CHAP is not built into this binary");
|
||||
}
|
||||
|
954
proxy/proxy.c
954
proxy/proxy.c
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,8 @@
|
||||
#define PUTTY_PROXY_H
|
||||
|
||||
typedef struct ProxySocket ProxySocket;
|
||||
typedef struct ProxyNegotiator ProxyNegotiator;
|
||||
typedef struct ProxyNegotiatorVT ProxyNegotiatorVT;
|
||||
|
||||
struct ProxySocket {
|
||||
const char *error;
|
||||
@ -25,59 +27,58 @@ struct ProxySocket {
|
||||
bufchain pending_input_data;
|
||||
bool pending_eof;
|
||||
|
||||
#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.
|
||||
*/
|
||||
bool 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_RECEIVE 2
|
||||
|
||||
/* 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) (ProxySocket * /* this */, int /* change type */);
|
||||
|
||||
/* current arguments of plug handlers
|
||||
* (for use by proxy's negotiate function)
|
||||
*/
|
||||
|
||||
/* receive */
|
||||
bool receive_urgent;
|
||||
const char *receive_data;
|
||||
int receive_len;
|
||||
ProxyNegotiator *pn; /* non-NULL if still negotiating */
|
||||
bufchain output_from_negotiator;
|
||||
|
||||
/* configuration, used to look up proxy settings */
|
||||
Conf *conf;
|
||||
|
||||
/* CHAP transient data */
|
||||
int chap_num_attributes;
|
||||
int chap_num_attributes_processed;
|
||||
int chap_current_attribute;
|
||||
int chap_current_datalen;
|
||||
|
||||
Socket sock;
|
||||
Plug plugimpl;
|
||||
};
|
||||
|
||||
extern void proxy_activate (ProxySocket *);
|
||||
struct ProxyNegotiator {
|
||||
const ProxyNegotiatorVT *vt;
|
||||
|
||||
extern int proxy_http_negotiate (ProxySocket *, int);
|
||||
extern int proxy_telnet_negotiate (ProxySocket *, int);
|
||||
extern int proxy_socks4_negotiate (ProxySocket *, int);
|
||||
extern int proxy_socks5_negotiate (ProxySocket *, int);
|
||||
/* Standard fields for any ProxyNegotiator. new() and free() don't
|
||||
* have to set these up; that's done centrally, to save duplication. */
|
||||
ProxySocket *ps;
|
||||
bufchain *input;
|
||||
bufchain_sink output[1];
|
||||
|
||||
/* Set to report success during proxy negotiation. */
|
||||
bool done;
|
||||
|
||||
/* Set to report an error during proxy negotiation. The main
|
||||
* ProxySocket will free it, and will then guarantee never to call
|
||||
* process_queue again. */
|
||||
char *error;
|
||||
};
|
||||
|
||||
struct ProxyNegotiatorVT {
|
||||
ProxyNegotiator *(*new)(const ProxyNegotiatorVT *);
|
||||
void (*process_queue)(ProxyNegotiator *);
|
||||
void (*free)(ProxyNegotiator *);
|
||||
const char *type;
|
||||
};
|
||||
|
||||
static inline ProxyNegotiator *proxy_negotiator_new(
|
||||
const ProxyNegotiatorVT *vt)
|
||||
{ return vt->new(vt); }
|
||||
static inline void proxy_negotiator_process_queue(ProxyNegotiator *pn)
|
||||
{ pn->vt->process_queue(pn); }
|
||||
static inline void proxy_negotiator_free(ProxyNegotiator *pn)
|
||||
{ pn->vt->free(pn); }
|
||||
|
||||
extern const ProxyNegotiatorVT http_proxy_negotiator_vt;
|
||||
extern const ProxyNegotiatorVT socks4_proxy_negotiator_vt;
|
||||
extern const ProxyNegotiatorVT socks5_proxy_negotiator_vt;
|
||||
extern const ProxyNegotiatorVT telnet_proxy_negotiator_vt;
|
||||
|
||||
/*
|
||||
* This may be reused by local-command proxies on individual
|
||||
@ -89,8 +90,7 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf);
|
||||
* These are implemented in cproxy.c or nocproxy.c, depending on
|
||||
* whether encrypted proxy authentication is available.
|
||||
*/
|
||||
extern void proxy_socks5_offerencryptedauth(BinarySink *);
|
||||
extern int proxy_socks5_handlechap (ProxySocket *);
|
||||
extern int proxy_socks5_selectchap(ProxySocket *);
|
||||
extern const bool socks5_chap_available;
|
||||
strbuf *chap_response(ptrlen challenge, ptrlen password);
|
||||
|
||||
#endif
|
||||
|
136
proxy/socks4.c
Normal file
136
proxy/socks4.c
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* SOCKS 4 proxy negotiation.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
#include "network.h"
|
||||
#include "proxy.h"
|
||||
#include "socks.h"
|
||||
#include "sshcr.h"
|
||||
|
||||
typedef struct Socks4ProxyNegotiator {
|
||||
int crLine;
|
||||
ProxyNegotiator pn;
|
||||
} Socks4ProxyNegotiator;
|
||||
|
||||
static ProxyNegotiator *proxy_socks4_new(const ProxyNegotiatorVT *vt)
|
||||
{
|
||||
Socks4ProxyNegotiator *s = snew(Socks4ProxyNegotiator);
|
||||
s->pn.vt = vt;
|
||||
s->crLine = 0;
|
||||
return &s->pn;
|
||||
}
|
||||
|
||||
static void proxy_socks4_free(ProxyNegotiator *pn)
|
||||
{
|
||||
Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn);
|
||||
sfree(s);
|
||||
}
|
||||
|
||||
static void proxy_socks4_process_queue(ProxyNegotiator *pn)
|
||||
{
|
||||
Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn);
|
||||
|
||||
crBegin(s->crLine);
|
||||
|
||||
{
|
||||
char hostname[512];
|
||||
bool write_hostname = false;
|
||||
|
||||
/*
|
||||
* SOCKS 4 request packet:
|
||||
*
|
||||
* byte version
|
||||
* byte command
|
||||
* uint16 destination port number
|
||||
* uint32 destination IPv4 address (or something in the
|
||||
* SOCKS4A_NAME_FOLLOWS range)
|
||||
* asciz username
|
||||
* asciz destination hostname (if we sent SOCKS4A_NAME_FOLLOWS_*)
|
||||
*/
|
||||
|
||||
put_byte(pn->output, SOCKS4_REQUEST_VERSION);
|
||||
put_byte(pn->output, SOCKS_CMD_CONNECT);
|
||||
put_uint16(pn->output, pn->ps->remote_port);
|
||||
|
||||
switch (sk_addrtype(pn->ps->remote_addr)) {
|
||||
case ADDRTYPE_IPV4: {
|
||||
char addr[4];
|
||||
sk_addrcopy(pn->ps->remote_addr, addr);
|
||||
put_data(pn->output, addr, 4);
|
||||
break;
|
||||
}
|
||||
case ADDRTYPE_NAME:
|
||||
put_uint32(pn->output, SOCKS4A_NAME_FOLLOWS_BASE);
|
||||
sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname));
|
||||
write_hostname = true;
|
||||
break;
|
||||
case ADDRTYPE_IPV6:
|
||||
pn->error = dupstr("SOCKS version 4 does not support IPv6");
|
||||
crStopV;
|
||||
}
|
||||
|
||||
put_asciz(pn->output, conf_get_str(pn->ps->conf, CONF_proxy_username));
|
||||
|
||||
if (write_hostname)
|
||||
put_asciz(pn->output, hostname);
|
||||
}
|
||||
|
||||
crReturnV;
|
||||
|
||||
{
|
||||
unsigned char data[8];
|
||||
crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 8));
|
||||
|
||||
/*
|
||||
* SOCKS 4 response packet:
|
||||
*
|
||||
* byte version
|
||||
* byte status
|
||||
* uint16 port number
|
||||
* uint32 IPv4 address
|
||||
*
|
||||
* We don't need to worry about the port and destination address.
|
||||
*/
|
||||
|
||||
if (data[0] != SOCKS4_REPLY_VERSION) {
|
||||
pn->error = dupprintf("SOCKS proxy response contained reply "
|
||||
"version number %d (expected 0)",
|
||||
(int)data[0]);
|
||||
crStopV;
|
||||
}
|
||||
|
||||
switch (data[1]) {
|
||||
case SOCKS4_RESP_SUCCESS:
|
||||
pn->done = true;
|
||||
break;
|
||||
|
||||
case SOCKS4_RESP_FAILURE:
|
||||
pn->error = dupstr("SOCKS server reported failure to connect");
|
||||
break;
|
||||
|
||||
case SOCKS4_RESP_WANT_IDENTD:
|
||||
pn->error = dupstr("SOCKS server wanted IDENTD on client");
|
||||
break;
|
||||
|
||||
case SOCKS4_RESP_IDENTD_MISMATCH:
|
||||
pn->error = dupstr("Username and IDENTD on client don't agree");
|
||||
break;
|
||||
|
||||
default:
|
||||
pn->error = dupprintf("SOCKS server sent unrecognised error "
|
||||
"code %d", (int)data[1]);
|
||||
break;
|
||||
}
|
||||
crStopV;
|
||||
}
|
||||
|
||||
crFinishV;
|
||||
}
|
||||
|
||||
const struct ProxyNegotiatorVT socks4_proxy_negotiator_vt = {
|
||||
.new = proxy_socks4_new,
|
||||
.free = proxy_socks4_free,
|
||||
.process_queue = proxy_socks4_process_queue,
|
||||
.type = "SOCKS 4",
|
||||
};
|
434
proxy/socks5.c
Normal file
434
proxy/socks5.c
Normal file
@ -0,0 +1,434 @@
|
||||
/*
|
||||
* SOCKS 5 proxy negotiation.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
#include "network.h"
|
||||
#include "proxy.h"
|
||||
#include "socks.h"
|
||||
#include "sshcr.h"
|
||||
|
||||
static inline const char *socks5_auth_name(unsigned char m)
|
||||
{
|
||||
switch (m) {
|
||||
case SOCKS5_AUTH_NONE: return "none";
|
||||
case SOCKS5_AUTH_GSSAPI: return "GSSAPI";
|
||||
case SOCKS5_AUTH_PASSWORD: return "password";
|
||||
case SOCKS5_AUTH_CHAP: return "CHAP";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char *socks5_response_text(unsigned char m)
|
||||
{
|
||||
switch (m) {
|
||||
case SOCKS5_RESP_SUCCESS: return "success";
|
||||
case SOCKS5_RESP_FAILURE: return "unspecified failure";
|
||||
case SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET:
|
||||
return "connection not allowed by ruleset";
|
||||
case SOCKS5_RESP_NETWORK_UNREACHABLE: return "network unreachable";
|
||||
case SOCKS5_RESP_HOST_UNREACHABLE: return "host unreachable";
|
||||
case SOCKS5_RESP_CONNECTION_REFUSED: return "connection refused";
|
||||
case SOCKS5_RESP_TTL_EXPIRED: return "TTL expired";
|
||||
case SOCKS5_RESP_COMMAND_NOT_SUPPORTED: return "command not supported";
|
||||
case SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED:
|
||||
return "address type not supported";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct Socks5ProxyNegotiator {
|
||||
int crLine;
|
||||
strbuf *auth_methods_offered;
|
||||
unsigned char auth_method;
|
||||
unsigned n_chap_attrs;
|
||||
unsigned chap_attr, chap_attr_len;
|
||||
unsigned char chap_buf[256];
|
||||
int response_addr_length;
|
||||
ProxyNegotiator pn;
|
||||
} Socks5ProxyNegotiator;
|
||||
|
||||
static ProxyNegotiator *proxy_socks5_new(const ProxyNegotiatorVT *vt)
|
||||
{
|
||||
Socks5ProxyNegotiator *s = snew(Socks5ProxyNegotiator);
|
||||
memset(s, 0, sizeof(*s));
|
||||
s->pn.vt = vt;
|
||||
s->auth_methods_offered = strbuf_new();
|
||||
return &s->pn;
|
||||
}
|
||||
|
||||
static void proxy_socks5_free(ProxyNegotiator *pn)
|
||||
{
|
||||
Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn);
|
||||
strbuf_free(s->auth_methods_offered);
|
||||
smemclr(s, sizeof(s));
|
||||
sfree(s);
|
||||
}
|
||||
|
||||
static void proxy_socks5_process_queue(ProxyNegotiator *pn)
|
||||
{
|
||||
Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn);
|
||||
|
||||
crBegin(s->crLine);
|
||||
|
||||
/*
|
||||
* SOCKS 5 initial client packet:
|
||||
*
|
||||
* byte version
|
||||
* byte number of available auth methods
|
||||
* byte[] that many bytes indicating auth types
|
||||
*/
|
||||
|
||||
put_byte(pn->output, SOCKS5_REQUEST_VERSION);
|
||||
|
||||
strbuf_clear(s->auth_methods_offered);
|
||||
|
||||
put_byte(s->auth_methods_offered, SOCKS5_AUTH_NONE);
|
||||
|
||||
{
|
||||
const char *username = conf_get_str(pn->ps->conf, CONF_proxy_username);
|
||||
const char *password = conf_get_str(pn->ps->conf, CONF_proxy_password);
|
||||
if (username[0] || password[0]) {
|
||||
if (socks5_chap_available)
|
||||
put_byte(s->auth_methods_offered, SOCKS5_AUTH_CHAP);
|
||||
|
||||
put_byte(s->auth_methods_offered, SOCKS5_AUTH_PASSWORD);
|
||||
}
|
||||
}
|
||||
|
||||
put_byte(pn->output, s->auth_methods_offered->len);
|
||||
put_datapl(pn->output, ptrlen_from_strbuf(s->auth_methods_offered));
|
||||
|
||||
crReturnV;
|
||||
|
||||
/*
|
||||
* SOCKS 5 initial server packet:
|
||||
*
|
||||
* byte version
|
||||
* byte selected auth method, or SOCKS5_AUTH_REJECTED
|
||||
*/
|
||||
{
|
||||
unsigned char data[2];
|
||||
crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2));
|
||||
|
||||
if (data[0] != SOCKS5_REPLY_VERSION) {
|
||||
pn->error = dupprintf("SOCKS proxy returned unexpected "
|
||||
"reply version %d (expected %d)",
|
||||
(int)data[0], SOCKS5_REPLY_VERSION);
|
||||
crStopV;
|
||||
}
|
||||
|
||||
if (data[1] == SOCKS5_AUTH_REJECTED) {
|
||||
pn->error = dupstr("SOCKS server rejected every authentication "
|
||||
"method we offered");
|
||||
crStopV;
|
||||
}
|
||||
|
||||
{
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < s->auth_methods_offered->len; i++)
|
||||
if (s->auth_methods_offered->u[i] == data[1]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
pn->error = dupprintf("SOCKS server asked for auth method %d "
|
||||
"(%s), which we did not offer",
|
||||
(int)data[1], socks5_auth_name(data[1]));
|
||||
crStopV;
|
||||
}
|
||||
}
|
||||
|
||||
s->auth_method = data[1];
|
||||
}
|
||||
|
||||
/*
|
||||
* Process each auth method. Note we can't do this using the
|
||||
* natural idiom of a switch statement, because there are
|
||||
* crReturns inside some cases.
|
||||
*/
|
||||
if (s->auth_method == SOCKS5_AUTH_NONE) {
|
||||
/* 'none' auth needs no further negotiation, so skip ahead
|
||||
* to actually making the connection */
|
||||
goto authenticated;
|
||||
}
|
||||
|
||||
if (s->auth_method == SOCKS5_AUTH_PASSWORD) {
|
||||
/*
|
||||
* SOCKS 5 password auth packet:
|
||||
*
|
||||
* byte version
|
||||
* pstring username
|
||||
* pstring password
|
||||
*/
|
||||
const char *username = conf_get_str(
|
||||
pn->ps->conf, CONF_proxy_username);
|
||||
const char *password = conf_get_str(
|
||||
pn->ps->conf, CONF_proxy_password);
|
||||
put_byte(pn->output, SOCKS5_AUTH_PASSWORD_VERSION);
|
||||
if (!put_pstring(pn->output, username)) {
|
||||
pn->error = dupstr("SOCKS 5 authentication cannot support "
|
||||
"usernames longer than 255 chars");
|
||||
crStopV;
|
||||
}
|
||||
if (!put_pstring(pn->output, password)) {
|
||||
pn->error = dupstr("SOCKS 5 authentication cannot support "
|
||||
"passwords longer than 255 chars");
|
||||
crStopV;
|
||||
}
|
||||
|
||||
/*
|
||||
* SOCKS 5 password reply packet:
|
||||
*
|
||||
* byte version
|
||||
* byte 0 for success, >0 for failure
|
||||
*/
|
||||
unsigned char data[2];
|
||||
crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2));
|
||||
|
||||
if (data[0] != SOCKS5_AUTH_PASSWORD_VERSION) {
|
||||
pn->error = dupprintf(
|
||||
"SOCKS 5 password reply had version number %d (expected "
|
||||
"%d)", (int)data[0], SOCKS5_AUTH_PASSWORD_VERSION);
|
||||
crStopV;
|
||||
}
|
||||
|
||||
if (data[1] != 0) {
|
||||
pn->error = dupstr("SOCKS 5 server rejected our password");
|
||||
crStopV;
|
||||
}
|
||||
} else if (s->auth_method == SOCKS5_AUTH_CHAP) {
|
||||
assert(socks5_chap_available);
|
||||
|
||||
/*
|
||||
* All CHAP packets, in both directions, have the same
|
||||
* overall format:
|
||||
*
|
||||
* byte version
|
||||
* byte number of attributes
|
||||
*
|
||||
* and then for each attribute:
|
||||
*
|
||||
* byte attribute type
|
||||
* byte length
|
||||
* byte[] that many bytes of payload
|
||||
*
|
||||
* In the initial outgoing packet we send two attributes:
|
||||
* the list of supported algorithm names, and the
|
||||
* username.
|
||||
*
|
||||
* (It's possible that we ought to delay sending the
|
||||
* username until the second packet, in case the proxy
|
||||
* sent back an attribute indicating which character set
|
||||
* it would like us to use.)
|
||||
*/
|
||||
put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION);
|
||||
put_byte(pn->output, 2); /* number of attributes */
|
||||
|
||||
put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_ALGLIST);
|
||||
put_byte(pn->output, 1); /* string length */
|
||||
put_byte(pn->output, SOCKS5_AUTH_CHAP_ALG_HMACMD5);
|
||||
|
||||
/* Second attribute: username */
|
||||
{
|
||||
const char *username = conf_get_str(
|
||||
pn->ps->conf, CONF_proxy_username);
|
||||
put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_USERNAME);
|
||||
if (!put_pstring(pn->output, username)) {
|
||||
pn->error = dupstr(
|
||||
"SOCKS 5 CHAP authentication cannot support "
|
||||
"usernames longer than 255 chars");
|
||||
crStopV;
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
/*
|
||||
* Process a CHAP response packet, which has the same
|
||||
* overall format as the outgoing packet shown above.
|
||||
*/
|
||||
unsigned char data[2];
|
||||
crMaybeWaitUntilV(bufchain_try_fetch_consume(
|
||||
pn->input, data, 2));
|
||||
if (data[0] != SOCKS5_AUTH_CHAP_VERSION) {
|
||||
pn->error = dupprintf(
|
||||
"SOCKS 5 CHAP reply had version number %d (expected "
|
||||
"%d)", (int)data[0], SOCKS5_AUTH_CHAP_VERSION);
|
||||
crStopV;
|
||||
}
|
||||
|
||||
s->n_chap_attrs = data[1];
|
||||
if (s->n_chap_attrs == 0) {
|
||||
/*
|
||||
* If we receive a CHAP packet containing no
|
||||
* attributes, then we have nothing we didn't have
|
||||
* before, and can't make further progress.
|
||||
*/
|
||||
pn->error = dupprintf(
|
||||
"SOCKS 5 CHAP reply sent no attributes");
|
||||
crStopV;
|
||||
}
|
||||
while (s->n_chap_attrs-- > 0) {
|
||||
unsigned char data[2];
|
||||
crMaybeWaitUntilV(bufchain_try_fetch_consume(
|
||||
pn->input, data, 2));
|
||||
s->chap_attr = data[0];
|
||||
s->chap_attr_len = data[1];
|
||||
crMaybeWaitUntilV(bufchain_try_fetch_consume(
|
||||
pn->input, s->chap_buf, s->chap_attr_len));
|
||||
|
||||
if (s->chap_attr == SOCKS5_AUTH_CHAP_ATTR_STATUS) {
|
||||
if (s->chap_attr_len == 1 && s->chap_buf[0] == 0) {
|
||||
/* Status 0 means success: we are authenticated! */
|
||||
goto authenticated;
|
||||
} else {
|
||||
pn->error = dupstr(
|
||||
"SOCKS 5 CHAP authentication failed");
|
||||
crStopV;
|
||||
}
|
||||
} else if (s->chap_attr==SOCKS5_AUTH_CHAP_ATTR_CHALLENGE) {
|
||||
/* The CHAP challenge string. Send the response */
|
||||
const char *password = conf_get_str(
|
||||
pn->ps->conf, CONF_proxy_password);
|
||||
strbuf *response = chap_response(
|
||||
make_ptrlen(s->chap_buf, s->chap_attr_len),
|
||||
ptrlen_from_asciz(password));
|
||||
|
||||
put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION);
|
||||
put_byte(pn->output, 1); /* number of attributes */
|
||||
put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_RESPONSE);
|
||||
put_byte(pn->output, response->len);
|
||||
put_datapl(pn->output, ptrlen_from_strbuf(response));
|
||||
|
||||
strbuf_free(response);
|
||||
} else {
|
||||
/* ignore all other attributes */
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable("bad auth method in SOCKS 5 negotiation");
|
||||
}
|
||||
|
||||
authenticated:
|
||||
|
||||
/*
|
||||
* SOCKS 5 connection command:
|
||||
*
|
||||
* byte version
|
||||
* byte command
|
||||
* byte reserved (send as zero)
|
||||
* byte address type
|
||||
* byte[] address, with variable size (see below)
|
||||
* uint16 port
|
||||
*/
|
||||
put_byte(pn->output, SOCKS5_REPLY_VERSION);
|
||||
put_byte(pn->output, SOCKS_CMD_CONNECT);
|
||||
put_byte(pn->output, 0); /* reserved byte */
|
||||
|
||||
switch (sk_addrtype(pn->ps->remote_addr)) {
|
||||
case ADDRTYPE_IPV4: {
|
||||
/* IPv4: address is 4 raw bytes */
|
||||
put_byte(pn->output, SOCKS5_ADDR_IPV4);
|
||||
char buf[4];
|
||||
sk_addrcopy(pn->ps->remote_addr, buf);
|
||||
put_data(pn->output, buf, sizeof(buf));
|
||||
break;
|
||||
}
|
||||
case ADDRTYPE_IPV6: {
|
||||
/* IPv6: address is 16 raw bytes */
|
||||
put_byte(pn->output, SOCKS5_ADDR_IPV6);
|
||||
char buf[16];
|
||||
sk_addrcopy(pn->ps->remote_addr, buf);
|
||||
put_data(pn->output, buf, sizeof(buf));
|
||||
break;
|
||||
}
|
||||
case ADDRTYPE_NAME: {
|
||||
/* Hostname: address is a pstring (Pascal-style string,
|
||||
* unterminated but with a one-byte prefix length) */
|
||||
put_byte(pn->output, SOCKS5_ADDR_HOSTNAME);
|
||||
char hostname[512];
|
||||
sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname));
|
||||
if (!put_pstring(pn->output, hostname)) {
|
||||
pn->error = dupstr(
|
||||
"SOCKS 5 cannot support host names longer than 255 chars");
|
||||
crStopV;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable("Unexpected addrtype in SOCKS 5 proxy");
|
||||
}
|
||||
|
||||
put_uint16(pn->output, pn->ps->remote_port);
|
||||
crReturnV;
|
||||
|
||||
/*
|
||||
* SOCKS 5 connection response:
|
||||
*
|
||||
* byte version
|
||||
* byte status
|
||||
* byte reserved
|
||||
* byte address type
|
||||
* byte[] address bound to (same formats as in connection request)
|
||||
* uint16 port
|
||||
*
|
||||
* We read the first four bytes and then decide what to do next.
|
||||
*/
|
||||
{
|
||||
unsigned char data[4];
|
||||
crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 4));
|
||||
|
||||
if (data[0] != SOCKS5_REPLY_VERSION) {
|
||||
pn->error = dupprintf("SOCKS proxy returned unexpected "
|
||||
"reply version %d (expected %d)",
|
||||
(int)data[0], SOCKS5_REPLY_VERSION);
|
||||
crStopV;
|
||||
}
|
||||
|
||||
if (data[1] != SOCKS5_RESP_SUCCESS) {
|
||||
pn->error = dupprintf("SOCKS proxy failed to connect, error %d "
|
||||
"(%s)", (int)data[1],
|
||||
socks5_response_text(data[1]));
|
||||
crStopV;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process each address type to find out the size of the rest
|
||||
* of the packet. Note we can't do this using the natural
|
||||
* idiom of a switch statement, because there are crReturns
|
||||
* inside some cases.
|
||||
*/
|
||||
if (data[3] == SOCKS5_ADDR_IPV4) {
|
||||
s->response_addr_length = 4;
|
||||
} else if (data[3] == SOCKS5_ADDR_IPV6) {
|
||||
s->response_addr_length = 16;
|
||||
} else if (data[3] == SOCKS5_ADDR_HOSTNAME) {
|
||||
/* Read the hostname length byte to find out how much to read */
|
||||
unsigned char len;
|
||||
crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, &len, 1));
|
||||
s->response_addr_length = len;
|
||||
break;
|
||||
} else {
|
||||
pn->error = dupprintf("SOCKS proxy response included unknown "
|
||||
"address type %d", (int)data[3]);
|
||||
crStopV;
|
||||
}
|
||||
|
||||
/* Read and ignore the address and port fields */
|
||||
crMaybeWaitUntilV(bufchain_try_consume(
|
||||
pn->input, s->response_addr_length + 2));
|
||||
}
|
||||
|
||||
/* And we're done! */
|
||||
pn->done = true;
|
||||
crFinishV;
|
||||
}
|
||||
|
||||
const struct ProxyNegotiatorVT socks5_proxy_negotiator_vt = {
|
||||
.new = proxy_socks5_new,
|
||||
.free = proxy_socks5_free,
|
||||
.process_queue = proxy_socks5_process_queue,
|
||||
.type = "SOCKS 5",
|
||||
};
|
246
proxy/telnet.c
Normal file
246
proxy/telnet.c
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* "Telnet" proxy negotiation.
|
||||
*
|
||||
* (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.)
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
#include "network.h"
|
||||
#include "proxy.h"
|
||||
|
||||
char *format_telnet_command(SockAddr *addr, int port, Conf *conf)
|
||||
{
|
||||
char *fmt = conf_get_str(conf, CONF_proxy_telnet_command);
|
||||
int so = 0, eo = 0;
|
||||
strbuf *buf = strbuf_new();
|
||||
|
||||
/* 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)
|
||||
put_data(buf, fmt + so, 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 '\\':
|
||||
put_byte(buf, '\\');
|
||||
eo++;
|
||||
break;
|
||||
|
||||
case '%':
|
||||
put_byte(buf, '%');
|
||||
eo++;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
put_byte(buf, '\r');
|
||||
eo++;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
put_byte(buf, '\n');
|
||||
eo++;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
put_byte(buf, '\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)
|
||||
*/
|
||||
put_byte(buf, '\\');
|
||||
eo = so + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* we only extract two hex characters */
|
||||
if (i == 1) {
|
||||
put_byte(buf, v);
|
||||
eo++;
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
v <<= 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
put_data(buf, fmt + so, 2);
|
||||
eo++;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
/* % escape. we recognize %%, %host, %port, %user, %pass.
|
||||
* %proxyhost, %proxyport. Anything else we just send
|
||||
* unescaped (including the %).
|
||||
*/
|
||||
|
||||
if (fmt[eo] == '%') {
|
||||
put_byte(buf, '%');
|
||||
eo++;
|
||||
}
|
||||
else if (strnicmp(fmt + eo, "host", 4) == 0) {
|
||||
char dest[512];
|
||||
sk_getaddr(addr, dest, lenof(dest));
|
||||
put_data(buf, dest, strlen(dest));
|
||||
eo += 4;
|
||||
}
|
||||
else if (strnicmp(fmt + eo, "port", 4) == 0) {
|
||||
put_fmt(buf, "%d", port);
|
||||
eo += 4;
|
||||
}
|
||||
else if (strnicmp(fmt + eo, "user", 4) == 0) {
|
||||
const char *username = conf_get_str(conf, CONF_proxy_username);
|
||||
put_data(buf, username, strlen(username));
|
||||
eo += 4;
|
||||
}
|
||||
else if (strnicmp(fmt + eo, "pass", 4) == 0) {
|
||||
const char *password = conf_get_str(conf, CONF_proxy_password);
|
||||
put_data(buf, password, strlen(password));
|
||||
eo += 4;
|
||||
}
|
||||
else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) {
|
||||
const char *host = conf_get_str(conf, CONF_proxy_host);
|
||||
put_data(buf, host, strlen(host));
|
||||
eo += 9;
|
||||
}
|
||||
else if (strnicmp(fmt + eo, "proxyport", 9) == 0) {
|
||||
int port = conf_get_int(conf, CONF_proxy_port);
|
||||
put_fmt(buf, "%d", port);
|
||||
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.
|
||||
*/
|
||||
put_byte(buf, '%');
|
||||
}
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
put_data(buf, fmt + so, eo - so);
|
||||
}
|
||||
|
||||
return strbuf_to_str(buf);
|
||||
}
|
||||
|
||||
typedef struct TelnetProxyNegotiator {
|
||||
ProxyNegotiator pn;
|
||||
} TelnetProxyNegotiator;
|
||||
|
||||
static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt)
|
||||
{
|
||||
TelnetProxyNegotiator *s = snew(TelnetProxyNegotiator);
|
||||
s->pn.vt = vt;
|
||||
return &s->pn;
|
||||
}
|
||||
|
||||
static void proxy_telnet_free(ProxyNegotiator *pn)
|
||||
{
|
||||
TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn);
|
||||
sfree(s);
|
||||
}
|
||||
|
||||
static void proxy_telnet_process_queue(ProxyNegotiator *pn)
|
||||
{
|
||||
// TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn);
|
||||
|
||||
char *formatted_cmd = format_telnet_command(
|
||||
pn->ps->remote_addr, pn->ps->remote_port, pn->ps->conf);
|
||||
|
||||
/*
|
||||
* Re-escape control chars in the command, for logging.
|
||||
*/
|
||||
strbuf *logmsg = strbuf_new();
|
||||
const char *in;
|
||||
|
||||
put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: "));
|
||||
|
||||
for (in = formatted_cmd; *in; in++) {
|
||||
if (*in == '\n') {
|
||||
put_datapl(logmsg, PTRLEN_LITERAL("\\n"));
|
||||
} else if (*in == '\r') {
|
||||
put_datapl(logmsg, PTRLEN_LITERAL("\\r"));
|
||||
} else if (*in == '\t') {
|
||||
put_datapl(logmsg, PTRLEN_LITERAL("\\t"));
|
||||
} else if (*in == '\\') {
|
||||
put_datapl(logmsg, PTRLEN_LITERAL("\\\\"));
|
||||
} else if (0x20 <= *in && *in < 0x7F) {
|
||||
put_byte(logmsg, *in);
|
||||
} else {
|
||||
put_fmt(logmsg, "\\x%02X", (unsigned)*in & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0);
|
||||
strbuf_free(logmsg);
|
||||
|
||||
put_dataz(pn->output, formatted_cmd);
|
||||
sfree(formatted_cmd);
|
||||
|
||||
/*
|
||||
* Unconditionally report success.
|
||||
*/
|
||||
pn->done = true;
|
||||
}
|
||||
|
||||
const struct ProxyNegotiatorVT telnet_proxy_negotiator_vt = {
|
||||
.new = proxy_telnet_new,
|
||||
.free = proxy_telnet_free,
|
||||
.process_queue = proxy_telnet_process_queue,
|
||||
.type = "Telnet",
|
||||
};
|
Loading…
Reference in New Issue
Block a user