mirror of
https://github.com/deneraraujo/OpenVPNAdapter.git
synced 2026-02-11 00:00:08 +08:00
1038 lines
27 KiB
C++
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
|