Skip to content

Commit

Permalink
Add on_proxy_fail to inform user's browser about sort of failure
Browse files Browse the repository at this point in the history
Use the feature with care, enable it only for HTTP port to avoid
confusion, no client protocol detection is done at the moment.
  • Loading branch information
darkk committed Apr 12, 2016
1 parent ec06dc6 commit 7963de7
Show file tree
Hide file tree
Showing 14 changed files with 236 additions and 67 deletions.
27 changes: 26 additions & 1 deletion http-auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

#include "md5.h"
#include "base64.h"

#include "log.h"
#include "http-auth.h"

char* basic_authentication_encode(const char *user, const char *passwd)
Expand Down Expand Up @@ -270,3 +270,28 @@ char* digest_authentication_encode(const char *line, const char *user, const cha

const char *auth_request_header = "Proxy-Authenticate:";
const char *auth_response_header = "Proxy-Authorization:";

char *http_auth_request_header(struct evbuffer *src, struct evbuffer *tee)
{
char *line;
for (;;) {
line = redsocks_evbuffer_readline(src);
if (tee && line) {
if (evbuffer_add(tee, line, strlen(line)) != 0 ||
evbuffer_add(tee, "\r\n", 2) != 0)
{
log_error(LOG_NOTICE, "evbuffer_add");
free(line);
return NULL; // I'm going up straight to the 403...
}
}
// FIXME: multi-line headers are not supported
if (line == NULL || *line == '\0' || strchr(line, ':') == NULL) {
free(line);
return NULL;
}
if (strncasecmp(line, auth_request_header, strlen(auth_request_header)) == 0)
return line;
free(line);
}
}
9 changes: 9 additions & 0 deletions http-auth.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
#ifndef HTTP_AUTH_H
#define HTTP_AUTH_H

#include "redsocks.h"

typedef struct http_auth_t {
char *last_auth_query;
int last_auth_count;
} http_auth;

static inline http_auth* red_http_auth(redsocks_instance *i)
{
return (http_auth*)(i + 1);
}

/*
* Create the authentication header contents for the `Basic' scheme.
* This is done by encoding the string "USER:PASS" to base64 and
Expand All @@ -23,4 +30,6 @@ char* basic_authentication_encode(const char *user, const char *passwd);
char* digest_authentication_encode(const char *line, const char *user, const char *passwd,
const char *method, const char *path, int count, const char *cnonce);

char *http_auth_request_header(struct evbuffer *src, struct evbuffer *tee);

#endif /* HTTP_AUTH_H */
96 changes: 62 additions & 34 deletions http-connect.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@
typedef enum httpc_state_t {
httpc_new,
httpc_request_sent,
httpc_reply_came,
httpc_headers_skipped,
httpc_reply_came, // 200 OK came, skipping headers...
httpc_headers_skipped, // starting pump!
httpc_no_way, // proxy can't handle the request
httpc_MAX,
} httpc_state;

Expand All @@ -50,7 +51,7 @@ static void httpc_client_init(redsocks_client *client)

static void httpc_instance_fini(redsocks_instance *instance)
{
http_auth *auth = (void*)(instance + 1);
http_auth *auth = red_http_auth(instance);
free(auth->last_auth_query);
auth->last_auth_query = NULL;
}
Expand All @@ -60,55 +61,47 @@ static struct evbuffer *httpc_mkconnect(redsocks_client *client);
extern const char *auth_request_header;
extern const char *auth_response_header;

static char *get_auth_request_header(struct evbuffer *buf)
{
char *line;
for (;;) {
line = redsocks_evbuffer_readline(buf);
if (line == NULL || *line == '\0' || strchr(line, ':') == NULL) {
free(line);
return NULL;
}
if (strncasecmp(line, auth_request_header, strlen(auth_request_header)) == 0)
return line;
free(line);
}
}

static void httpc_read_cb(struct bufferevent *buffev, void *_arg)
{
redsocks_client *client = _arg;
int dropped = 0;

assert(client->state >= httpc_request_sent);
assert(client->relay == buffev);
assert(client->state == httpc_request_sent || client->state == httpc_reply_came);

redsocks_touch_client(client);

// evbuffer_add() triggers callbacks, so we can't write to client->client
// till we know that we're going to ONFAIL_FORWARD_HTTP_ERR.
// And the decision is made when all the headers are processed.
struct evbuffer* tee = NULL;
const bool do_errtee = client->instance->config.on_proxy_fail == ONFAIL_FORWARD_HTTP_ERR;

if (client->state == httpc_request_sent) {
size_t len = evbuffer_get_length(buffev->input);
char *line = redsocks_evbuffer_readline(buffev->input);
if (line) {
unsigned int code;
if (sscanf(line, "HTTP/%*u.%*u %u", &code) == 1) { // 1 == one _assigned_ match
if (code == 407) { // auth failed
http_auth *auth = (void*)(client->instance + 1);
http_auth *auth = red_http_auth(client->instance);

if (auth->last_auth_query != NULL && auth->last_auth_count == 1) {
redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth failed: %s", line);
redsocks_drop_client(client);
dropped = 1;
client->state = httpc_no_way;
} else if (client->instance->config.login == NULL || client->instance->config.password == NULL) {
redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no login/password configured: %s", line);
redsocks_drop_client(client);
dropped = 1;
client->state = httpc_no_way;
} else {
char *auth_request = get_auth_request_header(buffev->input);
if (do_errtee)
tee = evbuffer_new();
char *auth_request = http_auth_request_header(buffev->input, tee);
if (!auth_request) {
redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no <%s> header found: %s", auth_request_header, line);
redsocks_drop_client(client);
dropped = 1;
client->state = httpc_no_way;
} else {
free(line);
if (tee)
evbuffer_free(tee);
free(auth->last_auth_query);
char *ptr = auth_request;

Expand Down Expand Up @@ -143,21 +136,56 @@ static void httpc_read_cb(struct bufferevent *buffev, void *_arg)
client->state = httpc_reply_came;
} else {
redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy error: %s", line);
redsocks_drop_client(client);
dropped = 1;
client->state = httpc_no_way;
}
} else {
redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy bad firstline: %s", line);
client->state = httpc_no_way;
}
if (do_errtee && client->state == httpc_no_way) {
if (bufferevent_write(client->client, line, strlen(line)) != 0 ||
bufferevent_write(client->client, "\r\n", 2) != 0)
{
redsocks_log_errno(client, LOG_NOTICE, "bufferevent_write");
goto fail;
}
}
free(line);
}
else if (len >= HTTP_HEAD_WM_HIGH) {
redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy reply is too long, %zu bytes", len);
redsocks_drop_client(client);
dropped = 1;
client->state = httpc_no_way;
}
}

if (dropped)
if (do_errtee && client->state == httpc_no_way) {
if (tee) {
if (bufferevent_write_buffer(client->client, tee) != 0) {
redsocks_log_errno(client, LOG_NOTICE, "bufferevent_write_buffer");
goto fail;
}
}
redsocks_shutdown(client, client->client, SHUT_RD);
const size_t avail = evbuffer_get_length(client->client->input);
if (avail) {
if (evbuffer_drain(client->client->input, avail) != 0) {
redsocks_log_errno(client, LOG_NOTICE, "evbuffer_drain");
goto fail;
}
}
redsocks_shutdown(client, client->relay, SHUT_WR);
client->state = httpc_headers_skipped;
}

fail:
if (tee) {
evbuffer_free(tee);
}

if (client->state == httpc_no_way) {
redsocks_drop_client(client);
return;
}

while (client->state == httpc_reply_came) {
char *line = redsocks_evbuffer_readline(buffev->input);
Expand Down Expand Up @@ -189,7 +217,7 @@ static struct evbuffer *httpc_mkconnect(redsocks_client *client)
goto fail;
}

http_auth *auth = (void*)(client->instance + 1);
http_auth *auth = red_http_auth(client->instance);
++auth->last_auth_count;

const char *auth_scheme = NULL;
Expand Down
27 changes: 8 additions & 19 deletions http-relay.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,26 +126,11 @@ static void httpr_instance_init(redsocks_instance *instance)

static void httpr_instance_fini(redsocks_instance *instance)
{
http_auth *auth = (void*)(instance + 1);
http_auth *auth = red_http_auth(instance);
free(auth->last_auth_query);
auth->last_auth_query = NULL;
}

static char *get_auth_request_header(struct evbuffer *buf)
{
char *line;
for (;;) {
line = redsocks_evbuffer_readline(buf);
if (line == NULL || *line == '\0' || strchr(line, ':') == NULL) {
free(line);
return NULL;
}
if (strncasecmp(line, auth_request_header, strlen(auth_request_header)) == 0)
return line;
free(line);
}
}

static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg)
{
redsocks_client *client = _arg;
Expand All @@ -168,7 +153,7 @@ static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg)
unsigned int code;
if (sscanf(line, "HTTP/%*u.%*u %u", &code) == 1) { // 1 == one _assigned_ match
if (code == 407) { // auth failed
http_auth *auth = (void*)(client->instance + 1);
http_auth *auth = red_http_auth(client->instance);

if (auth->last_auth_query != NULL && auth->last_auth_count == 1) {
redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth failed: %s", line);
Expand All @@ -180,7 +165,7 @@ static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg)
dropped = 1;
} else {
free(line);
char *auth_request = get_auth_request_header(buffev->input);
char *auth_request = http_auth_request_header(buffev->input, NULL);

if (!auth_request) {
redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no <%s> header found: %s", auth_request_header, line);
Expand Down Expand Up @@ -225,6 +210,10 @@ static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg)
redsocks_drop_client(client);
dropped = 1;
}
} else {
redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy bad firstline: %s", line);
redsocks_drop_client(client);
dropped = 1;
}
free(line);
}
Expand Down Expand Up @@ -285,7 +274,7 @@ static void httpr_relay_write_cb(struct bufferevent *buffev, void *_arg)
}


http_auth *auth = (void*)(client->instance + 1);
http_auth *auth = red_http_auth(client->instance);
++auth->last_auth_count;

const char *auth_scheme = NULL;
Expand Down
18 changes: 18 additions & 0 deletions parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,23 @@ static int vp_disclose_src(parser_context *context, void *addr, const char *toke
return -1;
}

static int vp_on_proxy_fail(parser_context *context, void *addr, const char *token)
{
enum on_proxy_fail_e *dst = addr;
struct { char *name; enum on_proxy_fail_e value; } opt[] = {
{ "close", ONFAIL_CLOSE },
{ "forward_http_err", ONFAIL_FORWARD_HTTP_ERR },
};
for (int i = 0; i < SIZEOF_ARRAY(opt); ++i) {
if (strcmp(token, opt[i].name) == 0) {
*dst = opt[i].value;
return 0;
}
}
parser_error(context, "on_proxy_fail <%s> is not parsed", token);
return -1;
}

static int vp_pchar(parser_context *context, void *addr, const char *token)
{
char *p = strdup(token);
Expand Down Expand Up @@ -421,6 +438,7 @@ static value_parser value_parser_by_type[] =
[pt_in_addr] = vp_in_addr,
[pt_in_addr2] = vp_in_addr2,
[pt_disclose_src] = vp_disclose_src,
[pt_on_proxy_fail] = vp_on_proxy_fail,
};

int parser_run(parser_context *context)
Expand Down
6 changes: 6 additions & 0 deletions parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ enum disclose_src_e {
DISCLOSE_FORWARDED_IPPORT,
};

enum on_proxy_fail_e {
ONFAIL_CLOSE,
ONFAIL_FORWARD_HTTP_ERR,
};

typedef enum {
pt_bool, // "bool" from stdbool.h, not "_Bool" or anything else
pt_pchar,
pt_uint16,
pt_in_addr,
pt_in_addr2, // inaddr[0] = net, inaddr[1] = netmask
pt_disclose_src,
pt_on_proxy_fail,
} parser_type;

typedef struct parser_entry_t {
Expand Down
Loading

0 comments on commit 7963de7

Please sign in to comment.