| /* |
| * 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 <arpa/inet.h> |
| #include <assert.h> |
| #ifndef __USE_MISC |
| #define __USE_MISC |
| #endif |
| #ifndef __USE_POSIX |
| #define __USE_POSIX |
| #endif |
| |
| #ifndef NI_MAXHOST |
| #define NI_MAXHOST 1025 |
| #endif |
| |
| #ifndef UINT8_MAX |
| #define UINT8_MAX (255) |
| #endif |
| |
| #include <netdb.h> |
| |
| #include <inttypes.h> |
| |
| #include "src/proxy-bio-plan9.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 (strlen(ctx->host) == 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 (strlen(host) == 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; |
| } |