/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2008 Leonid Evdokimov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** http-connect upstream module for redsocks */ #include #include #include #include #include #include #include #include "log.h" #include "redsocks.h" #include "http-auth.h" typedef enum httpc_state_t { httpc_new, httpc_request_sent, httpc_reply_came, httpc_headers_skipped, httpc_MAX, } httpc_state; #define HTTP_HEAD_WM_HIGH 4096 // that should be enough for one HTTP line. static void httpc_client_init(redsocks_client *client) { client->state = httpc_new; } static struct evbuffer *httpc_mkconnect(redsocks_client *client); static const char *auth_request_header = "Proxy-Authenticate:"; static const char *auth_response_header = "Proxy-Authorization:"; static const time_t auth_error_gap = 60; // quit after connsective auth fail in a time interval, in secs static char *get_auth_request_header(struct evbuffer *buf) { char *line; for (;;) { line = evbuffer_readline(buf); if (line == NULL || *line == '\0' || strchr(line, ':') == NULL) return NULL; if (strncmp_nocase(line, auth_request_header, strlen(auth_request_header)) == 0) return line; } } static void httpc_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; int dropped = 0; assert(client->state >= httpc_request_sent); redsocks_touch_client(client); if (client->state == httpc_request_sent) { size_t len = EVBUFFER_LENGTH(buffev->input); char *line = 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); time_t now_time = time(NULL); if (auth->last_auth_query != NULL && now_time - auth->last_auth_time < auth_error_gap) { redsocks_log_error(client, LOG_NOTICE, "consective auth failure"); redsocks_drop_client(client); dropped = 1; } else { free_null(auth->last_auth_query); char *auth_request = get_auth_request_header(buffev->input); char *ptr = auth_request; ptr += strlen(auth_request_header); while (isspace(*ptr)) ptr++; auth->last_auth_query = calloc(strlen(ptr) + 1, 1); strcpy(auth->last_auth_query, ptr); auth->last_auth_time = now_time; auth->last_auth_count = 0; free(auth_request); redsocks_log_error(client, LOG_NOTICE, "got challenge %s, restarting now", auth->last_auth_query); if (bufferevent_disable(client->relay, EV_WRITE)) { redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); return; } /* close relay tunnel */ close(EVENT_FD(&client->relay->ev_write)); bufferevent_free(client->relay); /* set to initial state*/ client->state = httpc_new; /* and reconnect */ redsocks_connect_relay(client); return; } } else if (200 <= code && code <= 299) { client->state = httpc_reply_came; } else { redsocks_log_error(client, LOG_NOTICE, "%s", line); redsocks_drop_client(client); dropped = 1; } } free(line); } else if (len >= HTTP_HEAD_WM_HIGH) { redsocks_drop_client(client); dropped = 1; } } if (dropped) return; while (client->state == httpc_reply_came) { char *line = evbuffer_readline(buffev->input); if (line) { if (strlen(line) == 0) { client->state = httpc_headers_skipped; } free(line); } else { break; } } if (client->state == httpc_headers_skipped) { redsocks_start_relay(client); } } static struct evbuffer *httpc_mkconnect(redsocks_client *client) { struct evbuffer *buff = NULL, *retval = NULL; int len; buff = evbuffer_new(); if (!buff) { redsocks_log_errno(client, LOG_ERR, "evbuffer_new"); goto fail; } http_auth *auth = (void*)(client->instance + 1); const char *auth_scheme = NULL; char *auth_string = NULL; if (auth->last_auth_query != NULL) { ++auth->last_auth_count; /* find previous auth challange */ redsocks_log_error(client, LOG_NOTICE, "find previous challange %s, apply it", auth->last_auth_query); if (strncmp_nocase(auth->last_auth_query, "Basic", 5) == 0) { auth_string = basic_authentication_encode(client->instance->config.login, client->instance->config.password); auth_scheme = "Basic"; } else if (strncmp_nocase(auth->last_auth_query, "Digest", 6) == 0) { /* calculate uri */ char uri[128]; snprintf(uri, 128, "%s:%u", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port)); /* prepare an random string for cnounce */ char cnounce[17]; srand(time(0)); for (int i = 0; i < 16; i += 4) sprintf(cnounce + i, "%02x", rand() & 65535); auth_string = digest_authentication_encode(auth->last_auth_query + 7, //line client->instance->config.login, client->instance->config.password, //user, pass "CONNECT", uri, auth->last_auth_count, cnounce); // method, path, nc, cnounce auth_scheme = "Digest"; } if (auth_string != NULL) { redsocks_log_error(client, LOG_NOTICE, "prepared answer %s", auth_string); } } if (auth_string == NULL) { len = evbuffer_add_printf(buff, "CONNECT %s:%u HTTP/1.0\r\n\r\n", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port) ); } else { len = evbuffer_add_printf(buff, "CONNECT %s:%u HTTP/1.0\r\n%s %s %s\r\n\r\n", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port), auth_response_header, auth_scheme, auth_string ); } free_null(auth_string); if (len < 0) { redsocks_log_errno(client, LOG_ERR, "evbufer_add_printf"); goto fail; } retval = buff; buff = NULL; fail: if (buff) evbuffer_free(buff); return retval; } static void httpc_write_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; redsocks_touch_client(client); if (client->state == httpc_new) { redsocks_write_helper_ex( buffev, client, httpc_mkconnect, httpc_request_sent, 1, HTTP_HEAD_WM_HIGH ); } else if (client->state >= httpc_request_sent) { bufferevent_disable(buffev, EV_WRITE); } } relay_subsys http_connect_subsys = { .name = "http-connect", .payload_len = 0, .instance_payload_len = sizeof(http_auth), .readcb = httpc_read_cb, .writecb = httpc_write_cb, .init = httpc_client_init, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */