25#define HTTP_SERVER_VERSION OPENVPN_STRINGIZE(VERSION)
27#define HTTP_SERVER_VERSION "0.1.0"
33#define OVPNAGENT_NAME_STRING OPENVPN_STRINGIZE(OVPNAGENT_NAME)
35#define OVPNAGENT_NAME_STRING "ovpnagent"
76#ifdef OPENVPN_AGENT_START_PROCESS
93 void error(
const size_t err_type,
const std::string *text =
nullptr)
override
100 std::ostringstream os;
101 os <<
"OpenVPN Agent Stats" << std::endl;
127 bool allow_local_dns_resolvers)
136 return tun->get_adapter_state();
140 const std::wstring &openvpn_app_path,
141 DWORD client_process_id,
145 bool allow_local_dns_resolvers,
152 tun->set_adapter_state(tap);
154 tun->set_process_id(client_process_id);
177 local_tap_handle.
ref(),
180 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
182 os <<
"destroy_tun: no client confirm, DuplicateHandle (close) succeeded" << std::endl;
187 os <<
"destroy_tun: no client confirm, DuplicateHandle (close) failed: " << err.message() << std::endl;
191 catch (
const std::exception &e)
193 os <<
"destroy_tun: exception in remote tap handle close: " << e.what() << std::endl;
207 catch (
const std::exception &e)
209 os <<
"destroy_tun: exception in tun teardown: " << e.what() << std::endl;
220 catch (
const std::exception &e)
222 os <<
"destroy_tun: exception in cleanup: " << e.what() << std::endl;
230 std::ostringstream os;
242 client_process.async_wait([self =
Ptr(
this)](
const openvpn_io::error_code &error)
247 std::ostringstream os;
248 self->remove_cmds_bypass_hosts.execute(os);
249 self->remove_cmds_bypass_hosts.clear();
255 std::ostringstream os;
256 self->destroy_tun(os);
266 const HANDLE remote_event = BufHex::parse<HANDLE>(confirm_handle_hex,
"confirm event handle");
274 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
291 throw Exception(
"set_client_confirm_event: confirm event is abandoned");
306 self->remote_tap_handle_hex.clear();
317 const HANDLE remote_event = BufHex::parse<HANDLE>(event_handle_hex,
"destroy event handle");
325 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
341 throw Exception(
"set_client_destroy_event: destroy event is already signaled");
343 throw Exception(
"set_client_destroy_event: destroy event is abandoned");
355 std::ostringstream os;
356 self->remove_cmds_bypass_hosts.execute(os);
357 self->remove_cmds_bypass_hosts.clear();
363 std::ostringstream os;
364 self->destroy_tun(os);
394 std::ostringstream os;
401 ADDRESS_FAMILY af =
ipv6 ? AF_INET6 : AF_INET;
409#ifdef OPENVPN_AGENT_START_PROCESS
410 void start_openvpn_process(HANDLE client_pipe,
411 const std::string &config_file,
412 const std::string &config_dir,
413 const std::string &exit_event_name,
414 const std::string &management_host,
415 const std::string &management_password,
416 const int management_port,
417 const std::string &log,
418 const bool log_append)
425 HANDLE imp_token = NULL, pri_token = NULL;
426 BOOL res = OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, FALSE, &imp_token);
429 const openvpn_io::error_code err(::GetLastError(), openvpn_io::error::get_system_category());
432 res = DuplicateTokenEx(imp_token, 0, NULL, SECURITY_IMPERSONATION_LEVEL::SecurityAnonymous, TokenPrimary, &pri_token);
435 const openvpn_io::error_code err(::GetLastError(), openvpn_io::error::get_system_category());
440 HANDLE stdin_read = NULL, stdin_write = NULL;
441 SECURITY_ATTRIBUTES inheritable = {
sizeof(inheritable), NULL, TRUE};
442 if (!CreatePipe(&stdin_read, &stdin_write, &inheritable, 0)
443 || !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0))
445 const openvpn_io::error_code err(::GetLastError(), openvpn_io::error::get_system_category());
450 std::ostringstream ss;
451 ss <<
"client --config \"" << config_dir <<
"\\" << config_file <<
"\" --exit-event-name "
452 << exit_event_name <<
" --auth-retry interact --management " << management_host <<
" "
453 << management_port <<
" stdin --management-query-passwords --management-hold "
455 << (log_append ?
"-append \"" :
" \"") << log <<
"\"";
456 std::string cmd = ss.str();
457 std::unique_ptr<char[]> buf(
new char[cmd.length() + 1]);
458 strcpy(buf.get(), cmd.c_str());
462 STARTUPINFO startup_info = {0};
463 startup_info.cb =
sizeof(startup_info);
464 startup_info.dwFlags = STARTF_USESTDHANDLES;
465 startup_info.hStdInput = stdin_read;
468 PROCESS_INFORMATION proc_info;
469 ZeroMemory(&proc_info,
sizeof(proc_info));
470 res = CreateProcessAsUser(pri_token,
476 CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
483 const openvpn_io::error_code err(::GetLastError(), openvpn_io::error::get_system_category());
486 CloseHandle(proc_info.hProcess);
487 CloseHandle(proc_info.hThread);
491 if (!is_safe_conversion<DWORD>(management_password.length()))
493 WriteFile(stdin_write, management_password.c_str(),
static_cast<DWORD
>(management_password.length()), &written, NULL);
506 AsioPolySock::NamedPipe *np =
dynamic_cast<AsioPolySock::NamedPipe *
>(&sock);
510 const std::string client_exe = wstring::to_utf8(npinfo.
exe_path);
514 OPENVPN_LOG(client_exe <<
" not recognized as a valid client");
517 OPENVPN_LOG(
"only named pipe clients are allowed");
553 ci.
type =
"application/json";
562 std::ostringstream os;
571 OPENVPN_LOG(
"HTTP request received from " <<
sock->remote_endpoint_str() <<
'\n'
585 if (!root.isObject())
586 throw Exception(
"json parse error: top level json object is not a dictionary");
588 if (req.
uri ==
"/tun-open")
591 if (
parent()->destroy_tun(os))
593 os <<
"Destroyed previous TAP instance" << std::endl;
600 const std::string confirm_event_hex =
json::get_string(root,
"confirm_event");
601 const std::string destroy_event_hex =
json::get_string(root,
"destroy_event");
620 Json::Value jout(Json::objectValue);
621 jout[
"log_txt"] = log_txt;
625 jout[
"adapter_guid"] = tap.
guid;
626 jout[
"adapter_index"] = Json::Int(tap.index);
627 jout[
"adapter_name"] = tap.name;
634 else if (req.
uri ==
"/tun-setup")
662 os <<
"Destroyed previous TAP instance" << std::endl;
674 const std::string destroy_event_hex =
json::get_string(root,
"destroy_event");
680 const std::string confirm_event_hex =
json::get_string(root,
"confirm_event");
706 Win::ScopedHANDLE tap_handle(
parent()->establish_tun(*tbc, client_exe, client_pid,
nullptr, os, tun_type, allow_local_dns_resolvers, tap));
718 Json::Value jout(Json::objectValue);
719 jout[
"log_txt"] = log_txt;
726 else if (req.
uri ==
"/add-bypass-route")
742 Json::Value jout(Json::objectValue);
746#ifdef OPENVPN_AGENT_START_PROCESS
747 else if (req.
uri ==
"/start")
751 const std::string exit_event_name =
json::get_string(root,
"exit_event_name");
752 const std::string management_host =
json::get_string(root,
"management_host");
753 const std::string management_password =
json::get_string(root,
"management_password") +
"\n";
754 const int management_port =
json::get_int(root,
"management_port");
756 const bool log_append =
json::get_int(root,
"log-append") == 1;
758 parent()->start_openvpn_process(client_pipe,
768 Json::Value jout(Json::objectValue);
778 ci.
type =
"text/plain";
784 catch (
const std::exception &e)
786 if (
parent()->destroy_tun(os))
787 os <<
"Destroyed previous TAP instance due to exception" << std::endl;
796 ci.
type =
"text/plain";
821 bool http_stop(
const int status,
const std::string &description)
override
834 AsioPolySock::NamedPipe *np =
dynamic_cast<AsioPolySock::NamedPipe *
>(
sock.
get());
836 throw Exception(
"only named pipe clients are allowed");
837 return np->handle.native_handle();
861 throw Exception(
"cannot determine client PID");
901 catch (
const std::exception &e)
903 std::cerr << e.what() << std::endl;
909 io_context.reset(
new openvpn_io::io_context(1));
924 hconf->sddl_string =
"D:"
925 "(D;OICI;GA;;;S-1-5-2)"
926 "(A;OICI;GA;;;S-1-5-32-544)"
927 "(A;OICI;GA;;;S-1-5-18)"
928 "(D;OICI;0x4;;;S-1-1-0)"
929 "(A;OICI;GRGW;;;S-1-5-11)"
930 "(A;OICI;GRGW;;;S-1-5-32-546)"
934 const unsigned int n_pipe_instances = 4;
935 for (
unsigned int i = 0; i < n_pipe_instances; ++i)
943 ll.push_back(std::move(li));
983 const std::string fn =
path::join(moddir,
"agent.log");
994int main(
int argc,
char *argv[])
1006 const std::string arg = argv[1];
1009 else if (arg ==
"install")
1011 else if (arg ==
"remove")
1013 else if (arg ==
"modname")
1015 else if (arg ==
"help")
1017 std::cout <<
"usage: ovpnagent [options]" << std::endl;
1018 std::cout <<
" run -- run in foreground (for debugging)" << std::endl;
1019 std::cout <<
" install -- install as service" << std::endl;
1020 std::cout <<
" remove -- uninstall" << std::endl;
1021 std::cout <<
" modname -- show module name" << std::endl;
1022 std::cout <<
" help -- show help message" << std::endl;
1023 std::cout <<
" [default] -- start as service" << std::endl;
1027 std::cout <<
"unrecognized option, use 'help' for more info" << std::endl;
1034 catch (
const std::exception &e)
1036 std::cout <<
"ovpnagent: " << e.what() << std::endl;
WS::Server::Listener::Client::Ptr new_client(WS::Server::Listener::Client::Initializer &ci) override
RCPtr< MyClientFactory > Ptr
DWORD get_client_pid(const HANDLE client_pipe)
Get the named pipe client process id.
void http_content_in(BufferAllocated &buf) override
bool http_stop(const int status, const std::string &description) override
void generate_reply(const Json::Value &jout)
MyClientInstance(WS::Server::Listener::Client::Initializer &ci)
bool http_out_eof() override
void http_request_received() override
BufferPtr http_content_out() override
std::wstring get_client_exe(const HANDLE client_pipe)
HANDLE get_client_pipe() const
virtual ~MyClientInstance()=default
RCPtr< MyClientInstance > Ptr
Win::ScopedHANDLE get_client_process(const HANDLE pipe, ULONG pid_hint) const
MyListener(const MyConfig &config_arg, openvpn_io::io_context &io_context, const WS::Server::Config::Ptr &hconf, const Listen::List &listen_list, const WS::Server::Listener::Client::Factory::Ptr &client_factory)
void set_client_destroy_event(const std::string &event_handle_hex)
bool allow_client(AsioPolySock::Base &sock) override
TunWin::Util::TapNameGuidPair get_adapter_state()
Win::ScopedHANDLE tun_get_handle(std::ostream &os, const TunWin::Type tun_type, bool allow_local_dns_resolvers)
Win::ScopedHANDLE establish_tun(const TunBuilderCapture &tbc, const std::wstring &openvpn_app_path, DWORD client_process_id, Stop *stop, std::ostream &os, TunWin::Type tun_type, bool allow_local_dns_resolvers, TunWin::Util::TapNameGuidPair tap)
const std::string & get_remote_tap_handle_hex()
void set_client_confirm_event(const std::string &confirm_handle_hex)
void set_remote_tap_handle_hex(const HANDLE tap_handle)
ActionList remove_cmds_bypass_hosts
DWORD vpn_interface_index
openvpn_io::io_context & io_context_
openvpn_io::windows::object_handle client_confirm_event
void set_client_process(Win::ScopedHANDLE &&proc)
bool destroy_tun(std::ostream &os)
void destroy_tun(std::ostream &os)
void add_bypass_route(const std::string &host, bool ipv6)
HANDLE get_client_process()
std::string remote_tap_handle_hex
TunWin::RingBuffer::Ptr ring_buffer
openvpn_io::windows::object_handle client_process
void assign_ring_buffer(TunWin::RingBuffer *ring_buffer_arg)
openvpn_io::windows::object_handle client_destroy_event
void service_stop() override
void service_work(DWORD argc, LPWSTR *argv) override
std::unique_ptr< openvpn_io::io_context > io_context
static std::string log_fn()
void error(const size_t err_type, const std::string *text=nullptr) override
RCPtr< MySessionStats > Ptr
virtual std::unordered_set< std::string > execute(std::ostream &os)
Executes a sequence of actions and returns marks of failed actions.
static std::string named_pipe_path()
static bool valid_pipe(const std::string &client_exe, const std::string &server_exe)
bool defined() const
Returns true if the buffer is not empty.
void reset() noexcept
Points this RCPtr<T> to nullptr safely.
void swap(RCPtr &rhs) noexcept
swaps the contents of two RCPtr<T>
T * get() const noexcept
Returns the raw pointer to the object T, or nullptr.
static Ptr Create(ArgsT &&...args)
Creates a new instance of RcEnable with the given arguments.
static void delete_rules(DWORD process_id)
Remove our NRPT rules from the registry.
static void add_bypass_route(const Util::BestGateway &gw, const std::string &route, bool ipv6, ActionList &add_cmds, ActionList &remove_cmds_bypass_gw)
void generate_reply_headers(ContentInfo ci)
AsioPolySock::Base::Ptr sock
Listener * get_parent() const
const HTTP::Request & request() const
openvpn_io::io_context & io_context
openvpn_io::io_context & io_context
Listener(openvpn_io::io_context &io_context_arg, const Config::Ptr &config_arg, const L &listen_item_or_list, const Client::Factory::Ptr &client_factory_arg)
Client::Factory::Ptr client_factory
void report_service_running()
#define OPENVPN_SIMPLE_EXCEPTION(C)
#define OPENVPN_THROW_EXCEPTION(stuff)
#define OPENVPN_LOG_NTNL(args)
#define OPENVPN_LOG(args)
#define OPENVPN_LOG_STRING(str)
const char * name(const size_t type)
std::string send_handle(const HANDLE handle, const HANDLE remote_process)
std::string module_name_utf8()
std::string omiclient_path()
std::wstring module_name()
int get_int(const Json::Value &root, const NAME &name, const TITLE &title)
bool get_bool_optional(const Json::Value &root, const NAME &name, const bool default_value=false)
bool get_bool(const Json::Value &root, const NAME &name, const TITLE &title)
unsigned int get_uint_optional(const Json::Value &root, const NAME &name, const unsigned int default_value, const TITLE &title)
const Json::Value & get_dict(const Json::Value &root, const NAME &name, const bool optional, const TITLE &title)
int get_int_optional(const Json::Value &root, const NAME &name, const int default_value, const TITLE &title)
Json::Value parse(const std::string &str, const TITLE &title)
std::string get_string(const Json::Value &root, const NAME &name, const TITLE &title)
std::string dirname(const std::string &path)
std::string join(const std::string &p1, const std::string &p2)
std::string remove_blanks(const std::string &str)
int strcasecmp(const char *s1, const char *s2)
Support deferred server-side state creation when client connects.
Frame::Ptr frame_init_simple(const size_t payload)
BufferPtr buf_from_string(const std::string &str)
unsigned int n_pipe_instances
std::string omiclient_exe
std::string to_string() const
std::string to_string() const
static std::string error_str(const size_t status)
static Win::ScopedHANDLE get_process(const ULONG pid, const bool limited)
static ULONG get_pid(const HANDLE np_handle, const bool client)
static void allow_client_query()
int main(int argc, char *argv[])
#define HTTP_SERVER_VERSION
#define OVPNAGENT_NAME_STRING