Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package libupnp for openSUSE:Factory checked 
in at 2026-11-06 19:33:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libupnp (Old)
 and      /work/SRC/openSUSE:Factory/.libupnp.new.3759290 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "libupnp"

Fri Nov  6 19:33:55 2026 rev:56 rq:1395070 version:22.2.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/libupnp/libupnp.changes  2026-11-01 
22:22:29.906709766 +0100
+++ /work/SRC/openSUSE:Factory/.libupnp.new.3759290/libupnp.changes     
2026-11-06 19:37:01.688575355 +0100
@@ -1,0 +2,6 @@
+Wed Nov  4 17:15:36 UTC 2026 - Jan Engelhardt <[email protected]>
+
+- Update to release 22.2
+  * Cast input to ctype calls to unsigned char
+
+-------------------------------------------------------------------

Old:
----
  libupnp-22.1.10.tar.bz2
  libupnp-22.1.8.tar.bz2

New:
----
  libupnp-22.2.0.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ libupnp.spec ++++++
--- /var/tmp/diff_new_pack.vPx9Cd/_old  2026-11-06 19:37:02.883625343 +0100
+++ /var/tmp/diff_new_pack.vPx9Cd/_new  2026-11-06 19:37:02.885625427 +0100
@@ -19,7 +19,7 @@
 
 %define pnpver 22
 Name:           libupnp
-Version:        22.1.10
+Version:        22.2.0
 Release:        0
 Summary:        An implementation of Universal Plug and Play (UPnP)
 License:        BSD-3-Clause

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.vPx9Cd/_old  2026-11-06 19:37:02.938627644 +0100
+++ /var/tmp/diff_new_pack.vPx9Cd/_new  2026-11-06 19:37:02.944627895 +0100
@@ -1,5 +1,5 @@
-mtime: 1793319610
-commit: fea2f899ca7120ff7016375ed47b89ee6539649b5b1510dce9c601405e3da356
+mtime: 1793812723
+commit: c6f10f7be6bdf52b7f3911ea4ddd51196dc6ac0cb6da7d81cbbcd0db80b789fe
 url: https://src.opensuse.org/jengelh/libupnp
 revision: master
 

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-11-04 18:18:43.000000000 +0100
@@ -0,0 +1 @@
+.osc

++++++ libupnp-22.1.10.tar.bz2 -> libupnp-22.2.0.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/CMakeLists.txt 
new/libupnp-22.2.0/CMakeLists.txt
--- old/libupnp-22.1.10/CMakeLists.txt  2026-10-29 21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/CMakeLists.txt   2026-11-04 14:48:44.000000000 +0100
@@ -7,7 +7,7 @@
 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 
 project(PUPNP
-       VERSION 22.1.10
+       VERSION 22.2.0
        LANGUAGES C)
 
 include(GNUInstallDirs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/ChangeLog 
new/libupnp-22.2.0/ChangeLog
--- old/libupnp-22.1.10/ChangeLog       2026-10-29 21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/ChangeLog        2026-11-04 14:48:44.000000000 +0100
@@ -1,4 +1,33 @@
 *******************************************************************************
+Version 22.2.0
+*******************************************************************************
+
+- GHSA-8rmx-x2cq-xh33: Authenticate the server of HTTPS connections. With
+  UPNP_ENABLE_OPEN_SSL, the client accepted any certificate and never checked
+  the host name, so an on-path attacker or a malicious device could intercept
+  an https:// connection. UpnpInitSslContext() now sets SSL_VERIFY_PEER with
+  the system's default trust store, and every connection checks that the
+  certificate matches the host of the URL (DNS name with SNI, or IP address).
+  SOAP actions, GENA subscriptions and NOTIFY, UpnpDownloadUrlItem() and
+  UpnpDownloadXmlDoc() have no TLS and sent https:// requests in clear; they
+  now return UPNP_E_INVALID_URL.
+  NOTE: this changes the default behaviour. A server with a self-signed or
+  otherwise untrusted certificate is now rejected; add its certificate or CA
+  to the context returned by UpnpGetSslCtx(), e.g. with
+  SSL_CTX_load_verify_locations(). pupnp honours the verify mode of the
+  context: SSL_VERIFY_NONE turns server authentication off, and a context
+  passed to UpnpSetSslCtx() must enable SSL_VERIFY_PEER and load a trust
+  store itself.
+
+- PR #666: Cast to unsigned char before calling the <ctype.h> functions in
+  the URI and HTTP parsers. parse_uri(), the HTTP header matching, the Range
+  parser and the pre-Vista inet_pton6() passed a plain char, so where char is
+  signed a byte from 0x80 to 0xFE became a negative int, which is undefined
+  behaviour (C11 7.4). These bytes come from the network: the request line,
+  SSDP LOCATION, description documents and GENA Callback headers.
+
+
+*******************************************************************************
 Version 22.1.10
 *******************************************************************************
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/README.md 
new/libupnp-22.2.0/README.md
--- old/libupnp-22.1.10/README.md       2026-10-29 21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/README.md        2026-11-04 14:48:44.000000000 +0100
@@ -108,6 +108,7 @@
 
 | Release Number | Date       | History                                  |
 | -------------- | ---------- | ---------------------------------------- |
+| 22.1.10        | 2026-10-29 | [Portable UPnP SDK][Portable UPnP SDK]   |
 | 22.1.9         | 2026-10-28 | [Portable UPnP SDK][Portable UPnP SDK]   |
 | 22.1.8         | 2026-09-30 | [Portable UPnP SDK][Portable UPnP SDK]   |
 | 22.1.7         | 2026-09-29 | [Portable UPnP SDK][Portable UPnP SDK]   |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/docs/Doxyfile 
new/libupnp-22.2.0/docs/Doxyfile
--- old/libupnp-22.1.10/docs/Doxyfile   2026-10-29 21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/docs/Doxyfile    2026-11-04 14:48:44.000000000 +0100
@@ -38,7 +38,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 22.1.10
+PROJECT_NUMBER         = 22.2.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/gtest/CMakeLists.txt 
new/libupnp-22.2.0/gtest/CMakeLists.txt
--- old/libupnp-22.1.10/gtest/CMakeLists.txt    2026-10-29 21:17:58.000000000 
+0100
+++ new/libupnp-22.2.0/gtest/CMakeLists.txt     2026-11-04 14:48:44.000000000 
+0100
@@ -8,6 +8,12 @@
        UPNP_addGTest(test_ssl_ctx test_ssl_ctx.cpp)
 endif()
 
+# regression test for GHSA-8rmx-x2cq-xh33 — server authentication; the test
+# server uses POSIX sockets.
+if(UPNP_ENABLE_OPEN_SSL AND NOT WIN32)
+       UPNP_addGTest(test_ssl_verify test_ssl_verify.cpp)
+endif()
+
 #UPNP_addGTest (test_UpnpHttpHeaderList test_UpnpHttpHeaderList.cpp
 #      ADDITIONAL_INCLUDE_DIRS 
${CMAKE_CURRENT_SOURCE_DIR}/../upnp/src/threadutil/
 #)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/gtest/test_ssl_verify.cpp 
new/libupnp-22.2.0/gtest/test_ssl_verify.cpp
--- old/libupnp-22.1.10/gtest/test_ssl_verify.cpp       1970-01-01 
01:00:00.000000000 +0100
+++ new/libupnp-22.2.0/gtest/test_ssl_verify.cpp        2026-11-04 
14:48:44.000000000 +0100
@@ -0,0 +1,330 @@
+// regression: GHSA-8rmx-x2cq-xh33
+//
+// The HTTPS client must authenticate the server: the certificate chain is
+// verified by default and the certificate must match the host of the URL.
+// HTTP paths that have no TLS must not send an https:// request in clear.
+
+#include "upnp.h"
+
+#include "gtest/gtest.h"
+
+#ifdef UPNP_ENABLE_OPEN_SSL
+
+       #include <openssl/x509v3.h>
+
+       #include <arpa/inet.h>
+       #include <netdb.h>
+       #include <netinet/in.h>
+       #include <poll.h>
+       #include <signal.h>
+       #include <sys/socket.h>
+       #include <sys/time.h>
+       #include <unistd.h>
+
+       #include <atomic>
+       #include <cstring>
+       #include <string>
+       #include <thread>
+
+namespace
+{
+
+EVP_PKEY *make_key()
+{
+       EVP_PKEY *pkey = nullptr;
+       EVP_PKEY_CTX *kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr);
+       if (kctx && EVP_PKEY_keygen_init(kctx) == 1 &&
+               EVP_PKEY_CTX_set_ec_paramgen_curve_nid(
+                       kctx, NID_X9_62_prime256v1) == 1) {
+               EVP_PKEY_keygen(kctx, &pkey);
+       }
+       EVP_PKEY_CTX_free(kctx);
+       return pkey;
+}
+
+/* Self-signed certificate with the given subjectAltName. */
+X509 *make_cert(EVP_PKEY *pkey, const char *san)
+{
+       X509 *x = X509_new();
+       X509_set_version(x, 2);
+       ASN1_INTEGER_set(X509_get_serialNumber(x), 1);
+       X509_gmtime_adj(X509_getm_notBefore(x), -60);
+       X509_gmtime_adj(X509_getm_notAfter(x), 3600);
+       X509_set_pubkey(x, pkey);
+       X509_NAME *name = X509_NAME_new();
+       X509_NAME_add_entry_by_txt(name,
+               "CN",
+               MBSTRING_ASC,
+               (const unsigned char *)"pupnp-test",
+               -1,
+               -1,
+               0);
+       X509_set_subject_name(x, name);
+       X509_set_issuer_name(x, name);
+       X509_NAME_free(name);
+       X509V3_CTX v3;
+       X509V3_set_ctx_nodb(&v3);
+       X509V3_set_ctx(&v3, x, x, nullptr, nullptr, 0);
+       X509_EXTENSION *ext = X509V3_EXT_conf_nid(
+               nullptr, &v3, NID_subject_alt_name, (char *)san);
+       X509_add_ext(x, ext, -1);
+       X509_EXTENSION_free(ext);
+       X509_sign(x, pkey, EVP_sha256());
+       return x;
+}
+
+/* The address pupnp connects to for a host name (first usable result). */
+int loopback_family(const char *host)
+{
+       struct addrinfo hints;
+       struct addrinfo *res = nullptr;
+       int family = AF_INET;
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+       if (getaddrinfo(host, nullptr, &hints, &res) == 0) {
+               for (struct addrinfo *r = res; r; r = r->ai_next) {
+                       if (r->ai_family == AF_INET ||
+                               r->ai_family == AF_INET6) {
+                               family = r->ai_family;
+                               break;
+                       }
+               }
+               freeaddrinfo(res);
+       }
+       return family;
+}
+
+/* One-shot TLS server on a loopback address. */
+class TlsServer
+{
+public:
+       std::atomic<bool> handshake_ok{false};
+       std::atomic<bool> plaintext{false};
+       unsigned short port = 0;
+
+       bool start(X509 *cert, EVP_PKEY *pkey, int family = AF_INET)
+       {
+               family_ = family;
+               ctx_ = SSL_CTX_new(TLS_server_method());
+               if (!ctx_ || SSL_CTX_use_certificate(ctx_, cert) != 1 ||
+                       SSL_CTX_use_PrivateKey(ctx_, pkey) != 1)
+                       return false;
+               lfd_ = socket(family, SOCK_STREAM, 0);
+               if (lfd_ < 0)
+                       return false;
+               struct sockaddr_storage ss;
+               socklen_t len;
+               memset(&ss, 0, sizeof(ss));
+               if (family == AF_INET6) {
+                       struct sockaddr_in6 *a = (struct sockaddr_in6 *)&ss;
+                       a->sin6_family = AF_INET6;
+                       a->sin6_addr = in6addr_loopback;
+                       len = sizeof(*a);
+               } else {
+                       struct sockaddr_in *a = (struct sockaddr_in *)&ss;
+                       a->sin_family = AF_INET;
+                       a->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+                       len = sizeof(*a);
+               }
+               if (bind(lfd_, (struct sockaddr *)&ss, len) != 0 ||
+                       listen(lfd_, 1) != 0 ||
+                       getsockname(lfd_, (struct sockaddr *)&ss, &len) != 0)
+                       return false;
+               port = ntohs(family == AF_INET6
+                                    ? ((struct sockaddr_in6 *)&ss)->sin6_port
+                                    : ((struct sockaddr_in *)&ss)->sin_port);
+               thread_ = std::thread(&TlsServer::serve, this);
+               return true;
+       }
+
+       /* Wakes the server if the client never connected, then joins it. */
+       void stop()
+       {
+               if (thread_.joinable()) {
+                       stopping_ = true;
+                       int fd = socket(family_, SOCK_STREAM, 0);
+                       struct sockaddr_storage ss;
+                       socklen_t len = sizeof(ss);
+                       getsockname(lfd_, (struct sockaddr *)&ss, &len);
+                       if (connect(fd, (struct sockaddr *)&ss, len) != 0) {
+                               /* The server already exited. */
+                       }
+                       thread_.join();
+                       close(fd);
+               }
+               if (lfd_ >= 0)
+                       close(lfd_);
+               lfd_ = -1;
+               SSL_CTX_free(ctx_);
+               ctx_ = nullptr;
+       }
+
+       ~TlsServer() { stop(); }
+
+private:
+       void serve()
+       {
+               struct pollfd p = {lfd_, POLLIN, 0};
+               if (poll(&p, 1, 10000) != 1)
+                       return;
+               int fd = accept(lfd_, nullptr, nullptr);
+               if (fd < 0)
+                       return;
+               if (stopping_) {
+                       close(fd);
+                       return;
+               }
+               struct timeval tv = {5, 0};
+               setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+               unsigned char first = 0;
+               if (recv(fd, &first, 1, MSG_PEEK) == 1 && first != 0x16)
+                       plaintext = true; /* not a TLS handshake record */
+               SSL *ssl = SSL_new(ctx_);
+               SSL_set_fd(ssl, fd);
+               if (SSL_accept(ssl) == 1) {
+                       handshake_ok = true;
+                       char buf[256];
+                       while (SSL_read(ssl, buf, sizeof(buf)) > 0) {
+                       }
+               }
+               SSL_free(ssl);
+               close(fd);
+       }
+
+       SSL_CTX *ctx_ = nullptr;
+       int lfd_ = -1;
+       int family_ = AF_INET;
+       std::atomic<bool> stopping_{false};
+       std::thread thread_;
+};
+
+class SslVerifyTest : public ::testing::Test
+{
+protected:
+       void SetUp() override
+       {
+               pkey_ = make_key();
+               ASSERT_NE(nullptr, pkey_);
+               ASSERT_EQ(UPNP_E_SUCCESS,
+                       UpnpInitSslContext(0, TLS_client_method()));
+       }
+
+       void TearDown() override
+       {
+               server_.stop();
+               UpnpSetSslCtx(nullptr);
+               X509_free(cert_);
+               EVP_PKEY_free(pkey_);
+       }
+
+       void serve(const char *san, int family = AF_INET)
+       {
+               cert_ = make_cert(pkey_, san);
+               ASSERT_NE(nullptr, cert_);
+               ASSERT_TRUE(server_.start(cert_, pkey_, family));
+       }
+
+       void trust_server_cert()
+       {
+               X509_STORE *store = SSL_CTX_get_cert_store(UpnpGetSslCtx());
+               ASSERT_EQ(1, X509_STORE_add_cert(store, cert_));
+       }
+
+       std::string url(const char *host)
+       {
+               return std::string("https://";) + host + ":" +
+                      std::to_string(server_.port) + "/";
+       }
+
+       int open(const std::string &u)
+       {
+               void *handle = nullptr;
+               int ret = UpnpOpenHttpConnection(u.c_str(), &handle, 5);
+               if (handle)
+                       UpnpCloseHttpConnection(handle);
+               server_.stop();
+               return ret;
+       }
+
+       EVP_PKEY *pkey_ = nullptr;
+       X509 *cert_ = nullptr;
+       TlsServer server_;
+};
+
+} // namespace
+
+TEST_F(SslVerifyTest, UntrustedCertificateIsRejected)
+{
+       serve("IP:127.0.0.1");
+       EXPECT_NE(UPNP_E_SUCCESS, open(url("127.0.0.1")));
+       EXPECT_FALSE(server_.handshake_ok);
+}
+
+TEST_F(SslVerifyTest, TrustedCertificateForAnotherIpIsRejected)
+{
+       serve("IP:127.0.0.2");
+       trust_server_cert();
+       EXPECT_NE(UPNP_E_SUCCESS, open(url("127.0.0.1")));
+       EXPECT_FALSE(server_.handshake_ok);
+}
+
+TEST_F(SslVerifyTest, TrustedCertificateForAnotherNameIsRejected)
+{
+       serve("DNS:not-the-host", loopback_family("localhost"));
+       trust_server_cert();
+       EXPECT_NE(UPNP_E_SUCCESS, open(url("localhost")));
+       EXPECT_FALSE(server_.handshake_ok);
+}
+
+TEST_F(SslVerifyTest, TrustedCertificateForTheIpIsAccepted)
+{
+       serve("IP:127.0.0.1");
+       trust_server_cert();
+       EXPECT_EQ(UPNP_E_SUCCESS, open(url("127.0.0.1")));
+       EXPECT_TRUE(server_.handshake_ok);
+}
+
+TEST_F(SslVerifyTest, TrustedCertificateForTheNameIsAccepted)
+{
+       serve("DNS:localhost", loopback_family("localhost"));
+       trust_server_cert();
+       EXPECT_EQ(UPNP_E_SUCCESS, open(url("localhost")));
+       EXPECT_TRUE(server_.handshake_ok);
+}
+
+/* An application may opt out of verification on the context it owns. */
+TEST_F(SslVerifyTest, ApplicationCanDisableVerification)
+{
+       serve("IP:127.0.0.2");
+       SSL_CTX_set_verify(UpnpGetSslCtx(), SSL_VERIFY_NONE, nullptr);
+       EXPECT_EQ(UPNP_E_SUCCESS, open(url("127.0.0.1")));
+       EXPECT_TRUE(server_.handshake_ok);
+}
+
+/* Paths without TLS must not send an https:// request in clear text. */
+TEST_F(SslVerifyTest, DownloadOfHttpsUrlIsNotSentInClear)
+{
+       serve("IP:127.0.0.1");
+       trust_server_cert();
+       char *buf = nullptr;
+       char content_type[LINE_SIZE];
+       int ret = UpnpDownloadUrlItem(
+               url("127.0.0.1").c_str(), &buf, content_type);
+       free(buf);
+       server_.stop();
+       EXPECT_FALSE(server_.plaintext);
+       EXPECT_EQ(UPNP_E_INVALID_URL, ret);
+}
+
+#endif /* UPNP_ENABLE_OPEN_SSL */
+
+int main(int argc, char **argv)
+{
+#ifdef UPNP_ENABLE_OPEN_SSL
+       /* The test server writes to clients that abort the handshake. */
+       signal(SIGPIPE, SIG_IGN);
+#endif
+       ::testing::InitGoogleTest(&argc, argv);
+       return RUN_ALL_TESTS();
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/upnp/inc/upnp.h 
new/libupnp-22.2.0/upnp/inc/upnp.h
--- old/libupnp-22.1.10/upnp/inc/upnp.h 2026-10-29 21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/upnp/inc/upnp.h  2026-11-04 14:48:44.000000000 +0100
@@ -555,6 +555,13 @@
  * \brief Initializes the OpenSSL library, and the OpenSSL context for use
  * with pupnp
  *
+ * The context authenticates HTTPS servers: it uses \c SSL_VERIFY_PEER with
+ * the system's default trust store, and every connection checks that the
+ * server certificate matches the host of the URL (DNS name or IP address).
+ * A server with a self-signed or otherwise untrusted certificate is
+ * rejected. To trust it, add its certificate or CA to the context returned by
+ * UpnpGetSslCtx() (e.g. with \c SSL_CTX_load_verify_locations()).
+ *
  * \note This method is only enabled if pupnp is compiled with open-ssl 
support.
  *
  * \return An integer representing one of the following:
@@ -580,6 +587,11 @@
  * Allows the application to configure the context after
  * UpnpInitSslContext() has created it (e.g. load certificates and keys).
  *
+ * pupnp honors the verify mode of the context. Setting \c SSL_VERIFY_NONE
+ * with \c SSL_CTX_set_verify() disables server authentication, including
+ * the host name check, and exposes HTTPS connections to man-in-the-middle
+ * attacks.
+ *
  * \note This method is only enabled if pupnp is compiled with open-ssl 
support.
  *
  * \return The SSL_CTX pointer, or NULL if not yet initialized.
@@ -598,6 +610,13 @@
  * When \b ctx is NULL, the current context is freed and cleared so that
  * a new one can be set.
  *
+ * pupnp uses \b ctx as given and honors its verify mode. A context from
+ * \c SSL_CTX_new() does not verify the server (\c SSL_VERIFY_NONE); call
+ * \c SSL_CTX_set_verify() with \c SSL_VERIFY_PEER and load a trust store
+ * (e.g. \c SSL_CTX_set_default_verify_paths()) to authenticate HTTPS servers.
+ * With \c SSL_VERIFY_PEER, the server certificate must also match the host
+ * of the URL.
+ *
  * \note This method is only enabled if pupnp is compiled with open-ssl 
support.
  *
  * \return An integer representing one of the following:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/upnp/src/api/upnpapi.c 
new/libupnp-22.2.0/upnp/src/api/upnpapi.c
--- old/libupnp-22.1.10/upnp/src/api/upnpapi.c  2026-10-29 21:17:58.000000000 
+0100
+++ new/libupnp-22.2.0/upnp/src/api/upnpapi.c   2026-11-04 14:48:44.000000000 
+0100
@@ -622,6 +622,13 @@
        if (!gSslCtx) {
                return UPNP_E_INIT_FAILED;
        }
+       /* Authenticate the server against the system trust store. */
+       SSL_CTX_set_verify(gSslCtx, SSL_VERIFY_PEER, NULL);
+       if (SSL_CTX_set_default_verify_paths(gSslCtx) != 1) {
+               SSL_CTX_free(gSslCtx);
+               gSslCtx = NULL;
+               return UPNP_E_INIT_FAILED;
+       }
        return UPNP_E_SUCCESS;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libupnp-22.1.10/upnp/src/genlib/net/http/httpparser.c 
new/libupnp-22.2.0/upnp/src/genlib/net/http/httpparser.c
--- old/libupnp-22.1.10/upnp/src/genlib/net/http/httpparser.c   2026-10-29 
21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/upnp/src/genlib/net/http/httpparser.c    2026-11-04 
14:48:44.000000000 +0100
@@ -919,8 +919,10 @@
        if (case_sensitive) {
                return c == scan_char ? PARSE_OK : PARSE_NO_MATCH;
        } else {
-               return tolower(c) == tolower(scan_char) ? PARSE_OK
-                                                       : PARSE_NO_MATCH;
+               return tolower((unsigned char)c) ==
+                                      tolower((unsigned char)scan_char)
+                              ? PARSE_OK
+                              : PARSE_NO_MATCH;
        }
 }
 
@@ -1462,10 +1464,10 @@
        /* skip 3 ints */
        for (i = 0; i < 3; i++) {
                /* go to start of num */
-               while (!isdigit(*p))
+               while (!isdigit((unsigned char)*p))
                        p++;
                /* skip int */
-               while (isdigit(*p))
+               while (isdigit((unsigned char)*p))
                        p++;
        }
        /* whitespace must exist after status code */
@@ -2339,7 +2341,8 @@
 
        /* Make it lowercase */
        for (i = 0; raw_value->buf[i]; ++i) {
-               raw_value->buf[i] = (char)tolower(raw_value->buf[i]);
+               raw_value->buf[i] =
+                       (char)tolower((unsigned char)raw_value->buf[i]);
        }
 
        /* null-terminate */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libupnp-22.1.10/upnp/src/genlib/net/http/httpreadwrite.c 
new/libupnp-22.2.0/upnp/src/genlib/net/http/httpreadwrite.c
--- old/libupnp-22.1.10/upnp/src/genlib/net/http/httpreadwrite.c        
2026-10-29 21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/upnp/src/genlib/net/http/httpreadwrite.c 2026-11-04 
14:48:44.000000000 +0100
@@ -285,6 +285,10 @@
        char errorBuffer[ERROR_BUFFER_LEN];
 
        http_FixUrl(destination_url, url);
+       /* No TLS on this path: never send an https request in clear. */
+       if (token_string_casecmp(&url->scheme, "https") == 0) {
+               return (SOCKET)(UPNP_E_INVALID_URL);
+       }
 
        http_socket =
                socket((int)url->hostport.IPaddress.ss_family, SOCK_STREAM, 0);
@@ -820,6 +824,11 @@
        int http_error_code;
        SOCKINFO info;
 
+       /* No TLS on this path: never send an https request in clear. */
+       if (token_string_casecmp(&destination->scheme, "https") == 0) {
+               parser_response_init(response, req_method);
+               return UPNP_E_INVALID_URL;
+       }
        tcp_connection = socket(
                (int)destination->hostport.IPaddress.ss_family, SOCK_STREAM, 0);
        if (tcp_connection == INVALID_SOCKET) {
@@ -1323,6 +1332,36 @@
        return UPNP_E_SUCCESS;
 }
 
+#ifdef UPNP_ENABLE_OPEN_SSL
+/*!
+ * \brief Copies the host of a "host[:port]" or "[v6]:port" token, without
+ * port or brackets, as a NUL-terminated string.
+ *
+ * \return 0 on success, -1 if the host is empty or does not fit.
+ */
+static int get_tls_host(const token *hostport, char *host, size_t size)
+{
+       const char *p = hostport->buff;
+       size_t len = hostport->size;
+       size_t n = 0;
+
+       if (len > 0 && p[0] == '[') {
+               p++;
+               len--;
+               while (n < len && p[n] != ']')
+                       n++;
+       } else {
+               while (n < len && p[n] != ':')
+                       n++;
+       }
+       if (n == 0 || n >= size)
+               return -1;
+       memcpy(host, p, n);
+       host[n] = '\0';
+       return 0;
+}
+#endif
+
 int http_OpenHttpConnection(const char *url_str, void **Handle, int timeout)
 {
        int ret_code;
@@ -1371,7 +1410,13 @@
 #ifdef UPNP_ENABLE_OPEN_SSL
        /* For HTTPS connections start the TLS/SSL handshake. */
        if (token_string_casecmp(&url.scheme, "https") == 0) {
-               ret_code = sock_ssl_connect(&handle->sock_info);
+               char host[256];
+               if (get_tls_host(&url.hostport.text, host, sizeof(host)) != 0) {
+                       sock_destroy(&handle->sock_info, SD_BOTH);
+                       ret_code = UPNP_E_INVALID_URL;
+                       goto errorHandler;
+               }
+               ret_code = sock_ssl_connect(&handle->sock_info, host);
                if (ret_code != UPNP_E_SUCCESS) {
                        sock_destroy(&handle->sock_info, SD_BOTH);
                        goto errorHandler;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/upnp/src/genlib/net/http/webserver.c 
new/libupnp-22.2.0/upnp/src/genlib/net/http/webserver.c
--- old/libupnp-22.1.10/upnp/src/genlib/net/http/webserver.c    2026-10-29 
21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/upnp/src/genlib/net/http/webserver.c     2026-11-04 
14:48:44.000000000 +0100
@@ -758,7 +758,7 @@
        char *s)
 {
        while (*s) {
-               *s = (char)toupper(*s);
+               *s = (char)toupper((unsigned char)*s);
                ++s;
        }
 }
@@ -865,7 +865,7 @@
                for (i = 0; i < (int)strlen(Tok); i++) {
                        if (Tok[i] == '-') {
                                break;
-                       } else if (isdigit(Tok[i])) {
+                       } else if (isdigit((unsigned char)Tok[i])) {
                                Is_Suffix_byte_Range = 0;
                                break;
                        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/upnp/src/genlib/net/sock.c 
new/libupnp-22.2.0/upnp/src/genlib/net/sock.c
--- old/libupnp-22.1.10/upnp/src/genlib/net/sock.c      2026-10-29 
21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/upnp/src/genlib/net/sock.c       2026-11-04 
14:48:44.000000000 +0100
@@ -63,6 +63,7 @@
 
 #ifdef UPNP_ENABLE_OPEN_SSL
        #include <openssl/ssl.h>
+       #include <openssl/x509v3.h>
 #endif
 
 #ifndef MSG_NOSIGNAL
@@ -102,13 +103,28 @@
 }
 
 #ifdef UPNP_ENABLE_OPEN_SSL
-int sock_ssl_connect(SOCKINFO *info)
+int sock_ssl_connect(SOCKINFO *info, const char *host)
 {
        int status = 0;
+       ASN1_OCTET_STRING *ip;
+
        info->ssl = SSL_new(gSslCtx);
        if (!info->ssl) {
                return UPNP_E_SOCKET_ERROR;
        }
+       /* Bind the expected peer identity to this connection. */
+       ip = a2i_IPADDRESS(host);
+       if (ip) {
+               ASN1_OCTET_STRING_free(ip);
+               status = X509_VERIFY_PARAM_set1_ip_asc(
+                       SSL_get0_param(info->ssl), host);
+       } else {
+               status = SSL_set_tlsext_host_name(info->ssl, host) == 1 &&
+                        SSL_set1_host(info->ssl, host) == 1;
+       }
+       if (status != 1) {
+               return UPNP_E_SOCKET_ERROR;
+       }
        status = SSL_set_fd(info->ssl, info->socket);
        if (status == 1) {
                status = SSL_connect(info->ssl);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/upnp/src/genlib/net/uri/uri.c 
new/libupnp-22.2.0/upnp/src/genlib/net/uri/uri.c
--- old/libupnp-22.1.10/upnp/src/genlib/net/uri/uri.c   2026-10-29 
21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/upnp/src/genlib/net/uri/uri.c    2026-11-04 
14:48:44.000000000 +0100
@@ -110,7 +110,7 @@
        /*! [in] Char to be matched for UNRESERVED characters. */
        char in)
 {
-       if (isalnum(in) || is_mark(in)) {
+       if (isalnum((unsigned char)in) || is_mark(in)) {
                return 1;
        } else {
                return 0;
@@ -129,7 +129,8 @@
        /*! [in] Char sequence to be matched for ESCAPED characters. */
        const char *in)
 {
-       if (in[0] == '%' && isxdigit(in[1]) && isxdigit(in[2])) {
+       if (in[0] == '%' && isxdigit((unsigned char)in[1]) &&
+               isxdigit((unsigned char)in[2])) {
                return 1;
        } else {
                return 0;
@@ -143,8 +144,9 @@
        size_t i = (size_t)0;
        size_t j = (size_t)0;
 
-       if (in[index] == '%' && isxdigit(in[index + (size_t)1]) &&
-               isxdigit(in[index + (size_t)2])) {
+       if (in[index] == '%' &&
+               isxdigit((unsigned char)in[index + (size_t)1]) &&
+               isxdigit((unsigned char)in[index + (size_t)2])) {
                /* Note the "%2x", makes sure that we convert a maximum of two
                 * characters. */
 #ifdef _WIN32
@@ -364,7 +366,8 @@
                /* IPv4 address -OR- host name. */
                srv_name = c;
                while (*c != ':' && *c != '/' &&
-                       (isalnum(*c) || *c == '.' || *c == '-')) {
+                       (isalnum((unsigned char)*c) || *c == '.' ||
+                               *c == '-')) {
                        if (*c == '.')
                                last_dot = c;
                        c++;
@@ -374,7 +377,7 @@
                *c = '\0';
                if (has_port == 1)
                        c++;
-               if (last_dot != NULL && isdigit(*(last_dot + 1)))
+               if (last_dot != NULL && isdigit((unsigned char)*(last_dot + 1)))
                        /* Must be an IPv4 address. */
                        af = AF_INET;
                /* Otherwise it's a host name: leave IPaddress unresolved
@@ -388,7 +391,7 @@
        if (has_port == 1) {
                /* Port is specified. */
                srv_port = c;
-               while (*c != '\0' && isdigit(*c)) {
+               while (*c != '\0' && isdigit((unsigned char)*c)) {
                        c++;
                }
                long port_l = strtol(srv_port, NULL, 10);
@@ -513,13 +516,13 @@
        out->size = (size_t)0;
        out->buff = NULL;
 
-       if ((max == (size_t)0) || (!isalpha(in[0])))
+       if ((max == (size_t)0) || (!isalpha((unsigned char)in[0])))
                return (size_t)0;
 
        i++;
        while ((i < max) && (in[i] != ':')) {
-               if (!(isalnum(in[i]) || (in[i] == '+') || (in[i] == '-') ||
-                           (in[i] == '.')))
+               if (!(isalnum((unsigned char)in[i]) || (in[i] == '+') ||
+                           (in[i] == '-') || (in[i] == '.')))
                        return (size_t)0;
                i++;
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/upnp/src/inc/sock.h 
new/libupnp-22.2.0/upnp/src/inc/sock.h
--- old/libupnp-22.1.10/upnp/src/inc/sock.h     2026-10-29 21:17:58.000000000 
+0100
+++ new/libupnp-22.2.0/upnp/src/inc/sock.h      2026-11-04 14:48:44.000000000 
+0100
@@ -124,6 +124,9 @@
  * \brief Associates an SSL object with the socket and begins
  * the client-side SSL/TLS handshake.
  *
+ * The server certificate must match \b host (a DNS name or an IP literal);
+ * the check takes effect when the context verifies the peer.
+ *
  * \return Integer:
  * \li \c UPNP_E_SUCCESS
  * \li \c UPNP_E_SOCKET_ERROR
@@ -131,7 +134,9 @@
 #ifdef UPNP_ENABLE_OPEN_SSL
 int sock_ssl_connect(
        /*! [out] Socket Information Object. */
-       SOCKINFO *info);
+       SOCKINFO *info,
+       /*! [in] Host of the URL, without port or brackets. */
+       const char *host);
 #endif
 
 /*!
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/upnp/src/inet_pton.c 
new/libupnp-22.2.0/upnp/src/inet_pton.c
--- old/libupnp-22.1.10/upnp/src/inet_pton.c    2026-10-29 21:17:58.000000000 
+0100
+++ new/libupnp-22.2.0/upnp/src/inet_pton.c     2026-11-04 14:48:44.000000000 
+0100
@@ -229,7 +229,7 @@
        curtok = src;
        saw_xdigit = 0;
        val = 0;
-       while ((ch = tolower(*src++)) != '\0') {
+       while ((ch = tolower((unsigned char)*src++)) != '\0') {
                const char *pch;
 
                pch = strchr(xdigits, ch);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/upnp/test/test_parse_uri.c 
new/libupnp-22.2.0/upnp/test/test_parse_uri.c
--- old/libupnp-22.1.10/upnp/test/test_parse_uri.c      2026-10-29 
21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/upnp/test/test_parse_uri.c       2026-11-04 
14:48:44.000000000 +0100
@@ -234,6 +234,52 @@
        return 0;
 }
 
+/*
+ * Regression test for PR #666: bytes from 0x80 to 0xFF in each part of a
+ * URL reach the <ctype.h> calls of uri.c, which test_parse_uri_impl.c wraps
+ * to count arguments outside the unsigned char range. The parse result does
+ * not matter here, only that no call received a negative value.
+ */
+extern int ctype_bad_args;
+
+static int check_high_bytes_ctype(void)
+{
+       static const char *const urls[] = {
+               "\xe9"
+               "ttp://192.0.2.1/", /* parse_scheme(): isalpha */
+               "h\xe9"
+               "tp://192.0.2.1/", /* parse_scheme(): isalnum */
+               "http://h\xe9";
+               "st/",                     /* parse_hostport(): isalnum */
+               "http://192.0.2.\xe9/";,    /* parse_hostport(): isdigit */
+               "http://192.0.2.1:8\xe9/";, /* parse_hostport(): port */
+               "http://192.0.2.1/%\xe9";
+               "0",                         /* is_escaped(): isxdigit */
+               "http://192.0.2.1/%0\xe9";,   /* is_escaped(): isxdigit */
+               "http://192.0.2.1/\x80\xe9";, /* is_unreserved(): isalnum */
+       };
+       uri_type url;
+       int i;
+       int failures = 0;
+
+       for (i = 0; i < (int)(sizeof(urls) / sizeof(urls[0])); i++) {
+               ctype_bad_args = 0;
+               (void)parse_uri(urls[i], strlen(urls[i]), &url);
+               if (ctype_bad_args != 0) {
+                       printf("%s:%d parse_uri(urls[%d]) passed %d negative "
+                              "char(s) to <ctype.h> -- cast to unsigned "
+                              "char\n",
+                               __FILE__,
+                               __LINE__,
+                               i,
+                               ctype_bad_args);
+                       failures++;
+               }
+       }
+
+       return failures;
+}
+
 int main(void)
 {
        int i;
@@ -267,6 +313,7 @@
        failures += check_literal_ip_is_noop();
        failures += check_resolve_hostport_localhost();
        failures += check_nul_terminates_uri();
+       failures += check_high_bytes_ctype();
 
 #ifdef _WIN32
        WSACleanup();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.1.10/upnp/test/test_parse_uri_impl.c 
new/libupnp-22.2.0/upnp/test/test_parse_uri_impl.c
--- old/libupnp-22.1.10/upnp/test/test_parse_uri_impl.c 2026-10-29 
21:17:58.000000000 +0100
+++ new/libupnp-22.2.0/upnp/test/test_parse_uri_impl.c  2026-11-04 
14:48:44.000000000 +0100
@@ -10,4 +10,43 @@
  *   object/dependency naming conflicts while keeping the library API surface
  *   unchanged.
  */
+
+/*
+ * Regression test for PR #666: the <ctype.h> functions take an int that must
+ * be representable as unsigned char or equal EOF (C11 7.4). uri.c passed a
+ * plain char, so a URL byte from 0x80 to 0xFF became a negative int where
+ * char is signed. glibc tolerates that, so the wrappers below count every
+ * out-of-range argument instead; test_parse_uri.c fails when the count is
+ * not zero. <ctype.h> is included first so that its include guard keeps
+ * uri.c from redefining the names.
+ */
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+
+int ctype_bad_args = 0;
+
+static int ctype_arg(int c)
+{
+       if (c != EOF && (c < 0 || c > UCHAR_MAX)) {
+               ctype_bad_args++;
+               return EOF;
+       }
+       return c;
+}
+
+static int checked_isalnum(int c) { return isalnum(ctype_arg(c)); }
+static int checked_isalpha(int c) { return isalpha(ctype_arg(c)); }
+static int checked_isdigit(int c) { return isdigit(ctype_arg(c)); }
+static int checked_isxdigit(int c) { return isxdigit(ctype_arg(c)); }
+
+#undef isalnum
+#undef isalpha
+#undef isdigit
+#undef isxdigit
+#define isalnum checked_isalnum
+#define isalpha checked_isalpha
+#define isdigit checked_isdigit
+#define isxdigit checked_isxdigit
+
 #include "../src/genlib/net/uri/uri.c"

Reply via email to