aboutsummaryrefslogtreecommitdiff
path: root/src/proxy-bio.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/proxy-bio.c')
-rw-r--r--src/proxy-bio.c430
1 files changed, 430 insertions, 0 deletions
diff --git a/src/proxy-bio.c b/src/proxy-bio.c
new file mode 100644
index 0000000..2576112
--- /dev/null
+++ b/src/proxy-bio.c
@@ -0,0 +1,430 @@
+/*
+ * proxy-bio.c - BIO layer for SOCKS4a/5 proxy connections
+ *
+ * Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * This file implements a SOCKS4a/SOCKS5 "filter" BIO. In SSL terminology, a BIO
+ * is a stackable IO filter, kind of like sysv streams. These filters are
+ * inserted into a stream to cause it to run SOCKS over whatever transport is
+ * being used. Most commonly, this would be:
+ * SSL BIO (filter) -> SOCKS BIO (filter) -> connect BIO (source/sink)
+ * This configuration represents doing an SSL connection through a SOCKS proxy,
+ * which is itself connected to in plaintext. You might also do:
+ * SSL BIO -> SOCKS BIO -> SSL BIO -> connect BIO
+ * This is an SSL connection through a SOCKS proxy which is itself reached over
+ * SSL.
+ */
+
+#include "config.h"
+
+#include <arpa/inet.h>
+#include <assert.h>
+#ifndef __USE_MISC
+#define __USE_MISC
+#endif
+#ifndef __USE_POSIX
+#define __USE_POSIX
+#endif
+#include <netdb.h>
+
+#include <stdint.h>
+
+#ifndef HAVE_STRNLEN
+#include "src/common/strnlen.h"
+#endif
+
+#include "src/proxy-bio.h"
+
+int socks4a_connect (BIO *b);
+int socks5_connect (BIO *b);
+int http_connect (BIO *b);
+
+int proxy_new (BIO *b)
+{
+ struct proxy_ctx *ctx = (struct proxy_ctx *) malloc (sizeof *ctx);
+ if (!ctx)
+ return 0;
+ ctx->connected = 0;
+ ctx->connect = NULL;
+ ctx->host = NULL;
+ ctx->port = 0;
+ b->init = 1;
+ b->flags = 0;
+ b->ptr = ctx;
+ return 1;
+}
+
+int proxy_free (BIO *b)
+{
+ struct proxy_ctx *c;
+ if (!b || !b->ptr)
+ return 1;
+ c = (struct proxy_ctx *) b->ptr;
+ if (c->host)
+ free (c->host);
+ c->host = NULL;
+ b->ptr = NULL;
+ free (c);
+ return 1;
+}
+
+int socks4a_connect (BIO *b)
+{
+ struct proxy_ctx *ctx = (struct proxy_ctx *) b->ptr;
+ int r;
+ unsigned char buf[NI_MAXHOST + 16];
+ uint16_t port_n = htons (ctx->port);
+ size_t sz = 0;
+ verb ("V: proxy4: connecting %s:%d", ctx->host, ctx->port);
+ /*
+ * Packet layout:
+ * 1b: Version (must be 0x04)
+ * 1b: command (0x01 is connect)
+ * 2b: port number, big-endian
+ * 4b: 0x00, 0x00, 0x00, 0x01 (bogus IPv4 addr)
+ * 1b: 0x00 (empty 'userid' field)
+ * nb: hostname, null-terminated
+ */
+ buf[0] = 0x04;
+ buf[1] = 0x01;
+ sz += 2;
+ memcpy (buf + 2, &port_n, sizeof (port_n));
+ sz += sizeof (port_n);
+ buf[4] = 0x00;
+ buf[5] = 0x00;
+ buf[6] = 0x00;
+ buf[7] = 0x01;
+ sz += 4;
+ buf[8] = 0x00;
+ sz += 1;
+
+ memcpy (buf + sz, ctx->host, strlen (ctx->host) + 1);
+ sz += strlen (ctx->host) + 1;
+ r = BIO_write (b->next_bio, buf, sz);
+ if ( -1 == r )
+ return -1;
+ if ( (size_t) r != sz)
+ return 0;
+ /* server reply: 1 + 1 + 2 + 4 */
+ r = BIO_read (b->next_bio, buf, 8);
+ if ( -1 == r )
+ return -1;
+ if ( (size_t) r != 8)
+ return 0;
+ if (buf[1] == 0x5a)
+ {
+ verb ("V: proxy4: connected");
+ ctx->connected = 1;
+ return 1;
+ }
+ return 0;
+}
+
+int socks5_connect (BIO *b)
+{
+ unsigned char buf[NI_MAXHOST + 16];
+ int r;
+ struct proxy_ctx *ctx = (struct proxy_ctx *) b->ptr;
+ uint16_t port_n = htons (ctx->port);
+ size_t sz = 0;
+ /* the length for SOCKS addresses is only one byte. */
+ if (strnlen (ctx->host, UINT8_MAX + 1) == UINT8_MAX + 1)
+ return 0;
+ verb ("V: proxy5: connecting %s:%d", ctx->host, ctx->port);
+ /*
+ * Hello packet layout:
+ * 1b: Version
+ * 1b: auth methods
+ * nb: method types
+ *
+ * We support only one method (no auth, 0x00). Others listed in RFC
+ * 1928.
+ */
+ buf[0] = 0x05;
+ buf[1] = 0x01;
+ buf[2] = 0x00;
+ r = BIO_write (b->next_bio, buf, 3);
+ if (r != 3)
+ return 0;
+ r = BIO_read (b->next_bio, buf, 2);
+ if (r != 2)
+ return 0;
+ if (buf[0] != 0x05 || buf[1] != 0x00)
+ {
+ verb ("V: proxy5: auth error %02x %02x", buf[0], buf[1]);
+ return 0;
+ }
+ /*
+ * Connect packet layout:
+ * 1b: version
+ * 1b: command (0x01 is connect)
+ * 1b: reserved, 0x00
+ * 1b: addr type (0x03 is domain name)
+ * nb: addr len (1b) + addr bytes, no null termination
+ * 2b: port, network byte order
+ */
+ buf[0] = 0x05;
+ buf[1] = 0x01;
+ buf[2] = 0x00;
+ buf[3] = 0x03;
+ buf[4] = strlen (ctx->host);
+ sz += 5;
+ memcpy (buf + 5, ctx->host, strlen (ctx->host));
+ sz += strlen (ctx->host);
+ memcpy (buf + sz, &port_n, sizeof (port_n));
+ sz += sizeof (port_n);
+ r = BIO_write (b->next_bio, buf, sz);
+ if ( -1 == r )
+ return -1;
+ if ( (size_t) r != sz)
+ return 0;
+ /*
+ * Server's response:
+ * 1b: version
+ * 1b: status (0x00 is okay)
+ * 1b: reserved, 0x00
+ * 1b: addr type (0x03 is domain name, 0x01 ipv4)
+ * nb: addr len (1b) + addr bytes, no null termination
+ * 2b: port, network byte order
+ */
+ /* grab up through the addr type */
+ r = BIO_read (b->next_bio, buf, 4);
+ if ( -1 == r )
+ return -1;
+ if (r != 4)
+ return 0;
+ if (buf[0] != 0x05 || buf[1] != 0x00)
+ {
+ verb ("V: proxy5: connect error %02x %02x", buf[0], buf[1]);
+ return 0;
+ }
+ if (buf[3] == 0x03)
+ {
+ unsigned int len;
+ r = BIO_read (b->next_bio, buf + 4, 1);
+ if (r != 1)
+ return 0;
+ /* host (buf[4] bytes) + port (2 bytes) */
+ len = buf[4] + 2;
+ while (len)
+ {
+ r = BIO_read (b->next_bio, buf + 5, min (len, sizeof (buf)));
+ if (r <= 0)
+ return 0;
+ len -= min (len, r);
+ }
+ }
+ else if (buf[3] == 0x01)
+ {
+ /* 4 bytes ipv4 addr, 2 bytes port */
+ r = BIO_read (b->next_bio, buf + 4, 6);
+ if (r != 6)
+ return 0;
+ }
+ verb ("V: proxy5: connected");
+ ctx->connected = 1;
+ return 1;
+}
+
+/* SSL socket BIOs don't support BIO_gets, so... */
+int sock_gets (BIO *b, char *buf, size_t sz)
+{
+ char c;
+ while (BIO_read (b, &c, 1) > 0 && sz > 1)
+ {
+ *buf++ = c;
+ sz--;
+ if (c == '\n')
+ {
+ *buf = '\0';
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int http_connect (BIO *b)
+{
+ int r;
+ struct proxy_ctx *ctx = (struct proxy_ctx *) b->ptr;
+ char buf[4096];
+ int retcode;
+ snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.1\r\n",
+ ctx->host, ctx->port);
+ r = BIO_write (b->next_bio, buf, strlen (buf));
+ if ( -1 == r )
+ return -1;
+ if ( (size_t) r != strlen(buf))
+ return 0;
+ /* required by RFC 2616 14.23 */
+ snprintf (buf, sizeof (buf), "Host: %s:%d\r\n", ctx->host, ctx->port);
+ r = BIO_write (b->next_bio, buf, strlen (buf));
+ if ( -1 == r )
+ return -1;
+ if ( (size_t) r != strlen(buf))
+ return 0;
+ strcpy (buf, "\r\n");
+ r = BIO_write (b->next_bio, buf, strlen (buf));
+ if ( -1 == r )
+ return -1;
+ if ( (size_t) r != strlen(buf))
+ return 0;
+ r = sock_gets (b->next_bio, buf, sizeof (buf));
+ if (r)
+ return 0;
+ /* use %*s to ignore the version */
+ if (sscanf (buf, "HTTP/%*s %d", &retcode) != 1)
+ return 0;
+ if (retcode < 200 || retcode > 299)
+ return 0;
+ while (! (r = sock_gets (b->next_bio, buf, sizeof (buf))))
+ {
+ if (!strcmp (buf, "\r\n"))
+ {
+ /* Done with the header */
+ ctx->connected = 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int proxy_write (BIO *b, const char *buf, int sz)
+{
+ int r;
+ struct proxy_ctx *ctx = (struct proxy_ctx *) b->ptr;
+
+ assert (buf);
+ if (sz <= 0)
+ return 0;
+ if (!b->next_bio)
+ return 0;
+ if (!ctx->connected)
+ {
+ assert (ctx->connect);
+ if (!ctx->connect (b))
+ return 0;
+ }
+ r = BIO_write (b->next_bio, buf, sz);
+ BIO_clear_retry_flags (b);
+ BIO_copy_next_retry (b);
+ return r;
+}
+
+int proxy_read (BIO *b, char *buf, int sz)
+{
+ int r;
+ struct proxy_ctx *ctx = (struct proxy_ctx *) b->ptr;
+
+ assert (buf);
+ if (!b->next_bio)
+ return 0;
+ if (!ctx->connected)
+ {
+ assert (ctx->connect);
+ if (!ctx->connect (b))
+ return 0;
+ }
+ r = BIO_read (b->next_bio, buf, sz);
+ BIO_clear_retry_flags (b);
+ BIO_copy_next_retry (b);
+ return r;
+}
+
+long proxy_ctrl (BIO *b, int cmd, long num, void *ptr)
+{
+ long ret;
+ struct proxy_ctx *ctx;
+ if (!b->next_bio)
+ return 0;
+ ctx = (struct proxy_ctx *) b->ptr;
+ assert (ctx);
+ switch (cmd)
+ {
+ case BIO_C_DO_STATE_MACHINE:
+ BIO_clear_retry_flags (b);
+ ret = BIO_ctrl (b->next_bio, cmd, num, ptr);
+ BIO_copy_next_retry (b);
+ break;
+ case BIO_CTRL_DUP:
+ ret = 0;
+ break;
+ default:
+ ret = BIO_ctrl (b->next_bio, cmd, num, ptr);
+ }
+ return ret;
+}
+
+int proxy_gets (BIO *b, char *buf, int size)
+{
+ return BIO_gets (b->next_bio, buf, size);
+}
+
+int proxy_puts (BIO *b, const char *str)
+{
+ return BIO_puts (b->next_bio, str);
+}
+
+long proxy_callback_ctrl (BIO *b, int cmd, bio_info_cb *fp)
+{
+ if (!b->next_bio)
+ return 0;
+ return BIO_callback_ctrl (b->next_bio, cmd, fp);
+}
+
+BIO_METHOD proxy_methods =
+{
+ BIO_TYPE_MEM,
+ "proxy",
+ proxy_write,
+ proxy_read,
+ proxy_puts,
+ proxy_gets,
+ proxy_ctrl,
+ proxy_new,
+ proxy_free,
+ proxy_callback_ctrl,
+};
+
+BIO_METHOD *BIO_f_proxy()
+{
+ return &proxy_methods;
+}
+
+/* API starts here */
+
+BIO API *BIO_new_proxy()
+{
+ return BIO_new (BIO_f_proxy());
+}
+
+int API BIO_proxy_set_type (BIO *b, const char *type)
+{
+ struct proxy_ctx *ctx = (struct proxy_ctx *) b->ptr;
+ if (!strcmp (type, "socks5"))
+ ctx->connect = socks5_connect;
+ else if (!strcmp (type, "socks4a"))
+ ctx->connect = socks4a_connect;
+ else if (!strcmp (type, "http"))
+ ctx->connect = http_connect;
+ else
+ return 1;
+ return 0;
+}
+
+int API BIO_proxy_set_host (BIO *b, const char *host)
+{
+ struct proxy_ctx *ctx = (struct proxy_ctx *) b->ptr;
+ if (strnlen (host, NI_MAXHOST) == NI_MAXHOST)
+ return 1;
+ ctx->host = strdup (host);
+ return 0;
+}
+
+void API BIO_proxy_set_port (BIO *b, uint16_t port)
+{
+ struct proxy_ctx *ctx = (struct proxy_ctx *) b->ptr;
+ ctx->port = port;
+}