mirror of
https://github.com/OpenVPN/openvpn3.git
synced 2024-09-20 12:12:15 +02:00
0668d9132b
Allows to pass event name which, when signalled, causes client to exit. Signed-off-by: Lev Stipakov <lev@openvpn.net>
1065 lines
25 KiB
C++
1065 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),
|
|
log_context(this),
|
|
exit_event(io_context)
|
|
{
|
|
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;
|
|
|
|
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;
|
|
}
|