// 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/>. // OpenVPN agent for Windows #include <iostream> #include <string> #include <vector> #include <memory> #include <utility> #include <openvpn/io/io.hpp> // debug settings (production setting in parentheses) #define OPENVPN_LOG_SSL(x) OPENVPN_LOG(x) #include <openvpn/common/stringize.hpp> // VERSION version can be passed on build command line #ifdef VERSION #define HTTP_SERVER_VERSION OPENVPN_STRINGIZE(VERSION) #else #define HTTP_SERVER_VERSION "0.1.0" #endif // OVPNAGENT_NAME can be passed on build command line. // Customized agent name is needed with purpose to install // few app with agents on one OS (e.g OC 3.0 and PT) #ifdef OVPNAGENT_NAME #define OVPNAGENT_NAME_STRING OPENVPN_STRINGIZE(OVPNAGENT_NAME) #else #define OVPNAGENT_NAME_STRING "ovpnagent" #endif #include <openvpn/log/logbase.hpp> #include <openvpn/common/exception.hpp> #include <openvpn/common/size.hpp> #include <openvpn/common/string.hpp> #include <openvpn/common/rc.hpp> #include <openvpn/common/path.hpp> #include <openvpn/common/file.hpp> #include <openvpn/common/splitlines.hpp> #include <openvpn/common/wstring.hpp> #include <openvpn/log/logbasesimple.hpp> #include <openvpn/buffer/buflist.hpp> #include <openvpn/buffer/bufhex.hpp> #include <openvpn/init/initprocess.hpp> #include <openvpn/ssl/sslchoose.hpp> #include <openvpn/ws/httpserv.hpp> #include <openvpn/win/winerr.hpp> #include <openvpn/client/win/agentconfig.hpp> #include <openvpn/win/scoped_handle.hpp> #include <openvpn/win/winsvc.hpp> #include <openvpn/win/logfile.hpp> #include <openvpn/tun/win/client/tunsetup.hpp> #include <openvpn/win/npinfo.hpp> #include <openvpn/win/handlecomm.hpp> void log_version() { OPENVPN_LOG("OpenVPN Agent " HTTP_SERVER_VERSION " [" SSL_LIB_NAME "] built on " __DATE__ " " __TIME__); } using namespace openvpn; struct MyConfig { MyConfig() { pipe_name = Agent::named_pipe_path(); server_exe = Win::module_name_utf8(); #ifdef OPENVPN_AGENT_START_PROCESS omiclient_exe = Win::omiclient_path(); #endif n_pipe_instances = 4; } std::string pipe_name; std::string server_exe; std::string omiclient_exe; unsigned int n_pipe_instances; }; class MySessionStats : public SessionStats { public: typedef RCPtr<MySessionStats> Ptr; virtual void error(const size_t err_type, const std::string* text=nullptr) override { OPENVPN_LOG(openvpn::Error::name(err_type)); } std::string dump() const { std::ostringstream os; os << "OpenVPN Agent Stats" << std::endl; return os.str(); } }; class MyListener : public WS::Server::Listener { public: typedef RCPtr<MyListener> Ptr; 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) : WS::Server::Listener(io_context, hconf, listen_list, client_factory), config(config_arg), client_process(io_context), client_confirm_event(io_context), client_destroy_event(io_context), io_context_(io_context) { } Win::ScopedHANDLE establish_tun(const TunBuilderCapture& tbc, const std::wstring& openvpn_app_path, Stop* stop, std::ostream& os, bool wintun) { if (!tun) tun.reset(new TunWin::Setup(io_context_, wintun)); auto th = tun->establish(tbc, openvpn_app_path, stop, os, ring_buffer); // store VPN interface index to be able to exclude it // when next time adding bypass route vpn_interface_index = tun->vpn_interface_index(); return Win::ScopedHANDLE(th); } // return true if we did any work bool destroy_tun(std::ostream& os) { bool ret = false; try { // close the remote tap handle in the client process if (client_process.is_open() && !remote_tap_handle_hex.empty()) { ret = true; const HANDLE remote_tap_handle = BufHex::parse<HANDLE>(remote_tap_handle_hex, "remote TAP handle"); Win::ScopedHANDLE local_tap_handle; // dummy handle, immediately closed after duplication if (::DuplicateHandle(client_process.native_handle(), remote_tap_handle, GetCurrentProcess(), local_tap_handle.ref(), 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { os << "destroy_tun: no client confirm, DuplicateHandle (close) succeeded" << std::endl; } else { const Win::LastError err; os << "destroy_tun: no client confirm, DuplicateHandle (close) failed: " << err.message() << std::endl; } } } catch (const std::exception& e) { os << "destroy_tun: exception in remote tap handle close: " << e.what() << std::endl; } try { ring_buffer.reset(); // undo the effects of establish_tun if (tun) { ret = true; tun->destroy(os); } } catch (const std::exception& e) { os << "destroy_tun: exception in tun teardown: " << e.what() << std::endl; } try { tun.reset(); remote_tap_handle_hex.clear(); client_process.close(); client_confirm_event.close(); client_destroy_event.close(); } catch (const std::exception& e) { os << "destroy_tun: exception in cleanup: " << e.what() << std::endl; } vpn_interface_index = DWORD(-1); return ret; } void destroy_tun_exit() { std::ostringstream os; destroy_tun(os); OPENVPN_LOG_NTNL("TUN CLOSE (exit)\n" << os.str()); } void set_client_process(Win::ScopedHANDLE&& proc) { client_process.close(); client_process.assign(proc.release()); // special failsafe to destroy tun in case client crashes without closing it client_process.async_wait([self=Ptr(this)](const openvpn_io::error_code& error) { if (!error) { { std::ostringstream os; self->remove_cmds_bypass_hosts.execute(os); self->remove_cmds_bypass_hosts.clear(); OPENVPN_LOG_NTNL("remove bypass route (failsafe)\n" << os.str()); } if (self->tun) { std::ostringstream os; self->destroy_tun(os); OPENVPN_LOG_NTNL("TUN CLOSE (failsafe)\n" << os.str()); } } }); } void set_client_confirm_event(const std::string& confirm_handle_hex) { client_confirm_event.close(); const HANDLE remote_event = BufHex::parse<HANDLE>(confirm_handle_hex, "confirm event handle"); HANDLE event_handle; if (!::DuplicateHandle(get_client_process(), remote_event, GetCurrentProcess(), &event_handle, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { const Win::LastError err; OPENVPN_THROW_EXCEPTION("set_client_confirm_event: DuplicateHandle failed: " << err.message()); } client_confirm_event.assign(event_handle); // Check if the event is okay { const DWORD status = ::WaitForSingleObject(client_confirm_event.native_handle(), 0); const Win::LastError err; switch (status) { case WAIT_OBJECT_0: // acceptable status case WAIT_TIMEOUT: // acceptable status break; case WAIT_ABANDONED: throw Exception("set_client_confirm_event: confirm event is abandoned"); default: OPENVPN_THROW_EXCEPTION("set_client_confirm_event: WaitForSingleObject failed: " << err.message()); } } // When the client signals the client_confirm event, it means // that the client has taken ownership of the TAP device HANDLE, // so we locally release ownership by clearing remote_tap_handle_hex, // effectively preventing the cross-process release of TAP device // HANDLE in destroy_tun() above. client_confirm_event.async_wait([self=Ptr(this)](const openvpn_io::error_code& error) { if (!error) { self->remote_tap_handle_hex.clear(); OPENVPN_LOG_STRING("TUN CONFIRM\n"); } }); } void set_client_destroy_event(const std::string& event_handle_hex) { client_destroy_event.close(); // Move the remote event HANDLE (already duplicated in remote process) // to local process. const HANDLE remote_event = BufHex::parse<HANDLE>(event_handle_hex, "destroy event handle"); HANDLE event_handle; if (!::DuplicateHandle(get_client_process(), remote_event, GetCurrentProcess(), &event_handle, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { const Win::LastError err; OPENVPN_THROW_EXCEPTION("set_client_destroy_event: DuplicateHandle failed: " << err.message()); } client_destroy_event.assign(event_handle); // Check if the event is already signaled, or has some other error { const DWORD status = ::WaitForSingleObject(client_destroy_event.native_handle(), 0); const Win::LastError err; switch (status) { case WAIT_TIMEOUT: // expected status break; case WAIT_OBJECT_0: throw Exception("set_client_destroy_event: destroy event is already signaled"); case WAIT_ABANDONED: throw Exception("set_client_destroy_event: destroy event is abandoned"); default: OPENVPN_THROW_EXCEPTION("set_client_destroy_event: WaitForSingleObject failed: " << err.message()); } } // normal event-based tun close processing client_destroy_event.async_wait([self=Ptr(this)](const openvpn_io::error_code& error) { if (!error) { { std::ostringstream os; self->remove_cmds_bypass_hosts.execute(os); self->remove_cmds_bypass_hosts.clear(); OPENVPN_LOG_NTNL("remove bypass route (event)\n" << os.str()); } if (self->tun) { std::ostringstream os; self->destroy_tun(os); OPENVPN_LOG_NTNL("TUN CLOSE (event)\n" << os.str()); } } }); } HANDLE get_client_process() { if (!client_process.is_open()) throw Exception("no client process"); return client_process.native_handle(); } void set_remote_tap_handle_hex(const HANDLE tap_handle) { remote_tap_handle_hex = Win::HandleComm::send_handle(tap_handle, get_client_process()); } const std::string& get_remote_tap_handle_hex() { return remote_tap_handle_hex; } void assign_ring_buffer(TunWin::RingBuffer* ring_buffer_arg) { ring_buffer.reset(ring_buffer_arg); } void add_bypass_route(const std::string& host, bool ipv6) { std::ostringstream os; remove_cmds_bypass_hosts.execute(os); remove_cmds_bypass_hosts.clear(); ActionList add_cmds; // we might have broken VPN connection up, so we must // exclude VPN interface whe searching for the best gateway const TunWin::Util::BestGateway gw { host, vpn_interface_index }; TunWin::Setup::add_bypass_route(gw, host, ipv6, add_cmds, remove_cmds_bypass_hosts); add_cmds.execute(os); OPENVPN_LOG(os.str()); } #ifdef OPENVPN_AGENT_START_PROCESS void start_openvpn_process(HANDLE client_pipe, const std::string& config_file, const std::string& config_dir, const std::string& exit_event_name, const std::string& management_host, const std::string& management_password, const int management_port, const std::string& log, const bool log_append) { // impersonate pipe client Win::NamedPipeImpersonate impersonate{ client_pipe }; { // create primary token from impersonation token HANDLE imp_token = NULL, pri_token = NULL; BOOL res = OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, FALSE, &imp_token); if (res == 0) { const openvpn_io::error_code err(::GetLastError(), openvpn_io::error::get_system_category()); OPENVPN_THROW_EXCEPTION("failed to open thread token: " << err.message()); } res = DuplicateTokenEx(imp_token, 0, NULL, SECURITY_IMPERSONATION_LEVEL::SecurityAnonymous, TokenPrimary, &pri_token); if (res == 0) { const openvpn_io::error_code err(::GetLastError(), openvpn_io::error::get_system_category()); OPENVPN_THROW_EXCEPTION("failed to duplicate token: " << err.message()); } // create pipe which is used to write password to openvpn process's stdin HANDLE stdin_read = NULL, stdin_write = NULL; SECURITY_ATTRIBUTES inheritable = { sizeof(inheritable), NULL, TRUE }; if (!CreatePipe(&stdin_read, &stdin_write, &inheritable, 0) || !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0)) { const openvpn_io::error_code err(::GetLastError(), openvpn_io::error::get_system_category()); OPENVPN_THROW_EXCEPTION("failed to set up pipe: " << err.message()); } // create command line for openvpn process std::ostringstream ss; ss << "client --config " << config_dir << "\\" << config_file << " --exit-event-name " << exit_event_name << " --auth-retry interact --management " << management_host << " " << management_port << " stdin --management-query-passwords --management-hold " << "--log" << (log_append ? "-append " : " ") << log; std::string cmd = ss.str(); std::unique_ptr<char[]> buf(new char[cmd.length() + 1]); strcpy(buf.get(), cmd.c_str()); STARTUPINFO startup_info = { 0 }; startup_info.cb = sizeof(startup_info); startup_info.dwFlags = STARTF_USESTDHANDLES; startup_info.hStdInput = stdin_read; // create openvpn process PROCESS_INFORMATION proc_info; ZeroMemory(&proc_info, sizeof(proc_info)); res = CreateProcessAsUser(pri_token, config.omiclient_exe.c_str(), buf.get(), NULL, NULL, TRUE, CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, 0, 0, &startup_info, &proc_info); if (res == 0) { const openvpn_io::error_code err(::GetLastError(), openvpn_io::error::get_system_category()); OPENVPN_THROW_EXCEPTION("failed to create openvpn process: " << err.message()); } CloseHandle(proc_info.hProcess); CloseHandle(proc_info.hThread); // write management password to process's stdin DWORD written; WriteFile(stdin_write, management_password.c_str(), management_password.length(), &written, NULL); } } #endif const MyConfig& config; ActionList remove_cmds_bypass_hosts; TunWin::RingBuffer::Ptr ring_buffer; private: virtual bool allow_client(AsioPolySock::Base& sock) override { AsioPolySock::NamedPipe* np = dynamic_cast<AsioPolySock::NamedPipe*>(&sock); if (np) { #if _WIN32_WINNT >= 0x0600 // Vista and higher Win::NamedPipePeerInfoClient npinfo(np->handle.native_handle()); const std::string client_exe = wstring::to_utf8(npinfo.exe_path); OPENVPN_LOG("connection from " << client_exe); if (Agent::valid_pipe(client_exe, config.server_exe)) return true; OPENVPN_LOG(client_exe << " not recognized as a valid client"); #else return true; #endif } else OPENVPN_LOG("only named pipe clients are allowed"); return false; } TunWin::Setup::Ptr tun; openvpn_io::windows::object_handle client_process; openvpn_io::windows::object_handle client_confirm_event; openvpn_io::windows::object_handle client_destroy_event; std::string remote_tap_handle_hex; openvpn_io::io_context& io_context_; // with persist tunnel and redirect-gw we must exclude // VPN interface when searching for best gateway when // adding bypass route for the next remote DWORD vpn_interface_index = DWORD(-1); }; class MyClientInstance : public WS::Server::Listener::Client { public: typedef RCPtr<MyClientInstance> Ptr; MyClientInstance(WS::Server::Listener::Client::Initializer& ci) : WS::Server::Listener::Client(ci) { //OPENVPN_LOG("INSTANCE START"); } virtual ~MyClientInstance() { //OPENVPN_LOG("INSTANCE DESTRUCT"); } private: void generate_reply(const Json::Value& jout) { out = buf_from_string(jout.toStyledString()); WS::Server::ContentInfo ci; ci.http_status = HTTP::Status::OK; ci.type = "application/json"; ci.length = out->size(); ci.keepalive = keepalive_request(); generate_reply_headers(ci); } virtual void http_request_received() override { // alloc output buffer std::ostringstream os; try { const HANDLE client_pipe = get_client_pipe(); const std::wstring client_exe = get_client_exe(client_pipe); const HTTP::Request& req = request(); OPENVPN_LOG("HTTP request received from " << sock->remote_endpoint_str() << '\n' << req.to_string()); // get content-type const std::string content_type = req.headers.get_value_trim("content-type"); if (req.method == "POST") { // verify correct content-type if (string::strcasecmp(content_type, "application/json")) throw Exception("bad content-type"); // parse the json dict const Json::Value root = json::parse(in.to_string(), "JSON request"); if (!root.isObject()) throw Exception("json parse error: top level json object is not a dictionary"); if (req.uri == "/tun-setup") { // get PID ULONG pid = json::get_uint_optional(root, "pid", 0); bool wintun = json::get_bool_optional(root, "wintun"); // get remote event handles for tun object confirmation/destruction const std::string confirm_event_hex = json::get_string(root, "confirm_event"); const std::string destroy_event_hex = json::get_string(root, "destroy_event"); // parse JSON data into a TunBuilderCapture object TunBuilderCapture::Ptr tbc = TunBuilderCapture::from_json(json::get_dict(root, "tun", false)); tbc->validate(); // destroy previous instance if (parent()->destroy_tun(os)) { os << "Destroyed previous TAP instance" << std::endl; ::Sleep(1000); } // pre-establish impersonation { Win::NamedPipeImpersonate impersonate(client_pipe); // remember the client process that sent the request parent()->set_client_process(get_client_process(client_pipe, pid)); // save the confirm/destroy events parent()->set_client_destroy_event(destroy_event_hex); parent()->set_client_confirm_event(confirm_event_hex); } if (wintun) { parent()->assign_ring_buffer(new TunWin::RingBuffer(io_context, parent()->get_client_process(), json::get_string(root, "send_ring_hmem"), json::get_string(root, "receive_ring_hmem"), json::get_string(root, "send_ring_tail_moved"), json::get_string(root, "receive_ring_tail_moved"))); } // establish the tun setup object Win::ScopedHANDLE tap_handle(parent()->establish_tun(*tbc, client_exe, nullptr, os, wintun)); // post-establish impersonation { Win::NamedPipeImpersonate impersonate(client_pipe); // duplicate the TAP handle into the client process parent()->set_remote_tap_handle_hex(tap_handle()); } // build JSON return dictionary const std::string log_txt = string::remove_blanks(os.str()); Json::Value jout(Json::objectValue); jout["log_txt"] = log_txt; jout["tap_handle_hex"] = parent()->get_remote_tap_handle_hex(); OPENVPN_LOG_NTNL("TUN SETUP\n" << log_txt); generate_reply(jout); } else if (req.uri == "/add-bypass-route") { ULONG pid = json::get_uint_optional(root, "pid", 0); bool ipv6 = json::get_bool(root, "ipv6"); const std::string host = json::get_string(root, "host"); // pre-establish impersonation { Win::NamedPipeImpersonate impersonate(client_pipe); // remember the client process that sent the request parent()->set_client_process(get_client_process(client_pipe, pid)); } parent()->add_bypass_route(host, ipv6); Json::Value jout(Json::objectValue); generate_reply(jout); } #ifdef OPENVPN_AGENT_START_PROCESS else if (req.uri == "/start") { const std::string config_file = json::get_string(root, "config_file"); const std::string config_dir = json::get_string(root, "config_dir"); const std::string exit_event_name = json::get_string(root, "exit_event_name"); const std::string management_host = json::get_string(root, "management_host"); const std::string management_password = json::get_string(root, "management_password") + "\n"; const int management_port = json::get_int(root, "management_port"); const std::string log = json::get_string(root, "log"); const bool log_append = json::get_int(root, "log-append") == 1; parent()->start_openvpn_process(client_pipe, config_file, config_dir, exit_event_name, management_host, management_password, management_port, log, log_append); Json::Value jout(Json::objectValue); generate_reply(jout); } #endif else { OPENVPN_LOG("PAGE NOT FOUND"); out = buf_from_string("page not found\n"); WS::Server::ContentInfo ci; ci.http_status = HTTP::Status::NotFound; ci.type = "text/plain"; ci.length = out->size(); generate_reply_headers(ci); } } } catch (const std::exception& e) { if (parent()->destroy_tun(os)) os << "Destroyed previous TAP instance due to exception" << std::endl; const std::string error_msg = string::remove_blanks(os.str() + e.what() + '\n'); OPENVPN_LOG_NTNL("EXCEPTION\n" << error_msg); out = buf_from_string(error_msg); WS::Server::ContentInfo ci; ci.http_status = HTTP::Status::BadRequest; ci.type = "text/plain"; ci.length = out->size(); generate_reply_headers(ci); } } virtual void http_content_in(BufferAllocated& buf) override { if (buf.defined()) in.emplace_back(new BufferAllocated(std::move(buf))); } virtual BufferPtr http_content_out() override { BufferPtr ret; ret.swap(out); return ret; } virtual bool http_out_eof() override { //OPENVPN_LOG("HTTP output EOF"); return true; } virtual bool http_stop(const int status, const std::string& description) override { if (status != WS::Server::Status::E_SUCCESS) { OPENVPN_LOG("INSTANCE STOP : " << WS::Server::Status::error_str(status) << " : " << description); return false; } else return true; } HANDLE get_client_pipe() const { AsioPolySock::NamedPipe* np = dynamic_cast<AsioPolySock::NamedPipe*>(sock.get()); if (!np) throw Exception("only named pipe clients are allowed"); return np->handle.native_handle(); } std::wstring get_client_exe(const HANDLE client_pipe) { #if _WIN32_WINNT >= 0x0600 // Vista and higher Win::NamedPipePeerInfoClient npinfo(client_pipe); return npinfo.exe_path; #else return std::wstring(); #endif } Win::ScopedHANDLE get_client_process(const HANDLE pipe, ULONG pid_hint) const { #if _WIN32_WINNT >= 0x0600 // Vista and higher pid_hint = Win::NamedPipePeerInfo::get_pid(pipe, true); #endif if (!pid_hint) throw Exception("cannot determine client PID"); return Win::NamedPipePeerInfo::get_process(pid_hint, false); } MyListener* parent() { return static_cast<MyListener*>(get_parent()); } BufferList in; BufferPtr out; }; class MyClientFactory : public WS::Server::Listener::Client::Factory { public: typedef RCPtr<MyClientFactory> Ptr; virtual WS::Server::Listener::Client::Ptr new_client(WS::Server::Listener::Client::Initializer& ci) override { return new MyClientInstance(ci); } }; class MyService : public Win::Service { public: MyService() : Win::Service(config()) { } virtual void service_work(DWORD argc, LPWSTR *argv) override { if (is_service()) { try { log.reset(new Win::LogFile(log_fn(), "", false)); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } } if (!log) log.reset(new LogBaseSimple()); io_context.reset(new openvpn_io::io_context(1)); // concurrency hint=1 log_version(); MyConfig conf; #if _WIN32_WINNT >= 0x0600 // Vista and higher Win::NamedPipePeerInfo::allow_client_query(); TunWin::NRPT::delete_rule(); // remove stale NRPT rules #endif WS::Server::Config::Ptr hconf = new WS::Server::Config(); hconf->http_server_id = OVPNAGENT_NAME_STRING "/" HTTP_SERVER_VERSION; hconf->frame = frame_init_simple(2048); hconf->stats.reset(new MySessionStats); // DACL string for creating named pipe hconf->sddl_string = "D:" // discretionary ACL "(D;OICI;GA;;;S-1-5-2)" // deny all access for network users "(A;OICI;GA;;;S-1-5-32-544)" // allow full access to Admin group "(A;OICI;GA;;;S-1-5-18)" // allow full access to Local System account "(D;OICI;0x4;;;S-1-1-0)" // deny FILE_CREATE_PIPE_INSTANCE for Everyone "(A;OICI;GRGW;;;S-1-5-11)" // allow read/write access for authenticated users "(A;OICI;GRGW;;;S-1-5-32-546)" // allow read/write access for built-in guest account ; Listen::List ll; const unsigned int n_pipe_instances = 4; for (unsigned int i = 0; i < n_pipe_instances; ++i) { Listen::Item li; li.directive = "http-listen"; li.addr = conf.pipe_name; li.proto = Protocol(Protocol::NamedPipe); li.ssl = Listen::Item::SSLOff; li.n_threads = n_pipe_instances; ll.push_back(std::move(li)); } MyClientFactory::Ptr factory = new MyClientFactory(); listener.reset(new MyListener(conf, *io_context, hconf, ll, factory)); listener->start(); report_service_running(); io_context->run(); } // Called by service control manager in another thread // to signal the service_work() method to exit. virtual void service_stop() override { openvpn_io::post(*io_context, [this]() { if (listener) { listener->destroy_tun_exit(); listener->stop(); } }); } private: static Config config() { Config c; c.name = OVPNAGENT_NAME_STRING; c.display_name = "OpenVPN Agent " OVPNAGENT_NAME_STRING; #if _WIN32_WINNT < 0x0600 // pre-Vista c.dependencies.push_back("Dhcp"); // DHCP client #endif c.autostart = true; c.restart_on_fail = true; return c; } static std::string log_fn() { const std::string modname = Win::module_name_utf8(); const std::string moddir = path::dirname(modname); const std::string fn = path::join(moddir, "agent.log"); return fn; } std::unique_ptr<openvpn_io::io_context> io_context; MyListener::Ptr listener; LogBase::Ptr log; }; OPENVPN_SIMPLE_EXCEPTION(usage); int main(int argc, char* argv[]) { int ret = 0; // process-wide initialization InitProcess::Init init; try { MyService serv; if (argc >= 2) { const std::string arg = argv[1]; if (arg == "run") serv.service_work(0, nullptr); else if (arg == "install") serv.install(); else if (arg == "remove") serv.remove(); else if (arg == "modname") std::wcout << Win::module_name() << std::endl; else if (arg == "help") { std::cout << "usage: ovpnagent [options]" << std::endl; std::cout << " run -- run in foreground (for debugging)" << std::endl; std::cout << " install -- install as service" << std::endl; std::cout << " remove -- uninstall" << std::endl; std::cout << " modname -- show module name" << std::endl; std::cout << " help -- show help message" << std::endl; std::cout << " [default] -- start as service" << std::endl; } else { std::cout << "unrecognized option, use 'help' for more info" << std::endl; ret = 2; } } else serv.start(); } catch (const std::exception& e) { std::cout << "ovpnagent: " << e.what() << std::endl; ret = 1; } return ret; }