mirror of
https://github.com/amnezia-vpn/OpenVPNAdapter.git
synced 2026-05-26 12:57:01 +03:00
316 lines
9.4 KiB
C++
316 lines
9.4 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-2020 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/>.
|
|
|
|
#pragma once
|
|
|
|
#include <utility>
|
|
|
|
#include <openvpn/common/exception.hpp>
|
|
#include <openvpn/common/options.hpp>
|
|
#include <openvpn/common/wstring.hpp>
|
|
#include <openvpn/common/jsonhelper.hpp>
|
|
#include <openvpn/buffer/bufstr.hpp>
|
|
#include <openvpn/buffer/bufhex.hpp>
|
|
#include <openvpn/frame/frame_init.hpp>
|
|
#include <openvpn/ws/httpcliset.hpp>
|
|
#include <openvpn/win/winerr.hpp>
|
|
#include <openvpn/client/win/agentconfig.hpp>
|
|
#include <openvpn/win/modname.hpp>
|
|
#include <openvpn/tun/win/client/setupbase.hpp>
|
|
#include <openvpn/win/npinfo.hpp>
|
|
#include <openvpn/win/handlecomm.hpp>
|
|
#include <openvpn/win/event.hpp>
|
|
#include <openvpn/error/error.hpp>
|
|
|
|
namespace openvpn {
|
|
|
|
class WinCommandAgent : public TunWin::SetupFactory
|
|
{
|
|
public:
|
|
typedef RCPtr<WinCommandAgent> Ptr;
|
|
|
|
OPENVPN_EXCEPTION(ovpnagent);
|
|
|
|
static TunWin::SetupFactory::Ptr new_agent(const OptionList& opt)
|
|
{
|
|
return new WinCommandAgent(opt);
|
|
}
|
|
|
|
static bool add_bypass_route(IP::Addr endpoint)
|
|
{
|
|
std::ostringstream os;
|
|
os << "WinCommandAgent: transmitting bypass route to " << endpoint.to_string() << std::endl;
|
|
|
|
// Build JSON request
|
|
Json::Value jreq(Json::objectValue);
|
|
#if _WIN32_WINNT < 0x0600 // pre-Vista needs us to explicitly communicate our PID
|
|
jreq["pid"] = Json::Value((Json::UInt)::GetProcessId(::GetCurrentProcess()));
|
|
#endif
|
|
|
|
jreq["host"] = endpoint.to_string();
|
|
jreq["ipv6"] = endpoint.is_ipv6();
|
|
const std::string jtxt = jreq.toStyledString();
|
|
os << jtxt; // dump it
|
|
|
|
OPENVPN_LOG(os.str());
|
|
|
|
// Create HTTP transaction container
|
|
WS::ClientSet::TransactionSet::Ptr ts = SetupClient::new_transaction_set(Agent::named_pipe_path(), 1, Win::module_name_utf8(), [](HANDLE) { });
|
|
|
|
SetupClient::make_transaction("add-bypass-route", jtxt, ts);
|
|
|
|
// Execute transaction
|
|
WS::ClientSet::new_request_synchronous(ts);
|
|
|
|
return ts->http_status_success();
|
|
}
|
|
|
|
private:
|
|
struct Config : public RC<thread_unsafe_refcount>
|
|
{
|
|
typedef RCPtr<Config> Ptr;
|
|
|
|
Config()
|
|
{
|
|
npserv = Agent::named_pipe_path();
|
|
client_exe = Win::module_name_utf8();
|
|
debug_level = 1;
|
|
wintun = false;
|
|
}
|
|
|
|
std::string npserv; // server pipe
|
|
std::string client_exe; // for validation
|
|
int debug_level;
|
|
bool wintun;
|
|
};
|
|
|
|
class SetupClient : public TunWin::SetupBase
|
|
{
|
|
public:
|
|
SetupClient(openvpn_io::io_context& io_context,
|
|
const Config::Ptr& config_arg)
|
|
: config(config_arg),
|
|
service_process(io_context)
|
|
{
|
|
}
|
|
|
|
template<class T>
|
|
static WS::ClientSet::TransactionSet::Ptr new_transaction_set(const std::string& host,
|
|
int debug_level,
|
|
const std::string& client_exe,
|
|
T cb)
|
|
{
|
|
WS::Client::Config::Ptr hc(new WS::Client::Config());
|
|
hc->frame = frame_init_simple(2048);
|
|
hc->connect_timeout = 30;
|
|
hc->general_timeout = 60;
|
|
|
|
WS::ClientSet::TransactionSet::Ptr ts = new WS::ClientSet::TransactionSet;
|
|
ts->host.host = host;
|
|
ts->host.port = "np";
|
|
ts->http_config = hc;
|
|
ts->debug_level = debug_level;
|
|
|
|
#if _WIN32_WINNT >= 0x0600 // Vista and higher
|
|
ts->post_connect = [host, client_exe, cb=std::move(cb)](WS::ClientSet::TransactionSet& ts, AsioPolySock::Base& sock) {
|
|
AsioPolySock::NamedPipe* np = dynamic_cast<AsioPolySock::NamedPipe*>(&sock);
|
|
if (np)
|
|
{
|
|
Win::NamedPipePeerInfoServer npinfo(np->handle.native_handle());
|
|
const std::string server_exe = wstring::to_utf8(npinfo.exe_path);
|
|
if (!Agent::valid_pipe(client_exe, server_exe))
|
|
OPENVPN_THROW(ovpnagent, host << " server running from " << server_exe << " could not be validated");
|
|
cb(npinfo.proc.release());
|
|
}
|
|
};
|
|
#endif
|
|
return ts;
|
|
}
|
|
|
|
static void make_transaction(const std::string& method, const std::string& content, WS::ClientSet::TransactionSet::Ptr ts)
|
|
{
|
|
std::unique_ptr<WS::ClientSet::Transaction> t(new WS::ClientSet::Transaction);
|
|
t->req.method = "POST";
|
|
t->req.uri = "/" + method;
|
|
t->ci.type = "application/json";
|
|
t->content_out.push_back(buf_from_string(content));
|
|
ts->transactions.push_back(std::move(t));
|
|
}
|
|
|
|
private:
|
|
virtual HANDLE establish(const TunBuilderCapture& pull,
|
|
const std::wstring& openvpn_app_path,
|
|
Stop* stop,
|
|
std::ostream& os,
|
|
TunWin::RingBuffer::Ptr ring_buffer) override // TunWin::SetupBase
|
|
{
|
|
os << "SetupClient: transmitting tun setup list to " << config->npserv << std::endl;
|
|
|
|
// Build JSON request
|
|
Json::Value jreq(Json::objectValue);
|
|
#if _WIN32_WINNT < 0x0600 // pre-Vista needs us to explicitly communicate our PID
|
|
jreq["pid"] = Json::Value((Json::UInt)::GetProcessId(::GetCurrentProcess()));
|
|
#endif
|
|
|
|
if (ring_buffer)
|
|
ring_buffer->serialize(jreq);
|
|
|
|
jreq["wintun"] = config->wintun;
|
|
jreq["confirm_event"] = confirm_event.duplicate_local();
|
|
jreq["destroy_event"] = destroy_event.duplicate_local();
|
|
jreq["tun"] = pull.to_json(); // convert TunBuilderCapture to JSON
|
|
const std::string jtxt = jreq.toStyledString();
|
|
os << jtxt; // dump it
|
|
|
|
// Create HTTP transaction container
|
|
WS::ClientSet::TransactionSet::Ptr ts = new_transaction_set(config->npserv, config->debug_level, config->client_exe, [this](HANDLE handle) {
|
|
service_process.assign(handle);
|
|
});
|
|
|
|
make_transaction("tun-setup", jtxt, ts);
|
|
|
|
// Execute transaction
|
|
WS::ClientSet::new_request_synchronous(ts, stop);
|
|
|
|
// Get result
|
|
const Json::Value jres = get_json_result(os, *ts);
|
|
|
|
// Dump log
|
|
const std::string log_txt = json::get_string(jres, "log_txt");
|
|
os << log_txt;
|
|
|
|
// Parse TAP handle
|
|
const std::string tap_handle_hex = json::get_string(jres, "tap_handle_hex");
|
|
os << "TAP handle: " << tap_handle_hex << std::endl;
|
|
const HANDLE tap = BufHex::parse<HANDLE>(tap_handle_hex, "TAP handle");
|
|
return tap;
|
|
}
|
|
|
|
virtual void l2_finish(const TunBuilderCapture& pull,
|
|
Stop* stop,
|
|
std::ostream& os) override
|
|
{
|
|
throw ovpnagent("l2_finish not implemented");
|
|
}
|
|
|
|
virtual bool l2_ready(const TunBuilderCapture& pull) override
|
|
{
|
|
throw ovpnagent("l2_ready not implemented");
|
|
}
|
|
|
|
virtual void confirm() override
|
|
{
|
|
confirm_event.signal_event();
|
|
}
|
|
|
|
virtual void set_service_fail_handler(std::function<void()>&& handler)
|
|
{
|
|
if (service_process.is_open())
|
|
{
|
|
service_process.async_wait([handler=std::move(handler)](const openvpn_io::error_code& error) {
|
|
if (!error)
|
|
handler();
|
|
});
|
|
}
|
|
}
|
|
|
|
virtual void destroy(std::ostream& os) override // defined by DestructorBase
|
|
{
|
|
os << "SetupClient: signaling tun destroy event" << std::endl;
|
|
service_process.close();
|
|
destroy_event.signal_event();
|
|
}
|
|
|
|
Json::Value get_json_result(std::ostream& os, WS::ClientSet::TransactionSet& ts)
|
|
{
|
|
// Get content
|
|
if (ts.transactions.size() != 1)
|
|
throw ovpnagent("unexpected transaction set size");
|
|
WS::ClientSet::Transaction& t = *ts.transactions[0];
|
|
const std::string content = t.content_in.to_string();
|
|
os << t.format_status(ts) << std::endl;
|
|
|
|
if (t.comm_status_timeout())
|
|
{
|
|
// this could be the case when agent service
|
|
// hasn't been started yet, so we throw a non-fatal
|
|
// exception which makes core retry.
|
|
os << "connection timeout";
|
|
throw ExceptionCode(Error::TUN_ERROR);
|
|
}
|
|
|
|
if (!t.comm_status_success())
|
|
{
|
|
os << content;
|
|
throw ovpnagent("communication error");
|
|
}
|
|
if (!t.request_status_success())
|
|
{
|
|
os << content;
|
|
throw ovpnagent("request error");
|
|
}
|
|
|
|
// Verify content-type
|
|
if (t.reply.headers.get_value_trim("content-type") != "application/json")
|
|
{
|
|
os << content;
|
|
throw ovpnagent("unexpected content-type");
|
|
}
|
|
|
|
// Parse the returned json dict
|
|
Json::CharReaderBuilder builder;
|
|
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
|
|
Json::Value jres;
|
|
std::string err;
|
|
if (!reader->parse(content.c_str(), content.c_str() + content.size(), &jres, &err))
|
|
{
|
|
os << content;
|
|
OPENVPN_THROW(ovpnagent, "error parsing returned JSON: " << err);
|
|
}
|
|
return jres;
|
|
}
|
|
|
|
Config::Ptr config;
|
|
openvpn_io::windows::object_handle service_process;
|
|
Win::Event confirm_event;
|
|
Win::DestroyEvent destroy_event;
|
|
};
|
|
|
|
virtual TunWin::SetupBase::Ptr new_setup_obj(openvpn_io::io_context& io_context, bool wintun) override
|
|
{
|
|
if (config)
|
|
{
|
|
config->wintun = wintun;
|
|
return new SetupClient(io_context, config);
|
|
}
|
|
else
|
|
return new TunWin::Setup(io_context, wintun);
|
|
}
|
|
|
|
WinCommandAgent(const OptionList& opt_parent)
|
|
{
|
|
config.reset(new Config);
|
|
}
|
|
|
|
Config::Ptr config;
|
|
};
|
|
}
|