Skip to content

Commit

Permalink
Merging in changes from OpenVPN 3 Core version 3.8.4
Browse files Browse the repository at this point in the history
Signed-off-by: David Sommerseth <davids@openvpn.net>
  • Loading branch information
dsommers committed Feb 19, 2024
2 parents cb9ce3d + 8f4cd95 commit bae1006
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 44 deletions.
2 changes: 1 addition & 1 deletion client/ovpncli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1055,7 +1055,7 @@ OPENVPN_CLIENT_EXPORT Status OpenVPNClient::status_from_exception(const std::exc
{
Status ret;
ret.error = true;
ret.message = Unicode::utf8_printable<std::string>(e.what(), 256);
ret.message = Unicode::utf8_printable<std::string>(e.what(), 256 | Unicode::UTF8_PASS_FMT);

// if exception is an ExceptionCode, translate the code
// to return status string
Expand Down
125 changes: 91 additions & 34 deletions openvpn/client/cliopt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#include <string>
#include <tuple>
#include <unordered_set>
#include <map>
#include <set>

#include <openvpn/error/excode.hpp>

Expand Down Expand Up @@ -665,18 +667,29 @@ class ClientOptions : public RC<thread_unsafe_refcount>
if (opt.exists("mode"))
{
const auto &mode = opt.get("mode");
if (mode.size() != 1 || mode.get(1, 128) != "p2p")
if (mode.size() != 2 || mode.get(1, 128) != "p2p")
{
throw option_error("Only 'mode p2p' supported");
}
}

// key-method 2 is the only thing that 2.5+ and 3.x support
if (opt.exists("key-method"))
{
auto keymethod = opt.get("key-method");
if (keymethod.size() != 2 || keymethod.get(1, 128) != "2")
{
throw option_error("Only 'key-method 2' is supported: " + keymethod.get(1, 128));
}
}
}

std::unordered_set<std::string> settings_ignoreWithWarning = {
"allow-compression", /* TODO: maybe check against our client option compression setting? */
"allow-recursive-routing",
"auth-nocache",
"auth-retry",
"block-outside-dns", /* Core will decide on its own when to block outside dns, so this is not 100% identical in behaviour, so still warn */
"compat-mode",
"connect-retry",
"connect-retry-max",
Expand Down Expand Up @@ -709,11 +722,13 @@ class ClientOptions : public RC<thread_unsafe_refcount>
"replay-window",
"resolv-retry",
"route-method", /* Windows specific fine tuning option */
"route-delay",
"show-net-up",
"socket-flags",
"suppress-timestamps", /* harmless to ignore */
"tcp-nodelay",
"tls-version-max", /* We don't allow restricting max version */
"tun-mtu-extra", /* (only really used in tap in OpenVPN 2.x)*/
"udp-mtu", /* Alias for link-mtu */
"user",
};
Expand Down Expand Up @@ -783,8 +798,7 @@ class ClientOptions : public RC<thread_unsafe_refcount>
"status",
"status-version",
"syslog",
"tls-server", /* No p2p mode in v3 */
"tun-mtu-extra", /*(only really used in tap in OpenVPN 2.x)*/
"tls-server", /* No p2p mode in v3 */
"verify-hash",
"win-sys",
"writepid",
Expand Down Expand Up @@ -819,7 +833,10 @@ class ClientOptions : public RC<thread_unsafe_refcount>
"key-derivation",
"peer-id",
"protocol-flags",
};
"ifconfig",
"ifconfig-ipv6",
"topology",
"route-gateway"};

/* Features related to scripts/plugins */
std::unordered_set<std::string> settings_script_plugin_feature = {
Expand Down Expand Up @@ -854,7 +871,7 @@ class ClientOptions : public RC<thread_unsafe_refcount>

/* Deprecated/throwing error in OpenVPN 2.x already: */
std::unordered_set<std::string> settings_removedOptions = {
"mtu-dynamic", "no-replay", "no-name-remapping", "compat-names", "ncp-disable"};
"mtu-dynamic", "no-replay", "no-name-remapping", "compat-names", "ncp-disable", "no-iv"};

std::unordered_set<std::string> settings_ignoreSilently = {
"ecdh-curve", /* Deprecated in v2, not needed with modern OpenSSL */
Expand All @@ -872,6 +889,54 @@ class ClientOptions : public RC<thread_unsafe_refcount>
"txqueuelen", /* so platforms evaluate that in tun, some do not, do not warn about that */
"verb"};

class OptionErrors
{
public:
void add_failed_opt(const Option &o, const std::string &message, bool fatal_arg)
{
if (options_per_category.find(message) == options_per_category.end())
{
options_per_category[message] = {};
}

fatal |= fatal_arg;
options_per_category[message].push_back(o);
}

void print_option_errors()
{
std::ostringstream os;

for (const auto &[category, options] : options_per_category)
{
if (!options.empty())
{
OPENVPN_LOG(category);

os << category << ": ";
std::vector<std::string> opts;
for (size_t i = 0; i < options.size(); ++i)
{
auto &o = options[i];
OPENVPN_LOG(std::to_string(i) << ' ' << o.render(Option::RENDER_BRACKET | Option::RENDER_TRUNC_64));
opts.push_back(o.get(0, 64));
}

os << string::join(opts, ",") << std::endl;
}
}

if (fatal)
{
throw ErrorCode(Error::UNUSED_OPTIONS, true, os.str());
}
}

private:
std::map<std::string, std::vector<Option>> options_per_category;
bool fatal = false;
};

/**
* This groups all the options that OpenVPN 2.x supports that the
* OpenVPN v3 client does not support into a number of different groups
Expand Down Expand Up @@ -933,73 +998,65 @@ class ClientOptions : public RC<thread_unsafe_refcount>

OPENVPN_LOG_NTNL("NOTE: This configuration contains options that were not used:" << std::endl);

OptionErrors errors{};

/* Go through all options and check all options that have not been
* touched (parsed) yet */
showUnusedOptionsByList(opt, settings_removedOptions, "Removed deprecated option", true);
showUnusedOptionsByList(opt, settings_serverOnlyOptions, "Server only option", true);
showUnusedOptionsByList(opt, settings_standalone_options, "OpenVPN 2.x command line operation", true);
showUnusedOptionsByList(opt, settings_feature_not_implemented_warn, "Feature not implemented (option ignored)", false);
showUnusedOptionsByList(opt, settings_pushonlyoptions, "Option allowed only to be pushed by the server", true);

showUnusedOptionsByList(opt, settings_feature_not_implemented_warn, "feature not implemented/available", false);
showUnusedOptionsByList(opt, settings_script_plugin_feature, "Ignored (no script/plugin support)", false);
showUnusedOptionsByList(opt, ignore_unknown_option_list, "Ignored by option 'ignore-unknown-option'", false);
showUnusedOptionsByList(opt, settings_ignoreWithWarning, "Unsupported option (ignored)", false);
showUnusedOptionsByList(opt, settings_removedOptions, "Removed deprecated option", true, errors);
showUnusedOptionsByList(opt, settings_serverOnlyOptions, "Server only option", true, errors);
showUnusedOptionsByList(opt, settings_standalone_options, "OpenVPN 2.x command line operation", true, errors);
showUnusedOptionsByList(opt, settings_feature_not_implemented_warn, "Feature not implemented (option ignored)", false, errors);
showUnusedOptionsByList(opt, settings_pushonlyoptions, "Option allowed only to be pushed by the server", true, errors);
showUnusedOptionsByList(opt, settings_script_plugin_feature, "Ignored (no script/plugin support)", false, errors);
showUnusedOptionsByList(opt, ignore_unknown_option_list, "Ignored by option 'ignore-unknown-option'", false, errors);
showUnusedOptionsByList(opt, settings_ignoreWithWarning, "Unsupported option (ignored)", false, errors);

auto ignoredBySetenvOpt = [](const Option &option)
{ return !option.touched() && option.warnonlyunknown(); };
showOptionsByFunction(opt, ignoredBySetenvOpt, "Ignored options prefixed with 'setenv opt'", false);
showOptionsByFunction(opt, ignoredBySetenvOpt, "Ignored options prefixed with 'setenv opt'", false, errors);

auto unusedMetaOpt = [](const Option &option)
{ return !option.touched() && option.meta(); };
showOptionsByFunction(opt, unusedMetaOpt, "Unused ignored meta options", false);
showOptionsByFunction(opt, unusedMetaOpt, "Unused ignored meta options", false, errors);

auto managmentOpt = [](const Option &option)
{ return !option.touched() && option.get(0, 0).rfind("management", 0) == 0; };
showOptionsByFunction(opt, managmentOpt, "OpenVPN management interface is not supported by this client", true);
showOptionsByFunction(opt, managmentOpt, "OpenVPN management interface is not supported by this client", true, errors);

// If we still have options that are unaccounted for, we print them and throw an error or just warn about them
auto onlyLightlyTouchedOptions = [](const Option &option)
{ return option.touched_lightly(); };
showOptionsByFunction(opt, onlyLightlyTouchedOptions, "Unused options, probably specified multiple times in the configuration file", false);
showOptionsByFunction(opt, onlyLightlyTouchedOptions, "Unused options, probably specified multiple times in the configuration file", false, errors);

auto nonTouchedOptions = [](const Option &option)
{ return !option.touched() && !option.touched_lightly(); };
showOptionsByFunction(opt, nonTouchedOptions, OPENVPN_UNUSED_OPTIONS, true);
showOptionsByFunction(opt, nonTouchedOptions, OPENVPN_UNUSED_OPTIONS, true, errors);

errors.print_option_errors();
}

void showUnusedOptionsByList(const OptionList &optlist, std::unordered_set<std::string> option_set, const std::string &message, bool fatal)
void showUnusedOptionsByList(const OptionList &optlist, std::unordered_set<std::string> option_set, const std::string &message, bool fatal, OptionErrors &errors)
{
auto func = [&option_set](const Option &opt)
{ return !opt.touched() && option_set.find(opt.get(0, 0)) != option_set.end(); };
showOptionsByFunction(optlist, func, message, fatal);
showOptionsByFunction(optlist, func, message, fatal, errors);
}

/* lambda expression that capture variables have complex signatures, avoid these by letting the compiler
* itself figure it out with a template */
template <typename T>
void showOptionsByFunction(const OptionList &opt, T func, const std::string &message, bool fatal)
void showOptionsByFunction(const OptionList &opt, T func, const std::string &message, bool fatal, OptionErrors &errors)
{
bool messageShown = false;
for (size_t i = 0; i < opt.size(); ++i)
{
auto &o = opt[i];
if (func(o))
{
if (!messageShown)
{
OPENVPN_LOG(message);
messageShown = true;
}
o.touch();

OPENVPN_LOG_NTNL(std::to_string(i) << ' ' << o.render(Option::RENDER_BRACKET | Option::RENDER_TRUNC_64) << std::endl);
errors.add_failed_opt(o, message, fatal);
}
}
if (fatal && messageShown)
{
throw option_error("sorry, unsupported options present in configuration: " + message);
}
}

static PeerInfo::Set::Ptr build_peer_info(const Config &config, const ParseClientConfig &pcc, const bool autologin_sessions)
Expand Down
2 changes: 1 addition & 1 deletion openvpn/compress/compress.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,10 @@ class CompressContext
{
case NONE:
return new CompressNull(frame, stats);
case ANY:
case ANY_LZO:
case LZO_STUB:
return new CompressStub(frame, stats, false);
case ANY:
case COMP_STUB:
return new CompressStub(frame, stats, true);
case COMP_STUBv2:
Expand Down
2 changes: 2 additions & 0 deletions openvpn/error/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ enum Type
PROXY_NEED_CREDS, // HTTP proxy needs credentials
EARLY_NEG_INVALID, // Early protoctol negotiation information invalid/parse error
NTLM_MISSING_CRYPTO, // crypto primitives requires for NTLM are unavailable
UNUSED_OPTIONS, // unused/unknown options found in configuration

// key event errors
KEV_NEGOTIATE_ERROR,
Expand Down Expand Up @@ -178,6 +179,7 @@ inline const char *name(const size_t type)
"PROXY_NEED_CREDS",
"EARLY_NEG_INVALID",
"NTLM_MISSING_CRYPTO",
"UNUSED_OPTIONS_ERROR",
"KEV_NEGOTIATE_ERROR",
"KEV_PENDING_ERROR",
"N_KEV_EXPIRE",
Expand Down
8 changes: 5 additions & 3 deletions openvpn/openssl/sign/pkcs7verify.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ inline void verify_pkcs7(const std::list<OpenSSLPKI::X509> &certs,
in = BIO_new_mem_buf(data.c_str(), numeric_cast<int>(data.length()));

/* OpenSSL 1.0.2e and higher no longer allows calling PKCS7_verify
with both data and content. Empty out the content. */
p7->d.sign->contents->d.ptr = 0;
with both data and content. Empty out the content if present.
Just calling PKCS7_set_detached call lead to a null pointer access */
if (!PKCS7_is_detached(p7))
PKCS7_set_detached(p7, 1);

/* do the verify */
if (PKCS7_verify(p7, x509_stack, NULL, in, NULL, PKCS7_NOVERIFY) != 1)
if (PKCS7_verify(p7, x509_stack, nullptr, in, nullptr, PKCS7_NOVERIFY) != 1)
throw OpenSSLException("OpenSSLSign::verify_pkcs7: verification failed");
}
} // namespace OpenSSLSign
Expand Down
2 changes: 2 additions & 0 deletions openvpn/openssl/sign/verify.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
#define OPENVPN_OPENSSL_SIGN_VERIFY_H

#include <string>
#include <list>

#include <openssl/ssl.h>
#include <openssl/bio.h>

#include <openvpn/common/cleanup.hpp>
#include <openvpn/common/numeric_cast.hpp>
#include <openvpn/common/base64.hpp>
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/openssl/pki/x509.hpp>
#include <openvpn/openssl/util/error.hpp>

Expand Down
2 changes: 1 addition & 1 deletion test/ovpncli/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ static void worker_thread()
{
std::cout << "connect error: ";
if (!connect_status.status.empty())
std::cout << connect_status.status << ": ";
std::cout << connect_status.status << ": " << std::endl;
std::cout << connect_status.message << std::endl;
}
}
Expand Down
1 change: 1 addition & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ else ()
test_openssl_x509certinfo.cpp
test_openssl_authcert.cpp
test_opensslpki.cpp
test_openssl_misc.cpp
test_session_id.cpp
)
endif ()
Expand Down
Loading

0 comments on commit bae1006

Please sign in to comment.