Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b763cfd
clients/upsclient.{c,h}: add support for connection-specific SSL cont…
jimklimov Jul 30, 2026
d4dec1e
clients/nutclient.cpp: make OpenSSL "ssl_ctx" and NSS "_nss_initializ…
jimklimov Jul 30, 2026
9c22639
tests/nutclienttest.cpp: add NutClientTest::test_ssl_context_registry…
jimklimov Jul 30, 2026
f4ad936
clients/nutclient.{h,cpp}: add Socket and TcpClient getters and sette…
jimklimov Aug 31, 2026
60a2b4b
tests/nutclienttest.cpp: fix NutClientTest::test_ssl_context_registry…
jimklimov Aug 31, 2026
1306c6e
docs/SSL_CONTEXT_ANALYSIS.adoc: add Copilot analysis of current situa…
jimklimov Aug 31, 2026
1f51b8a
docs/SSL_CONTEXT_ANALYSIS.adoc: follow-up discussion for true multi-c…
jimklimov Aug 31, 2026
706d8cd
clients/nutclient.cpp: nss_error(): pass `sock` to use desired cert w…
jimklimov Aug 31, 2026
a12061a
clients/upsclient.c: nss_password_callback(): prefer cert password st…
jimklimov Aug 31, 2026
97726f3
clients/upsclient.c: GetClientAuthData(): pass `UPSCONN_t arg` to use…
jimklimov Aug 31, 2026
3702d9b
clients/upsclient.{c,h}: track desired CERTIDENT name/pass via `UPSCO…
jimklimov Aug 31, 2026
cb14b9c
docs/SSL_CONTEXT_ANALYSIS.adoc: document the revisited multi-context …
jimklimov Aug 31, 2026
67e3ed0
clients/upsclient.h: document current return values for upscli_init*(…
jimklimov Aug 31, 2026
c3510aa
clients/upsclient.{c,h}: Complete registry-based SSL multi-context re…
jimklimov Aug 31, 2026
53b68a9
clients/upsclient.h: Expose opaque upscli_ssl_context_config_t type i…
jimklimov Aug 31, 2026
8a3aa32
clients/upslog.c, clients/upsmon.c, clients/upsstats.c: Update multi-…
jimklimov Aug 31, 2026
b69680e
NEWS.adoc: document ability for multiple SSL contexts [#3439]
jimklimov Aug 31, 2026
620878b
docs/new-clients.txt, docs/man/ups*.txt: document multiple SSL contex…
jimklimov Aug 31, 2026
0aae2cf
clients/upsclient.c, clients/upslog.c, clients/upsstats.c: upscli_ssl…
jimklimov Sep 1, 2026
9ddfbc7
clients/upsmon.c, clients/upslog.c, NEWS.adoc, docs, conf/* samples: …
jimklimov Aug 31, 2026
49d4dd1
clients/upsclient.c: upscli_tryconnect(): debug-log getting into an S…
jimklimov Sep 1, 2026
89f99a3
clients/upsmon.c: revise debug-logging of notifycmd_concat
jimklimov Sep 1, 2026
daea1dd
clients/nutclient.cpp: fix builds without OpenSSL [#3439]
jimklimov Sep 1, 2026
bbf86e2
tests/nutclienttest.cpp: test_ssl_context_registry(): adapt to builds…
jimklimov Sep 1, 2026
a18a250
clients/authconf.c, conf/*, docs/man/*: parse_authconf_file(): treat …
jimklimov Sep 2, 2026
4ca230b
clients/upsmon.c, client/upsstats.c, conf/*, docs/man/*: support spec…
jimklimov Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,19 @@ https://github.com/networkupstools/nut/milestone/13
report the ability to check `CERTIDENT` information. [#3331]
* Introduced support for "authconf" files to store and convey NUT client
authentication details. [issue #3329]
* Added C/C++ client support for multiple independent OpenSSL contexts
in the same process, or using multiple NSS client certificates with
its single context, for ability to connect to NUT data servers managed
by different entities (e.g. certificate authorities and client/server
realms). In-tree multiple-connection clients (`upsmon`, `upsstats.cgi`,
`upslog`) were updated to use this feature. [issue #3439]
* To support the ability of multiple SSL authentication details for
`upsmon` and `upsstats.cgi`, their configuration files were extended
with `AUTHCONF` keyword which can point to the `nutauth.conf` file
they should use, or pass the special values `default` (require a
user- or system-provided `nutauth.conf` file) or `none` (skip
authconf discovery entirely), matching the `-A` option semantics
of command-line NUT clients. [issue #3439]

- Various clients:
* Flush standard output and error buffers before handling clean exit
Expand All @@ -394,10 +407,10 @@ https://github.com/networkupstools/nut/milestone/13
SSL settings in the client which previously only did best-effort attempts
at secure communications without an individual certificate, and only
anonymously for reading. The new `-A filename` option defaults to trying
to use a `nutauth.conf` file (if found in one of the default locations)
but not failing if one is not usable; specific values can require use of
such a file (`default`) or to not even try reading one (`none`).
[issues #3329, #3411]
to use a `nutauth.conf` file (if NOT specified here, but rather found in
one of the default locations) but not failing if one is not usable; any
specific values can require use of such a file (`default`) or to not even
try reading one (`none`). [issues #3329, #3411]

- `upslog` client/tool updates:
* Added support for best-effort use of `nutauth.conf` files from default
Expand All @@ -411,6 +424,8 @@ https://github.com/networkupstools/nut/milestone/13
* Added support for best-effort use of `nutauth.conf` files from default
locations described above (no way to choose the location, other than
by web-server environment variables for CGI calls). [#3329]
* `upsstats.cgi` additionally has a new `AUTHCONF` configuration option
that can be passed via its `hosts.conf` file, as detailed above. [#3439]

- `upsmon` client updates:
* Introduced support for `CERTFILE` option, so the client can identify
Expand Down
21 changes: 17 additions & 4 deletions UPGRADING.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,23 @@ Changes from 2.8.5 to 2.8.6
for reading like `upsc`.
+
The new `-A filename` option defaults to trying to use a `nutauth.conf` file
(if found in one of the default locations) but not failing if one is not
usable; specific values can require use of such a file or to not even try
reading one ('none' as the legacy default). See the updated manual pages
for more details. [issues #3329, #3411]
(if NOT specified here, but rather found in one of the default locations)
but not failing if one is not usable; any specific values can require use
of such a file or to not even try reading one ('none' as the legacy default).
See the updated manual pages for more details. [issues #3329, #3411]

- The `upsmon` and `upsstats.cgi` clients capable of connections to multiple
NUT data servers, potentially in separately managed security realms, now
support an `AUTHCONF` option in their configuration files to specify the
SSL details to be used with each such server. Legacy `upsmon` configuration
keywords like `CERTPATH`, `CERTFILE`, `CERTIDENT`, `CERTHOST`, `CERTVERIFY`
and `FORCESSL` act as a "global defaults" section in `nutauth.conf` terms,
and any actual `nutauth.conf` file entries gets loaded on top of these data.
The default behavior (when `AUTHCONF` remains unspecified) is a best-effort
attempt to load the file from one of the standard locations. If your NUT
deployment defines an e.g. `/etc/nut/nutauth.conf` file which would be
loaded automatically, but its contents conflict with what `upsmon` needs
to know, please specify `AUTHCONF = none` in `upsmon.conf`. [issue #3439]

- The `powervar_cx_usb`, `tripplite_usb` drivers used a built-in limit on
reconnection attempts after which they exited ('60' and '10' respectively).
Expand Down
26 changes: 21 additions & 5 deletions clients/authconf.c
Original file line number Diff line number Diff line change
Expand Up @@ -966,8 +966,24 @@ static void handle_authconf_args(size_t numargs, char **arg, int global_scope)
static int parse_authconf_file(const char *filename, int fatal_errors, int global_scope)
{
PCONF_CTX_t ctx;
char fn[NUT_PATH_MAX + 1];
const char *filename_to_use = filename;

check_perms(filename);
if (filename[0] != '/'
#ifdef WIN32
&& filename[1] != ':'
#endif /* WIN32 */
) {
int path_len = snprintf(fn, sizeof(fn), "%s/%s", confpath(), filename);
if (path_len < 1 || (size_t)path_len >= sizeof(fn)
) {
upslogx(LOG_ERR, "Could not construct path for authconf file: %s", filename);
return -1;
}
filename_to_use = fn;
}

check_perms(filename_to_use);

if (!pconf_init(&ctx, authconf_err)) {
if (fatal_errors) {
Expand All @@ -976,19 +992,19 @@ static int parse_authconf_file(const char *filename, int fatal_errors, int globa
return -1;
}

if (!pconf_file_begin(&ctx, filename)) {
if (!pconf_file_begin(&ctx, filename_to_use)) {
Comment thread
jimklimov marked this conversation as resolved.
Dismissed
if (fatal_errors) {
fatalx(EXIT_FAILURE, "Can't open %s: %s", filename, ctx.errmsg);
fatalx(EXIT_FAILURE, "Can't open %s: %s", filename_to_use, ctx.errmsg);
} else {
upslogx(LOG_WARNING, "Can't open %s: %s", filename, ctx.errmsg);
upslogx(LOG_WARNING, "Can't open %s: %s", filename_to_use, ctx.errmsg);
pconf_finish(&ctx);
return -1;
}
}

while (pconf_file_next(&ctx)) {
if (pconf_parse_error(&ctx)) {
upslogx(LOG_ERR, "Parse error: %s:%d: %s", filename, ctx.linenum, ctx.errmsg);
upslogx(LOG_ERR, "Parse error: %s:%d: %s", filename_to_use, ctx.linenum, ctx.errmsg);
continue;
}
handle_authconf_args(ctx.numargs, ctx.arglist, global_scope);
Expand Down
67 changes: 59 additions & 8 deletions clients/nutclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ class Socket
void startTLS();
bool isSSL()const;

void *setSSLContext(void *ssl_ctx);
void *getSSLContext() const;

void setTimeout(time_t timeout);
bool hasTimeout()const{return _tv.tv_sec>=0;}

Expand All @@ -295,12 +298,13 @@ class Socket
#ifdef WITH_SSL_CXX
# ifdef WITH_OPENSSL
SSL* _ssl;
SSL_CTX* _ssl_ctx;
openssl_cert_verify_data_t openssl_cert_verify_data;
int _verify_depth;
static int _openssl_cert_verify_data_index;
static SSL_CTX* _ssl_ctx;
# elif defined(WITH_NSS)
PRFileDesc* _ssl;
bool _nss_initialized;
# endif
#endif
bool _debugConnect;
Expand Down Expand Up @@ -342,7 +346,6 @@ class Socket

#ifdef WITH_SSL_CXX
# ifdef WITH_OPENSSL
SSL_CTX* Socket::_ssl_ctx = nullptr;
int Socket::_openssl_cert_verify_data_index = 0;

/* Adapted from https://stackoverflow.com/a/42477707 with references to
Expand Down Expand Up @@ -824,11 +827,13 @@ static void nss_error(const char* text)

if (status == SECFailure) {
if (sock && sock->_ssl_config && !sock->_ssl_config->getCertIdentName().empty()) {
cert = PK11_FindCertFromNickname(sock->_ssl_config->getCertIdentName().c_str(), nullptr);
/* Pass "sock" through as wincx, so nss_password_callback()
* can resolve this same connection's per-Socket password. */
cert = PK11_FindCertFromNickname(sock->_ssl_config->getCertIdentName().c_str(), sock);
if (cert == nullptr) {
nss_error("GetClientAuthData / PK11_FindCertFromNickname");
} else {
privKey = PK11_FindKeyByAnyCert(cert, nullptr);
privKey = PK11_FindKeyByAnyCert(cert, sock);
if (privKey == nullptr) {
nss_error("GetClientAuthData / PK11_FindKeyByAnyCert");
CERT_DestroyCertificate(cert);
Expand Down Expand Up @@ -862,6 +867,11 @@ Socket::Socket():
# if defined(WITH_OPENSSL) || defined(WITH_NSS)
_ssl(nullptr),
# endif
# ifdef WITH_OPENSSL
_ssl_ctx(nullptr),
# elif defined(WITH_NSS)
_nss_initialized(false),
# endif
# if defined(WITH_OPENSSL)
_verify_depth(9), /* openssl default */
# endif
Expand Down Expand Up @@ -898,13 +908,40 @@ Socket::Socket():
Socket::~Socket()
{
disconnect();
#ifdef WITH_OPENSSL
if (_ssl_ctx) {
SSL_CTX_free(_ssl_ctx);
_ssl_ctx = nullptr;
}
#endif
}

void Socket::setTimeout(time_t timeout)
{
_tv.tv_sec = timeout;
}

void *Socket::setSSLContext(void *ssl_ctx)
{
#ifdef WITH_OPENSSL
void *previous = _ssl_ctx;
_ssl_ctx = static_cast<SSL_CTX*>(ssl_ctx);
return previous;
#else
NUT_UNUSED_VARIABLE(ssl_ctx);
return nullptr;
#endif
}

void *Socket::getSSLContext() const
{
#ifdef WITH_OPENSSL
return _ssl_ctx;
#else
return nullptr;
#endif
}

void Socket::setDebugConnect(bool d)
{
_debugConnect = d;
Expand Down Expand Up @@ -1658,10 +1695,8 @@ void Socket::startTLS()

# elif defined(WITH_NSS)
/* NSS implementation following upsclient.c logic */
static bool nss_initialized = false;

/* FIXME: Support several NSS databases, use prefix parameters? */
if (!nss_initialized) {
if (!_nss_initialized) {
PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
PK11_SetPasswordFunc(nss_password_callback);

Expand All @@ -1682,7 +1717,7 @@ void Socket::startTLS()
if (status != SECSuccess) {
throw nut::SSLException_NSS("NSS initialization failed");
}
nss_initialized = true;
_nss_initialized = true;
}

PRFileDesc *socket = PR_ImportTCPSocket(static_cast<int>(_sock));
Expand Down Expand Up @@ -3814,6 +3849,22 @@ void TcpClient::setSSLConfig(const SSLConfig& config)
config.apply(*this);
}

void *TcpClient::setSSLContext(void *ssl_ctx)
{
if (!_socket) {
return nullptr;
}
return _socket->setSSLContext(ssl_ctx);
}

void *TcpClient::getSSLContext() const
{
if (!_socket) {
return nullptr;
}
return _socket->getSSLContext();
}

void TcpClient::setSSLConfig_OpenSSL(int forcessl, int certverify, const char *ca_path, const char *ca_file, const char *cert_file, const char *key_file, const char *key_pass, const char *certident_name, const char *certhost_addr, const char *certhost_name)
{
delete _ssl_config_openssl;
Expand Down
17 changes: 17 additions & 0 deletions clients/nutclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,23 @@ class TcpClient : public Client
*/
void setSSLConfig(const SSLConfig& config);

/**
* Set a per-connection SSL context (advanced usage).
* Allows a specific connection to use its own SSL context instead of
* the library's default shared context. This enables clients to connect
* to multiple servers with different CA trust domains or client certificates
* simultaneously within the same process.
* \param ssl_ctx Opaque SSL context (SSL_CTX* for OpenSSL, etc.)
* \return Previous SSL context if one was set, nullptr otherwise.
*/
void *setSSLContext(void *ssl_ctx);

/**
* Get the per-connection SSL context if one has been set.
* \return The SSL context set via setSSLContext(), or nullptr if none.
*/
void *getSSLContext() const;

/**
* Connect it to the specified server.
* \param host Server host name.
Expand Down
Loading
Loading