Files

1038 lines
27 KiB
C++

// OpenVPN -- An application to securely tunnel IP networks
// over a single port, with support for SSL/TLS-based
// session authentication and key exchange,
// packet encryption, packet authentication, and
// packet compression.
//
// Copyright (C) 2012-2017 OpenVPN Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License Version 3
// as published by the Free Software Foundation.
//
// 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program in the COPYING file.
// If not, see <http://www.gnu.org/licenses/>.
// HTTP proxy transport object.
#ifndef OPENVPN_TRANSPORT_CLIENT_HTTPCLI_H
#define OPENVPN_TRANSPORT_CLIENT_HTTPCLI_H
#include <vector>
#include <string>
#include <sstream>
#include <algorithm> // for std::min
#include <memory>
#include <openvpn/io/io.hpp>
#include <openvpn/common/size.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/string.hpp>
#include <openvpn/common/base64.hpp>
#include <openvpn/common/split.hpp>
#include <openvpn/common/options.hpp>
#include <openvpn/common/number.hpp>
#include <openvpn/common/userpass.hpp>
#include <openvpn/buffer/bufstr.hpp>
#include <openvpn/buffer/buflimit.hpp>
#include <openvpn/transport/tcplink.hpp>
#include <openvpn/transport/client/transbase.hpp>
#include <openvpn/transport/socket_protect.hpp>
#include <openvpn/transport/protocol.hpp>
#include <openvpn/http/reply.hpp>
#include <openvpn/http/status.hpp>
#include <openvpn/http/htmlskip.hpp>
#include <openvpn/proxy/proxyauth.hpp>
#include <openvpn/proxy/httpdigest.hpp>
#include <openvpn/proxy/ntlm.hpp>
#include <openvpn/client/remotelist.hpp>
#include <openvpn/crypto/digestapi.hpp>
namespace openvpn {
namespace HTTPProxyTransport {
class Options : public RC<thread_safe_refcount>
{
public:
struct CustomHeader : public RC<thread_unsafe_refcount>
{
typedef RCPtr<CustomHeader> Ptr;
std::string p1;
std::string p2;
};
struct CustomHeaderList : public std::vector<CustomHeader::Ptr>
{
};
typedef RCPtr<Options> Ptr;
Options() : allow_cleartext_auth(false) {}
RemoteList::Ptr proxy_server;
std::string username;
std::string password;
bool allow_cleartext_auth;
std::string http_version;
std::string user_agent;
CustomHeaderList headers;
void set_proxy_server(const std::string& host, const std::string& port)
{
proxy_server.reset(new RemoteList(host, port, Protocol(Protocol::TCP), "http proxy port"));
}
void proxy_server_set_enable_cache(const bool enable_cache)
{
proxy_server->set_enable_cache(enable_cache);
}
void proxy_server_precache(RemoteList::Ptr& r)
{
if (proxy_server->get_enable_cache())
r = proxy_server;
}
static Ptr parse(const OptionList& opt)
{
if (opt.exists("http-proxy"))
{
Ptr obj(new Options);
if (obj->parse_options(opt))
return obj;
}
return Ptr();
}
private:
bool parse_options(const OptionList& opt)
{
const Option* hp = opt.get_ptr("http-proxy");
if (hp)
{
// get server/port
set_proxy_server(hp->get(1, 256), hp->get(2, 16));
// get creds
{
std::vector<std::string> user_pass;
if (UserPass::parse(opt, "http-proxy-user-pass", 0, &user_pass))
{
if (user_pass.size() >= 1)
username = user_pass[0];
if (user_pass.size() >= 2)
password = user_pass[1];
}
}
// allow cleartext auth?
allow_cleartext_auth = (hp->get_optional(3, 16) != "auto-nct");
// get options
const OptionList::IndexList* hpo = opt.get_index_ptr("http-proxy-option");
if (hpo)
{
for (OptionList::IndexList::const_iterator i = hpo->begin(); i != hpo->end(); ++i)
{
const Option& o = opt[*i];
const std::string& type = o.get(1, 64);
if (type == "VERSION")
{
http_version = o.get(2, 16);
o.touch();
}
else if (type == "AGENT")
{
user_agent = o.get(2, 256);
o.touch();
}
else if (type == "EXT1" || type == "EXT2" || type == "CUSTOM-HEADER")
{
CustomHeader::Ptr h(new CustomHeader());
h->p1 = o.get(2, 512);
h->p2 = o.get_optional(3, 512);
headers.push_back(h);
o.touch();
}
}
}
return true;
}
else
return false;
}
};
class ClientConfig : public TransportClientFactory
{
public:
typedef RCPtr<ClientConfig> Ptr;
RemoteList::Ptr remote_list;
size_t free_list_max_size;
Frame::Ptr frame;
SessionStats::Ptr stats;
Options::Ptr http_proxy_options;
RandomAPI::Ptr rng; // random data source
DigestFactory::Ptr digest_factory; // needed by proxy auth methods
SocketProtect* socket_protect;
bool skip_html;
static Ptr new_obj()
{
return new ClientConfig;
}
virtual TransportClient::Ptr new_transport_client_obj(openvpn_io::io_context& io_context,
TransportClientParent* parent);
private:
ClientConfig()
: free_list_max_size(8),
socket_protect(nullptr),
skip_html(false)
{}
};
class Client : public TransportClient, AsyncResolvableTCP
{
typedef RCPtr<Client> Ptr;
typedef TCPTransport::Link<openvpn_io::ip::tcp, Client*, false> LinkImpl;
friend class ClientConfig; // calls constructor
friend LinkImpl::Base; // calls tcp_read_handler
public:
void transport_start() override
{
if (!impl)
{
if (!config->http_proxy_options)
{
parent->proxy_error(Error::PROXY_ERROR, "http_proxy_options not defined");
return;
}
halt = false;
// Get target server host:port. We don't care about resolving it
// since proxy server will do that for us.
remote_list().endpoint_available(&server_host, &server_port, nullptr);
// Get proxy server host:port, and resolve it if not already cached
if (proxy_remote_list().endpoint_available(&proxy_host, &proxy_port, nullptr))
{
// already cached
start_connect_();
}
else
{
// resolve it
parent->transport_pre_resolve();
async_resolve_lock();
async_resolve_name(proxy_host, proxy_port);
}
}
}
bool transport_send_const(const Buffer& buf) override
{
return send_const(buf);
}
bool transport_send(BufferAllocated& buf) override
{
return send(buf);
}
bool transport_send_queue_empty() override
{
if (impl)
return impl->send_queue_empty();
else
return false;
}
bool transport_has_send_queue() override
{
return true;
}
void transport_stop_requeueing() override { }
unsigned int transport_send_queue_size() override
{
if (impl)
return impl->send_queue_size();
else
return 0;
}
void reset_align_adjust(const size_t align_adjust) override
{
if (impl)
impl->reset_align_adjust(align_adjust);
}
void server_endpoint_info(std::string& host, std::string& port, std::string& proto, std::string& ip_addr) const override
{
host = server_host;
port = server_port;
const IP::Addr addr = server_endpoint_addr();
proto = "TCP";
proto += addr.version_string();
proto += "-via-HTTP";
ip_addr = addr.to_string();
}
IP::Addr server_endpoint_addr() const override
{
return IP::Addr::from_asio(server_endpoint.address());
}
Protocol transport_protocol() const override
{
if (server_endpoint.address().is_v4())
return Protocol(Protocol::TCPv4);
else if (server_endpoint.address().is_v6())
return Protocol(Protocol::TCPv6);
else
return Protocol();
}
void stop() override { stop_(); }
virtual ~Client() { stop_(); }
private:
struct ProxyResponseLimit : public BufferLimit<size_t>
{
ProxyResponseLimit() : BufferLimit(1024, 65536) {}
virtual void bytes_exceeded() {
OPENVPN_THROW_EXCEPTION("HTTP proxy response too large (> " << max_bytes << " bytes)");
}
virtual void lines_exceeded() {
OPENVPN_THROW_EXCEPTION("HTTP proxy response too large (> " << max_lines << " lines)");
}
};
Client(openvpn_io::io_context& io_context_arg,
ClientConfig* config_arg,
TransportClientParent* parent_arg)
: AsyncResolvableTCP(io_context_arg),
socket(io_context_arg),
config(config_arg),
parent(parent_arg),
halt(false),
n_transactions(0),
proxy_established(false),
http_reply_status(HTTP::ReplyParser::pending),
ntlm_phase_2_response_pending(false),
drain_content_length(0)
{
}
void transport_reparent(TransportClientParent* parent_arg) override
{
parent = parent_arg;
}
bool send_const(const Buffer& cbuf)
{
if (impl)
{
BufferAllocated buf(cbuf, 0);
return impl->send(buf);
}
else
return false;
}
bool send(BufferAllocated& buf)
{
if (impl)
return impl->send(buf);
else
return false;
}
void tcp_error_handler(const char *error) // called by LinkImpl and internally
{
std::ostringstream os;
os << "Transport error on '" << server_host << "' via HTTP proxy " << proxy_host << ':' << proxy_port << " : " << error;
stop();
parent->transport_error(Error::TRANSPORT_ERROR, os.str());
}
void proxy_error(const Error::Type fatal_err, const std::string& what)
{
std::ostringstream os;
os << "on " << proxy_host << ':' << proxy_port << ": " << what;
stop();
parent->proxy_error(fatal_err, os.str());
}
bool tcp_read_handler(BufferAllocated& buf) // called by LinkImpl
{
if (proxy_established)
{
if (!html_skip)
parent->transport_recv(buf);
else
drain_html(buf); // skip extraneous HTML after header
}
else
{
try {
proxy_read_handler(buf);
}
catch (const std::exception& e)
{
proxy_error(Error::PROXY_ERROR, e.what());
}
}
return true;
}
void tcp_write_queue_needs_send() // called by LinkImpl
{
if (proxy_established)
parent->transport_needs_send();
}
void tcp_eof_handler() // called by LinkImpl
{
if (proxy_established)
{
config->stats->error(Error::NETWORK_EOF_ERROR);
tcp_error_handler("NETWORK_EOF_ERROR");
}
else
{
try {
proxy_eof_handler();
}
catch (const std::exception& e)
{
proxy_error(Error::PROXY_ERROR, e.what());
}
}
}
void proxy_read_handler(BufferAllocated& buf)
{
// for anti-DoS, only allow a maximum number of chars in HTTP response
proxy_response_limit.add(buf);
if (http_reply_status == HTTP::ReplyParser::pending)
{
OPENVPN_LOG_NTNL("FROM PROXY: " << buf_to_string(buf));
for (size_t i = 0; i < buf.size(); ++i)
{
http_reply_status = http_parser.consume(http_reply, (char)buf[i]);
if (http_reply_status != HTTP::ReplyParser::pending)
{
buf.advance(i+1);
if (http_reply_status == HTTP::ReplyParser::success)
{
//OPENVPN_LOG("*** HTTP header parse complete, resid_size=" << buf.size());
//OPENVPN_LOG(http_reply.to_string());
// we are connected, switch socket to tunnel mode
if (http_reply.status_code == HTTP::Status::Connected)
{
if (config->skip_html)
{
proxy_half_connected();
html_skip.reset(new HTTP::HTMLSkip());
drain_html(buf);
}
else
proxy_connected(buf, true);
}
else if (ntlm_phase_2_response_pending)
ntlm_auth_phase_2_pre();
}
else
{
throw Exception("HTTP proxy header parse error");
}
break;
}
}
}
// handle draining of content controlled by Content-length header
if (drain_content_length)
{
const size_t drain = std::min(drain_content_length, buf.size());
buf.advance(drain);
drain_content_length -= drain;
if (!drain_content_length)
{
if (ntlm_phase_2_response_pending)
ntlm_auth_phase_2();
}
}
}
void proxy_connected(BufferAllocated& buf, const bool notify_parent)
{
proxy_established = true;
if (parent->transport_is_openvpn_protocol())
{
// switch socket from HTTP proxy handshake mode to OpenVPN protocol mode
impl->set_raw_mode(false);
if (notify_parent)
parent->transport_connecting();
try {
impl->inject(buf);
}
catch (const std::exception& e)
{
proxy_error(Error::PROXY_ERROR, std::string("post-header inject error: ") + e.what());
return;
}
}
else
{
if (notify_parent)
parent->transport_connecting();
parent->transport_recv(buf);
}
}
// Called after header received but before possible extraneous HTML
// is drained. At this point, we are in a state where output data
// (if OpenVPN protocol) is packetized, but input data is still in
// raw mode as we search the input stream for the end of the
// extraneous HTML. When we reach the beginning of payload data,
// proxy_connected() should be called with notify_parent == false.
void proxy_half_connected()
{
proxy_established = true;
if (parent->transport_is_openvpn_protocol())
impl->set_raw_mode_write(false);
parent->transport_connecting();
}
void drain_html(BufferAllocated& buf)
{
while (!buf.empty())
{
switch (html_skip->add(buf.pop_front()))
{
case HTTP::HTMLSkip::MATCH:
case HTTP::HTMLSkip::NOMATCH:
{
OPENVPN_LOG("Proxy: Skipped " << html_skip->n_bytes() << " byte(s) of HTML");
html_skip->get_residual(buf);
html_skip.reset();
proxy_connected(buf, false);
return;
}
case HTTP::HTMLSkip::PENDING:
break;
}
}
}
HTTPProxy::ProxyAuthenticate::Ptr get_proxy_authenticate_header(const char *type)
{
for (HTTP::HeaderList::const_iterator i = http_reply.headers.begin(); i != http_reply.headers.end(); ++i)
{
const HTTP::Header& h = *i;
if (string::strcasecmp(h.name, "proxy-authenticate") == 0)
{
HTTPProxy::ProxyAuthenticate::Ptr pa = new HTTPProxy::ProxyAuthenticate(h.value);
if (string::strcasecmp(type, pa->method) == 0)
return pa;
}
}
return HTTPProxy::ProxyAuthenticate::Ptr();
}
void proxy_eof_handler()
{
if (http_reply_status == HTTP::ReplyParser::success)
{
if (http_reply.status_code == HTTP::Status::ProxyAuthenticationRequired)
{
if (n_transactions <= 1)
{
//OPENVPN_LOG("*** PROXY AUTHENTICATION REQUIRED");
if (config->http_proxy_options->username.empty())
{
proxy_error(Error::PROXY_NEED_CREDS, "HTTP proxy requires credentials");
return;
}
HTTPProxy::ProxyAuthenticate::Ptr pa;
// NTLM
pa = get_proxy_authenticate_header("ntlm");
if (pa)
{
ntlm_auth_phase_1(*pa);
return;
}
// Digest
pa = get_proxy_authenticate_header("digest");
if (pa)
{
digest_auth(*pa);
return;
}
// Basic
pa = get_proxy_authenticate_header("basic");
if (pa)
{
if (config->http_proxy_options->allow_cleartext_auth)
{
basic_auth(*pa);
return;
}
else
throw Exception("HTTP proxy Basic authentication not allowed by user preference");
}
throw Exception("HTTP proxy-authenticate method must be Basic, Digest, or NTLM");
}
else
{
proxy_error(Error::PROXY_NEED_CREDS, "HTTP proxy credentials were not accepted");
return;
}
}
else if (http_reply.status_code == HTTP::Status::ProxyError
|| http_reply.status_code == HTTP::Status::NotFound
|| http_reply.status_code == HTTP::Status::ServiceUnavailable)
{
// this is a nonfatal error, so we pass Error::UNDEF to tell the upper layer to
// retry the connection
proxy_error(Error::UNDEF, "HTTP proxy server could not connect to OpenVPN server");
return;
}
else if (http_reply.status_code == HTTP::Status::Forbidden)
OPENVPN_THROW_EXCEPTION("HTTP proxy returned Forbidden status code");
else
OPENVPN_THROW_EXCEPTION("HTTP proxy status code: " << http_reply.status_code);
}
else if (http_reply_status == HTTP::ReplyParser::pending)
throw Exception("HTTP proxy unexpected EOF: reply incomplete");
else
throw Exception("HTTP proxy general error");
}
void basic_auth(HTTPProxy::ProxyAuthenticate& pa)
{
OPENVPN_LOG("Proxy method: Basic" << std::endl << pa.to_string());
std::ostringstream os;
gen_headers(os);
os << "Proxy-Authorization: Basic "
<< base64->encode(config->http_proxy_options->username + ':' + config->http_proxy_options->password)
<< "\r\n";
http_request = os.str();
reset();
start_connect_();
}
void digest_auth(HTTPProxy::ProxyAuthenticate& pa)
{
try {
OPENVPN_LOG("Proxy method: Digest" << std::endl << pa.to_string());
// constants
const std::string http_method = "CONNECT";
const std::string nonce_count = "00000001";
const std::string qop = "auth";
// get values from Proxy-Authenticate header
const std::string realm = pa.parms.get_value("realm");
const std::string nonce = pa.parms.get_value("nonce");
const std::string algorithm = pa.parms.get_value("algorithm");
const std::string opaque = pa.parms.get_value("opaque");
// generate a client nonce
unsigned char cnonce_raw[8];
config->rng->assert_crypto();
config->rng->rand_bytes(cnonce_raw, sizeof(cnonce_raw));
const std::string cnonce = render_hex(cnonce_raw, sizeof(cnonce_raw));
// build URI
const std::string uri = server_host + ":" + server_port;
// calculate session key
const std::string session_key = HTTPProxy::Digest::calcHA1(
*config->digest_factory,
algorithm,
config->http_proxy_options->username,
realm,
config->http_proxy_options->password,
nonce,
cnonce);
// calculate response
const std::string response = HTTPProxy::Digest::calcResponse(
*config->digest_factory,
session_key,
nonce,
nonce_count,
cnonce,
qop,
http_method,
uri,
"");
// generate proxy request
std::ostringstream os;
gen_headers(os);
os << "Proxy-Authorization: Digest username=\"" << config->http_proxy_options->username << "\", realm=\"" << realm << "\", nonce=\"" << nonce << "\", uri=\"" << uri << "\", qop=" << qop << ", nc=" << nonce_count << ", cnonce=\"" << cnonce << "\", response=\"" << response << "\"";
if (!opaque.empty())
os << ", opaque=\"" + opaque + "\"";
os << "\r\n";
http_request = os.str();
reset();
start_connect_();
}
catch (const std::exception& e)
{
proxy_error(Error::PROXY_NEED_CREDS, std::string("Digest Auth: ") + e.what());
}
}
std::string get_ntlm_phase_2_response()
{
for (HTTP::HeaderList::const_iterator i = http_reply.headers.begin(); i != http_reply.headers.end(); ++i)
{
const HTTP::Header& h = *i;
if (string::strcasecmp(h.name, "proxy-authenticate") == 0)
{
std::vector<std::string> v = Split::by_space<std::vector<std::string>, StandardLex, SpaceMatch, Split::NullLimit>(h.value);
if (v.size() >= 2 && string::strcasecmp("ntlm", v[0]) == 0)
return v[1];
}
}
return "";
}
void ntlm_auth_phase_1(HTTPProxy::ProxyAuthenticate& pa)
{
OPENVPN_LOG("Proxy method: NTLM" << std::endl << pa.to_string());
const std::string phase_1_reply = HTTPProxy::NTLM::phase_1();
std::ostringstream os;
gen_headers(os);
os << "Proxy-Connection: Keep-Alive\r\n";
os << "Proxy-Authorization: NTLM " << phase_1_reply << "\r\n";
http_request = os.str();
reset();
ntlm_phase_2_response_pending = true;
start_connect_();
}
void ntlm_auth_phase_2_pre()
{
// if content exists, drain it first, then progress to ntlm_auth_phase_2
const std::string content_length_str = http_reply.headers.get_value_trim("content-length");
const unsigned int content_length = parse_number_throw<unsigned int>(content_length_str, "content-length");
if (content_length)
drain_content_length = content_length;
else
ntlm_auth_phase_2();
}
void ntlm_auth_phase_2()
{
ntlm_phase_2_response_pending = false;
if (http_reply.status_code != HTTP::Status::ProxyAuthenticationRequired)
throw Exception("NTLM phase-2 status is not ProxyAuthenticationRequired");
const std::string phase_2_response = get_ntlm_phase_2_response();
if (!phase_2_response.empty())
ntlm_auth_phase_3(phase_2_response);
else
throw Exception("NTLM phase-2 response missing");
}
void ntlm_auth_phase_3(const std::string& phase_2_response)
{
// do the NTLMv2 handshake
try {
//OPENVPN_LOG("NTLM phase 3: " << phase_2_response);
const std::string phase_3_reply = HTTPProxy::NTLM::phase_3(
*config->digest_factory,
phase_2_response,
config->http_proxy_options->username,
config->http_proxy_options->password,
*config->rng);
std::ostringstream os;
gen_headers(os);
os << "Proxy-Connection: Keep-Alive\r\n";
os << "Proxy-Authorization: NTLM " << phase_3_reply << "\r\n";
http_request = os.str();
reset_partial();
http_proxy_send();
}
catch (const std::exception& e)
{
proxy_error(Error::PROXY_NEED_CREDS, std::string("NTLM Auth: ") + e.what());
}
}
void gen_headers(std::ostringstream& os)
{
bool host_header_sent = false;
// emit custom headers
{
const Options::CustomHeaderList& headers = config->http_proxy_options->headers;
for (Options::CustomHeaderList::const_iterator i = headers.begin(); i != headers.end(); ++i)
{
const Options::CustomHeader& h = **i;
if (!h.p2.empty())
{
os << h.p1 << ": " << h.p2 << "\r\n";
if (!string::strcasecmp(h.p1, "host"))
host_header_sent = true;
}
else
{
os << h.p1 << "\r\n";
const std::string h5 = h.p1.substr(0, 5);
if (!string::strcasecmp(h5, "host:"))
host_header_sent = true;
}
}
}
// emit user-agent header
{
const std::string& user_agent = config->http_proxy_options->user_agent;
if (!user_agent.empty())
os << "User-Agent: " << user_agent << "\r\n";
}
// emit host header
if (!host_header_sent)
os << "Host: " << server_host << "\r\n";
}
void stop_()
{
if (!halt)
{
halt = true;
if (impl)
impl->stop();
socket.close();
async_resolve_cancel();
}
}
// do DNS resolve
void resolve_callback(const openvpn_io::error_code& error,
openvpn_io::ip::tcp::resolver::results_type results) override
{
// release resolver allocated resources
async_resolve_cancel();
if (!halt)
{
if (!error)
{
// save resolved endpoint list in proxy remote_list
proxy_remote_list().set_endpoint_range(results);
start_connect_();
}
else
{
std::ostringstream os;
os << "DNS resolve error on '" << proxy_host << "' for TCP (HTTP proxy): " << error.message();
config->stats->error(Error::RESOLVE_ERROR);
stop();
parent->transport_error(Error::UNDEF, os.str());
}
}
}
void reset()
{
stop();
halt = false;
proxy_response_limit.reset();
proxy_established = false;
reset_partial();
}
void reset_partial()
{
http_reply_status = HTTP::ReplyParser::pending;
http_reply.reset();
http_parser.reset();
ntlm_phase_2_response_pending = false;
drain_content_length = 0;
html_skip.reset();
}
// do TCP connect
void start_connect_()
{
proxy_remote_list().get_endpoint(server_endpoint);
OPENVPN_LOG("Contacting " << server_endpoint << " via HTTP Proxy");
parent->transport_wait_proxy();
socket.open(server_endpoint.protocol());
if (config->socket_protect)
{
if (!config->socket_protect->socket_protect(socket.native_handle(), server_endpoint_addr()))
{
config->stats->error(Error::SOCKET_PROTECT_ERROR);
stop();
parent->transport_error(Error::UNDEF, "socket_protect error (HTTP Proxy)");
return;
}
}
socket.set_option(openvpn_io::ip::tcp::no_delay(true));
socket.async_connect(server_endpoint, [self=Ptr(this)](const openvpn_io::error_code& error)
{
OPENVPN_ASYNC_HANDLER;
self->start_impl_(error);
});
}
// start I/O on TCP socket
void start_impl_(const openvpn_io::error_code& error)
{
if (!halt)
{
if (!error)
{
parent->transport_wait();
impl.reset(new LinkImpl(this,
socket,
0, // send_queue_max_size is unlimited because we regulate size in cliproto.hpp
config->free_list_max_size,
(*config->frame)[Frame::READ_LINK_TCP],
config->stats));
impl->set_raw_mode(true);
impl->start();
++n_transactions;
// tell proxy to connect through to OpenVPN server
http_proxy_send();
}
else
{
proxy_remote_list().next();
std::ostringstream os;
os << "TCP connect error on '" << proxy_host << ':' << proxy_port << "' (" << server_endpoint << ") for TCP-via-HTTP-proxy session: " << error.message();
config->stats->error(Error::TCP_CONNECT_ERROR);
stop();
parent->transport_error(Error::UNDEF, os.str());
}
}
}
void http_proxy_send()
{
BufferAllocated buf;
create_http_connect_msg(buf);
send(buf);
}
// create HTTP CONNECT message
void create_http_connect_msg(BufferAllocated& buf)
{
std::ostringstream os;
const std::string& http_version = config->http_proxy_options->http_version;
os << "CONNECT " << server_host << ':' << server_port << " HTTP/";
if (!http_version.empty())
os << http_version;
else
os << "1.0";
os << "\r\n";
if (!http_request.empty())
os << http_request;
else
gen_headers(os);
os << "\r\n";
const std::string str = os.str();
http_request = "";
OPENVPN_LOG_NTNL("TO PROXY: " << str);
config->frame->prepare(Frame::WRITE_HTTP, buf);
buf_write_string(buf, str);
}
RemoteList& remote_list() const { return *config->remote_list; }
RemoteList& proxy_remote_list() const { return *config->http_proxy_options->proxy_server; }
std::string proxy_host;
std::string proxy_port;
std::string server_host;
std::string server_port;
openvpn_io::ip::tcp::socket socket;
ClientConfig::Ptr config;
TransportClientParent* parent;
LinkImpl::Ptr impl;
LinkImpl::protocol::endpoint server_endpoint;
bool halt;
unsigned int n_transactions;
ProxyResponseLimit proxy_response_limit;
bool proxy_established;
HTTP::ReplyParser::status http_reply_status;
HTTP::Reply http_reply;
HTTP::ReplyParser http_parser;
std::string http_request;
bool ntlm_phase_2_response_pending;
size_t drain_content_length;
std::unique_ptr<HTTP::HTMLSkip> html_skip;
};
inline TransportClient::Ptr ClientConfig::new_transport_client_obj(openvpn_io::io_context& io_context, TransportClientParent* parent)
{
return TransportClient::Ptr(new Client(io_context, this, parent));
}
}
} // namespace openvpn
#endif