From 5b72d2d14496759e8feabca9af788781adbf3a8c Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 21 Feb 2026 01:30:37 +0100 Subject: [PATCH 01/44] Introduce and use methods to query and report SSL support in libupsclient, its consumers, and upsd [#3328] Signed-off-by: Jim Klimov --- NEWS.adoc | 5 +++ UPGRADING.adoc | 4 ++ clients/upsc.c | 3 +- clients/upsclient.c | 51 +++++++++++++++++++++- clients/upsclient.h | 12 +++++- clients/upscmd.c | 1 + clients/upslog.c | 3 +- clients/upsmon.c | 3 +- clients/upsrw.c | 1 + docs/man/Makefile.am | 12 ++++++ docs/man/upscli_add_host_cert.txt | 12 ++++-- docs/man/upscli_init.txt | 9 +++- docs/man/upscli_report_build_details.txt | 34 +++++++++++++++ docs/man/upscli_ssl.txt | 5 +++ docs/man/upscli_ssl_caps.txt | 55 ++++++++++++++++++++++++ docs/nut.dict | 4 +- drivers/dummy-ups.c | 5 ++- server/netssl.c | 35 ++++++++++++++- server/netssl.h | 5 ++- server/upsd.c | 1 + tools/nut-scanner/scan_nut.c | 20 +++++++++ 21 files changed, 264 insertions(+), 16 deletions(-) create mode 100644 docs/man/upscli_report_build_details.txt create mode 100644 docs/man/upscli_ssl_caps.txt diff --git a/NEWS.adoc b/NEWS.adoc index 23b9f8f38d..9334454deb 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -104,6 +104,11 @@ https://github.com/networkupstools/nut/milestone/12 characters. Now it is evaluated at `configure` time (to check that the characters may be used), and if not -- during `nut_stdint.h` parsing to fit known `int`/`long`/`long long` types. [#3300] + * Added new API methods and defined bitmap values for `libupsclient` + C binding to query and report SSL capabilities of the current library + build (none, OpenSSL, Mozilla NSS): `upscli_ssl_caps_descr()` and + `upscli_ssl_caps()`. Updated common NUT clients to report this info + in their detailed help banners. Done similarly for `upsd`. [issue #3328] - NUT for Windows specific updates: * Revised detection of (relative) paths to program and configuration files diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 534bdf8e04..11c0a38fbb 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -99,6 +99,10 @@ Changes from 2.8.4 to 2.8.5 use `upsdrvquery_NOSIGPIPE=0` to disable neutering of the signal inside the API itself. [PR #3277] +- Added new API methods and defined bitmap values for `libupsclient` C binding + to query and report SSL capabilities of the library build (none, OpenSSL, + Mozilla NSS): `upscli_ssl_caps_descr()` and `upscli_ssl_caps()`. [PR #33xx] + - Fixed man page naming for `nutdrv_siemens-sitop(.8)` (dash vs. underscore) to match the driver program name. Packaging recipes may have to be updated. Follow-up from slightly botched renaming in original contribution. [PR #545] diff --git a/clients/upsc.c b/clients/upsc.c index f3736b8a4f..56778ff407 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -2,7 +2,7 @@ Copyright (C) 1999 Russell Kroll Copyright (C) 2012 Arnaud Quette - Copyright (C) 2020-2025 Jim Klimov + Copyright (C) 2020-2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -91,6 +91,7 @@ static void usage(const char *prog) printf(" -h - display this help text\n"); nut_report_config_flags(); + upscli_report_build_details(); printf("\n%s", suggest_doc_links(prog, NULL)); } diff --git a/clients/upsclient.c b/clients/upsclient.c index deebb7fbda..4e23b9e3a6 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -3,7 +3,7 @@ Copyright (C) 2002 Russell Kroll 2008 Arjen de Korte - 2020 - 2025 Jim Klimov + 2020 - 2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1881,6 +1881,55 @@ int upscli_ssl(UPSCONN_t *ups) return 0; } +/* Return a bitmap of the abilities for the current libupsclient build */ +int upscli_ssl_caps(void) +{ + int ret = UPSCLI_SSL_CAPS_NONE; + +#ifdef WITH_SSL +# ifdef WITH_OPENSSL + ret |= UPSCLI_SSL_CAPS_OPENSSL; +# endif +# ifdef WITH_NSS + ret |= UPSCLI_SSL_CAPS_NSS; +# endif +#endif /* WITH_SSL */ + + return ret; +} + +/* String version (English) for program help banners etc. */ +const char *upscli_ssl_caps_descr(void) +{ + static const char *ret = "with" +#ifndef WITH_SSL + "out SSL support"; +#else /* WITH_SSL */ + " SSL support: " +# ifdef WITH_OPENSSL + "OpenSSL" +# ifdef WITH_NSS + /* Not likely we'd get here, but... */ + " and " +# endif +# endif +# ifdef WITH_NSS + "Mozilla NSS" +# endif +# if !(defined WITH_NSS) && !(defined WITH_OPENSSL) + "oddly undefined" +# endif + ; +#endif /* WITH_SSL */ + + return ret; +} + +void upscli_report_build_details(void) +{ + upsdebugx(1, "Using NUT libupsclient library built %s", upscli_ssl_caps_descr()); +} + int upscli_set_default_connect_timeout(const char *secs) { double fsecs; diff --git a/clients/upsclient.h b/clients/upsclient.h index 6990b44eee..7a203d9f8e 100644 --- a/clients/upsclient.h +++ b/clients/upsclient.h @@ -2,7 +2,7 @@ Copyright (C) 2002 Russell Kroll - 2020 - 2025 Jim Klimov + 2020 - 2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -140,6 +140,16 @@ int upscli_upserror(UPSCONN_t *ups); /* returns 1 if SSL mode is active for this connection */ int upscli_ssl(UPSCONN_t *ups); +#define UPSCLI_SSL_CAPS_NONE 0 /* No ability to use SSL */ +#define UPSCLI_SSL_CAPS_OPENSSL 1 /* Can use OpenSSL-specific setup */ +#define UPSCLI_SSL_CAPS_NSS 2 /* Can use Mozilla NSS-specific setup */ + +/* Return a bitmap of the above for the current libupsclient build */ +int upscli_ssl_caps(void); +/* String version for program help banners etc. */ +const char *upscli_ssl_caps_descr(void); +void upscli_report_build_details(void); + /* Assign default upscli_connect() from string; return 0 if OK, or * return -1 if parsing failed and current value was kept */ int upscli_set_default_connect_timeout(const char *secs); diff --git a/clients/upscmd.c b/clients/upscmd.c index 561d7d5bb6..d5f696aeca 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -74,6 +74,7 @@ static void usage(const char *prog) printf(" -h - display this help text\n"); nut_report_config_flags(); + upscli_report_build_details(); printf("\n%s", suggest_doc_links(prog, "upsd.users")); } diff --git a/clients/upslog.c b/clients/upslog.c index c5c6a9a038..088808f577 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -2,7 +2,7 @@ Copyright (C) 1998 Russell Kroll - 2020-2025 Jim Klimov + 2020-2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -239,6 +239,7 @@ static void help(const char *prog) printf("%s\n", DEFAULT_LOGFORMAT); nut_report_config_flags(); + upscli_report_build_details(); printf("\n%s", suggest_doc_links(prog, NULL)); diff --git a/clients/upsmon.c b/clients/upsmon.c index 1e29250ce9..e5105d239b 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -4,7 +4,7 @@ 1998 Russell Kroll 2012 Arnaud Quette 2017 Eaton (author: Arnaud Quette ) - 2020-2025 Jim Klimov + 2020-2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -3446,6 +3446,7 @@ static void help(const char *arg_progname) printf(" -h - display this help text\n"); nut_report_config_flags(); + upscli_report_build_details(); printf("\n%s", suggest_doc_links(arg_progname, "upsmon.conf")); diff --git a/clients/upsrw.c b/clients/upsrw.c index 797445eeee..0646538e6e 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -75,6 +75,7 @@ static void usage(const char *prog) printf("Call without -s to show all possible read/write variables (same as -l).\n"); nut_report_config_flags(); + upscli_report_build_details(); printf("\n%s", suggest_doc_links(prog, "upsd.users")); } diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 444710adfe..164c39d912 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -544,10 +544,12 @@ SRC_DEV_PAGES = \ upscli_list_next.txt \ upscli_list_start.txt \ upscli_readline.txt \ + upscli_report_build_details.txt \ upscli_sendline.txt \ upscli_splitaddr.txt \ upscli_splitname.txt \ upscli_ssl.txt \ + upscli_ssl_caps.txt \ upscli_strerror.txt \ upscli_upserror.txt \ upscli_str_add_unique_token.txt \ @@ -649,6 +651,11 @@ LIBNUTCLIENT_DEVICES_DEPS = \ $(LIBNUTCLIENT_DEVICES_DEPS): libnutclient_devices.$(MAN_SECTION_API) touch $@ +UPSCLI_SSL_CAPS_DEPS = \ + upscli_ssl_caps_descr.$(MAN_SECTION_API) + +$(UPSCLI_SSL_CAPS_DEPS): upscli_ssl_caps.$(MAN_SECTION_API) + INST_MAN_DEV_API_PAGES = \ upsclient.$(MAN_SECTION_API) \ upscli_add_host_cert.$(MAN_SECTION_API) \ @@ -666,11 +673,14 @@ INST_MAN_DEV_API_PAGES = \ upscli_list_start.$(MAN_SECTION_API) \ upscli_readline.$(MAN_SECTION_API) \ upscli_readline_timeout.$(MAN_SECTION_API) \ + upscli_report_build_details.$(MAN_SECTION_API) \ upscli_sendline.$(MAN_SECTION_API) \ upscli_sendline_timeout.$(MAN_SECTION_API) \ upscli_splitaddr.$(MAN_SECTION_API) \ upscli_splitname.$(MAN_SECTION_API) \ upscli_ssl.$(MAN_SECTION_API) \ + upscli_ssl_caps.$(MAN_SECTION_API) \ + $(UPSCLI_SSL_CAPS_DEPS) \ upscli_strerror.$(MAN_SECTION_API) \ upscli_upserror.$(MAN_SECTION_API) \ upscli_str_add_unique_token.$(MAN_SECTION_API) \ @@ -781,10 +791,12 @@ INST_HTML_DEV_MANS = \ upscli_list_next.html \ upscli_list_start.html \ upscli_readline.html \ + upscli_report_build_details.html \ upscli_sendline.html \ upscli_splitaddr.html \ upscli_splitname.html \ upscli_ssl.html \ + upscli_ssl_caps.html \ upscli_strerror.html \ upscli_upserror.html \ upscli_str_add_unique_token.html \ diff --git a/docs/man/upscli_add_host_cert.txt b/docs/man/upscli_add_host_cert.txt index 10192b6688..ef88874164 100644 --- a/docs/man/upscli_add_host_cert.txt +++ b/docs/man/upscli_add_host_cert.txt @@ -29,9 +29,12 @@ The rule is composed of the certificate name 'certname' expected for the host, 'certverify' if the certificate must be validated for the host and 'forcessl' if a secured connection must be used to connect to the host. -Note: This call only functions if upsclient has been compiled with NSS -support. If instead it was compiled with OpenSSL support, this function -contains an empty definition and will take no action when called. +NOTE: This call currently only functions if `libupsclient` has been compiled +with NSS support. If instead it was compiled with OpenSSL support, this method +contains an empty definition and will take no action when called. Starting with +NUT v2.8.5, you can use linkman:upscli_ssl_caps[3] from consumer code to query +the library about its build circumstances (whether it supports SSL, and which +backend(s)). RETURN VALUE ------------ @@ -41,5 +44,6 @@ RETURN VALUE SEE ALSO -------- -linkman:upscli_init[3], linkman:upscli_connect[3], linkman:upscli_ssl[3], +linkman:upscli_init[3], linkman:upscli_connect[3], +linkman:upscli_ssl[3], linkman:upscli_ssl_caps[3], linkman:upscli_strerror[3], linkman:upscli_upserror[3] diff --git a/docs/man/upscli_init.txt b/docs/man/upscli_init.txt index 37b0e69094..85c012d675 100644 --- a/docs/man/upscli_init.txt +++ b/docs/man/upscli_init.txt @@ -69,6 +69,10 @@ security policy before initialize connections to them. You must call linkman:upscli_cleanup[3] when exiting application. +Starting with NUT v2.8.5, you can use linkman:upscli_ssl_caps[3] from consumer +code to query the library about its build circumstances (whether it supports +SSL, and which backend(s)). + RETURN VALUE ------------ @@ -82,5 +86,6 @@ linkman:upscli_add_host_cert[3], linkman:upscli_cleanup[3], linkman:upscli_connect[3], linkman:upscli_disconnect[3], linkman:upscli_init_default_connect_timeout[3], linkman:upscli_fd[3], linkman:upscli_splitaddr[3], linkman:upscli_splitname[3], -linkman:upscli_ssl[3], linkman:upscli_strerror[3], -linkman:upscli_upserror[3] +linkman:upscli_ssl[3], linkman:upscli_ssl_caps[3], +linkman:upscli_ssl_caps_descr[3], +linkman:upscli_strerror[3], linkman:upscli_upserror[3] diff --git a/docs/man/upscli_report_build_details.txt b/docs/man/upscli_report_build_details.txt new file mode 100644 index 0000000000..d184cafa6b --- /dev/null +++ b/docs/man/upscli_report_build_details.txt @@ -0,0 +1,34 @@ +UPSCLI_REPORT_BUILD_DETAILS(3) +============================== + +NAME +---- + +upscli_report_build_details - Print a debug message to report details of the +current libupsclient build such as its source-code version and SSL capabilities + +SYNOPSIS +-------- + +------ + #include + + void upscli_report_build_details(void); +------ + +DESCRIPTION +----------- + +The *upscli_report_build_details*() function prints a debug log message at +verbosity '1' to summarize details about the currently used build of the +linkman:upsclient[3] library, including its SSL support. + +RETURN VALUE +------------ + +There is no return value. + +SEE ALSO +-------- + +linkman:upscli_ssl_caps[3], linkman:upscli_ssl_caps_descr[3] diff --git a/docs/man/upscli_ssl.txt b/docs/man/upscli_ssl.txt index 6113de4cc4..fac04866c4 100644 --- a/docs/man/upscli_ssl.txt +++ b/docs/man/upscli_ssl.txt @@ -23,6 +23,10 @@ The *upscli_ssl*() function takes the pointer 'ups' to a compiled into the linkman:upsclient[3] library, and if it was successfully enabled for this connection. +Starting with NUT v2.8.5, you can use linkman:upscli_ssl_caps[3] from consumer +code to query the library about its build circumstances (whether it supports +SSL, and which backend(s)). + RETURN VALUE ------------ @@ -32,6 +36,7 @@ not. It returns '-1' in the event of an error. SEE ALSO -------- +linkman:upscli_ssl_caps[3], linkman:upscli_ssl_caps_descr[3], linkman:upscli_fd[3], linkman:upscli_get[3], linkman:upscli_readline[3], linkman:upscli_sendline[3], linkman:upscli_strerror[3], linkman:upscli_upserror[3] diff --git a/docs/man/upscli_ssl_caps.txt b/docs/man/upscli_ssl_caps.txt new file mode 100644 index 0000000000..0970ea8947 --- /dev/null +++ b/docs/man/upscli_ssl_caps.txt @@ -0,0 +1,55 @@ +UPSCLI_SSL_CAPS(3) +================== + +NAME +---- + +upscli_ssl_caps, upscli_ssl_caps_descr - Report SSL capabilities of the +current libupsclient build as a bitmap or string + +SYNOPSIS +-------- + +------ + #include + + /* Header above defines bitmap values: + * UPSCLI_SSL_CAPS_NONE - No ability to use SSL + * UPSCLI_SSL_CAPS_OPENSSL - Can use OpenSSL-specific setup + * UPSCLI_SSL_CAPS_NSS - Can use Mozilla NSS-specific setup + */ + + int upscli_ssl_caps(void); + const char *upscli_ssl_caps_descr(void); +------ + +DESCRIPTION +----------- + +The *upscli_ssl_caps*() function returns an `int` as a bitmap of SSL capability +flags which may be of interest to the consumer of the linkman:upsclient[3] +library (e.g. how to set up cryptographic material storage, and whether that +should have any effect when initializing connections). + +The *upscli_ssl_caps_descr*() function returns a string with such information +primarily intended for humans, e.g. to be used in NUT client program banners. + +If there is no built-in SSL support, alternative methods like SSH tunnels or +the `stunnel` tool can be explored. + +RETURN VALUE +------------ + +The *upscli_ssl_caps*() function effectively returns '0' if SSL support is +missing, '1' if OpenSSL can be used, or '2' if Mozilla NSS can be used, +since currently the backends are exclusive and it matters because they +are differently capable. + +In the future it may return '-1' in the event of an error, or e.g. '3' if +both OpenSSL and Mozilla NSS could be supported simultaneously (current code +structure does not allow for that though). + +SEE ALSO +-------- + +linkman:upscli_ssl[3], linkman:upscli_report_build_details[3] diff --git a/docs/nut.dict b/docs/nut.dict index 6f295f2b46..60d9d669b3 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3687 utf-8 +personal_ws-1.1 en 3689 utf-8 AAC AAS ABI @@ -1907,6 +1907,7 @@ crossbuild crt crw crypto +cryptographic csh cshdelay csi @@ -3280,6 +3281,7 @@ structs sts stst stty +stunnel stylesheet stylesheets su diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index a4b455b64a..82de836382 100644 --- a/drivers/dummy-ups.c +++ b/drivers/dummy-ups.c @@ -2,7 +2,7 @@ Copyright (C) 2005 - 2015 Arnaud Quette - 2014 - 2025 Jim Klimov + 2014 - 2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -48,7 +48,7 @@ #include "dummy-ups.h" #define DRIVER_NAME "Device simulation and repeater driver" -#define DRIVER_VERSION "0.23" +#define DRIVER_VERSION "0.24" /* driver description structure */ upsdrv_info_t upsdrv_info = @@ -415,6 +415,7 @@ static int instcmd(const char *cmdname, const char *extra) void upsdrv_help(void) { + upscli_report_build_details(); } /* optionally tweak prognames[] entries */ diff --git a/server/netssl.c b/server/netssl.c index 5a48201a87..e79f24b41e 100644 --- a/server/netssl.c +++ b/server/netssl.c @@ -3,6 +3,7 @@ Copyright (C) 2002 Russell Kroll 2008 Arjen de Korte + 2020 - 2026 Jim Klimov based on the original implementation: @@ -67,11 +68,43 @@ char *certpasswd = NULL; int disable_weak_ssl = 0; #ifdef WITH_CLIENT_CERTIFICATE_VALIDATION -int certrequest = 0; +int certrequest = 0; #endif /* WITH_CLIENT_CERTIFICATE_VALIDATION */ static int ssl_initialized = 0; +/* Similar to upscli_ssl_caps_descr() for client library, + * but with more bells and whistles */ +const char *net_ssl_caps_descr(void) +{ + static const char *ret = "with" +#ifndef WITH_SSL + "out SSL support"; +#else + " SSL support: " +# ifdef WITH_OPENSSL + "OpenSSL" +# ifdef WITH_NSS + /* Not likely we'd get here, but... */ + " and " +# endif +# endif +# ifdef WITH_NSS + "Mozilla NSS" +# endif +# if !(defined WITH_NSS) && !(defined WITH_OPENSSL) + "oddly undefined" +# endif + "; with" +# ifndef WITH_CLIENT_CERTIFICATE_VALIDATION + "out" +# endif /* WITH_CLIENT_CERTIFICATE_VALIDATION */ + " client certificate validation"; +#endif + + return ret; +} + #ifndef WITH_SSL /* stubs for non-ssl compiles */ diff --git a/server/netssl.h b/server/netssl.h index 1cff564b16..8b27420ed1 100644 --- a/server/netssl.h +++ b/server/netssl.h @@ -33,7 +33,7 @@ extern char *certname; extern char *certpasswd; extern int disable_weak_ssl; #ifdef WITH_CLIENT_CERTIFICATE_VALIDATION -extern int certrequest; +extern int certrequest; #endif /* WITH_CLIENT_CERTIFICATE_VALIDATION */ /* List possible values for certrequested */ @@ -44,6 +44,9 @@ extern int certrequest; /* Required (cnx failed if no certificate or invalid CA chain) */ #define NETSSL_CERTREQ_REQUIRE 2 +/* Similar to upscli_ssl_caps_descr() for client library, + * but with more bells and whistles */ +const char *net_ssl_caps_descr(void); void ssl_init(void); void ssl_finish(nut_ctype_t *client); diff --git a/server/upsd.c b/server/upsd.c index fd82500f75..5fb0004a32 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -2211,6 +2211,7 @@ static void help(const char *arg_progname) printf(" -6 IPv6 only\n"); nut_report_config_flags(); + upsdebugx(1, "NUT data server was built %s", net_ssl_caps_descr()); printf("\n%s", suggest_doc_links(progname, "ups.conf, upsd.conf and upsd.users")); diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index acdaa16c47..98a6518179 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -51,6 +51,8 @@ static int (*nut_upscli_list_start)(UPSCONN_t *ups, size_t numq, static int (*nut_upscli_list_next)(UPSCONN_t *ups, size_t numq, const char **query, size_t *numa, char ***answer); static int (*nut_upscli_disconnect)(UPSCONN_t *ups); +static int (*nut_upscli_ssl_caps)(void); +static void (*nut_upscli_report_build_details)(void); /* This variable collects device(s) from a sequential or parallel scan, * is returned to caller, and cleared to allow subsequent independent scans */ @@ -148,6 +150,24 @@ int nutscan_load_upsclient_library(const char *libname_path) goto err; } + *(void **) (&nut_upscli_ssl_caps) = lt_dlsym(dl_handle, + symbol = "upscli_ssl_caps"); + if ((dl_error = lt_dlerror()) != NULL) { + nut_upscli_ssl_caps = NULL; + upsdebugx(1, "%s: %s() not found, using older libupsclient build?", + __func__, symbol); + } + + *(void **) (&nut_upscli_report_build_details) = lt_dlsym(dl_handle, + symbol = "upscli_report_build_details"); + if ((dl_error = lt_dlerror()) != NULL) { + nut_upscli_report_build_details = NULL; + upsdebugx(1, "%s: %s() not found, using older libupsclient build?", + __func__, symbol); + } else { + (*nut_upscli_report_build_details)(); + } + /* Passed final lt_dlsym() */ symbol = NULL; From ec931bf68251ff7a67f73f6d284ccc899b423554 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Wed, 25 Feb 2026 17:10:23 +0100 Subject: [PATCH 02/44] clients/upsclient.c: also report NUT source version the library was built from [#3328] Signed-off-by: Jim Klimov --- clients/upsclient.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clients/upsclient.c b/clients/upsclient.c index 4e23b9e3a6..19253a61cf 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -96,6 +96,9 @@ # define shutdown_how 2 #endif +#include "nut_version.h" +static const char *UPSCLI_VERSION = NUT_VERSION_MACRO; + static struct { int flags; const char *str; @@ -1927,7 +1930,7 @@ const char *upscli_ssl_caps_descr(void) void upscli_report_build_details(void) { - upsdebugx(1, "Using NUT libupsclient library built %s", upscli_ssl_caps_descr()); + upsdebugx(1, "Using NUT libupsclient library version %s built %s", UPSCLI_VERSION, upscli_ssl_caps_descr()); } int upscli_set_default_connect_timeout(const char *secs) { From 80c97a5346aeb5cc4faf623d5274138ebd09b369 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Wed, 25 Feb 2026 17:25:39 +0100 Subject: [PATCH 03/44] clients/upsclient.c: upscli_sslinit(): condense WITH_SSL/!WITH_SSL into one method body code [#3328] Signed-off-by: Jim Klimov --- clients/upsclient.c | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/clients/upsclient.c b/clients/upsclient.c index 19253a61cf..91e6776e64 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -814,23 +814,28 @@ static ssize_t net_write(UPSCONN_t *ups, const char *buf, size_t buflen, const t # pragma GCC diagnostic pop #endif - -#ifdef WITH_SSL - /* - * 1 : OK + * 1 : OK * -1 : ERROR - * 0 : SSL NOT SUPPORTED + * 0 : SSL NOT SUPPORTED (whether by library or by server) */ static int upscli_sslinit(UPSCONN_t *ups, int verifycert) { -#ifdef WITH_OPENSSL +#ifndef WITH_SSL + NUT_UNUSED_VARIABLE(ups); + NUT_UNUSED_VARIABLE(verifycert); + + return 0; /* not supported */ + +#else /* WITH_SSL */ + +# ifdef WITH_OPENSSL int res; -#elif defined(WITH_NSS) /* WITH_OPENSSL */ +# elif defined(WITH_NSS) /* WITH_OPENSSL */ SECStatus status; PRFileDesc *socket; HOST_CERT_t *cert; -#endif /* WITH_OPENSSL | WITH_NSS */ +# endif /* WITH_OPENSSL | WITH_NSS */ char buf[UPSCLI_NETBUF_LEN]; /* Intend to initialize upscli with no ssl db if not already done. @@ -859,7 +864,7 @@ static int upscli_sslinit(UPSCONN_t *ups, int verifycert) /* upsd is happy, so let's crank up the client */ -#ifdef WITH_OPENSSL +# ifdef WITH_OPENSSL if (!ssl_ctx) { upsdebugx(3, "SSL context is not available"); @@ -901,7 +906,7 @@ static int upscli_sslinit(UPSCONN_t *ups, int verifycert) return 1; -#elif defined(WITH_NSS) /* WITH_OPENSSL */ +# elif defined(WITH_NSS) /* WITH_OPENSSL */ socket = PR_ImportTCPSocket(ups->fd); if (socket == NULL){ @@ -990,20 +995,9 @@ static int upscli_sslinit(UPSCONN_t *ups, int verifycert) return 1; -#endif /* WITH_OPENSSL | WITH_NSS */ -} - -#else /* WITH_SSL */ - -static int upscli_sslinit(UPSCONN_t *ups, int verifycert) -{ - NUT_UNUSED_VARIABLE(ups); - NUT_UNUSED_VARIABLE(verifycert); - - return 0; /* not supported */ -} - +# endif /* WITH_OPENSSL | WITH_NSS */ #endif /* WITH_SSL */ +} int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags, struct timeval * timeout) { From f6f058a4b5a092fe41ea30a63b4edf92ab7e7a4b Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Wed, 25 Feb 2026 20:13:03 +0100 Subject: [PATCH 04/44] clients/upsclient.c, conf/upsmon.conf.sample.in, docs/man/upsmon.conf.txt: debug-log methods called which are no-op in current build and document keywords which would be no-op in some cases [#3328] Signed-off-by: Jim Klimov --- clients/upsclient.c | 32 +++++++++++++++++++++++++------- conf/upsmon.conf.sample.in | 2 ++ docs/man/upsmon.conf.txt | 4 ++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/clients/upsclient.c b/clients/upsclient.c index 91e6776e64..7efbbf5d7a 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -340,20 +340,25 @@ static void HandshakeCallback(PRFileDesc *fd, UPSCONN_t *client_data) int upscli_init(int certverify, const char *certpath, const char *certname, const char *certpasswd) { - const char *quiet_init_ssl; + const char *quiet_init_ssl; #ifdef WITH_OPENSSL - long ret; - int ssl_mode = SSL_VERIFY_NONE; + long ret; + int ssl_mode = SSL_VERIFY_NONE; + /* Now these are technically "used" below for the log messaging. + * Keeping macros here to remind devs that these arguments are + * not really used by library code in this variant of the build. + */ NUT_UNUSED_VARIABLE(certname); NUT_UNUSED_VARIABLE(certpasswd); -#elif defined(WITH_NSS) /* WITH_OPENSSL */ +#elif defined(WITH_NSS) /* WITH_OPENSSL */ SECStatus status; -#else +#else /* neither backend: */ + /* See comment above */ NUT_UNUSED_VARIABLE(certverify); NUT_UNUSED_VARIABLE(certpath); NUT_UNUSED_VARIABLE(certname); NUT_UNUSED_VARIABLE(certpasswd); -#endif /* WITH_OPENSSL | WITH_NSS */ +#endif /* WITH_OPENSSL | WITH_NSS */ if (upscli_initialized == 1) { upslogx(LOG_WARNING, "upscli already initialized"); @@ -381,6 +386,10 @@ int upscli_init(int certverify, const char *certpath, #ifdef WITH_OPENSSL + if (certname || certpasswd) { + upslogx(LOG_ERR, "upscli_init called with 'certname' and/or 'certpasswd' but OpenSSL backend does not use them"); + } + # if OPENSSL_VERSION_NUMBER < 0x10100000L SSL_load_error_strings(); SSL_library_init(); @@ -492,9 +501,12 @@ int upscli_init(int certverify, const char *certpath, verify_certificate = certverify; #else /* Note: historically we do not return with error here, + * and nowadays have the default timeout handling etc., * just fall through to below and treat as initialized. */ - upslogx(LOG_ERR, "upscli_init called but SSL wasn't compiled in"); + if (certverify || certpath || certname || certpasswd) { + upslogx(LOG_ERR, "upscli_init called but SSL wasn't compiled in"); + } #endif /* WITH_OPENSSL | WITH_NSS */ upscli_initialized = 1; @@ -516,6 +528,8 @@ void upscli_add_host_cert(const char* hostname, const char* certname, int certve NUT_UNUSED_VARIABLE(certname); NUT_UNUSED_VARIABLE(certverify); NUT_UNUSED_VARIABLE(forcessl); + + upsdebugx(1, "%s: no-op when libupsclient was not built WITH_NSS", __func__); #endif /* WITH_NSS */ } @@ -533,6 +547,8 @@ static HOST_CERT_t* upscli_find_host_cert(const char* hostname) } #else NUT_UNUSED_VARIABLE(hostname); + + upsdebugx(4, "%s: no-op when libupsclient was not built WITH_NSS", __func__); #endif /* WITH_NSS */ return NULL; } @@ -825,6 +841,8 @@ static int upscli_sslinit(UPSCONN_t *ups, int verifycert) NUT_UNUSED_VARIABLE(ups); NUT_UNUSED_VARIABLE(verifycert); + upsdebugx(1, "%s: no-op when libupsclient was not built WITH_SSL", __func__); + return 0; /* not supported */ #else /* WITH_SSL */ diff --git a/conf/upsmon.conf.sample.in b/conf/upsmon.conf.sample.in index 1a0be42969..0353fa8a72 100644 --- a/conf/upsmon.conf.sample.in +++ b/conf/upsmon.conf.sample.in @@ -608,6 +608,7 @@ ALARMCRITICAL 1 # When compiled with SSL support with NSS, you can specify the certificate # name to retrieve from database to authenticate itself and the password # required to access certificate related private key. +# Currently no-op when built with OpenSSL. # # CERTIDENT "my nut monitor" "MyPasSw0rD" # @@ -623,6 +624,7 @@ ALARMCRITICAL 1 # Each entry maps server name with the expected certificate name and flags # indicating if the server certificate is verified and if the connection # must be secure. +# Currently no-op when built with OpenSSL. # # CERTHOST localhost "My nut server" 1 1 # diff --git a/docs/man/upsmon.conf.txt b/docs/man/upsmon.conf.txt index 0772be515e..aeba57bb8b 100644 --- a/docs/man/upsmon.conf.txt +++ b/docs/man/upsmon.conf.txt @@ -600,6 +600,8 @@ When compiled with SSL support with NSS, you can specify the certificate name to retrieve from database to authenticate itself and the password required to access certificate related private key. + +Currently no-op when built with OpenSSL. ++ NOTE: Be sure to enclose "certificate name" in double-quotes if you are using a value with spaces in it. @@ -612,6 +614,8 @@ Each entry maps server name with the expected certificate name and flags indicating if the server certificate is verified and if the connection must be secure. + +Currently no-op when built with OpenSSL. ++ NOTE: Be sure to enclose "certificate name" in double-quotes if you are using a value with spaces in it. From 0b9b712ba15013bbd6c9a3989c220d4e19db19aa Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 10:59:20 +0100 Subject: [PATCH 05/44] drivers/main.h: upsdrv_callbacks: we now have in fact 16 not 9 pointers passed [#2800] Signed-off-by: Jim Klimov --- drivers/main.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/main.h b/drivers/main.h index e7e2cb00d4..b4caa1f374 100644 --- a/drivers/main.h +++ b/drivers/main.h @@ -275,7 +275,7 @@ void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_s memset((cbptr), 0, sizeof(upsdrv_callback_t)); \ (cbptr)->struct_version = 1; \ (cbptr)->ptr_size = sizeof(void*); \ - (cbptr)->ptr_count = 9; \ + (cbptr)->ptr_count = 16; \ (cbptr)->sentinel = NULL; \ for (cbptr_counter = 0; cbptr_counter < UPSDRV_CALLBACK_PADDING; cbptr_counter++) \ (cbptr)->padding[cbptr_counter] = NULL; \ @@ -288,7 +288,7 @@ void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_s if ((cbsz) != sizeof(upsdrv_callback_t)) fatalx(EXIT_FAILURE, "Could not register callbacks for shared driver code: unexpected structure size"); \ upsdebugx(5, "validate_upsdrv_callbacks: ver=%" PRIu64 " ptr_count=%" PRIu64, (cbptr)->struct_version, (cbptr)->ptr_count); \ if ((cbptr)->struct_version != 1 \ - || (cbptr)->ptr_count != 9 \ + || (cbptr)->ptr_count != 16 \ ) fatalx(EXIT_FAILURE, "Could not register callbacks for shared driver code: unexpected structure contents"); \ upsdebugx(5, "validate_upsdrv_callbacks: ptr_size: passed=%" PRIu64 " expected=%" PRIuSIZE, (cbptr)->ptr_size, sizeof(void*)); \ if ((cbptr)->ptr_size != sizeof(void*)) \ From 7e33263aa3c37064259ad20606ab2d034d6e0bc2 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 11:00:11 +0100 Subject: [PATCH 06/44] drivers/main.h: upsdrv_callbacks: move magic to start of struct [#2800] Signed-off-by: Jim Klimov --- drivers/main.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/main.h b/drivers/main.h index b4caa1f374..4ae46cb89d 100644 --- a/drivers/main.h +++ b/drivers/main.h @@ -218,11 +218,12 @@ typedef struct upsdrv_callback_s { * generations of NUT drivers try to link with the * main() logic shipped as a shared library aimed * at its particular release */ + char struct_magic[16]; /* should be UPSDRV_CALLBACK_MAGIC */ + uint64_t struct_version; /* how to interpret what we see */ uint64_t ptr_size; /* be sure about bitness */ uint64_t ptr_count; /* amount of non-null pointers passed here */ - char struct_magic[16]; - /* char NUT_VERSION[32]; / * Just have it built into each binary */ + /* char NUT_VERSION[32]; / * TO THINK: Just have it built into each binary */ /* Do not change the order of these entries, * only add at the end of list (if needed). @@ -273,19 +274,21 @@ void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_s if ((cbptr) == NULL) fatalx(EXIT_FAILURE, "Could not init callbacks for shared driver code: null structure pointer"); \ if ((cbsz) != sizeof(upsdrv_callback_t)) fatalx(EXIT_FAILURE, "Could not init callbacks for shared driver code: unexpected structure size"); \ memset((cbptr), 0, sizeof(upsdrv_callback_t)); \ + snprintf((cbptr)->struct_magic, sizeof((cbptr)->struct_magic), "%s", UPSDRV_CALLBACK_MAGIC); \ (cbptr)->struct_version = 1; \ (cbptr)->ptr_size = sizeof(void*); \ (cbptr)->ptr_count = 16; \ (cbptr)->sentinel = NULL; \ for (cbptr_counter = 0; cbptr_counter < UPSDRV_CALLBACK_PADDING; cbptr_counter++) \ (cbptr)->padding[cbptr_counter] = NULL; \ - snprintf((cbptr)->struct_magic, sizeof((cbptr)->struct_magic), "%s", UPSDRV_CALLBACK_MAGIC); \ } while (0) #define validate_upsdrv_callbacks(cbptr, cbsz, isnew) do { \ upsdebugx(5, "validate_upsdrv_callbacks: cbsz=%" PRIuMAX, (uintmax_t)cbsz); \ if ((cbptr) == NULL) fatalx(EXIT_FAILURE, "Could not register callbacks for shared driver code: null structure pointer"); \ if ((cbsz) != sizeof(upsdrv_callback_t)) fatalx(EXIT_FAILURE, "Could not register callbacks for shared driver code: unexpected structure size"); \ + if (strcmp((cbptr)->struct_magic, UPSDRV_CALLBACK_MAGIC)) \ + fatalx(EXIT_FAILURE, "Could not register callbacks for shared driver code: wrong magic"); \ upsdebugx(5, "validate_upsdrv_callbacks: ver=%" PRIu64 " ptr_count=%" PRIu64, (cbptr)->struct_version, (cbptr)->ptr_count); \ if ((cbptr)->struct_version != 1 \ || (cbptr)->ptr_count != 16 \ @@ -293,8 +296,6 @@ void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_s upsdebugx(5, "validate_upsdrv_callbacks: ptr_size: passed=%" PRIu64 " expected=%" PRIuSIZE, (cbptr)->ptr_size, sizeof(void*)); \ if ((cbptr)->ptr_size != sizeof(void*)) \ fatalx(EXIT_FAILURE, "Could not register callbacks for shared driver code: wrong structure bitness"); \ - if (strcmp((cbptr)->struct_magic, UPSDRV_CALLBACK_MAGIC)) \ - fatalx(EXIT_FAILURE, "Could not register callbacks for shared driver code: wrong magic"); \ upsdebugx(5, "validate_upsdrv_callbacks: NULL-check: sentinel: %s", (cbptr)->sentinel == NULL ? "Y" : "N"); \ if ((cbptr)->sentinel != NULL) \ fatalx(EXIT_FAILURE, "Could not register callbacks for shared driver code: wrong sentinels"); \ From d543ed993df37a9dbcac13af3675a55a9a274b8d Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 11:00:38 +0100 Subject: [PATCH 07/44] drivers/main.h: upsdrv_callbacks: document the macros [#2800] Signed-off-by: Jim Klimov --- drivers/main.h | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/drivers/main.h b/drivers/main.h index 4ae46cb89d..e9ced7078e 100644 --- a/drivers/main.h +++ b/drivers/main.h @@ -259,15 +259,25 @@ typedef struct upsdrv_callback_s { } upsdrv_callback_t; void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_struct_sz); -/* simple call to register implementations named as dictated - * by this header, which (being a macro) can be called easily - * from both static and shared builds; keep in mind that builds - * using these macros for binaries that try to fit together may - * be years apart eventually. Note that consumers may have to - * use HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ADDRESS and/or - * some of HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE* - * pragmas around these macros, for builds to succeed under - * stricter compiler settings (see examples in existing code). +/* Suite of simple calls to register driver callback method implementations + * named as dictated by this header, which (being macros) can be called easily + * from both static and shared builds (with libnutprivate-X_Y_Z-drivers-common). + * Keep in mind that practical builds using these macros for binaries that try + * to fit together may be years apart eventually. + * + * Note that consumers may have to use HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ADDRESS + * and/or some of HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE* pragmas + * around these macros, for builds to succeed under stricter compiler settings + * (see examples in existing code). + */ + +/* Initialize an upsdrv_callback_t* cbptr memory block sized cbsz (should + * be sizeof(upsdrv_callback_t)) with basic metadata (version, ARCH info). + * Claims to serve the 9 non-NULL method pointers currently defined by the + * NUT driver coding standard, and some more from common_nut-version.c, + * named in the current structure version definition above (which are in fact + * NULL just after this call -- so we must use validate_upsdrv_callbacks() + * before actually referencing or calling that stuff). */ #define init_upsdrv_callbacks(cbptr, cbsz) do { \ size_t cbptr_counter = 0; \ @@ -283,6 +293,8 @@ void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_s (cbptr)->padding[cbptr_counter] = NULL; \ } while (0) +/* Make sure metadata seems valid and method pointers are not NULL (if !isnew), + * or crash with reasonable diagnostics */ #define validate_upsdrv_callbacks(cbptr, cbsz, isnew) do { \ upsdebugx(5, "validate_upsdrv_callbacks: cbsz=%" PRIuMAX, (uintmax_t)cbsz); \ if ((cbptr) == NULL) fatalx(EXIT_FAILURE, "Could not register callbacks for shared driver code: null structure pointer"); \ @@ -335,6 +347,7 @@ void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_s if (isnew) upsdebugx(5, "validate_upsdrv_callbacks: this is a newly created structure, so some/all NULL references are okay"); \ } while (0) +/* Populate library's copy of callbacks and other info with what the driver tells us */ #define safe_copy_upsdrv_callbacks(cbptrDrv, cbptrLib, cbszDrv) do { \ validate_upsdrv_callbacks(cbptrDrv, cbszDrv, 0); \ init_upsdrv_callbacks(cbptrLib, sizeof(upsdrv_callback_t)); \ @@ -357,6 +370,10 @@ void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_s (cbptrLib)->suggest_NDE_conflict = (cbptrDrv)->suggest_NDE_conflict; \ } while (0) +/* Simplify life for driver implementations which have the legacy-named methods + * required by NUT coding standards (including test code that partially poses + * as a driver). + */ #define default_register_upsdrv_callbacks() do { \ upsdrv_callback_t callbacksTmp; \ init_upsdrv_callbacks(&callbacksTmp, sizeof(callbacksTmp)); \ From fe32e7820c8e0864f9c1f40c15d6e1ffa10a4158 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 1 Mar 2026 12:01:03 +0100 Subject: [PATCH 08/44] m4/ax_c_pragmas.m4, drivers/main.c: handle "-Wmissing-braces" for upsdrv_callback_t init [#2800] Signed-off-by: Jim Klimov --- drivers/main.c | 34 +++++++++++++++++++++++++--------- m4/ax_c_pragmas.m4 | 30 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/drivers/main.c b/drivers/main.c index 9f42910978..847afc435e 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -164,13 +164,29 @@ static int handle_reload_flag(void); /* Set in do_ups_confargs() for consumers like handle_reload_flag() */ static int reload_requires_restart = -1; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_FIELD_INITIALIZERS_BESIDEFUNC) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_FIELD_INITIALIZERS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_BRACES_BESIDEFUNC) ) +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_FIELD_INITIALIZERS_BESIDEFUNC +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_BRACES_BESIDEFUNC +#pragma GCC diagnostic ignored "-Wmissing-braces" #endif +/* Note about the pragmas: for pedantic warnings, the sub-arrays in our struct + * (struct_magic and padding) are "sub-objects" that should all have their own + * initializers, e.g. { {0}, 0, ..., 0, {0} } which would be insane to maintain. + * Newer C standards require that a statically initialized object is zeroed by + * the compiler (so `= {0}` part is not needed at all), but I guess NUT with + * decades of backwards compatibility should not rely on that being the case + * with each and every one of 1990's compilers. Likewise, this initializer + * should ensure that all memory in the struct is zeroed out, and so does the + * init_upsdrv_callbacks() macro. So belts and suspenders, better safe than + * sorry, and all that. + */ static upsdrv_callback_t upsdrv_callbacks = {0}; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_FIELD_INITIALIZERS_BESIDEFUNC) -# pragma GCC diagnostic pop +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_FIELD_INITIALIZERS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_BRACES_BESIDEFUNC) ) +#pragma GCC diagnostic pop #endif void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_struct_sz) { /* Plain memcpy of arbitrarily ordered list of function pointers @@ -179,17 +195,17 @@ void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_s * shared library, years apart from a driver that tries to use it) * was built against: */ #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ADDRESS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE)) -# pragma GCC diagnostic push +#pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ADDRESS -# pragma GCC diagnostic ignored "-Waddress" +#pragma GCC diagnostic ignored "-Waddress" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE -# pragma GCC diagnostic ignored "-Wunreachable-code" +#pragma GCC diagnostic ignored "-Wunreachable-code" #endif safe_copy_upsdrv_callbacks(runtime_callbacks, &upsdrv_callbacks, cb_struct_sz); #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ADDRESS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE)) -# pragma GCC diagnostic pop +#pragma GCC diagnostic pop #endif } diff --git a/m4/ax_c_pragmas.m4 b/m4/ax_c_pragmas.m4 index 9800f03fcc..b93b63dede 100644 --- a/m4/ax_c_pragmas.m4 +++ b/m4/ax_c_pragmas.m4 @@ -923,6 +923,36 @@ dnl ### [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning" AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_FIELD_INITIALIZERS_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wmissing-field-initializers" (outside functions)]) ]) + dnl Older GCC and CLANG complain about later-standardized `something={0}` + dnl syntax of initialization which zeroes out (struct) bytes even with a + dnl non-nested struct like upsdrv_callback_t: + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wmissing-braces"], + [ax_cv__pragma__gcc__diags_ignored_missing_braces], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[void func(void) { +#pragma GCC diagnostic ignored "-Wmissing-braces" +} +]], [])], + [ax_cv__pragma__gcc__diags_ignored_missing_braces=yes], + [ax_cv__pragma__gcc__diags_ignored_missing_braces=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_missing_braces" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_BRACES], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wmissing-braces"]) + ]) + + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wmissing-braces" (outside functions)], + [ax_cv__pragma__gcc__diags_ignored_missing_braces_besidefunc], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wmissing-braces"]], [])], + [ax_cv__pragma__gcc__diags_ignored_missing_braces_besidefunc=yes], + [ax_cv__pragma__gcc__diags_ignored_missing_braces_besidefunc=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_missing_braces_besidefunc" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_MISSING_BRACES_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wmissing-braces" (outside functions)]) + ]) + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wassign-enum"], [ax_cv__pragma__gcc__diags_ignored_assign_enum], [AC_COMPILE_IFELSE( From a033d5903d3aace5a252a6794e23858dec122b7a Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Wed, 25 Feb 2026 17:52:55 +0100 Subject: [PATCH 09/44] tests/NIT/nit.sh: detect run-time behavior of WITH_SSL* in built clients and server programs [#3328, #1711] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 43df6e47fc..40c4c4c17e 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -438,6 +438,33 @@ PID_DUMMYUPS="" PID_DUMMYUPS1="" PID_DUMMYUPS2="" +WITH_SSL_CLIENT="`upsmon -Dh 2>&1 | grep 'Using NUT libupsclient library'`" || WITH_SSL_CLIENT="none" +# NOTE: Currently OpenSSL/NSS builds and codepaths are exclusive of each other! +# Interesting idea: build and test server with one and clients with the other... +# SIDE NOTE: As of NUT v2.8.5, it seems that only upsmon client cares about SSL! +case "${WITH_SSL_CLIENT}" in + *"without SSL"*|none|"") WITH_SSL_CLIENT="none" ;; + *OpenSSL*) WITH_SSL_CLIENT="OpenSSL" ;; + *NSS*) WITH_SSL_CLIENT="NSS" ;; + *) log_warn "Unexpected client SSL support reported, ignoring: ${WITH_SSL_CLIENT}" ; WITH_SSL_CLIENT="none" ;; +esac +log_info "Tested client binaries SSL support: ${WITH_SSL_CLIENT}" + +WITH_SSL_SERVER="`upsd -Dh 2>&1 | grep 'NUT data server was built with'`" || WITH_SSL_SERVER="none" +WITH_SSL_SERVER_CLIVAL="none" +case "${WITH_SSL_SERVER}" in + *"without client certificate validation"*) WITH_SSL_SERVER_CLIVAL="false" ;; + *"with client certificate validation"*) WITH_SSL_SERVER_CLIVAL="true" ;; +esac +case "${WITH_SSL_SERVER}" in + *"without SSL"*|none|"") WITH_SSL_SERVER="none" ;; + *OpenSSL*) WITH_SSL_SERVER="OpenSSL" ;; + *NSS*) WITH_SSL_SERVER="NSS" ;; + *) log_warn "Unexpected server SSL support reported, ignoring: ${WITH_SSL_SERVER}" ; WITH_SSL_SERVER="none" ;; +esac +log_info "Tested server binaries SSL support: ${WITH_SSL_SERVER}" +log_info "Tested server binaries client certificate validation: ${WITH_SSL_SERVER_CLIVAL}" + # Platforms vary in abilities to report this... I_AM_NAME="" get_my_user_name() { From 5fc39e4cf0e79953843a4ab778e34dddcc92112f Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Wed, 25 Feb 2026 20:21:19 +0100 Subject: [PATCH 10/44] tests/NIT/nit.sh, NEWS.adoc: introduce generation of upsmon/upsd configs for SSL test cases [#3328, #1711] Signed-off-by: Jim Klimov --- NEWS.adoc | 4 +- tests/NIT/nit.sh | 104 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/NEWS.adoc b/NEWS.adoc index 9334454deb..de3c1e3e25 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -108,7 +108,9 @@ https://github.com/networkupstools/nut/milestone/12 C binding to query and report SSL capabilities of the current library build (none, OpenSSL, Mozilla NSS): `upscli_ssl_caps_descr()` and `upscli_ssl_caps()`. Updated common NUT clients to report this info - in their detailed help banners. Done similarly for `upsd`. [issue #3328] + in their detailed help banners. Done similarly for `upsd`. The NIT + (NUT Integration Test) suite piggy-backs on this to add run-time + dependent tests of SSL capability. [issues #3328, #1771] - NUT for Windows specific updates: * Revised detection of (relative) paths to program and configuration files diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 40c4c4c17e..ed253344f7 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -465,6 +465,11 @@ esac log_info "Tested server binaries SSL support: ${WITH_SSL_SERVER}" log_info "Tested server binaries client certificate validation: ${WITH_SSL_SERVER_CLIVAL}" +TESTCERT_CLIENT_NAME="NIT upsmon" +TESTCERT_CLIENT_PASS="MyPasSw0rD" +TESTCERT_SERVER_NAME="NIT data server" +TESTCERT_SERVER_PASS="TestS@rv!" + # Platforms vary in abilities to report this... I_AM_NAME="" get_my_user_name() { @@ -863,6 +868,56 @@ generatecfg_upsd_nodev() { || die "Failed to populate temporary FS structure for the NIT: upsd.conf" } +generatecfg_upsd_add_SSL() { + # May first call one of the above consumers of generatecfg_upsd_trivial() + if [ ! -s "$NUT_CONFPATH/upsd.conf" ] ; then + generatecfg_upsd_trivial + fi + + if grep CERT "$NUT_CONFPATH/upsd.conf" >/dev/null ; then + # Already configured for SSL + return 0 + fi + + case "${WITH_SSL_SERVER}" in + none) return 0;; + OpenSSL) + { cat << EOF +# OpenSSL CERTFILE: PEM file with data server cert, possibly the +# intermediate and root CA's, and finally corresponding private key +CERTFILE "${NUT_CONFPATH}/cert/upsd/upsd.pem" +EOF + } >> "$NUT_CONFPATH/upsd.conf" \ + && mkdir -p "${NUT_CONFPATH}/cert/upsd" \ + || die "Failed to populate temporary FS structure for the NIT: upsd.conf" + ;; + NSS) + { cat << EOF +# NSS CERTPATH: Directory with 3-file database of cert/key store +CERTPATH "${NUT_CONFPATH}/cert/upsd" +CERTIDENT "${TESTCERT_SERVER_NAME}" "${TESTCERT_SERVER_PASS}" +EOF + + if [ x"${WITH_SSL_SERVER_CLIVAL}" = xtrue ]; then + cat << EOF +# - 0 to not request to clients to provide any certificate +# - 1 to require to all clients a certificate +# - 2 to require to all clients a valid certificate +CERTREQUEST 2 +EOF + fi + } >> "$NUT_CONFPATH/upsd.conf" \ + && mkdir -p "${NUT_CONFPATH}/cert/upsd" \ + || die "Failed to populate temporary FS structure for the NIT: upsd.conf" + ;; + esac + + # FIXME: Check for old/new OS and libs to toggle this? + # echo "DISABLE_WEAK_SSL true" >> "$NUT_CONFPATH/upsd.conf" \ + # || die "Failed to populate temporary FS structure for the NIT: upsd.conf" + +} + ### upsd.users: ################################################## TESTPASS_ADMIN='mypass' @@ -1022,6 +1077,55 @@ generatecfg_upsmon_secondary() { fi } +generatecfg_upsmon_add_SSL() { + # May first call one of the above consumers of generatecfg_upsmon_trivial() + if [ ! -s "$NUT_CONFPATH/upsmon.conf" ] ; then + generatecfg_upsmon_trivial + fi + + if grep CERTPATH "$NUT_CONFPATH/upsmon.conf" >/dev/null ; then + # Already configured for SSL + return 0 + fi + + case "${WITH_SSL_CLIENT}" in + none) return 0;; + OpenSSL) + { cat << EOF +# OpenSSL CERTPATH: Directory with PEM file(s), looked up by the +# CA subject name hash value (which must include our NUT server). +# Here we just use the path for PEM file that should be populated +# by the generatecfg_upsd_add_SSL() method. +# We only support CERTPATH (to recognize servers), FORCESSL and +# CERTVERIFY in OpenSSL builds. +CERTPATH "${NUT_CONFPATH}/cert/upsd" +# With OpenSSL this is the only way to configure these behaviors, +# no CERTHOST setting here so far: +FORCESSL 1 +CERTVERIFY 1 +EOF + } >> "$NUT_CONFPATH/upsmon.conf" \ + && mkdir -p "${NUT_CONFPATH}/cert/upsd" \ + || die "Failed to populate temporary FS structure for the NIT: upsmon.conf" + ;; + NSS) + { cat << EOF +# NSS CERTPATH: Directory with 3-file database of cert/key store +CERTPATH "${NUT_CONFPATH}/cert/upsmon" +CERTIDENT "${TESTCERT_CLIENT_NAME}" "${TESTCERT_CLIENT_PASS}" +CERTHOST localhost "${TESTCERT_SERVER_NAME}" 1 1 +# Defaults that NSS CERTHOST may override per-server, but +# note that this impacts also the general upsmon behavior: +FORCESSL 1 +CERTVERIFY 1 +EOF + } >> "$NUT_CONFPATH/upsmon.conf" \ + && mkdir -p "${NUT_CONFPATH}/cert/upsmon" \ + || die "Failed to populate temporary FS structure for the NIT: upsmon.conf" + ;; + esac +} + ### ups.conf: ################################################## generatecfg_ups_trivial() { From 319ad0711eaf0a923607b28f5f58b0edfbc79249 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 12:04:29 +0100 Subject: [PATCH 11/44] tests/NIT/nit.sh: SSL tests: check for presence of third-party tooling to manipulate the crypto credential stores [#3328, #1711] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index ed253344f7..add56ba674 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -465,6 +465,26 @@ esac log_info "Tested server binaries SSL support: ${WITH_SSL_SERVER}" log_info "Tested server binaries client certificate validation: ${WITH_SSL_SERVER_CLIVAL}" +case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in + *NSS*) + (command -v certutil) || { + log_warn "NUT can use NSS, but needed third-party tooling was not found to produce the crypto credential stores" + if [ x"${WITH_SSL_CLIENT}" = xNSS ] ; then WITH_SSL_CLIENT="none" ; fi + if [ x"${WITH_SSL_SERVER}" = xNSS ] ; then WITH_SSL_SERVER="none" ; fi + } + ;; +esac + +case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in + *OpenSSL*) + (command -v openssl) || { + log_warn "NUT can use OpenSSL, but needed third-party tooling was not found to produce the crypto credential stores" + if [ x"${WITH_SSL_CLIENT}" = xOpenSSL ] ; then WITH_SSL_CLIENT="none" ; fi + if [ x"${WITH_SSL_SERVER}" = xOpenSSL ] ; then WITH_SSL_SERVER="none" ; fi + } + ;; +esac + TESTCERT_CLIENT_NAME="NIT upsmon" TESTCERT_CLIENT_PASS="MyPasSw0rD" TESTCERT_SERVER_NAME="NIT data server" From c550fbfa952a9756e76358ef370453cb5cb43537 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 12:18:44 +0100 Subject: [PATCH 12/44] tests/NIT/nit.sh: generatecfg_upsd_add_SSL(), generatecfg_upsmon_add_SSL(): cross-link server/client capabilities to set expectations in configs [#3328, #1711] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index add56ba674..381a4527d3 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -918,7 +918,7 @@ CERTPATH "${NUT_CONFPATH}/cert/upsd" CERTIDENT "${TESTCERT_SERVER_NAME}" "${TESTCERT_SERVER_PASS}" EOF - if [ x"${WITH_SSL_SERVER_CLIVAL}" = xtrue ]; then + if [ x"${WITH_SSL_SERVER_CLIVAL}" = xtrue -a x"${WITH_SSL_CLIENT}" = xNSS ]; then cat << EOF # - 0 to not request to clients to provide any certificate # - 1 to require to all clients a certificate @@ -1119,11 +1119,16 @@ generatecfg_upsmon_add_SSL() { # We only support CERTPATH (to recognize servers), FORCESSL and # CERTVERIFY in OpenSSL builds. CERTPATH "${NUT_CONFPATH}/cert/upsd" +EOF + + if [ x"${WITH_SSL_SERVER}" != xnone ] ; then + cat << EOF # With OpenSSL this is the only way to configure these behaviors, # no CERTHOST setting here so far: FORCESSL 1 CERTVERIFY 1 EOF + fi } >> "$NUT_CONFPATH/upsmon.conf" \ && mkdir -p "${NUT_CONFPATH}/cert/upsd" \ || die "Failed to populate temporary FS structure for the NIT: upsmon.conf" @@ -1133,12 +1138,30 @@ EOF # NSS CERTPATH: Directory with 3-file database of cert/key store CERTPATH "${NUT_CONFPATH}/cert/upsmon" CERTIDENT "${TESTCERT_CLIENT_NAME}" "${TESTCERT_CLIENT_PASS}" +EOF + + case "${WITH_SSL_SERVER}" in + none) ;; + NSS) + cat << EOF CERTHOST localhost "${TESTCERT_SERVER_NAME}" 1 1 +EOF + ;; + *) # OpenSSL + cat << EOF +CERTHOST localhost "${TESTCERT_SERVER_NAME}" 0 0 +EOF + ;; + esac + + if [ x"${WITH_SSL_SERVER}" != xnone ] ; then + cat << EOF # Defaults that NSS CERTHOST may override per-server, but # note that this impacts also the general upsmon behavior: FORCESSL 1 CERTVERIFY 1 EOF + fi } >> "$NUT_CONFPATH/upsmon.conf" \ && mkdir -p "${NUT_CONFPATH}/cert/upsmon" \ || die "Failed to populate temporary FS structure for the NIT: upsmon.conf" From 504b6c3da91b09b1e270c9258ec9e454f63fda1c Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 17:31:36 +0100 Subject: [PATCH 13/44] tests/NIT/nit.sh: populate OpenSSL and/or Mozilla NSS keystores if we can test those [#3328, #1711] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 180 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 171 insertions(+), 9 deletions(-) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 381a4527d3..de439fc242 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -485,10 +485,14 @@ case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in ;; esac +TESTCERT_ROOTCA_NAME="NUT Mock Root CA" +TESTCERT_ROOTCA_PASS="VeryS@cur@1337" TESTCERT_CLIENT_NAME="NIT upsmon" TESTCERT_CLIENT_PASS="MyPasSw0rD" TESTCERT_SERVER_NAME="NIT data server" TESTCERT_SERVER_PASS="TestS@rv!" +# Continued below regarding setup of crypto material data files +# (and possibly skipping SSL tests if we fail this setup). # Platforms vary in abilities to report this... I_AM_NAME="" @@ -813,6 +817,168 @@ export NUT_PORT # Help track collisions in log, if someone else starts a test in same directory log_info "Using NUT_PORT=${NUT_PORT} for this test run" +# SSL preparations: Can only do this after we learn NUT_CONFPATH +TESTCERT_PATH_ROOTCA="${NUT_CONFPATH}/cert/rootca" +TESTCERT_PATH_SERVER="${NUT_CONFPATH}/cert/upsd" +# TOTHINK: If other NUT clients get SSL support tested, +# should they use same or different cryptostore?.. +TESTCERT_PATH_CLIENT="${NUT_CONFPATH}/cert/upsmon" + +# Follow docs/security.txt points about setting up the crypto material +# stores and their contents (mock a self-signed CA here where appropriate) +case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in + *OpenSSL*|*NSS*) + ( # Sub-shelling here to keep soft failure cases handled once, + # and changes of directory constrained without pushd/popd + # (not in all shells) or remembering of `pwd` (clumsy-ish) + log_info "Setting up crypto material storage for SSL capability tests..." + + if shouldDebug ; then + set -x + fi + set -e + + mkdir -p "${TESTCERT_PATH_ROOTCA}" + ( cd "${TESTCERT_PATH_ROOTCA}" + log_info "SSL: Preparing test Root CA..." + echo "${TESTCERT_ROOTCA_PASS}" > ".pwfile" + case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in + *NSS*) + { [ -e /dev/urandom ] && \ + dd if=/dev/urandom of=.random bs=16 count=1 + } || { + [ -e /dev/random ] && \ + dd if=/dev/random of=.random bs=16 count=1 + } || date > .random + # Create the certificate database: + certutil -N -d . -f .pwfile + # Generate a certificate for CA: + # HACK NOTE: The first "yes" is for "Is this a CA certificate [y/N]?" question, + # others default (empty) for possible other questions, e.g. + # Enter the path length constraint, enter to skip [<0 for unlimited path]: > + # Is this a critical extension [y/N]? : + (echo y; yes "") | certutil -S -d . -f .pwfile -n "${TESTCERT_ROOTCA_NAME}" -s "CN=${TESTCERT_ROOTCA_NAME},OU=Test,O=NIT,ST=StateOfChaos,C=US" -t "CT,," -x -2 -z .random + # Extract the CA certificate to be able to use or import it later: + certutil -L -d . -f .pwfile -n "${TESTCERT_ROOTCA_NAME}" -a -o rootca.pem + # Use this later for signing, move on to server/client requests... + + ls -l "${TESTCERT_PATH_ROOTCA}"/*.db "${TESTCERT_PATH_ROOTCA}"/*.txt + ;; + esac + + [ -s rootca.pem ] || \ + case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in + *OpenSSL*) + # Generate an AES encrypted private key: + openssl genrsa -aes256 -out rootca.key -passout file:.pwfile 4096 + # Generate a certificate for CA using that key: + openssl req -x509 -new -nodes -key rootca.key -passin file:.pwfile -sha256 -days 1826 -out rootca.pem -subj "/CN=${TESTCERT_ROOTCA_NAME}/OU=Test/O=NIT/ST=StateOfChaos/C=US" + ;; + esac + + ls -l "${TESTCERT_PATH_ROOTCA}"/rootca.pem + ) + + mkdir -p "${TESTCERT_PATH_SERVER}" + ( cd "${TESTCERT_PATH_SERVER}" + log_info "SSL: Preparing test server certificate..." + echo "${TESTCERT_SERVER_PASS}" > ".pwfile" + case "${WITH_SSL_SERVER}" in + NSS) + # Create the certificate database: + certutil -N -d . -f .pwfile + # Import the CA certificate, so users of this DB trust it: + certutil -A -d . -f .pwfile -n "${TESTCERT_ROOTCA_NAME}" -t "TC,," -a -i "${TESTCERT_PATH_ROOTCA}"/rootca.pem + # Create a server certificate request: + # NOTE: IRL Each run should have a separate random seed; for tests we cut a few corners! + certutil -R -d . -f .pwfile -s "CN=${TESTCERT_SERVER_NAME},OU=Test,O=NIT,ST=StateOfChaos,C=US" -a -o server.req -z "${TESTCERT_PATH_ROOTCA}"/.random + + # Sign a certificate request with the CA certificate: + # HACK NOTE: "No" for "Is this a CA certificate" question, defaults for others + (echo n; yes "") | certutil -C -d "${TESTCERT_PATH_ROOTCA}" -f "${TESTCERT_PATH_ROOTCA}"/.pwfile -c "${TESTCERT_ROOTCA_NAME}" -a -i server.req -o server.crt -2 --extKeyUsage "serverAuth" --nsCertType sslServer + + # Import the signed certificate into server database: + certutil -A -d . -f .pwfile -n "${TESTCERT_SERVER_NAME}" -a -i server.crt -t ",," + + ls -l "${TESTCERT_PATH_SERVER}"/*.db "${TESTCERT_PATH_SERVER}"/*.txt + ;; + OpenSSL) + # Create a server certificate request: + openssl req -new -nodes -out server.req -newkey rsa:4096 -passout file:.pwfile -keyout server.key -subj "/CN=${TESTCERT_SERVER_NAME}/OU=Test/O=NIT/ST=StateOfChaos/C=US" + cat > server.v3.ext << EOF +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +subjectAltName = @alt_names +[alt_names] +DNS.1 = localhost +DNS.2 = localhost6 +IP.1 = 127.0.0.1 +IP.2 = ::1 +EOF + # Sign a certificate request with the CA certificate: + ( cd "${TESTCERT_PATH_ROOTCA}" + openssl x509 -req -in "${TESTCERT_PATH_SERVER}/server.req" -passin file:.pwfile -CA rootca.pem -CAkey rootca.key -CAcreateserial -out "${TESTCERT_PATH_SERVER}/server.crt" -days 730 -sha256 -extfile "${TESTCERT_PATH_SERVER}/server.v3.ext" + ) + cat server.crt "${TESTCERT_PATH_ROOTCA}"/rootca.pem server.key > upsd.pem + + ls -l "${TESTCERT_PATH_SERVER}"/upsd.pem + ;; + esac + ) + + mkdir -p "${TESTCERT_PATH_CLIENT}" + ( cd "${TESTCERT_PATH_CLIENT}" + case "${WITH_SSL_CLIENT}" in + NSS) + log_info "SSL: Preparing test client certificate..." + # Also create 3-file database of client key+cert store + echo "${TESTCERT_CLIENT_PASS}" > ".pwfile" + # Create the certificate database: + certutil -N -d . -f .pwfile + # Import the CA certificate, so users of this DB trust it: + certutil -A -d . -f .pwfile -n "${TESTCERT_ROOTCA_NAME}" -t "TC,," -a -i "${TESTCERT_PATH_ROOTCA}"/rootca.pem + + # Import server cert into client database so we can trust it (CERTHOST directive): + # NOTE: Seems we must do this before requesting or signing the client cert, + # otherwise (if importing server cert after doing everything about the + # client one) we get an error: + # certutil: could not decode certificate: SEC_ERROR_REUSED_ISSUER_AND_SERIAL: + # You are attempting to import a cert with the same issuer/serial + # as an existing cert, but that is not the same cert. + certutil -A -d . -f .pwfile -n "${TESTCERT_SERVER_NAME}" -a -i "${TESTCERT_PATH_SERVER}/server.crt" -t ",," + + # Create a client certificate request: + # NOTE: IRL Each run should have a separate random seed; for tests we cut a few corners! + certutil -R -d . -f .pwfile -s "CN=${TESTCERT_CLIENT_NAME},OU=Test,O=NIT,ST=StateOfChaos,C=US" -a -o client.req -z "${TESTCERT_PATH_ROOTCA}"/.random + + # Sign a certificate request with the CA certificate: + # HACK NOTE: "No" for "Is this a CA certificate" question, defaults for others + (echo n; yes "") | certutil -C -d "${TESTCERT_PATH_ROOTCA}" -f "${TESTCERT_PATH_ROOTCA}"/.pwfile -c "${TESTCERT_ROOTCA_NAME}" -a -i client.req -o client.crt -2 --extKeyUsage "clientAuth" --nsCertType sslClient + + # Import the signed certificate into client database: + certutil -A -d . -f .pwfile -n "${TESTCERT_CLIENT_NAME}" -a -i client.crt -t ",," + + ls -l "${TESTCERT_PATH_CLIENT}"/*.db "${TESTCERT_PATH_CLIENT}"/*.txt + ;; + OpenSSL) + # NOTE: No special keys for an OpenSSL client so far, + # it only checks/trusts a server (public data in a PEM file) + log_info "SSL: Exporting public data of server certificate for client use..." + cat "${TESTCERT_PATH_SERVER}"/server.crt "${TESTCERT_PATH_ROOTCA}"/rootca.pem > upsd-public.pem + + ls -l "${TESTCERT_PATH_CLIENT}/upsd-public.pem" + ;; + esac + ) + ) || { + log_warn "Something failed about setup of crypto credential stores, will skip SSL tests" + WITH_SSL_CLIENT="none" + WITH_SSL_SERVER="none" + } + ;; +esac + # This file is not used by the test code, it is an # aid for "DEBUG_SLEEP=X" mode so the caller can # quickly source needed values into their shell. @@ -828,7 +994,7 @@ log_info "Using NUT_PORT=${NUT_PORT} for this test run" # the values when fallback is used. If this is a # problem on any platform (Win/Mac and spaces in # paths?) please investigate and fix accordingly. -set | ${EGREP} '^(NUT_|TESTDIR|LD_LIBRARY_PATH|DEBUG|PATH).*=' \ +set | ${EGREP} '^(NUT_|TESTDIR|TESTCERT|LD_LIBRARY_PATH|DEBUG|PATH).*=' \ | while IFS='=' read K V ; do case "$K" in LD_LIBRARY_PATH_CLIENT|LD_LIBRARY_PATH_ORIG|PATH_*|NUT_PORT_*|TESTDIR_*) @@ -905,16 +1071,15 @@ generatecfg_upsd_add_SSL() { { cat << EOF # OpenSSL CERTFILE: PEM file with data server cert, possibly the # intermediate and root CA's, and finally corresponding private key -CERTFILE "${NUT_CONFPATH}/cert/upsd/upsd.pem" +CERTFILE "${TESTCERT_PATH_SERVER}/upsd.pem" EOF } >> "$NUT_CONFPATH/upsd.conf" \ - && mkdir -p "${NUT_CONFPATH}/cert/upsd" \ || die "Failed to populate temporary FS structure for the NIT: upsd.conf" ;; NSS) { cat << EOF # NSS CERTPATH: Directory with 3-file database of cert/key store -CERTPATH "${NUT_CONFPATH}/cert/upsd" +CERTPATH "${TESTCERT_PATH_SERVER}" CERTIDENT "${TESTCERT_SERVER_NAME}" "${TESTCERT_SERVER_PASS}" EOF @@ -927,7 +1092,6 @@ CERTREQUEST 2 EOF fi } >> "$NUT_CONFPATH/upsd.conf" \ - && mkdir -p "${NUT_CONFPATH}/cert/upsd" \ || die "Failed to populate temporary FS structure for the NIT: upsd.conf" ;; esac @@ -1118,7 +1282,7 @@ generatecfg_upsmon_add_SSL() { # by the generatecfg_upsd_add_SSL() method. # We only support CERTPATH (to recognize servers), FORCESSL and # CERTVERIFY in OpenSSL builds. -CERTPATH "${NUT_CONFPATH}/cert/upsd" +CERTPATH "${TESTCERT_PATH_CLIENT}" EOF if [ x"${WITH_SSL_SERVER}" != xnone ] ; then @@ -1130,13 +1294,12 @@ CERTVERIFY 1 EOF fi } >> "$NUT_CONFPATH/upsmon.conf" \ - && mkdir -p "${NUT_CONFPATH}/cert/upsd" \ || die "Failed to populate temporary FS structure for the NIT: upsmon.conf" ;; NSS) { cat << EOF # NSS CERTPATH: Directory with 3-file database of cert/key store -CERTPATH "${NUT_CONFPATH}/cert/upsmon" +CERTPATH "${TESTCERT_PATH_CLIENT}" CERTIDENT "${TESTCERT_CLIENT_NAME}" "${TESTCERT_CLIENT_PASS}" EOF @@ -1163,7 +1326,6 @@ CERTVERIFY 1 EOF fi } >> "$NUT_CONFPATH/upsmon.conf" \ - && mkdir -p "${NUT_CONFPATH}/cert/upsmon" \ || die "Failed to populate temporary FS structure for the NIT: upsmon.conf" ;; esac From d4d20b767ea6442e3ac0191f805a2a29ee9456bd Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 18:35:40 +0100 Subject: [PATCH 14/44] tests/NIT/nit.sh: add server/client-side SSL configs for tests with upsd-client interaction [#3328, #1711] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index de439fc242..2991d548bf 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -1068,6 +1068,7 @@ generatecfg_upsd_add_SSL() { case "${WITH_SSL_SERVER}" in none) return 0;; OpenSSL) + log_info "Adding ${WITH_SSL_SERVER} server-side SSL config to upsd.conf" { cat << EOF # OpenSSL CERTFILE: PEM file with data server cert, possibly the # intermediate and root CA's, and finally corresponding private key @@ -1077,6 +1078,7 @@ EOF || die "Failed to populate temporary FS structure for the NIT: upsd.conf" ;; NSS) + log_info "Adding ${WITH_SSL_SERVER} server-side SSL config to upsd.conf" { cat << EOF # NSS CERTPATH: Directory with 3-file database of cert/key store CERTPATH "${TESTCERT_PATH_SERVER}" @@ -1085,9 +1087,10 @@ EOF if [ x"${WITH_SSL_SERVER_CLIVAL}" = xtrue -a x"${WITH_SSL_CLIENT}" = xNSS ]; then cat << EOF -# - 0 to not request to clients to provide any certificate -# - 1 to require to all clients a certificate -# - 2 to require to all clients a valid certificate +# - 0 to not request clients to provide any certificate +# - 1 to require all clients to present some certificate +# - 2 to require all clients to present a valid certificate +# (trusted by server database) CERTREQUEST 2 EOF fi @@ -1275,6 +1278,7 @@ generatecfg_upsmon_add_SSL() { case "${WITH_SSL_CLIENT}" in none) return 0;; OpenSSL) + log_info "Adding ${WITH_SSL_CLIENT} client-side SSL config to upsmon.conf" { cat << EOF # OpenSSL CERTPATH: Directory with PEM file(s), looked up by the # CA subject name hash value (which must include our NUT server). @@ -1297,6 +1301,7 @@ EOF || die "Failed to populate temporary FS structure for the NIT: upsmon.conf" ;; NSS) + log_info "Adding ${WITH_SSL_CLIENT} client-side SSL config to upsmon.conf" { cat << EOF # NSS CERTPATH: Directory with 3-file database of cert/key store CERTPATH "${TESTCERT_PATH_CLIENT}" @@ -1671,6 +1676,7 @@ sandbox_generate_configs() { log_info "Generating configs for sandbox" generatecfg_upsd_nodev + generatecfg_upsd_add_SSL generatecfg_upsdusers_trivial generatecfg_ups_dummy SANDBOX_CONFIG_GENERATED=true @@ -2501,6 +2507,7 @@ sandbox_start_upsmon_master() { sandbox_generate_configs generatecfg_upsmon_master "$@" + generatecfg_upsmon_add_SSL log_info "Starting UPSMON as master for sandbox" From 22734ddc614e5d389aa947010a480753caefc580 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 18:55:10 +0100 Subject: [PATCH 15/44] tests/NIT/nit.sh: fix running as a script from root or tests directory [#3294] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 2991d548bf..a2de62ee94 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -337,7 +337,16 @@ if [ x"${TOP_BUILDDIR}" = x ] || [ ! -d "${TOP_BUILDDIR}" ] ; then case "${BUILDDIR}" in */tests/NIT) TOP_BUILDDIR="`cd \"${BUILDDIR}\"/../.. && pwd`" ;; - *) log_info "Current directory '${BUILDDIR}' is not a .../tests/NIT" ;; + *) if [ -x ./tests/NIT/nit.sh ] ; then + TOP_BUILDDIR="`pwd`" + else + if [ -x ./NIT/nit.sh ] ; then + TOP_BUILDDIR="`cd .. && pwd`" + else + log_info "Current directory '${BUILDDIR}' is not a .../tests/NIT or similar" + fi + fi + ;; esac log_info "Guessing TOP_BUILDDIR='${TOP_BUILDDIR}' from script location and/or BUILDDIR value..." else From d0af6a5dc245cbcdb46f67c4c34fb7588f6d0522 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 19:07:09 +0100 Subject: [PATCH 16/44] tests/NIT/nit.sh: leave a commented stub for OpenSSL SSL_CERT_DIR hackery [#1711] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index a2de62ee94..8df8ec3928 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -988,6 +988,13 @@ EOF ;; esac +# This does not seem to cause NUT clients to trust nor distrust +# (or anyhow verify) a presented server certificate: +#if [ "${WITH_SSL_CLIENT}" = OpenSSL ] ; then +# SSL_CERT_DIR="${TESTCERT_PATH_CLIENT}" +# export SSL_CERT_DIR +#fi + # This file is not used by the test code, it is an # aid for "DEBUG_SLEEP=X" mode so the caller can # quickly source needed values into their shell. From c521a4229bedecb0202edb65c0d6cfba6fada758 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 22:54:14 +0100 Subject: [PATCH 17/44] configure.ac and docs: introduce --with-ssl-client-validation option [#3330, #3329] Signed-off-by: Jim Klimov --- NEWS.adoc | 3 +++ conf/upsd.conf.sample | 7 +++++-- configure.ac | 13 +++++++++++++ docs/configure.txt | 15 +++++++++++++-- docs/man/upsd.conf.txt | 8 ++++++-- docs/security.txt | 8 ++++++-- 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index de3c1e3e25..fc25b57370 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -309,6 +309,9 @@ https://github.com/networkupstools/nut/milestone/12 or Windows `HANDLE`'s at a time, and moving on to another chunk. The system-provided value can be further limited by `NUT_SYSMAXCONN_LIMIT` environment variable (e.g. in tests). [#3302] + * Added `configure --with-ssl-client-validation` toggle to expose the + macro previously meant to be passed via `make` command line. [PR #3330, + but beware issue #3329] - `upsdrvctl` tool updates: * Make use of `setproctag()` and `getproctag()` to report parent/child diff --git a/conf/upsd.conf.sample b/conf/upsd.conf.sample index f03b67b480..291e810113 100644 --- a/conf/upsd.conf.sample +++ b/conf/upsd.conf.sample @@ -170,9 +170,9 @@ # CERTREQUEST # CERTREQUEST REQUIRE # -# When compiled with SSL support with NSS backend and client certificate +# When compiled with SSL support with NSS backend *and* client certificate # validation (disabled by default, see 'docs/security.txt'), -# you can specify if upsd requests or requires client's' certificates. +# you can specify if upsd requests or requires clients' certificates. # Possible values are : # - 0 to not request to clients to provide any certificate # - 1 to require to all clients a certificate @@ -180,6 +180,9 @@ # # See 'docs/security.txt' or the Security chapter of NUT user manual # for more information on the SSL support in NUT. +# +# WARNING: Until https://github.com/networkupstools/nut/issues/3329 is solved, +# not all stock NUT clients even have a way to present some certain certificate. # ======================================================================= # DISABLE_WEAK_SSL diff --git a/configure.ac b/configure.ac index f894b2b766..b26269f038 100644 --- a/configure.ac +++ b/configure.ac @@ -2840,6 +2840,7 @@ nut_ssl_lib="" NUT_ARG_WITH([ssl], [enable SSL support (either NSS or OpenSSL)], [auto]) NUT_ARG_WITH([nss], [enable SSL support using Mozilla NSS], [auto]) NUT_ARG_WITH([openssl], [enable SSL support using OpenSSL], [auto]) +NUT_ARG_WITH([ssl-client-validation], [enable SSL client certificate validation ability (currently requires NSS, beware issue #3329)], [auto]) dnl ${nut_with_ssl}: any value except "yes" or "no" is treated as "auto". if test "${nut_with_ssl}" != "no"; then @@ -2882,6 +2883,18 @@ AM_CONDITIONAL(WITH_OPENSSL, test "${nut_with_openssl}" = "yes") NUT_REPORT_FEATURE([enable SSL support], [${nut_with_ssl}], [${nut_ssl_lib}], [WITH_SSL], [Define to enable SSL]) +AS_CASE([${nut_with_ssl_client_validation}], + [no], [], + [yes|""], [nut_with_ssl_client_validation="yes"], + dnl TOTHINK: reject if openssl/none build? + [auto|*], [nut_with_ssl_client_validation="no"] + dnl Until #3329 is solved, auto==no +) +NUT_REPORT_FEATURE([enable SSL client certificate validation], + [${nut_with_ssl_client_validation}], [], + [WITH_CLIENT_CERTIFICATE_VALIDATION], + [Define to enable SSL client certificate validation]) + dnl ---------------------------------------------------------------------- dnl Check for presence and compiler flags of various libraries diff --git a/docs/configure.txt b/docs/configure.txt index 0b70cf5cfc..ccb3702cd1 100644 --- a/docs/configure.txt +++ b/docs/configure.txt @@ -789,10 +789,21 @@ Enable SSL support, using either Mozilla NSS or OpenSSL. If both are present, and nothing was specified, OpenSSL support will be preferred. -Read link:docs/security.txt[] for instructions on SSL support. - NOTE: Currently the two implementations differ in supported features. + --with-ssl-client-validation (default: no) + +Enable the `upsd` server-side ability to set up `CERTREQUEST` directive in +`upsd.conf`, which lets the server reject clients which do not present a +certificate. + +WARNING: Until link:https://github.com/networkupstools/nut/issues/3329[issue +#3329] is solved, not all stock NUT clients may even have a way to present +some certain certificate. + +Read link:docs/security.txt[] for instructions on practical setup of SSL +support. + Networking access security ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/man/upsd.conf.txt b/docs/man/upsd.conf.txt index f6d538e7c6..3a318e402e 100644 --- a/docs/man/upsd.conf.txt +++ b/docs/man/upsd.conf.txt @@ -188,9 +188,13 @@ are using a value with spaces in it. *CERTREQUEST 'certificate request level'*:: -When compiled with SSL support with NSS backend and client certificate +When compiled with SSL support with NSS backend *and* client certificate validation (disabled by default, see `docs/security.txt` in NUT sources), -you can specify if `upsd` requests or requires clients' certificates. +you can specify whether `upsd` requests or requires clients' certificates. ++ +WARNING: Until link:https://github.com/networkupstools/nut/issues/3329[issue +#3329] is solved, not all stock NUT clients may even have a way to present +some certain certificate. + Possible values are: + diff --git a/docs/security.txt b/docs/security.txt index 6a96d4f8ac..e15b5cf71a 100644 --- a/docs/security.txt +++ b/docs/security.txt @@ -582,8 +582,9 @@ a hard rule. The certificate can be named as useful. upsd (optional): client authentication ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -NOTE: This functionality is disabled by default. To activate it, recompile -NUT with `WITH_CLIENT_CERTIFICATE_VALIDATION` defined: +NOTE: This functionality is disabled by default. To activate it, initially +`configure --with-ssl-client-validation` or just recompile NUT with macro +`WITH_CLIENT_CERTIFICATE_VALIDATION` defined: make CFLAGS="-DWITH_CLIENT_CERTIFICATE_VALIDATION" @@ -601,6 +602,9 @@ If the client does not send any certificate, the connection is closed. Like CA certificates, you can add many "trusted" client and CA certificates in server's certificate databases. +WARNING: Until link:https://github.com/networkupstools/nut/issues/3329[issue +#3329] is solved, not all stock NUT clients may even have a way to present +some certain certificate. upsmon (required): upsd authentication ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From a19c8bd122d3c60a5920f82e7f9dcffe538fc3af Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Feb 2026 23:02:03 +0100 Subject: [PATCH 18/44] server/conf.c and docs: Extended processing of `CERTREQUEST` setting to handle numeric or specific string values, to match both ways of reading ambiguous documentation [#3330] Signed-off-by: Jim Klimov --- NEWS.adoc | 4 +++- conf/upsd.conf.sample | 7 ++++--- docs/man/upsd.conf.txt | 7 ++++--- docs/security.txt | 19 ++++++++++--------- server/conf.c | 16 +++++++++++++++- server/netssl.c | 14 +++++++------- 6 files changed, 43 insertions(+), 24 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index fc25b57370..fa32899de4 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -309,7 +309,9 @@ https://github.com/networkupstools/nut/milestone/12 or Windows `HANDLE`'s at a time, and moving on to another chunk. The system-provided value can be further limited by `NUT_SYSMAXCONN_LIMIT` environment variable (e.g. in tests). [#3302] - * Added `configure --with-ssl-client-validation` toggle to expose the + * Extended processing of `CERTREQUEST` setting to handle numeric or specific + string values, to match both ways of reading ambiguous documentation. + Added `configure --with-ssl-client-validation` toggle to expose the macro previously meant to be passed via `make` command line. [PR #3330, but beware issue #3329] diff --git a/conf/upsd.conf.sample b/conf/upsd.conf.sample index 291e810113..81f70a999e 100644 --- a/conf/upsd.conf.sample +++ b/conf/upsd.conf.sample @@ -174,9 +174,10 @@ # validation (disabled by default, see 'docs/security.txt'), # you can specify if upsd requests or requires clients' certificates. # Possible values are : -# - 0 to not request to clients to provide any certificate -# - 1 to require to all clients a certificate -# - 2 to require to all clients a valid certificate +# - '0' or 'NO' to not request that clients provide any certificate +# - '1' or 'REQUEST' to require all clients to present some certificate +# - '2' or 'REQUIRE' to require all clients to present a valid certificate +# (trusted by server database) # # See 'docs/security.txt' or the Security chapter of NUT user manual # for more information on the SSL support in NUT. diff --git a/docs/man/upsd.conf.txt b/docs/man/upsd.conf.txt index 3a318e402e..c42170399d 100644 --- a/docs/man/upsd.conf.txt +++ b/docs/man/upsd.conf.txt @@ -198,9 +198,10 @@ some certain certificate. + Possible values are: + -- '0' to not request to clients to provide any certificate -- '1' to require to all clients a certificate -- '2' to require to all clients a valid certificate +- '0' or 'NO' to not request that clients provide any certificate +- '1' or 'REQUEST' to require all clients to present *some* certificate +- '2' or 'REQUIRE' to require all clients to present a valid certificate + (with auth chain trusted by server database) *DISABLE_WEAK_SSL 'BOOLEAN'*:: diff --git a/docs/security.txt b/docs/security.txt index e15b5cf71a..5c4bf9d7f8 100644 --- a/docs/security.txt +++ b/docs/security.txt @@ -589,15 +589,16 @@ NOTE: This functionality is disabled by default. To activate it, initially make CFLAGS="-DWITH_CLIENT_CERTIFICATE_VALIDATION" UPSD can accept three levels of client authentication. Just specify it with -the directive `CERTREQUEST` with the corresponding value in the `upsd.conf` -file: - -- NO: no client authentication. -- REQUEST: a certificate is request to the client but it is not strictly -validated. -If the client does not send any certificate, the connection is closed. -- REQUIRE: a certificate is requested to the client and if it is not valid -(no validation chain) the connection is closed. +the directive `CERTREQUEST` with the corresponding numeric or string value +in the `upsd.conf` file: + +- ('0') 'NO': no client authentication. +- ('1') 'REQUEST': a certificate is requested from the client, but it is not + strictly validated. ++ +NOTE: If the client does not send *any* certificate, the connection is closed. +- ('2') 'REQUIRE': a certificate is requested from the client, and if it is + not valid (no trusted validation chain) the connection is closed. Like CA certificates, you can add many "trusted" client and CA certificates in server's certificate databases. diff --git a/server/conf.c b/server/conf.c index b2edf311b6..89a804b7b4 100644 --- a/server/conf.c +++ b/server/conf.c @@ -1,6 +1,8 @@ /* conf.c - configuration handlers for upsd - Copyright (C) 2001 Russell Kroll + Copyright (C) + 2001 Russell Kroll + 2019 - 2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -285,6 +287,18 @@ static int parse_upsd_conf_args(size_t numargs, char **arg) return 1; } else { + if (!strcmp(arg[1], "NO")) { + certrequest = NETSSL_CERTREQ_NO; /* 0 */ + return 1; + } + if (!strcmp(arg[1], "REQUEST")) { + certrequest = NETSSL_CERTREQ_REQUEST; /* 1 */ + return 1; + } + if (!strcmp(arg[1], "REQUIRE")) { + certrequest = NETSSL_CERTREQ_REQUIRE; /* 2 */ + return 1; + } upslogx(LOG_ERR, "CERTREQUEST has non numeric value (%s)!", arg[1]); return 0; } diff --git a/server/netssl.c b/server/netssl.c index e79f24b41e..b04603a7b2 100644 --- a/server/netssl.c +++ b/server/netssl.c @@ -461,7 +461,7 @@ void net_starttls(nut_ctype_t *client, size_t numarg, const char **arg) if (status != SECSuccess) { PRErrorCode code = PR_GetError(); if (code==SSL_ERROR_NO_CERTIFICATE) { - upslogx(LOG_WARNING, "Client %s do not provide certificate.", + upslogx(LOG_WARNING, "Client %s did not provide any certificate.", client->addr); } else { nss_error("net_starttls / SSL_ForceHandshake"); @@ -637,16 +637,16 @@ void ssl_init(void) # endif /* NSS_VMAJOR */ } -# ifdef WITH_CLIENT_CERTIFICATE_VALIDATION - if (certrequest < NETSSL_CERTREQ_NO - && certrequest > NETSSL_CERTREQ_REQUEST +#ifdef WITH_CLIENT_CERTIFICATE_VALIDATION + if (certrequest < NETSSL_CERTREQ_NO /* < 0 */ + || certrequest > NETSSL_CERTREQ_REQUIRE /* > 2 */ ) { upslogx(LOG_ERR, "Invalid certificate requirement"); return; } - if (certrequest == NETSSL_CERTREQ_REQUEST - || certrequest == NETSSL_CERTREQ_REQUIRE + if (certrequest == NETSSL_CERTREQ_REQUEST /* 1 */ + || certrequest == NETSSL_CERTREQ_REQUIRE /* 2 */ ) { status = SSL_OptionSetDefault(SSL_REQUEST_CERTIFICATE, PR_TRUE); if (status != SECSuccess) { @@ -656,7 +656,7 @@ void ssl_init(void) } } - if (certrequest == NETSSL_CERTREQ_REQUIRE) { + if (certrequest == NETSSL_CERTREQ_REQUIRE) { /* 2 */ status = SSL_OptionSetDefault(SSL_REQUIRE_CERTIFICATE, PR_TRUE); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not enable certificate requirement"); From e533c1d3812a65bd11ed87b42ab3284345d73fa7 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Feb 2026 00:30:38 +0100 Subject: [PATCH 19/44] docs/config-prereqs.txt: add packages for `openssl` and `certutil` where currently missing, to NIT-test SSL support on as many platforms as we can [#1711] Signed-off-by: Jim Klimov --- docs/config-prereqs.txt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/config-prereqs.txt b/docs/config-prereqs.txt index 9a22df0ad0..8d3c091cd3 100644 --- a/docs/config-prereqs.txt +++ b/docs/config-prereqs.txt @@ -255,9 +255,11 @@ metadata about recently published package revisions: # NOTE: Some older Debian-like distributions, could ship "libcrypto-dev" # and/or "openssl-dev" instead of "libssl-dev" by its modern name # and may lack a libgpiod2 + libgpiod-dev altogether +# For NIT tests of SSL support you would also want key store management tools. :; apt-get install \ libcppunit-dev \ - libssl-dev libnss3-dev \ + libssl-dev openssl \ + libnss3-dev libnss3-tools \ augeas-tools libaugeas-dev augeas-lenses \ libusb-dev libusb-1.0-0-dev \ libglib2.0-dev \ @@ -269,6 +271,9 @@ metadata about recently published package revisions: libavahi-common-dev libavahi-core-dev libavahi-client-dev # For libneon, see below +# NOTE: On Termux the -dev suffixes may be missing, so you would need e.g. +:; pkg install libnss nss-utils + # NOTE: Older Debian-like distributions may lack a "libgpiod-dev" # Others above are present as far back as Debian 7 at least :; apt-get install \ @@ -1451,6 +1456,10 @@ Typical tooling would include: freeipmi \ avahi +# For tests with NSS, the certutil tool is needed: +:; pkg install \ + system/mozilla-nss + # With 2024.04, some packages were split for development vs. run-time # and/or based on architecture: :; pkg install \ @@ -2024,6 +2033,12 @@ export PATH mingw-w64-x86_64-libgd \ mingw-w64-clang-x86_64-libgd +# For SSL support (note that OpenSSL is usually available +# in the default MSYS2 footprint, but may get upgraded here): +:; pacman -S --needed \ + mingw-w64-x86_64-openssl \ + mingw-w64-x86_64-nss + # For C++ tests: :; pacman -S --needed \ mingw-w64-x86_64-cppunit \ From 90e5afa4f070da4ff3ca37bb8cd558770bc91911 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Feb 2026 01:32:37 +0100 Subject: [PATCH 20/44] server/upsd.c: mainloop(POSIX): log in more detail why we disconnect a socket [#1711] Signed-off-by: Jim Klimov --- server/upsd.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/server/upsd.c b/server/upsd.c index 5fb0004a32..b843f1b5e1 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -1759,6 +1759,21 @@ static void mainloop(void) if (fds[i].revents & (POLLHUP|POLLERR|POLLNVAL)) { + upsdebug_with_errno(3, "%s: Disconnect %s%s due to%s%s%s", + __func__, + (handler[i].type==DRIVER ? "driver " : + (handler[i].type==CLIENT ? "client " : + (handler[i].type==SERVER ? "server" : + ""))), + (handler[i].type==DRIVER ? ((upstype_t *)handler[i].data)->name : + (handler[i].type==CLIENT ? ((nut_ctype_t *)handler[i].data)->addr : + (handler[i].type==SERVER ? "" : + ""))), + (fds[i].revents & POLLHUP ? " POLLHUP" : ""), + (fds[i].revents & POLLERR ? " POLLERR" : ""), + (fds[i].revents & POLLNVAL ? " POLLNVAL" : "") + ); + switch(handler[i].type) { case DRIVER: From 8fe772c92473971e8d2908bef51833e59ee73a8c Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Feb 2026 01:48:13 +0100 Subject: [PATCH 21/44] server/netssl.c: net_starttls(): reject clients without any cert if WITH_CLIENT_CERTIFICATE_VALIDATION earlier and more visibly [#3329] Signed-off-by: Jim Klimov --- server/netssl.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/netssl.c b/server/netssl.c index b04603a7b2..70ad6639e8 100644 --- a/server/netssl.c +++ b/server/netssl.c @@ -461,6 +461,18 @@ void net_starttls(nut_ctype_t *client, size_t numarg, const char **arg) if (status != SECSuccess) { PRErrorCode code = PR_GetError(); if (code==SSL_ERROR_NO_CERTIFICATE) { +# ifdef WITH_CLIENT_CERTIFICATE_VALIDATION + if (certrequest == NETSSL_CERTREQ_REQUEST + || certrequest == NETSSL_CERTREQ_REQUIRE + ) { + upslogx(LOG_ERR, "Client %s did not provide any certificate while we %s one.", + client->addr, + (certrequest == NETSSL_CERTREQ_REQUIRE ? "require" : "request") + ); + nss_error("net_starttls / SSL_ForceHandshake"); + return; + } +# endif upslogx(LOG_WARNING, "Client %s did not provide any certificate.", client->addr); } else { From c01e67ab7c51705e3dffb8dbf3528cad833f9f39 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Feb 2026 02:37:08 +0100 Subject: [PATCH 22/44] clients/upsc.c: revise failure logging [#1711] Signed-off-by: Jim Klimov --- clients/upsc.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/clients/upsc.c b/clients/upsc.c index 56778ff407..39af7275e6 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -48,14 +48,14 @@ static void fatalx_error_json_simple(int msg_is_simple, const char *msg) { if (output_json) { if (msg_is_simple) { /* Caller knows there is nothing to escape here, pass through */ - printf("{\"error\": \"%s\"}\n", msg); + printf("{\"error\": \"%s\"}\n", NUT_STRARG(msg)); } else { printf("{\"error\": \""); json_print_esc(msg); printf("\"}\n"); } } - fatalx(EXIT_FAILURE, "Error: %s", msg); + fatalx(EXIT_FAILURE, "Error: %s", NUT_STRARG(msg)); } static void usage(const char *prog) @@ -169,6 +169,8 @@ static void list_vars(void) int msg_is_simple = 1; /* check for an old upsd */ + upsdebugx(1, "%s: got code %d, upserror %d", + __func__, ret, upscli_upserror(ups)); if (upscli_upserror(ups) == UPSCLI_ERR_UNKCOMMAND) { msg = "upsd is too old to support this query"; } else { @@ -178,14 +180,14 @@ static void list_vars(void) if (output_json) { if (msg_is_simple) { - printf(" \"error\": \"%s\"\n}\n", msg); + printf(" \"error\": \"%s\"\n}\n", NUT_STRARG(msg)); } else { printf(" \"error\": \""); json_print_esc(msg); printf("\"\n}\n"); } } - fatalx(EXIT_FAILURE, "Error: %s", msg); + fatalx(EXIT_FAILURE, "Error: %s", NUT_STRARG(msg)); } while (upscli_list_next(ups, numq, query, &numa, &answer) == 1) { @@ -238,6 +240,8 @@ static void list_upses(int verbose) int msg_is_simple = 1; /* check for an old upsd */ + upsdebugx(1, "%s: got code %d, upserror %d", + __func__, ret, upscli_upserror(ups)); if (upscli_upserror(ups) == UPSCLI_ERR_UNKCOMMAND) { msg = "upsd is too old to support this query"; } else { From c2c58cd7097cf5e0c8c25f297c7dc6bb69b8d357 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Feb 2026 02:39:29 +0100 Subject: [PATCH 23/44] clients/upsclient.c: upscli_strerror(): revise failure logging with NSS [#1711, #3329] The NSS library can return error codes with empty strings attached, log some reasonable info then. Signed-off-by: Jim Klimov --- clients/upsclient.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/clients/upsclient.c b/clients/upsclient.c index 7efbbf5d7a..7dbbbb68e8 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -596,7 +596,7 @@ const char *upscli_strerror(UPSCONN_t *ups) return upscli_errlist[UPSCLI_ERR_INVALIDARG].str; } - if (ups->upserror > UPSCLI_ERR_MAX) { + if (ups->upserror < 0 || ups->upserror > UPSCLI_ERR_MAX) { return "Invalid error number"; } @@ -612,7 +612,7 @@ const char *upscli_strerror(UPSCONN_t *ups) "%s", strerror(ups->syserrno)); return ups->errbuf; - case 2: /* SSL error */ + case 2: /* SSL error, with 1 arg */ #ifdef WITH_OPENSSL err = ERR_get_error(); if (err) { @@ -628,12 +628,19 @@ const char *upscli_strerror(UPSCONN_t *ups) "%s", "peer disconnected"); } #elif defined(WITH_NSS) /* WITH_OPENSSL */ - if (PR_GetErrorTextLength() < UPSCLI_ERRBUF_LEN) { - PR_GetErrorText(ups->errbuf); + if (PR_GetErrorTextLength() > 0 && PR_GetErrorTextLength() + strlen(upscli_errlist[ups->upserror].str) < UPSCLI_ERRBUF_LEN) { + char errbuf[UPSCLI_ERRBUF_LEN]; + memset(errbuf, 0, UPSCLI_ERRBUF_LEN); + PR_GetErrorText(errbuf); + snprintf_dynamic( + ups->errbuf, UPSCLI_ERRBUF_LEN, + upscli_errlist[ups->upserror].str, + "%s", errbuf); } else { snprintf(ups->errbuf, UPSCLI_ERRBUF_LEN, - "SSL error #%ld, message too long to be displayed", - (long)PR_GetError()); + "SSL error #%ld, message too %s to be displayed", + (long)PR_GetError(), + PR_GetErrorTextLength() > 0 ? "long" : "short"); } #else snprintf(ups->errbuf, UPSCLI_ERRBUF_LEN, From 35ef15125f64d1ea8d94feeca455fad8ba5772a4 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Feb 2026 03:21:26 +0100 Subject: [PATCH 24/44] tests/NIT/nit.sh: adjust TESTCERT_PATH_* to WIN32 runs if needed [#1711] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 8df8ec3928..422b01a434 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -826,12 +826,34 @@ export NUT_PORT # Help track collisions in log, if someone else starts a test in same directory log_info "Using NUT_PORT=${NUT_PORT} for this test run" +# Adjust path spelling to run-time platform, libraries seem to want that on WIN32 +# NOTE: Windows backslashes are pre-escaped in the configure-generated value +case "${ABS_TOP_BUILDDIR}" in + ?":\\"*) + TESTCERT_PATH_SEP='\\' + TESTCERT_PATH_BASE="${ABS_TOP_BUILDDIR}${TESTCERT_PATH_SEP}tests${TESTCERT_PATH_SEP}NIT${TESTCERT_PATH_SEP}tmp${TESTCERT_PATH_SEP}etc${TESTCERT_PATH_SEP}cert" + ;; + "") case "${TOP_BUILDDIR}" in + ?":\\"*) TESTCERT_PATH_SEP='\\' + TESTCERT_PATH_BASE="${TOP_BUILDDIR}${TESTCERT_PATH_SEP}tests${TESTCERT_PATH_SEP}NIT${TESTCERT_PATH_SEP}tmp${TESTCERT_PATH_SEP}etc${TESTCERT_PATH_SEP}cert" + ;; + *) TESTCERT_PATH_SEP="/" ;; + esac + ;; + *) TESTCERT_PATH_SEP="/" + ;; +esac + +case "${TESTCERT_PATH_SEP}" in + /) TESTCERT_PATH_BASE="${NUT_CONFPATH}${TESTCERT_PATH_SEP}cert" ;; +esac + # SSL preparations: Can only do this after we learn NUT_CONFPATH -TESTCERT_PATH_ROOTCA="${NUT_CONFPATH}/cert/rootca" -TESTCERT_PATH_SERVER="${NUT_CONFPATH}/cert/upsd" +TESTCERT_PATH_ROOTCA="${TESTCERT_PATH_BASE}${TESTCERT_PATH_SEP}rootca" +TESTCERT_PATH_SERVER="${TESTCERT_PATH_BASE}${TESTCERT_PATH_SEP}upsd" # TOTHINK: If other NUT clients get SSL support tested, # should they use same or different cryptostore?.. -TESTCERT_PATH_CLIENT="${NUT_CONFPATH}/cert/upsmon" +TESTCERT_PATH_CLIENT="${TESTCERT_PATH_BASE}${TESTCERT_PATH_SEP}upsmon" # Follow docs/security.txt points about setting up the crypto material # stores and their contents (mock a self-signed CA here where appropriate) @@ -1088,7 +1110,7 @@ generatecfg_upsd_add_SSL() { { cat << EOF # OpenSSL CERTFILE: PEM file with data server cert, possibly the # intermediate and root CA's, and finally corresponding private key -CERTFILE "${TESTCERT_PATH_SERVER}/upsd.pem" +CERTFILE "${TESTCERT_PATH_SERVER}${TESTCERT_PATH_SEP}upsd.pem" EOF } >> "$NUT_CONFPATH/upsd.conf" \ || die "Failed to populate temporary FS structure for the NIT: upsd.conf" From 0e4d3a4e8c6b98719549b76882b663ed2ba3b875 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Feb 2026 15:50:54 +0100 Subject: [PATCH 25/44] */Makefile.am: revise libupsclient* dependencies to honour ENABLE_SHARED_PRIVATE_LIBS [#2800, #3328] In some builds, nut_debug_level of the client program and the library was different because it was not dynamically linked to private properly. Signed-off-by: Jim Klimov drivers/Makefile.am: rectify dependencies using libcommonclient.la Signed-off-by: Jim Klimov --- Makefile.am | 1 + clients/Makefile.am | 6 ++++++ drivers/Makefile.am | 14 +++++++++----- tools/nut-scanner/Makefile.am | 9 +++++++-- tools/nutconf/Makefile.am | 3 ++- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Makefile.am b/Makefile.am index acc00c75ad..39bfa7162d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -250,6 +250,7 @@ all-libs-local/common: all-libs-local/include @dotMAKE@ ### Delivers: libupsclient.la libnutclient.la libnutclientstub.la ### Delivers: libupsclient-version.h ### LIB-Requires-ext: common/libcommonclient.la +### Requires-ext: include/nut_version.h ### Requires-ext: common/libcommon.la common/libcommonclient.la ### Requires-ext: common/libcommonversion.la ### Requires-ext: common/libparseconf.la diff --git a/clients/Makefile.am b/clients/Makefile.am index ab81516a16..ed68283355 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -165,9 +165,15 @@ upsstats_cgi_LDADD = $(LDADD_CLIENT) $(top_builddir)/common/libcommonstrjson.la # not LDADD... why? libupsclient_la_SOURCES = upsclient.c upsclient.h +upsclient.c: $(top_builddir)/include/nut_version.h # NOTE: The library does not require libcommonversion.la +if ENABLE_SHARED_PRIVATE_LIBS +libupsclient_la_LIBADD = \ + $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-client.la +else !ENABLE_SHARED_PRIVATE_LIBS libupsclient_la_LIBADD = \ $(top_builddir)/common/libcommonclient.la +endif !ENABLE_SHARED_PRIVATE_LIBS if HAVE_WINDOWS_SOCKETS libupsclient_la_LIBADD += -lws2_32 endif HAVE_WINDOWS_SOCKETS diff --git a/drivers/Makefile.am b/drivers/Makefile.am index ee1d90cde8..93b1e4d927 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -12,6 +12,7 @@ # Make sure out-of-dir dependencies exist (especially when dev-building parts): $(top_builddir)/include/nut_version.h \ $(top_builddir)/common/libcommon.la \ +$(top_builddir)/common/libcommonclient.la \ $(top_builddir)/common/libcommonversion.la \ $(top_builddir)/common/libparseconf.la \ $(top_builddir)/clients/libupsclient.la: dummy @dotMAKE@ @@ -19,11 +20,10 @@ $(top_builddir)/clients/libupsclient.la: dummy @dotMAKE@ # Builds from root dir arrange stuff decently. Make sure parallel builds # started from scratch right in this dir get dependencies in proper order -# (sub-makes are independent as far as trying to write into same files): -$(top_builddir)/common/libcommon.la: $(top_builddir)/common/libparseconf.la +# (sub-makes are independent as far as trying to write into same files). # Internally (clients/Makefile.am) libupsclient.la requires libcommonclient.la -# and that in turn libparseconf.la: -$(top_builddir)/clients/libupsclient.la: $(top_builddir)/common/libparseconf.la +# (or the shared libnutprivate equivalent), and that in turn libparseconf.la: +$(top_builddir)/common/libcommon.la $(top_builddir)/common/libcommonclient.la: $(top_builddir)/common/libparseconf.la $(top_builddir)/common/libcommonversion.la: $(top_builddir)/include/nut_version.h if ENABLE_SHARED_PRIVATE_LIBS @@ -35,7 +35,11 @@ $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-comm $(top_builddir)/common/libcommonversion-private.la: $(top_builddir)/include/nut_version.h $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-all.la: $(top_builddir)/common/libcommon.la $(top_builddir)/common/libcommonversion-private.la $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-client.la: $(top_builddir)/common/libcommonclient.la $(top_builddir)/common/libcommonversion-private.la -endif ENABLE_SHARED_PRIVATE_LIBS + +$(top_builddir)/clients/libupsclient.la: $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-client.la $(top_builddir)/include/nut_version.h +else !ENABLE_SHARED_PRIVATE_LIBS +$(top_builddir)/clients/libupsclient.la: $(top_builddir)/common/libcommonclient.la $(top_builddir)/include/nut_version.h +endif !ENABLE_SHARED_PRIVATE_LIBS lib_LTLIBRARIES = diff --git a/tools/nut-scanner/Makefile.am b/tools/nut-scanner/Makefile.am index f4295ffff6..b25237b3b0 100644 --- a/tools/nut-scanner/Makefile.am +++ b/tools/nut-scanner/Makefile.am @@ -59,7 +59,7 @@ $(top_builddir)/common/libcommonversion.la: $(top_builddir)/include/nut_version. # To extract the values used in the factually built library, this header's # recipe internally (clients/Makefile.am) requires libupsclient.la, and in # turn libcommonclient.la and in turn libparseconf.la: -$(top_builddir)/clients/libupsclient-version.h: $(top_builddir)/common/libparseconf.la +$(top_builddir)/common/libcommonclient.la: $(top_builddir)/common/libparseconf.la # do not hard depend on '../clients/libupsclient-version.h', since it blocks # 'dist', and is only required for actual build, in which case @@ -81,7 +81,12 @@ $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-comm $(top_builddir)/common/libcommonversion-private.la: $(top_builddir)/include/nut_version.h $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-all.la: $(top_builddir)/common/libcommon.la $(top_builddir)/common/libcommonversion-private.la $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-client.la: $(top_builddir)/common/libcommonclient.la $(top_builddir)/common/libcommonversion-private.la -endif ENABLE_SHARED_PRIVATE_LIBS + +# Additionally depend on SO made from libcommonclient.la: +$(top_builddir)/clients/libupsclient-version.h: $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-client.la +else !ENABLE_SHARED_PRIVATE_LIBS +$(top_builddir)/clients/libupsclient-version.h: $(top_builddir)/common/libcommonclient.la $(top_builddir)/include/nut_version.h +endif !ENABLE_SHARED_PRIVATE_LIBS # We optionally append values to this below bin_PROGRAMS = diff --git a/tools/nutconf/Makefile.am b/tools/nutconf/Makefile.am index 784ee16ae5..dc5a884aff 100644 --- a/tools/nutconf/Makefile.am +++ b/tools/nutconf/Makefile.am @@ -30,7 +30,6 @@ $(top_builddir)/tools/nut-scanner/libnutscan.la: dummy @dotMAKE@ $(top_builddir)/common/libcommon.la $(top_builddir)/common/libcommonclient.la: $(top_builddir)/common/libparseconf.la $(top_builddir)/common/libcommonversion.la: $(top_builddir)/include/nut_version.h $(top_builddir)/clients/libupsclient-version.h: $(top_builddir)/clients/libupsclient.la $(top_builddir)/common/libparseconf.la -$(top_builddir)/clients/libupsclient.la: $(top_builddir)/common/libcommonclient.la if ENABLE_SHARED_PRIVATE_LIBS $(top_builddir)/common/libcommonversion-private.la \ @@ -42,9 +41,11 @@ $(top_builddir)/common/libcommonversion-private.la: $(top_builddir)/include/nut_ $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-all.la: $(top_builddir)/common/libcommon.la $(top_builddir)/common/libcommonversion-private.la $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-client.la: $(top_builddir)/common/libcommonclient.la $(top_builddir)/common/libcommonversion-private.la +$(top_builddir)/clients/libupsclient.la: $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-client.la $(top_builddir)/include/nut_version.h $(top_builddir)/common/libnutconf.la: $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-client.la $(top_builddir)/tools/nut-scanner/libnutscan.la: $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-all.la $(top_builddir)/clients/libupsclient-version.h else !ENABLE_SHARED_PRIVATE_LIBS +$(top_builddir)/clients/libupsclient.la: $(top_builddir)/common/libcommonclient.la $(top_builddir)/include/nut_version.h $(top_builddir)/common/libnutconf.la: $(top_builddir)/common/libcommonclient.la $(top_builddir)/tools/nut-scanner/libnutscan.la: $(top_builddir)/common/libcommon.la $(top_builddir)/common/libcommonversion.la $(top_builddir)/common/libcommonclient.la $(top_builddir)/clients/libupsclient-version.h $(top_builddir)/common/libcommonstr.la endif !ENABLE_SHARED_PRIVATE_LIBS From 628e412afa1a08dd77704a4ab9432fced9ffb4ed Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Feb 2026 16:08:46 +0100 Subject: [PATCH 26/44] appveyor.yml: enable possibility of NSS builds (and confirm OpenSSL) for Windows with Appveyor CI [#1711] Signed-off-by: Jim Klimov --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1ec6a5250d..f905890566 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -63,7 +63,7 @@ install: # versions of packages. - cmd: | REM Prerequisites for NUT per https://github.com/networkupstools/nut/blob/master/docs/config-prereqs.txt : - C:\msys64\usr\bin\bash -lc "date -u; pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain autoconf-wrapper automake-wrapper libtool mingw-w64-x86_64-libltdl gcc ccache mingw-w64-x86_64-ccache git aspell aspell-en python mingw-w64-x86_64-python-pygments mingw-w64-x86_64-winpthreads-git mingw-w64-x86_64-libusb mingw-w64-x86_64-libusb-compat-git mingw-w64-x86_64-neon libneon-devel mingw-w64-x86_64-libgd mingw-w64-x86_64-cppunit" + C:\msys64\usr\bin\bash -lc "date -u; pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain autoconf-wrapper automake-wrapper libtool mingw-w64-x86_64-libltdl gcc ccache mingw-w64-x86_64-ccache git aspell aspell-en python mingw-w64-x86_64-python-pygments mingw-w64-x86_64-winpthreads-git mingw-w64-x86_64-libusb mingw-w64-x86_64-libusb-compat-git mingw-w64-x86_64-neon libneon-devel mingw-w64-x86_64-libgd mingw-w64-x86_64-cppunit mingw-w64-x86_64-nss mingw-w64-x86_64-openssl" REM SKIP mingw-w64-x86_64-libmodbus-git : we custom-build one with USB support REM SKIP for now NUT-Monitor prereqs (runtime Python would require somilar modules; need to fix localization builds like "fr.po"): gettext mingw-w64-x86_64-python-pyqt6 From 64852dda35ccc05ae87b0095f5955b1a73a5d0b1 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Feb 2026 18:58:09 +0100 Subject: [PATCH 27/44] clients/upsclient.{c,h}, docs: introduce upscli_set_debug_level() and a getter to fix shared-private-library builds [#2800, #1711] Surprise (not really, had similar with libnutscanner, but solved differently) - at least WIN32 builds require that all symbols are inside a library we build, so tend to include the binary code regardless of sort-of-shared linking. As the result, we end up linking upsmon etc. with two DLL libraries each carrying its nut_debug_level (and methods that work with it). Signed-off-by: Jim Klimov --- NEWS.adoc | 5 ++- UPGRADING.adoc | 4 +++ clients/Makefile.am | 9 +++++ clients/upsclient.c | 17 ++++++++++ clients/upsclient.h | 10 ++++++ docs/man/Makefile.am | 9 +++++ docs/man/upscli_set_debug_level.txt | 51 +++++++++++++++++++++++++++++ docs/nut.dict | 3 +- 8 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 docs/man/upscli_set_debug_level.txt diff --git a/NEWS.adoc b/NEWS.adoc index fa32899de4..52e8f5a502 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -110,7 +110,10 @@ https://github.com/networkupstools/nut/milestone/12 `upscli_ssl_caps()`. Updated common NUT clients to report this info in their detailed help banners. Done similarly for `upsd`. The NIT (NUT Integration Test) suite piggy-backs on this to add run-time - dependent tests of SSL capability. [issues #3328, #1771] + dependent tests of SSL capability. Added `upscli_set_debug_level()` + and `upscli_set_debug_level()` methods to facilitate NUT debugging + for clients built with shared NUT private libraries. [issues #3328, + #1771, #2800, PR #3330] - NUT for Windows specific updates: * Revised detection of (relative) paths to program and configuration files diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 11c0a38fbb..752de764cc 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -35,6 +35,10 @@ Changes from 2.8.4 to 2.8.5 library files to deliver with the packages (formally versioned and named by NUT release semantic version triplet). [issue #2800] +- Related to the above, `libupsclient` will remove the exported symbol for + `nut_debug_level` variable in a later NUT release, and now introduces the + `upscli_set_debug_level()` and `upscli_get_debug_level()` methods. [PR #3330] + - For ages, most recipes for building NUT had customized the `sysconfdir` to be `/etc/nut`, which is not exactly the *system* configuration directory. This is finally deprecated, with new `--with-confdir` configuration option diff --git a/clients/Makefile.am b/clients/Makefile.am index ed68283355..c4a0ac0894 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -187,6 +187,15 @@ endif WITH_SSL # object .so names would differ) # libupsclient version information +# NOTE: with libnutprivate*common* builds dynamically linked into the same +# program (a typical in-tree NUT client, not typical for out-of-tree third +# party clients) we can end up with two copies of libcommon symbols present +# in each library. It's recommended to explicitly call upscli_set_debug_level() +# instead of ambiguously manipulating the nut_debug_level variable by name. +# TOTHINK: Un-export nut_debug_level from this library to avoid ambiguity +# for the run-time dynamic linker resolution? For now the shared-library +# builds are "exotic", but it makes sense to deprecate this export in a +# future release. libupsclient_la_LDFLAGS = -version-info 7:0:0 libupsclient_la_LDFLAGS += -export-symbols-regex '^(upscli_|nut_debug_level)' #|s_upsdebug|fatalx|fatal_with_errno|xcalloc|xbasename|print_banner_once)' diff --git a/clients/upsclient.c b/clients/upsclient.c index 7dbbbb68e8..5e6f5f1905 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -2074,3 +2074,20 @@ int upscli_str_add_unique_token(char *tgt, size_t tgtsize, const char *token, ) { return str_add_unique_token(tgt, tgtsize, token, callback_always, callback_unique); } + +/* On some platforms, libupsclient builds tend to get a built-in copy + * of the internal code from NUT libcommon library, so for NUT client + * programs using both libraries as dynamically-linked shared code, + * the nut_debug_level setting is backed by independent variables in + * active memory, and upsdebugx() calls suffer if the library's copy + * is never changed from zero. + */ +void upscli_set_debug_level(int lvl) +{ + nut_debug_level = lvl; +} + +int upscli_get_debug_level(void) +{ + return nut_debug_level; +} diff --git a/clients/upsclient.h b/clients/upsclient.h index 7a203d9f8e..ea80550bc6 100644 --- a/clients/upsclient.h +++ b/clients/upsclient.h @@ -99,6 +99,16 @@ typedef struct { const char *upscli_strerror(UPSCONN_t *ups); +/* On some platforms, libupsclient builds tend to get a built-in copy + * of the internal code from NUT libcommon library, so for NUT client + * programs using both libraries as dynamically-linked shared code, + * the nut_debug_level setting is backed by independent variables in + * active memory, and upsdebugx() calls suffer if the library's copy + * is never changed from zero. + */ +void upscli_set_debug_level(int lvl); +int upscli_get_debug_level(void); + /* NOTE: effectively only runs once; re-runs quickly skip out */ int upscli_init(int certverify, const char *certpath, const char *certname, const char *certpasswd); int upscli_cleanup(void); diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 164c39d912..e5a74e8a18 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -546,6 +546,7 @@ SRC_DEV_PAGES = \ upscli_readline.txt \ upscli_report_build_details.txt \ upscli_sendline.txt \ + upscli_set_debug_level.txt \ upscli_splitaddr.txt \ upscli_splitname.txt \ upscli_ssl.txt \ @@ -656,6 +657,11 @@ UPSCLI_SSL_CAPS_DEPS = \ $(UPSCLI_SSL_CAPS_DEPS): upscli_ssl_caps.$(MAN_SECTION_API) +UPSCLI_SET_DEBUG_LEVEL_DEPS = \ + upscli_get_debug_level.$(MAN_SECTION_API) + +$(UPSCLI_SET_DEBUG_LEVEL_DEPS): upscli_set_debug_level.$(MAN_SECTION_API) + INST_MAN_DEV_API_PAGES = \ upsclient.$(MAN_SECTION_API) \ upscli_add_host_cert.$(MAN_SECTION_API) \ @@ -676,6 +682,8 @@ INST_MAN_DEV_API_PAGES = \ upscli_report_build_details.$(MAN_SECTION_API) \ upscli_sendline.$(MAN_SECTION_API) \ upscli_sendline_timeout.$(MAN_SECTION_API) \ + upscli_set_debug_level.$(MAN_SECTION_API) \ + $(UPSCLI_SET_DEBUG_LEVEL_DEPS) \ upscli_splitaddr.$(MAN_SECTION_API) \ upscli_splitname.$(MAN_SECTION_API) \ upscli_ssl.$(MAN_SECTION_API) \ @@ -793,6 +801,7 @@ INST_HTML_DEV_MANS = \ upscli_readline.html \ upscli_report_build_details.html \ upscli_sendline.html \ + upscli_set_debug_level.html \ upscli_splitaddr.html \ upscli_splitname.html \ upscli_ssl.html \ diff --git a/docs/man/upscli_set_debug_level.txt b/docs/man/upscli_set_debug_level.txt new file mode 100644 index 0000000000..f7e057283e --- /dev/null +++ b/docs/man/upscli_set_debug_level.txt @@ -0,0 +1,51 @@ +UPSCLI_SET_DEBUG_LEVEL(3) +========================= + +NAME +---- + +upscli_set_debug_level, upscli_get_debug_level - manipulate the possibly +separate copy of the `nut_debug_level` variable in the `libupsclient` build + +SYNOPSIS +-------- + +------ + #include + + void upscli_set_debug_level(int); + int upscli_get_debug_level(void); +------ + +DESCRIPTION +----------- + +On some platforms, 'libupsclient' builds tend to get a built-in copy +of the internal code from NUT 'libcommon' library, so for NUT client +programs using both libraries as dynamically-linked shared code, +the `nut_debug_level` setting is backed by independent variables in +active memory, and `upsdebugx()` calls suffer if the library's copy +is never changed from zero. + +These methods allow to set or retrieve the value of `nut_debug_level` +setting known by the 'libupsclient' library, regardless of build mode. + +The most likely use (at least in NUT programs) is to call +`upscli_set_debug_level(nut_debug_level);` after changing the +original variable. Values of the debugging level are zero to disable +debug, may be negative for a few special cases, and generally are +positive numbers to cut off the more verbose logging attempts; +otherwise it is up to the code base and NUT style guide practices +to assign certain levels to some classes of messages. + +RETURN VALUE +------------ + +There is no return value for the setter. + +The getter returns the current value of the internal variable. + +SEE ALSO +-------- + +linkman:upscli_init[3], linkman:upscli_report_build_details[3] diff --git a/docs/nut.dict b/docs/nut.dict index 60d9d669b3..16f641d3cb 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3689 utf-8 +personal_ws-1.1 en 3690 utf-8 AAC AAS ABI @@ -2206,6 +2206,7 @@ getenv gethostbyname getopt getproctag +getter gettext gettextize getval From f7ec80312748058a1d786b91673063fb189bd416 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Feb 2026 19:13:12 +0100 Subject: [PATCH 28/44] clients/upsmon.c: use `upscli_set_debug_level(nut_debug_level)` wherever we change nut_debug_level [#1711, #2800, #3330] Signed-off-by: Jim Klimov --- clients/upsmon.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/clients/upsmon.c b/clients/upsmon.c index e5105d239b..f00d01ba00 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -2670,6 +2670,7 @@ static void loadconfig(void) nut_debug_level_args); nut_debug_level = nut_debug_level_args; } + upscli_set_debug_level(nut_debug_level); if (pollfail_log_throttle_max >= 0) { upslogx(LOG_INFO, @@ -3407,12 +3408,14 @@ static void help(const char *arg_progname) nut_debug_level = -2; nut_debug_level_args = -2; nut_debug_level_global = -2; + upscli_set_debug_level(nut_debug_level); loadconfig(); nut_debug_level = old_debug_level; nut_debug_level_args = old_debug_level_args; nut_debug_level_global = old_debug_level_global; + upscli_set_debug_level(nut_debug_level); /* Separate from logs emitted by loadconfig() */ /* printf("\n"); */ @@ -3848,6 +3851,7 @@ int main(int argc, char *argv[]) case 'D': nut_debug_level++; nut_debug_level_args++; + upscli_set_debug_level(nut_debug_level); break; case 'F': foreground = 1; @@ -3916,6 +3920,7 @@ int main(int argc, char *argv[]) nut_debug_level_args = l; } /* else follow -D settings */ } /* else nothing to bother about */ + upscli_set_debug_level(nut_debug_level); } if (cmd) { @@ -4065,8 +4070,10 @@ int main(int argc, char *argv[]) if (checking_flag) { /* Do not normally report the UPSes we would monitor, etc. * from loadconfig() for just checking the killpower flag */ - if (nut_debug_level == 0) + if (nut_debug_level == 0) { nut_debug_level = -2; + upscli_set_debug_level(nut_debug_level); + } } loadconfig(); @@ -4075,8 +4082,10 @@ int main(int argc, char *argv[]) * in upsmon.conf. Note that non-zero debug_min does not impact * foreground running mode. */ - if (nut_debug_level_global > nut_debug_level) + if (nut_debug_level_global > nut_debug_level) { nut_debug_level = nut_debug_level_global; + upscli_set_debug_level(nut_debug_level); + } upsdebugx(1, "debug level is '%d'", nut_debug_level); if (checking_flag) From 41f12ce8fe819a7a984d5014caf216cf68cfc072 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Feb 2026 19:26:13 +0100 Subject: [PATCH 29/44] clients/*.c, drivers/dummy-ups.c, tools/nut-scanner/scan_nut.c: fix libupsclient consumers to use upscli_set_debug_level() where available [#1711, #2800, #3330] Signed-off-by: Jim Klimov --- clients/upsc.c | 1 + clients/upscmd.c | 1 + clients/upsimage.c | 2 ++ clients/upslog.c | 1 + clients/upsrw.c | 1 + clients/upsset.c | 2 ++ clients/upsstats.c | 2 ++ drivers/dummy-ups.c | 7 +++++++ tools/nut-scanner/scan_nut.c | 11 +++++++++++ 9 files changed, 28 insertions(+) diff --git a/clients/upsc.c b/clients/upsc.c index 39af7275e6..eaca4d834d 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -399,6 +399,7 @@ int main(int argc, char **argv) s = getenv("NUT_DEBUG_LEVEL"); if (s && str_to_int(s, &i, 10) && i > 0) { nut_debug_level = i; + upscli_set_debug_level(nut_debug_level); } upsdebugx(1, "Starting NUT client: %s", prog); diff --git a/clients/upscmd.c b/clients/upscmd.c index d5f696aeca..0206918595 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -306,6 +306,7 @@ int main(int argc, char **argv) s = getenv("NUT_DEBUG_LEVEL"); if (s && str_to_int(s, &i, 10) && i > 0) { nut_debug_level = i; + upscli_set_debug_level(nut_debug_level); } upsdebugx(1, "Starting NUT client: %s", prog); diff --git a/clients/upsimage.c b/clients/upsimage.c index 18d7cf882b..d3399d938d 100644 --- a/clients/upsimage.c +++ b/clients/upsimage.c @@ -645,6 +645,7 @@ int main(int argc, char **argv) s = getenv("NUT_DEBUG_LEVEL"); if (s && str_to_int(s, &i, 10) && i > 0) { nut_debug_level = i; + upscli_set_debug_level(nut_debug_level); } #ifdef NUT_CGI_DEBUG_UPSIMAGE @@ -654,6 +655,7 @@ int main(int argc, char **argv) # endif /* Un-comment via make flags when developer-troubleshooting: */ nut_debug_level = NUT_CGI_DEBUG_UPSIMAGE; + upscli_set_debug_level(nut_debug_level); #endif if (nut_debug_level > 0) { diff --git a/clients/upslog.c b/clients/upslog.c index 088808f577..b8561203d9 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -538,6 +538,7 @@ int main(int argc, char **argv) case 'D': nut_debug_level++; + upscli_set_debug_level(nut_debug_level); break; case 'm': { /* var scope */ diff --git a/clients/upsrw.c b/clients/upsrw.c index 0646538e6e..c5ab5fa2ff 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -664,6 +664,7 @@ int main(int argc, char **argv) s = getenv("NUT_DEBUG_LEVEL"); if (s && str_to_int(s, &i, 10) && i > 0) { nut_debug_level = i; + upscli_set_debug_level(nut_debug_level); } upsdebugx(1, "Starting NUT client: %s", prog); diff --git a/clients/upsset.c b/clients/upsset.c index 3074bf63ef..36d231f7d3 100644 --- a/clients/upsset.c +++ b/clients/upsset.c @@ -1149,6 +1149,7 @@ int main(int argc, char **argv) s = getenv("NUT_DEBUG_LEVEL"); if (s && str_to_int(s, &i, 10) && i > 0) { nut_debug_level = i; + upscli_set_debug_level(nut_debug_level); } #ifdef NUT_CGI_DEBUG_UPSSET @@ -1158,6 +1159,7 @@ int main(int argc, char **argv) # endif /* Un-comment via make flags when developer-troubleshooting: */ nut_debug_level = NUT_CGI_DEBUG_UPSSET; + upscli_set_debug_level(nut_debug_level); #endif if (nut_debug_level > 0) { diff --git a/clients/upsstats.c b/clients/upsstats.c index d630769c8c..55ad9b8cf4 100644 --- a/clients/upsstats.c +++ b/clients/upsstats.c @@ -1709,6 +1709,7 @@ int main(int argc, char **argv) s = getenv("NUT_DEBUG_LEVEL"); if (s && str_to_int(s, &i, 10) && i > 0) { nut_debug_level = i; + upscli_set_debug_level(nut_debug_level); } @@ -1719,6 +1720,7 @@ int main(int argc, char **argv) # endif /* Un-comment via make flags when developer-troubleshooting: */ nut_debug_level = NUT_CGI_DEBUG_UPSSTATS; + upscli_set_debug_level(nut_debug_level); #endif if (nut_debug_level > 0) { diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index 82de836382..863740890e 100644 --- a/drivers/dummy-ups.c +++ b/drivers/dummy-ups.c @@ -120,6 +120,8 @@ void upsdrv_initinfo(void) { dummy_info_t *item; + upscli_set_debug_level(nut_debug_level); + switch (mode) { case MODE_DUMMY_ONCE: @@ -266,6 +268,8 @@ void upsdrv_updateinfo(void) { upsdebugx(1, "upsdrv_updateinfo..."); + upscli_set_debug_level(nut_debug_level); + sleep(1); switch (mode) @@ -415,6 +419,7 @@ static int instcmd(const char *cmdname, const char *extra) void upsdrv_help(void) { + upscli_set_debug_level(nut_debug_level); upscli_report_build_details(); } @@ -433,6 +438,8 @@ void upsdrv_initups(void) { const char *val; + upscli_set_debug_level(nut_debug_level); + val = dstate_getinfo("driver.parameter.mode"); if (val) { if (!strcmp(val, "dummy-loop") diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index 98a6518179..f143b67a36 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -52,6 +52,7 @@ static int (*nut_upscli_list_next)(UPSCONN_t *ups, size_t numq, const char **query, size_t *numa, char ***answer); static int (*nut_upscli_disconnect)(UPSCONN_t *ups); static int (*nut_upscli_ssl_caps)(void); +static int (*nut_upscli_set_debug_level)(int); static void (*nut_upscli_report_build_details)(void); /* This variable collects device(s) from a sequential or parallel scan, @@ -158,6 +159,16 @@ int nutscan_load_upsclient_library(const char *libname_path) __func__, symbol); } + *(void **) (&nut_upscli_set_debug_level) = lt_dlsym(dl_handle, + symbol = "upscli_set_debug_level"); + if ((dl_error = lt_dlerror()) != NULL) { + nut_upscli_set_debug_level = NULL; + upsdebugx(1, "%s: %s() not found, using older libupsclient build?", + __func__, symbol); + } else { + (*nut_upscli_set_debug_level)(nut_debug_level); + } + *(void **) (&nut_upscli_report_build_details) = lt_dlsym(dl_handle, symbol = "upscli_report_build_details"); if ((dl_error = lt_dlerror()) != NULL) { From ddd119831fd4c15e46da99a1d19e9016043ff2f4 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Feb 2026 19:50:32 +0100 Subject: [PATCH 30/44] tests/NIT/nit.sh: fix running OpenSSL tests on Windows [#1711] Avoid MSYS2 mangling of cert request subject separated by slashes as if it were a Unix-style path, before it even gets to `openssl`. Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 422b01a434..d4bfd9de40 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -903,6 +903,7 @@ case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in # Generate an AES encrypted private key: openssl genrsa -aes256 -out rootca.key -passout file:.pwfile 4096 # Generate a certificate for CA using that key: + MSYS_NO_PATHCONV=1 \ openssl req -x509 -new -nodes -key rootca.key -passin file:.pwfile -sha256 -days 1826 -out rootca.pem -subj "/CN=${TESTCERT_ROOTCA_NAME}/OU=Test/O=NIT/ST=StateOfChaos/C=US" ;; esac @@ -935,6 +936,7 @@ case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in ;; OpenSSL) # Create a server certificate request: + MSYS_NO_PATHCONV=1 \ openssl req -new -nodes -out server.req -newkey rsa:4096 -passout file:.pwfile -keyout server.key -subj "/CN=${TESTCERT_SERVER_NAME}/OU=Test/O=NIT/ST=StateOfChaos/C=US" cat > server.v3.ext << EOF authorityKeyIdentifier=keyid,issuer From cd41f31f765e362a1737de76789147243a6b5bc3 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Feb 2026 19:56:10 +0100 Subject: [PATCH 31/44] tests/NIT/nit.sh: add a way to run WITHOUT_SSL_TESTS on platforms known to currently fail them [#1711] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index d4bfd9de40..50800b83d6 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -474,6 +474,12 @@ esac log_info "Tested server binaries SSL support: ${WITH_SSL_SERVER}" log_info "Tested server binaries client certificate validation: ${WITH_SSL_SERVER_CLIVAL}" +if [ x"${WITHOUT_SSL_TESTS}" = xtrue ]; then + log_info "Disabling SSL tests (even if they are possible) due to WITHOUT_SSL_TESTS='${WITHOUT_SSL_TESTS}'" + WITH_SSL_CLIENT="none" + WITH_SSL_SERVER="none" +fi + case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in *NSS*) (command -v certutil) || { From 25f0a33213ec94fb6eb7738d4e88eb3b2603f299 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Feb 2026 20:08:30 +0100 Subject: [PATCH 32/44] tests/NIT/Makefile.am: default to run WITHOUT_SSL_TESTS for OpenSSL builds [#1711, #3331] Signed-off-by: Jim Klimov --- tests/NIT/Makefile.am | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/NIT/Makefile.am b/tests/NIT/Makefile.am index b6c929b2bd..944b274240 100644 --- a/tests/NIT/Makefile.am +++ b/tests/NIT/Makefile.am @@ -24,6 +24,15 @@ endif !WITH_CHECK_NIT ABS_TOP_SRCDIR = @ABS_TOP_SRCDIR@ ABS_TOP_BUILDDIR = @ABS_TOP_BUILDDIR@ +# Currently OpenSSL tests fail more often than we like +# Developers can run the script directly or override the make variable +# but default builds should for now turn a blind eye, I guess +if WITH_OPENSSL +WITHOUT_SSL_TESTS = true +else !WITH_OPENSSL +WITHOUT_SSL_TESTS = false +endif !WITH_OPENSSL + # Run in builddir, use script from srcdir # Avoid running "$<" - not all make implementations handle that check-NIT: $(abs_srcdir)/nit.sh @@ -33,6 +42,7 @@ check-NIT: $(abs_srcdir)/nit.sh abs_top_srcdir='$(abs_top_srcdir)' abs_top_builddir='$(abs_top_builddir)' \ ABS_TOP_SRCDIR='$(ABS_TOP_SRCDIR)' ABS_TOP_BUILDDIR='$(ABS_TOP_BUILDDIR)' \ EXEEXT='$(EXEEXT)' \ + WITHOUT_SSL_TESTS='$(WITHOUT_SSL_TESTS)' \ "$(abs_srcdir)/nit.sh" # Make sure pre-requisites for NIT are fresh as we iterate @@ -57,6 +67,7 @@ check-NIT-sandbox: $(abs_srcdir)/nit.sh abs_top_srcdir='$(abs_top_srcdir)' abs_top_builddir='$(abs_top_builddir)' \ ABS_TOP_SRCDIR='$(ABS_TOP_SRCDIR)' ABS_TOP_BUILDDIR='$(ABS_TOP_BUILDDIR)' \ EXEEXT='$(EXEEXT)' \ + WITHOUT_SSL_TESTS='$(WITHOUT_SSL_TESTS)' \ "$(abs_srcdir)/nit.sh" check-NIT-sandbox-devel: $(abs_srcdir)/nit.sh @dotMAKE@ From f2db253f5b5f13017ff155f40d56878bd9270507 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Feb 2026 23:29:48 +0100 Subject: [PATCH 33/44] docs/security.txt, docs/nut.dict: add a note on checking SSL support in run-time build of NUT [#3328] Signed-off-by: Jim Klimov --- docs/nut.dict | 3 ++- docs/security.txt | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/nut.dict b/docs/nut.dict index 16f641d3cb..86b73968fa 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3690 utf-8 +personal_ws-1.1 en 3691 utf-8 AAC AAS ABI @@ -304,6 +304,7 @@ DeviceKit DeviceLogin DeviceLogout Dgtk +Dh Dharm Diehl Dietze diff --git a/docs/security.txt b/docs/security.txt index 5c4bf9d7f8..49811992db 100644 --- a/docs/security.txt +++ b/docs/security.txt @@ -314,6 +314,16 @@ You can choose to use one of them by specifying it with a build option (`--with-nss` or `--with-openssl`). If neither is specified, the configure script will try to detect one of them, with a precedence for OpenSSL. +Starting with NUT v2.8.5, you can check the actual support of your NUT build +with `upsd -Dh` for the server, and `upsmon -Dh` for the typical client, e.g.: + +------ + 0.000000 [D1] Network UPS Tools version 2.8.4.... + 0.019319 [D1] NUT data server was built with SSL support: OpenSSL; + without client certificate validation +------ + + OpenSSL backend usage ~~~~~~~~~~~~~~~~~~~~~ From 0d8ecf6842e8c8c9e38d6ece872e8aa605876856 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 1 Mar 2026 12:23:14 +0100 Subject: [PATCH 34/44] common/common-nut_version.c: add comments to clarify LIBNUTPRIVATE_UPS_VERSION vs UPS_VERSION [#2800] Signed-off-by: Jim Klimov --- common/common-nut_version.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/common-nut_version.c b/common/common-nut_version.c index cbaf506955..a30b31ee31 100644 --- a/common/common-nut_version.c +++ b/common/common-nut_version.c @@ -43,7 +43,11 @@ # define suggest_doc_links LIBNUTPRIVATE_suggest_doc_links # define suggest_NDE_conflict LIBNUTPRIVATE_suggest_NDE_conflict # else /* !BUILD_FOR_SHARED_PRIVATE_LIBS => for leaf binary */ - extern const char *LIBNUTPRIVATE_UPS_VERSION; + /* Refer to UPS_VERSION => LIBNUTPRIVATE_UPS_VERSION (renamed by macro + * above) as exported by the library build so a NUT program can see + * the source code version of libnutprivate-XYZ-...so binary library. + */ + extern const char *LIBNUTPRIVATE_UPS_VERSION; # endif /* !BUILD_FOR_SHARED_PRIVATE_LIBS */ #endif @@ -63,6 +67,8 @@ * depends on nut_version.h), and also to prevent all sources from having * to be recompiled each time the version changes (they only need to be * re-linked). Similarly for other variables below in the code. + * NOTE: Depending on build circumstances, the symbol may in fact be named + * LIBNUTPRIVATE_UPS_VERSION, or not (when built into leaf binaries). */ #include "nut_version.h" const char *UPS_VERSION = NUT_VERSION_MACRO; From f3ac50b2969b9b921c8bad60f29696f04f96bc28 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 1 Mar 2026 13:05:18 +0100 Subject: [PATCH 35/44] ci_build.sh, scripts/Windows/build-mingw-nut.sh, NEWS.adoc: introduce more generally useful support for NUT_SSL_VARIANTS [#1711] Signed-off-by: Jim Klimov --- NEWS.adoc | 4 ++++ ci_build.sh | 33 +++++++++++++++++++++++++++++- scripts/Windows/build-mingw-nut.sh | 12 +++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/NEWS.adoc b/NEWS.adoc index 52e8f5a502..6b1db2998c 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -545,6 +545,10 @@ several `FSD` notifications into one executed action. [PR #3097] * Dropped the `compile` script from Git sources. It originates from automake and is added to work area (if missing) during `autogen.sh` rituals anyway. It is still distributed as part of `make dist` tarball. [#1209] + * Extended `ci_build.sh` and `build-mingw-nut.sh` so that certain values + of `NUT_SSL_VARIANTS=[yes, no, auto, ssl, nss, openssl]` can be used + with generic builds to test a specific code path and not only the + auto-detected one. [#1711] - Upstreamed reference packaging recipes (DEB, RPM) from the 42ITy project which can be used with OBS (Open Build Service by SUSE), both to support diff --git a/ci_build.sh b/ci_build.sh index ddd4159c22..f21387bd27 100755 --- a/ci_build.sh +++ b/ci_build.sh @@ -35,6 +35,11 @@ SCRIPT_ARGS=("$@") # in a different directory and then it would be used with a warning. This may # require that you `make distclean` the original source checkout first: # CI_BUILDDIR=obj BUILD_TYPE=default-all-errors ./ci_build.sh +# +# The NUT_SSL_VARIANTS=[yes, no, auto, ssl, nss, openssl] values can be used +# with generic builds (not only iteration of a default-all-errors* matrix) +# to set specific SSL options in tested NUT builds. +# case "$BUILD_TYPE" in fightwarn) ;; # for default compiler fightwarn-all) @@ -1947,7 +1952,20 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-al case "$BUILD_TYPE" in "default-all-errors"*) ;; # Treated below - *) configure_nut ;; + *) # Final choices that can conflict with the matrix + # tried in default-all-errors* builds + case "${NUT_SSL_VARIANTS}" in + ssl|nss|openssl) + CONFIG_OPTS+=("--with-${NUT_SSL_VARIANTS}") + ;; + yes) CONFIG_OPTS+=("--with-ssl") ;; + no) CONFIG_OPTS+=("--without-ssl") ;; + auto) CONFIG_OPTS+=("--with-ssl=auto") ;; + "") ;; + *) echo "WARNING: Unrecognized NUT_SSL_VARIANTS='${NUT_SSL_VARIANTS}' for a general deterministic build, ignored" >&2 ;; + esac + configure_nut + ;; esac # NOTE: There is also a case "$BUILD_TYPE" above for setting CONFIG_OPTS @@ -2842,6 +2860,17 @@ bindings) CONFIG_OPTS+=("--enable-shared-private-libs") fi + case "${NUT_SSL_VARIANTS}" in + ssl|nss|openssl) + CONFIG_OPTS+=("--with-${NUT_SSL_VARIANTS}") + ;; + yes) CONFIG_OPTS+=("--with-ssl") ;; + no) CONFIG_OPTS+=("--without-ssl") ;; + auto) CONFIG_OPTS+=("--with-ssl=auto") ;; + "") ;; + *) echo "WARNING: Unrecognized NUT_SSL_VARIANTS='${NUT_SSL_VARIANTS}' for a general deterministic build, ignored" >&2 ;; + esac + if [ -n "${BUILD_DEBUGINFO-}" ]; then CONFIG_OPTS+=("--with-debuginfo=${BUILD_DEBUGINFO}") else @@ -2987,6 +3016,8 @@ cross-windows-mingw*) fi # else we have some value from caller export WITH_LIBNUTPRIVATE + export NUT_SSL_VARIANTS + SOURCEMODE="out-of-tree" \ MAKEFLAGS="$PARMAKE_FLAGS" \ KEEP_NUT_REPORT_FEATURE="true" \ diff --git a/scripts/Windows/build-mingw-nut.sh b/scripts/Windows/build-mingw-nut.sh index aca64aa86b..e17aff65fc 100755 --- a/scripts/Windows/build-mingw-nut.sh +++ b/scripts/Windows/build-mingw-nut.sh @@ -158,6 +158,17 @@ do_build_mingw_nut() { ENABLE_NUT_SHARED_PRIVATE_LIBS_FLAG="--enable-shared-private-libs" fi + WITH_SSL_FLAG="" + case "${NUT_SSL_VARIANTS}" in + ssl|nss|openssl) + WITH_SSL_FLAG="--with-${NUT_SSL_VARIANTS}" ;; + yes) WITH_SSL_FLAG="--with-ssl" ;; + no) WITH_SSL_FLAG="--without-ssl" ;; + auto) WITH_SSL_FLAG="--with-ssl=auto" ;; + "") ;; + *) echo "WARNING: Unrecognized NUT_SSL_VARIANTS='${NUT_SSL_VARIANTS}' for a general deterministic build, ignored" >&2 ;; + esac + # Note: installation prefix here is "/" and desired INSTALL_DIR # location is passed to `make install` as DESTDIR below. # WIN32 builds resolve PREFIX'ed paths relative to each current binary @@ -171,6 +182,7 @@ do_build_mingw_nut() { $CONFIGURE_SCRIPT $HOST_FLAG $BUILD_FLAG --prefix=/ \ $KEEP_NUT_REPORT_FEATURE_FLAG \ $ENABLE_NUT_SHARED_PRIVATE_LIBS_FLAG \ + $WITH_SSL_FLAG \ PKG_CONFIG_PATH="${ARCH_PREFIX}/lib/pkgconfig" \ --with-all=auto \ --with-doc="man=auto html-single=auto html-chunked=skip pdf=skip" \ From c149f6f01127f64e16ba799b01297cb18fe9a303 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 1 Mar 2026 13:07:40 +0100 Subject: [PATCH 36/44] appveyor.yml: pass NUT_SSL_VARIANTS=nss to the builds, so we have some known-functional SSL support in the NUT for Windows pre-built tarballs [#3328, #1711] Signed-off-by: Jim Klimov --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index f905890566..b9c29eef93 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -115,7 +115,7 @@ build_script: REM to find "nearby" program or configuration files (see common.c REM for current implementation). Hard-coded fallback strings may REM end up getting used in those cases. - C:\msys64\usr\bin\bash -lc 'date -u; PATH="/mingw64/bin:$PATH" CI_SKIP_CHECK=true CANBUILD_WITH_LIBMODBUS_USB=yes WITH_LIBNUTPRIVATE=true ./ci_build.sh --with-docs=no' + C:\msys64\usr\bin\bash -lc 'date -u; PATH="/mingw64/bin:$PATH" CI_SKIP_CHECK=true CANBUILD_WITH_LIBMODBUS_USB=yes WITH_LIBNUTPRIVATE=true NUT_SSL_VARIANTS=nss ./ci_build.sh --with-docs=no' after_build: From 4b250347f183a1ea7638172efd64b125c7bc117b Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 1 Mar 2026 13:32:57 +0100 Subject: [PATCH 37/44] tests/NIT/nit.sh: disable NUT_QUIET_INIT_SSL when we actually test SSL [#1711] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 50800b83d6..dce26f6253 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -75,6 +75,7 @@ LANG=C LC_ALL=C export TZ LANG LC_ALL +# May flip to "false" if SSL tests are enabled NUT_QUIET_INIT_SSL="true" export NUT_QUIET_INIT_SSL @@ -1149,6 +1150,8 @@ EOF # echo "DISABLE_WEAK_SSL true" >> "$NUT_CONFPATH/upsd.conf" \ # || die "Failed to populate temporary FS structure for the NIT: upsd.conf" + NUT_QUIET_INIT_SSL=false + export NUT_QUIET_INIT_SSL } ### upsd.users: ################################################## @@ -1380,6 +1383,9 @@ EOF || die "Failed to populate temporary FS structure for the NIT: upsmon.conf" ;; esac + + NUT_QUIET_INIT_SSL=false + export NUT_QUIET_INIT_SSL } ### ups.conf: ################################################## From 7396576784faf1b1fc07b7047f1daaabc95b6a72 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 1 Mar 2026 15:59:33 +0100 Subject: [PATCH 38/44] clients/upsclient.c: report in log if Certificate verification (by client) succeeded [#1711] Signed-off-by: Jim Klimov --- clients/upsclient.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clients/upsclient.c b/clients/upsclient.c index 5e6f5f1905..4db525a3a1 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -1274,7 +1274,9 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags upslogx(LOG_INFO, "Connected to NUT server %s in SSL", host); if (certverify == 0) { /* you REALLY should set CERTVERIFY to 1 if using SSL... */ - upslogx(LOG_WARNING, "Certificate verification is disabled"); + upslogx(LOG_WARNING, "Certificate verification (by client) is disabled"); + } else { + upsdebugx(1, "Certificate verification (by client) is enabled, and apparently succeeded"); } } } From 15c78db01aec511d94d57176efaf699ba5cac857 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 1 Mar 2026 16:00:33 +0100 Subject: [PATCH 39/44] tests/NIT/nit.sh: fix use of CERTPATH with OpenSSL tests [#1711] Signed-off-by: Jim Klimov --- tests/NIT/nit.sh | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index dce26f6253..82a7700543 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -915,7 +915,20 @@ case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in ;; esac - ls -l "${TESTCERT_PATH_ROOTCA}"/rootca.pem + case "${WITH_SSL_CLIENT}${WITH_SSL_SERVER}" in + *OpenSSL*) + # OpenSSL CA trust "database" should include hashes + # of CA PEM certificates as symlinks to actual files: + CERTHASH="`openssl x509 -subject_hash -in rootca.pem | head -1`" + ln -s rootca.pem "${CERTHASH}".0 + ln -s rootca.pem "${CERTHASH}" + ls -l "${TESTCERT_PATH_ROOTCA}"/rootca.pem "${TESTCERT_PATH_ROOTCA}/${CERTHASH}"* + ;; + *) + ls -l "${TESTCERT_PATH_ROOTCA}"/rootca.pem + ;; + esac + ) mkdir -p "${TESTCERT_PATH_SERVER}" @@ -1020,10 +1033,13 @@ EOF esac # This does not seem to cause NUT clients to trust nor distrust -# (or anyhow verify) a presented server certificate: +# (or anyhow verify) a presented server certificate, but just in case: #if [ "${WITH_SSL_CLIENT}" = OpenSSL ] ; then -# SSL_CERT_DIR="${TESTCERT_PATH_CLIENT}" +# SSL_CERT_DIR="${TESTCERT_PATH_ROOTCA}" # export SSL_CERT_DIR +# +# SSL_CERT_FILE="${TESTCERT_PATH_ROOTCA}/rootca.pem" +# export SSL_CERT_FILE #fi # This file is not used by the test code, it is an @@ -1335,7 +1351,7 @@ generatecfg_upsmon_add_SSL() { # by the generatecfg_upsd_add_SSL() method. # We only support CERTPATH (to recognize servers), FORCESSL and # CERTVERIFY in OpenSSL builds. -CERTPATH "${TESTCERT_PATH_CLIENT}" +CERTPATH "${TESTCERT_PATH_ROOTCA}" EOF if [ x"${WITH_SSL_SERVER}" != xnone ] ; then From 538c287e62969a3dea3a7c22e7da8f15231a6b1b Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 1 Mar 2026 16:17:56 +0100 Subject: [PATCH 40/44] tests/NIT/Makefile.am: re-allow running OpenSSL tests by default [#1711] Signed-off-by: Jim Klimov --- tests/NIT/Makefile.am | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/NIT/Makefile.am b/tests/NIT/Makefile.am index 944b274240..1e12a84980 100644 --- a/tests/NIT/Makefile.am +++ b/tests/NIT/Makefile.am @@ -24,14 +24,14 @@ endif !WITH_CHECK_NIT ABS_TOP_SRCDIR = @ABS_TOP_SRCDIR@ ABS_TOP_BUILDDIR = @ABS_TOP_BUILDDIR@ -# Currently OpenSSL tests fail more often than we like -# Developers can run the script directly or override the make variable -# but default builds should for now turn a blind eye, I guess -if WITH_OPENSSL -WITHOUT_SSL_TESTS = true -else !WITH_OPENSSL +# Early in issue #1711 OpenSSL tests failed more often than we liked, so +# this toggle was added. It may still be useful for CI on some platforms. +# Developers can run the script directly or override the make variable. +### if WITH_OPENSSL +### WITHOUT_SSL_TESTS = true +### else !WITH_OPENSSL WITHOUT_SSL_TESTS = false -endif !WITH_OPENSSL +### endif !WITH_OPENSSL # Run in builddir, use script from srcdir # Avoid running "$<" - not all make implementations handle that From af5da88033992f8c0d23e39c86bacf39c5cd7cae Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 2 Mar 2026 10:35:20 +0100 Subject: [PATCH 41/44] tests/NIT/Makefile.am: OpenSSL remains flaky on many platforms, work on actual code may be needed to rectify it after all [#3331, #1711] Signed-off-by: Jim Klimov --- tests/NIT/Makefile.am | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/NIT/Makefile.am b/tests/NIT/Makefile.am index 1e12a84980..eac7c165ca 100644 --- a/tests/NIT/Makefile.am +++ b/tests/NIT/Makefile.am @@ -27,11 +27,11 @@ ABS_TOP_BUILDDIR = @ABS_TOP_BUILDDIR@ # Early in issue #1711 OpenSSL tests failed more often than we liked, so # this toggle was added. It may still be useful for CI on some platforms. # Developers can run the script directly or override the make variable. -### if WITH_OPENSSL -### WITHOUT_SSL_TESTS = true -### else !WITH_OPENSSL +if WITH_OPENSSL +WITHOUT_SSL_TESTS = true +else !WITH_OPENSSL WITHOUT_SSL_TESTS = false -### endif !WITH_OPENSSL +endif !WITH_OPENSSL # Run in builddir, use script from srcdir # Avoid running "$<" - not all make implementations handle that From 88cb9b00ef0f6101e8dc232eff6a19b1067891e8 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Wed, 4 Mar 2026 09:09:09 +0100 Subject: [PATCH 42/44] NEWS.adoc, UPGRADING.adoc: update document intro paragraphs Signed-off-by: Jim Klimov --- NEWS.adoc | 7 ++++++- UPGRADING.adoc | 11 +++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index 6b1db2998c..859ae7188b 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -3,7 +3,12 @@ NUT Release Notes ================= endif::txt[] -If you're upgrading from an earlier version, see the link:UPGRADING.adoc[] file. +This document summarizes the practical side of changes coming with each +newer release development, as compared to the preceding release. + +If you're upgrading from an earlier version, or are a package maintainer, +please see also the link:UPGRADING.adoc[] file about anticipated impacts +of ongoing development on existing deployments and third-party consumers. Please note that web and source document links, product and service names listed in historic entries of past releases may no longer be relevant. diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 752de764cc..d840bee5c0 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -4,8 +4,15 @@ Upgrading notes endif::txt[] This file lists changes that affect users who installed older versions -of this software. When upgrading from an older version, be sure to -check this file to see if you need to make changes to your system. +of this software, or third-party integrations and library or data consumers. +When upgrading from an older NUT version, be sure to check this file to +see if you need to make changes to your system. + +We welcome feedback from package maintainers -- if you had to patch something +out, or work around something in NUT code or recipes, please let us know in +the issue tracker. Chances are, other distributions feel your pain, and some +generalized solution belongs in the upstream project as an easy to use build +configuration toggle to be shared by all interested downstream projects. [NOTE] ====== From fd7f4f48f36753eadec013dcdd123b3114a02dad Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 5 Mar 2026 00:36:11 +0100 Subject: [PATCH 43/44] clients/Makefile.am: fix dependency between upsclient.c and nut_version.h [#3328] Signed-off-by: Jim Klimov --- clients/Makefile.am | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/clients/Makefile.am b/clients/Makefile.am index c4a0ac0894..c7194d0b83 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -163,10 +163,14 @@ upsstats_cgi_LDADD = $(LDADD_CLIENT) $(top_builddir)/common/libcommonstrjson.la ################################## Plain C client library (libupsclient) : -# not LDADD... why? -libupsclient_la_SOURCES = upsclient.c upsclient.h -upsclient.c: $(top_builddir)/include/nut_version.h # NOTE: The library does not require libcommonversion.la +# but it needs nut_version.h made before the rest of build, +# to include it into upsclient.c (without an explicit link, +# this target is sometimes missed in parallel builds): +libupsclient_la_SOURCES = $(top_builddir)/include/nut_version.h $(srcdir)/upsclient.c upsclient.h +### $(srcdir)/upsclient.c: $(top_builddir)/include/nut_version.h + +# not LDADD... why? if ENABLE_SHARED_PRIVATE_LIBS libupsclient_la_LIBADD = \ $(top_builddir)/common/libnutprivate-@NUT_SOURCE_GITREV_SEMVER_UNDERSCORES@-common-client.la From ddde23bc85ff2c9d93a53c7d7bc6a42bac2c9ad4 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 5 Mar 2026 02:47:48 +0100 Subject: [PATCH 44/44] clients/Makefile.am: libupsclient-version.h: sanity-check that input file libupsclient.la exists and is not empty [#3330] Signed-off-by: Jim Klimov --- clients/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/clients/Makefile.am b/clients/Makefile.am index c7194d0b83..659dfdd51e 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -226,6 +226,7 @@ endif HAVE_WINDOWS # ways to skip this rebuild noise for common NUT parallel `make -j N all` runs. CLEANFILES += libupsclient-version.h libupsclient-version.h.tmp* libupsclient-version.h: libupsclient.la + @test -s '$?' || { echo "[Error] Missing or empty input file: $?" >&2 ; exit 1; } @if [ -s '$@' -a -s '$?' ] ; then \ if test -n "`find '$@' -newer '$?' 2>/dev/null`" ; then \ if [ x"$(MAINTAINER_GENERATE_HEADER_DEBUG)" = xyes ] ; then \