0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 12:12:15 +02:00
openvpn3/openvpn/omi/openvpn.cpp
Lev Stipakov 7cf2e210d1
mingw: fix various warnings
- remove unused variable
 - replace deprecated JsonReader with CharReader
 - fix initialization order
 - fix signed-unsigned comparison
 - fix string constant to char* conversion
 - fix unknown (to mingw) format character
 - fix passing NULL to non-pointer agrument
 - remove unneeded #pragma once

Signed-off-by: Lev Stipakov <lev@openvpn.net>
2020-03-11 19:44:40 +01:00

1069 lines
25 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/>.
// OpenVPN 3 client with Management Interface
#define OMI_VERSION "1.0.0"
#include <string>
#include <vector>
#include <thread>
#include <memory>
#include <utility>
#include <mutex>
#include <condition_variable>
// don't export core symbols
#define OPENVPN_CORE_API_VISIBILITY_HIDDEN
// should be included before other openvpn includes,
// with the exception of openvpn/log includes
#include <client/ovpncli.cpp>
#include <openvpn/common/platform.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/file.hpp>
#include <openvpn/common/string.hpp>
#include <openvpn/common/to_string.hpp>
#include <openvpn/common/platform_string.hpp>
#include <openvpn/common/options.hpp>
#include <openvpn/asio/asiosignal.hpp>
#include <openvpn/common/stop.hpp>
#include <openvpn/time/asiotimersafe.hpp>
#include <openvpn/omi/omi.hpp>
using namespace openvpn;
std::string log_version()
{
return platform_string("OpenVPN Management Interface", OMI_VERSION)
+ " [" SSL_LIB_NAME "] built on " __DATE__ " " __TIME__;
}
class OMI;
class Client : public ClientAPI::OpenVPNClient
{
public:
Client(OMI* omi)
: parent(omi)
{
}
private:
bool socket_protect(int socket, std::string remote, bool ipv6) override
{
return true;
}
virtual bool pause_on_connection_timeout() override
{
return false;
}
virtual void event(const ClientAPI::Event& ev) override;
virtual void log(const ClientAPI::LogInfo& msg) override;
virtual void external_pki_cert_request(ClientAPI::ExternalPKICertRequest& certreq) override;
virtual void external_pki_sign_request(ClientAPI::ExternalPKISignRequest& signreq) override;
OMI* parent;
};
class OMI : public OMICore, public ClientAPI::LogReceiver
{
public:
typedef RCPtr<OMI> Ptr;
OMI(openvpn_io::io_context& io_context, OptionList opt_arg)
: OMICore(io_context),
opt(std::move(opt_arg)),
reconnect_timer(io_context),
bytecount_timer(io_context),
exit_event(io_context),
log_context(this)
{
signals.reset(new ASIOSignals(io_context));
signal_rearm();
}
void start()
{
log_setup(OMICore::LogFn(opt));
OPENVPN_LOG(log_version());
// command line options
connection_timeout = opt.get_num<decltype(connection_timeout)>("connection-timeout", 1, 30);
management_query_passwords = opt.exists("management-query-passwords");
auth_nocache = opt.exists("auth-nocache");
management_external_key = opt.exists("management-external-key");
proto_override = opt.get_default("proto-force", 1, 16, "adaptive");
remote_override = opt.get_optional("remote-override", 1, 256);
management_up_down = opt.exists("management-up-down");
management_query_remote = opt.exists("management-query-remote");
exit_event_name = opt.get_optional("exit-event-name", 1, 256);
// passed by OpenVPN GUI to trigger exit
if (!exit_event_name.empty())
{
exit_event.assign(::CreateEvent(NULL, FALSE, FALSE, exit_event_name.c_str()));
exit_event.async_wait([self = Ptr(this)](const openvpn_io::error_code& error) {
self->stop();
});
}
// http-proxy-override
{
const Option* o = opt.get_ptr("http-proxy-override");
if (o)
{
http_proxy_host = o->get(1, 128);
http_proxy_port = o->get(2, 16);
}
}
// begin listening/connecting on OMI port
OMICore::start(opt);
}
virtual void log(const ClientAPI::LogInfo& msg) override
{
openvpn_io::post(io_context, [this, msg]() {
log_msg(msg);
});
}
void event(const ClientAPI::Event& ev)
{
openvpn_io::post(io_context, [this, ev]() {
event_msg(ev, nullptr);
});
}
void event(const ClientAPI::Event& ev, const ClientAPI::ConnectionInfo& ci)
{
openvpn_io::post(io_context, [this, ev, ci]() {
event_msg(ev, &ci);
});
}
void external_pki_cert_request(ClientAPI::ExternalPKICertRequest& certreq)
{
// not currently supported, <cert> must be in config
}
void external_pki_sign_request(ClientAPI::ExternalPKISignRequest& signreq)
{
try {
// publish signreq to main thread
{
std::lock_guard<std::mutex> lock(epki_mutex);
epki_signreq = &signreq;
}
// message main thread that signreq is published and pending
openvpn_io::post(io_context, [this]() {
epki_sign_request();
});
// allow asynchronous stop
Stop::Scope stop_scope(&async_stop, [this, &signreq]() {
{
std::lock_guard<std::mutex> lock(epki_mutex);
epki_signreq = nullptr;
signreq.error = true;
signreq.errorText = "External PKI OMI: stop";
}
epki_cv.notify_all();
});
// wait for main thread to signal readiness by nulling epki_signreq ptr
{
std::unique_lock<std::mutex> lock(epki_mutex);
epki_cv.wait(lock, [this]() {
return epki_signreq == nullptr;
});
}
}
catch (const std::exception& e)
{
std::lock_guard<std::mutex> lock(epki_mutex);
epki_signreq = nullptr;
signreq.error = true;
signreq.errorText = std::string("External PKI OMI: ") + e.what();
}
}
void epki_sign_request()
{
std::string sr;
{
std::lock_guard<std::mutex> lock(epki_mutex);
if (epki_signreq)
sr = epki_signreq->data;
}
send(">RSA_SIGN:" + sr + "\r\n");
}
void epki_sign_reply(const Command& cmd)
{
// get base64 signature from command
std::string sig64;
for (auto &line : cmd.extra)
{
sig64 += line;
}
// commit to connection thread
bool fail = false;
{
std::lock_guard<std::mutex> lock(epki_mutex);
if (epki_signreq)
{
epki_signreq->sig = sig64;
epki_signreq = nullptr;
}
else
fail = true;
}
if (fail)
send("ERROR: unsolicited rsa-sig command\r\n");
else
{
epki_cv.notify_all();
send("SUCCESS: rsa-sig command succeeded\r\n");
}
}
virtual bool omi_command_is_multiline(const std::string& arg0, const Option& o) override
{
if (arg0 == "rsa-sig")
return true;
return false;
}
private:
virtual bool omi_command_in(const std::string& arg0, const Command& cmd) override
{
switch (arg0.at(0))
{
case 'p':
{
if (is_auth_cmd(arg0))
{
process_auth_cmd(cmd.option);
return false;
}
break;
}
case 'r':
{
if (arg0 == "remote")
{
process_remote_cmd(cmd.option);
return false;
}
else if (arg0 == "rsa-sig")
{
epki_sign_reply(cmd);
return false;
}
break;
}
case 'u':
{
if (is_auth_cmd(arg0))
{
process_auth_cmd(cmd.option);
return false;
}
break;
}
}
send("ERROR: unknown command, enter 'help' for more options\r\n");
return false;
}
virtual void omi_done(const bool eof) override
{
//OPENVPN_LOG("OMI DONE eof=" << eof);
}
std::vector<ClientAPI::KeyValue> get_peer_info() const
{
std::vector<ClientAPI::KeyValue> ret;
OptionList::IndexMap::const_iterator se = opt.map().find("setenv");
if (se != opt.map().end())
{
for (OptionList::IndexList::const_iterator i = se->second.begin(); i != se->second.end(); ++i)
{
const Option& o = opt[*i];
o.touch();
const std::string& k = o.get(1, 64);
if (string::starts_with(k, "IV_") || string::starts_with(k, "UV_"))
{
const std::string& v = o.get(2, 256);
ret.emplace_back(k, v);
}
}
}
return ret;
}
virtual void omi_start_connection() override
{
try {
//OPENVPN_LOG("OMI START CONNECTION");
// reset state
reconnect_timer.cancel();
reconnect_reason = "";
if (!config)
{
config.reset(new ClientAPI::Config);
config->guiVersion = "ovpnmi " OMI_VERSION;
config->content = get_config(opt);
config->peerInfo = get_peer_info();
config->connTimeout = connection_timeout;
config->protoOverride = proto_override;
config->serverOverride = remote_override;
config->tunPersist = true;
config->googleDnsFallback = true;
config->autologinSessions = true;
config->compressionMode = "yes";
config->proxyHost = http_proxy_host;
config->proxyPort = http_proxy_port;
config->echo = true;
if (management_external_key)
config->externalPkiAlias = "EPKI"; // dummy alias
did_query_remote = false;
}
const ClientAPI::EvalConfig eval = Client::eval_config_static(*config);
if (eval.error)
OPENVPN_THROW_EXCEPTION("eval config error: " << eval.message);
autologin = eval.autologin;
// for compatibility with openvpn2
if (eval.windowsDriver == "wintun")
config->wintun = true;
if (!eval.autologin && management_query_passwords && !creds)
query_username_password("Auth", false, eval.staticChallenge, eval.staticChallengeEcho);
else if (proxy_need_creds)
query_username_password("HTTP Proxy", false, "", false);
else if (management_query_remote && !did_query_remote)
query_remote(eval.remoteHost, eval.remotePort, eval.remoteProto);
else
start_connection_thread();
}
catch (const std::exception& e)
{
set_final_error(e.what());
stop();
}
}
void query_username_password(const std::string& type,
const bool password_only,
const std::string& static_challenge,
const bool static_challenge_echo)
{
reset_auth_cmd();
auth_type = type;
auth_password_only = password_only;
std::string notify = ">PASSWORD:Need '" + type + "' ";
if (password_only)
notify += "password";
else
notify += "username/password";
// static challenge
if (!static_challenge.empty())
{
notify += " SC:";
if (static_challenge_echo)
notify += '1';
else
notify += '0';
notify += ',';
notify += static_challenge;
}
notify += "\r\n";
send(notify);
}
bool is_auth_cmd(const std::string& arg0) const
{
return arg0 == "username" || arg0 == "password";
}
void process_auth_cmd(const Option& o)
{
const std::string up = o.get(0, 0);
const std::string type = o.get(1, 64);
const std::string cred = o.get(2, 512);
if (auth_type.empty() || type != auth_type || (up == "username" && auth_password_only))
{
send("ERROR: no " + up + " is currently needed at this time\r\n");
return;
}
bool changed = false;
if (up == "username")
{
auth_username = cred;
changed = true;
}
else if (up == "password")
{
auth_password = cred;
changed = true;
}
if (changed)
send("SUCCESS: '" + auth_type + "' " + up + " entered, but not yet verified\r\n");
if ((!auth_username.empty() || auth_password_only) && !auth_password.empty())
{
provide_username_password(auth_type, auth_username, auth_password);
reset_auth_cmd();
}
}
void reset_auth_cmd()
{
auth_type = "";
auth_password_only = false;
auth_username = "";
auth_password = "";
}
void provide_username_password(const std::string& type, const std::string& username, const std::string& password)
{
if (type == "Auth")
{
creds.reset(new ClientAPI::ProvideCreds);
creds->username = username;
creds->password = password;
creds->replacePasswordWithSessionID = true;
creds->cachePassword = !auth_nocache;
}
else if (type == "HTTP Proxy")
{
if (config)
{
config->proxyUsername = username;
config->proxyPassword = password;
}
proxy_need_creds = false;
}
omi_start_connection();
}
void query_remote(const std::string& host, const std::string& port, const std::string& proto)
{
send(">REMOTE:" + host + ',' + port + ',' + proto + "\r\n");
remote_pending = true;
}
void process_remote_cmd(const Option& o)
{
if (!remote_pending)
{
send("ERROR: no pending remote query\r\n");
return;
}
std::string host;
std::string port;
bool mod = false;
const std::string type = o.get(1, 16);
if (type == "MOD")
{
host = o.get(2, 256);
port = o.get_optional(3, 16);
mod = true;
}
else if (type == "ACCEPT")
{
;
}
else
{
send("ERROR: remote type must be MOD or ACCEPT\r\n");
return;
}
send("SUCCESS: remote command succeeded\r\n");
remote_pending = false;
if (mod && config)
{
config->serverOverride = host;
// fixme -- override port
}
did_query_remote = true;
omi_start_connection();
}
void schedule_bytecount_timer()
{
if (get_bytecount())
{
bytecount_timer.expires_after(Time::Duration::seconds(get_bytecount()));
bytecount_timer.async_wait([self=Ptr(this)](const openvpn_io::error_code& error)
{
if (!error)
self->report_bytecount();
});
}
else
bytecount_timer.cancel();
}
void report_bytecount()
{
if (client && get_bytecount())
{
const ClientAPI::TransportStats ts = client->transport_stats();
send(">BYTECOUNT:" + openvpn::to_string(ts.bytesIn) + ',' + openvpn::to_string(ts.bytesOut) + "\r\n");
}
schedule_bytecount_timer();
}
void start_connection_thread()
{
try {
// reset client instance
client.reset(new Client(this));
// evaluate config
const ClientAPI::EvalConfig eval = client->eval_config(*config);
if (eval.error)
OPENVPN_THROW_EXCEPTION("eval config error: " << eval.message);
// add credentials, if available
if (creds)
{
const ClientAPI::Status creds_status = client->provide_creds(*creds);
if (creds_status.error)
OPENVPN_THROW_EXCEPTION("creds error: " << creds_status.message);
}
// bytecount
schedule_bytecount_timer();
// start connection thread
thread.reset(new std::thread([this]() {
connection_thread();
}));
}
catch (const std::exception& e)
{
set_final_error(e.what());
stop();
}
}
void connection_thread()
{
openvpn_io::detail::signal_blocker signal_blocker; // signals should be handled by parent thread
std::string error;
try {
const ClientAPI::Status cs = client->connect();
if (cs.error)
{
error = "connect error: ";
if (!cs.status.empty())
{
error += cs.status;
error += ": ";
}
error += cs.message;
}
}
catch (const std::exception& e)
{
error = "connect thread exception: ";
error += e.what();
}
// generate an internal event for client exceptions
if (!error.empty())
{
ClientAPI::Event ev;
ev.error = true;
ev.fatal = true;
ev.name = "CLIENT_EXCEPTION";
ev.info = error;
event(ev);
}
}
void join_thread()
{
try {
if (thread)
thread->join(); // may throw if thread has already exited
}
catch (const std::exception& e)
{
}
}
virtual bool omi_stop() override
{
bool ret = false;
// in case connect thread is blocking in external_pki_sign_request
async_stop.stop();
// stop timers
reconnect_timer.cancel();
bytecount_timer.cancel();
// stop the client
if (client)
client->stop();
// wait for client thread to exit
join_thread();
// if there's a final error, dump to management interface
const std::string fe = get_final_error();
if (!fe.empty())
{
send(string::add_trailing_crlf_copy(fe));
if (is_errors_to_stderr())
std::cerr << fe << std::endl;
OPENVPN_LOG_STRING(fe + '\n');
ret = true;
}
// cancel signals
if (signals)
signals->cancel();
return ret;
}
void retry()
{
// wait for client thread to exit
join_thread();
// restart connection
omi_start_connection();
}
void deferred_reconnect(const unsigned int seconds, const std::string& reason)
{
reconnect_timer.expires_after(Time::Duration::seconds(seconds));
reconnect_timer.async_wait([self=Ptr(this), reason](const openvpn_io::error_code& error)
{
if (!error)
{
self->state_line(gen_state_msg(false, "RECONNECTING", reason));
self->retry();
}
});
}
virtual void omi_sigterm() override
{
if (client)
set_final_error(gen_state_msg(true, "EXITING", "exit-with-notification"));
stop();
}
virtual bool omi_is_sighup_implemented() override
{
return true;
}
virtual void omi_sighup() override
{
if (client)
client->reconnect(1);
}
void log_msg(const ClientAPI::LogInfo& msg)
{
log_full(msg.text);
}
static std::string event_format(const ClientAPI::Event& ev, const ClientAPI::ConnectionInfo* ci)
{
const time_t now = ::time(NULL);
std::string evstr = openvpn::to_string(now) + ',' + ev.name;
if (ev.name == "CONNECTED" && ci)
evstr += ",SUCCESS," + ci->vpnIp4 + ',' + ci->serverIp + ',' + ci->serverPort + ",,," + ci->vpnIp6;
else
evstr += ',' + ev.info + ",,";
evstr += "\r\n";
return evstr;
}
static std::string gen_state_msg(const bool prefix, std::string name, std::string info)
{
ClientAPI::Event ev;
ev.name = std::move(name);
ev.info = std::move(info);
std::string ret;
if (prefix)
ret = ">STATE:";
ret += event_format(ev, nullptr);
return ret;
}
void event_msg(const ClientAPI::Event& ev, const ClientAPI::ConnectionInfo* ci)
{
// log events (even if in stopping state)
{
ClientAPI::LogInfo li;
li.text = ev.name;
if (!ev.info.empty())
{
li.text += " : ";
li.text += ev.info;
}
if (ev.fatal)
li.text += " [FATAL-ERR]";
else if (ev.error)
li.text += " [ERR]";
li.text += '\n';
log_msg(li);
}
// if we are in a stopping state, don't process the event
if (is_stopping())
return;
// process events
if ((ev.name == "AUTH_FAILED" || ev.name == "DYNAMIC_CHALLENGE") && management_query_passwords)
{
// handle auth failures
std::string msg = ">PASSWORD:Verification Failed: 'Auth'";
if (!ev.info.empty())
msg += " ['" + ev.info + "']";
msg += "\r\n";
send(msg);
// reset query state
creds.reset();
did_query_remote = false;
// exit/reconnect
if (autologin)
{
set_final_error(">FATAL: auth-failure: " + ev.info + "\r\n");
stop();
}
else
deferred_reconnect(1, "auth-failure");
}
else if (ev.name == "CLIENT_HALT")
{
std::string reason = ev.info;
if (reason.empty())
reason = "client was disconnected from server";
send(">NOTIFY:info,server-pushed-halt," + reason + "\r\n");
set_final_error(gen_state_msg(true, "EXITING", "exit-with-notification"));
stop();
}
else if (ev.name == "CLIENT_RESTART")
{
// fixme -- handle PSID
std::string reason = ev.info;
if (reason.empty())
reason = "server requested a client reconnect";
reconnect_reason = "server-pushed-connection-reset";
send(">NOTIFY:info,"+ reconnect_reason + ',' + reason + "\r\n");
omi_sighup();
}
else if (ev.name == "RECONNECTING")
{
ClientAPI::Event nev(ev);
if (nev.info.empty())
nev.info = reconnect_reason;
reconnect_reason = "";
state_line(event_format(nev, nullptr));
}
else if (ev.name == "PROXY_NEED_CREDS" && management_query_passwords)
{
// need proxy credentials, retry
proxy_need_creds = true;
state_line(event_format(ev, nullptr));
retry();
}
else if (ev.name == "DISCONNECTED")
{
// for now, we ignore DISCONNECTED messages
}
else if (ev.fatal)
{
// this event is a fatal error
std::string reason = ev.name;
if (!ev.info.empty())
{
reason += ": ";
reason += ev.info;
}
set_final_error(">FATAL:" + reason + "\r\n");
stop();
}
else if (ev.name == "ECHO")
{
echo_line(openvpn::to_string(::time(NULL)) + ',' + ev.info + "\r\n");
}
else if (ev.name == "CONNECTED")
{
// generate >UPDOWN: event
if (management_up_down)
emit_up_down("UP");
// reset pre-connection state
creds.reset();
reconnect_reason = "";
// generate a TCP_CONNECT event if TCP connection
if (ci && string::starts_with(ci->serverProto, "TCP"))
state_line(gen_state_msg(false, "TCP_CONNECT", ""));
// push the event string to state notification/history
state_line(event_format(ev, ci));
}
else
{
// push the event string to state notification/history
state_line(event_format(ev, ci));
}
}
void emit_up_down(const std::string& state)
{
std::string out = ">UPDOWN:" + state + "\r\n";
out += ">UPDOWN:ENV,END\r\n";
send(out);
}
void set_final_error(const std::string& err)
{
if (!err.empty())
final_error = string::trim_crlf_copy(err);
}
std::string get_final_error()
{
return final_error;
}
void signal(const openvpn_io::error_code& error, int signum)
{
if (!error && !is_stopping())
{
OPENVPN_LOG("ASIO SIGNAL " << signum);
switch (signum)
{
case SIGINT:
case SIGTERM:
omi_sigterm();
break;
#if !defined(OPENVPN_PLATFORM_WIN)
case SIGHUP:
omi_sighup();
signal_rearm();
break;
#endif
}
}
}
void signal_rearm()
{
signals->register_signals_all([self=Ptr(this)](const openvpn_io::error_code& error, int signal_number)
{
self->signal(error, signal_number);
});
}
// options
OptionList opt;
// general
std::unique_ptr<Client> client;
std::unique_ptr<ClientAPI::Config> config;
std::unique_ptr<ClientAPI::ProvideCreds> creds;
std::unique_ptr<std::thread> thread;
std::string final_error;
Stop async_stop;
// timeout
int connection_timeout = 0;
// auth
bool management_query_passwords = false;
bool auth_nocache = false;
std::string auth_type;
bool auth_password_only = false;
std::string auth_username;
std::string auth_password;
// remote override
bool management_query_remote = false;
bool did_query_remote = false;
bool remote_pending = false;
std::string remote_override;
// protocol override (udp/tcp)
std::string proto_override;
// proxy
std::string http_proxy_host;
std::string http_proxy_port;
bool proxy_need_creds = false;
// reconnections
std::string reconnect_reason;
// reconnect
AsioTimerSafe reconnect_timer;
// bytecount
AsioTimerSafe bytecount_timer;
// external PKI
bool management_external_key = false;
std::mutex epki_mutex;
std::condition_variable epki_cv;
ClientAPI::ExternalPKISignRequest* epki_signreq = nullptr; // protected by epki_mutex
// up/down
bool management_up_down = false;
// autologin
bool autologin = false;
// signals
ASIOSignals::Ptr signals;
typedef openvpn_io::windows::object_handle AsioEvent;
AsioEvent exit_event;
std::string exit_event_name;
Log::Context log_context; // should be initialized last
};
void Client::event(const ClientAPI::Event& ev)
{
if (ev.name == "CONNECTED")
{
const ClientAPI::ConnectionInfo ci = connection_info();
parent->event(ev, ci);
}
else
parent->event(ev);
}
void Client::log(const ClientAPI::LogInfo& msg)
{
parent->log(msg);
}
void Client::external_pki_cert_request(ClientAPI::ExternalPKICertRequest& certreq)
{
parent->external_pki_cert_request(certreq);
}
void Client::external_pki_sign_request(ClientAPI::ExternalPKISignRequest& signreq)
{
parent->external_pki_sign_request(signreq);
}
int run(OptionList opt)
{
openvpn_io::io_context io_context(1);
bool io_context_run_called = false;
int ret = 0;
OMI::Ptr omi;
try {
#if _WIN32_WINNT >= 0x0600 // Vista+
TunWin::NRPT::delete_rule(); // delete stale NRPT rules
#endif
omi.reset(new OMI(io_context, std::move(opt)));
omi->start();
io_context_run_called = true;
io_context.run();
omi->stop();
}
catch (const std::exception& e)
{
if (omi)
omi->stop();
if (io_context_run_called)
io_context.poll(); // execute completion handlers,
std::cerr << "openvpn: run loop exception: " << e.what() << std::endl;
ret = 1;
}
return ret;
}
int main(int argc, char *argv[])
{
int ret = 0;
try {
Client::init_process();
if (argc >= 2)
{
ret = run(OptionList::parse_from_argv_static(string::from_argv(argc, argv, true)));
}
else
{
std::cout << log_version() << std::endl;
std::cout << "Usage: openvpn [args...]" << std::endl;
ret = 2;
}
}
catch (const std::exception& e)
{
std::cerr << "openvpn: " << e.what() << std::endl;
ret = 1;
}
Client::uninit_process();
return ret;
}