Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package ssdp-responder for openSUSE:Factory 
checked in at 2023-03-08 14:53:09
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ssdp-responder (Old)
 and      /work/SRC/openSUSE:Factory/.ssdp-responder.new.31432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ssdp-responder"

Wed Mar  8 14:53:09 2023 rev:6 rq:1070023 version:2.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/ssdp-responder/ssdp-responder.changes    
2022-10-31 10:45:08.232985987 +0100
+++ /work/SRC/openSUSE:Factory/.ssdp-responder.new.31432/ssdp-responder.changes 
2023-03-08 14:53:12.782885297 +0100
@@ -1,0 +2,44 @@
+Mon Feb 27 15:05:17 UTC 2023 - Martin Hauke <mar...@gmx.de>
+
+- Update to version 2.0
+  Changes
+  Add suport for 
+  * -c NAME to override ssdpd UUID cache file.
+  * -d URL to override the UPnP description URL, a single %s is
+    supported to be replaced with the interface address.
+  * -m NAME to override the manufacturer in the default
+    description.xml.
+  * -M URL to override the manufacturerURL in the default
+    description.xml.
+  * -p URL to override the presentationURL in the default
+    description.xml, a single %s is supported.
+  * -P FILE to override the default PID file location default
+    description.xml, a single %s is supported.
+  * -u UUID to use a custom UUID, useful when the built-in micro
+    HTTP server is disabled.
+  * -w to disable built-in micro HTTP server, useful when other,
+    more capable, web servers are available. Make sure to have the
+    alternate web server running on port 1901 to serve the file
+    /description.xml, see also -d URL above, which details the
+    location of the UPnP description URL.
+  * Increased debug messages in ssdp_init(), for troubleshooting.
+  * dropping root privileges after initial setup. The used ssdp
+    is searched for first, with fallback to nobody.
+  * Add support for an initial retry interval at startup, in
+    case ssdpd is started before any interface has been configured.
+    Configurable retry count using -R NUM command line option.
+  * Add support for an IP address monitor using Linux netlink
+    interfaces coming and going at runtime. Meaning, -R NUM is
+    unused on Linux. For this to work, ssdpd must start as root or
+    have CAP_NET_RAW.
+  Fixes
+  * Fix #11: periodic busy loop causing intermittent 100% CPU load.
+  * Fix invalid <UDN>uuid:uuid:...</UDN> in description.xml.
+  * Add Date: and Server: to HTTP header in micro HTTP server.
+  * Add support for HTTP HEAD requests to micro HTTP server.
+  * Handle case when /etc/os-release does not have VERSION_ID fall
+    back to use VERSION_CODENAME.
+  * Don't overwrite CPPFLAGS from the command line.
+  * Portability fix to utimensat() replacement function.
+
+-------------------------------------------------------------------

Old:
----
  ssdp-responder-1.9.tar.gz

New:
----
  ssdp-responder-2.0.tar.gz

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

Other differences:
------------------
++++++ ssdp-responder.spec ++++++
--- /var/tmp/diff_new_pack.5Wh7iZ/_old  2023-03-08 14:53:13.290888063 +0100
+++ /var/tmp/diff_new_pack.5Wh7iZ/_new  2023-03-08 14:53:13.298888106 +0100
@@ -1,8 +1,8 @@
 #
 # spec file for package ssdp-responder
 #
-# Copyright (c) 2022 SUSE LLC
-# Copyright (c) 2018, Martin Hauke <mar...@gmx.de>
+# Copyright (c) 2023 SUSE LLC
+# Copyright (c) 2018-2023, Martin Hauke <mar...@gmx.de>
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
 
 
 Name:           ssdp-responder
-Version:        1.9
+Version:        2.0
 Release:        0
 Summary:        SSDP responder for Linux
 License:        ISC
@@ -49,7 +49,7 @@
 
 %install
 %make_install
-rm -rf %{buildroot}/%{_datadir}/doc
+rm -Rv %{buildroot}/%{_datadir}/doc
 
 %preun
 %service_del_preun ssdpd.service

++++++ ssdp-responder-1.9.tar.gz -> ssdp-responder-2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/.github/workflows/release.yml 
new/ssdp-responder-2.0/.github/workflows/release.yml
--- old/ssdp-responder-1.9/.github/workflows/release.yml        2022-10-30 
17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/.github/workflows/release.yml        2023-02-19 
21:40:58.000000000 +0100
@@ -7,36 +7,9 @@
 
 jobs:
   release:
-    name: Create GitHub release
-    runs-on: ubuntu-latest
-    if: startsWith(github.ref, 'refs/tags/')
-    outputs:
-      upload_url: ${{ steps.create_release.outputs.upload_url }}
-      release_id: ${{ steps.create_release.outputs.id }}
-    steps:
-      - uses: actions/checkout@v3
-      - name: Extract ChangeLog entry ...
-        # Hack to extract latest entry for body_path below
-        run: |
-          awk '/-----*/{if (x == 1) exit; x=1;next}x' ChangeLog.md \
-              |head -n -1 > release.md
-          cat release.md
-      - name: Create release ...
-        id: create_release
-        uses: actions/create-release@v1
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        with:
-          tag_name: ${{ github.ref }}
-          release_name: ssdp-responder ${{ github.ref }}
-          body_path: release.md
-          draft: false
-          prerelease: false
-  tarball:
     name: Build and upload release tarball
-    needs: release
-    if: startsWith(github.ref, 'refs/tags/')
     runs-on: ubuntu-latest
+    if: startsWith(github.ref, 'refs/tags/')
     steps:
       - uses: actions/checkout@v3
       - name: Installing dependencies ...
@@ -53,10 +26,13 @@
           ls -lF ../
           mkdir -p artifacts/
           mv ../*.tar.* artifacts/
-      - name: Upload release artifacts ...
-        uses: skx/github-action-publish-binaries@release-0.15
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      - name: Extract ChangeLog entry ...
+        run: |
+          awk '/-----*/{if (x == 1) exit; x=1;next}x' ChangeLog.md \
+              |head -n -1 > release.md
+          cat release.md
+      - uses: ncipollo/release-action@v1
         with:
-          releaseId: ${{ needs.release.outputs.release_id }}
-          args: artifacts/*
+          name: ssdp-responder ${{ github.ref_name }}
+          bodyFile: "release.md"
+          artifacts: "artifacts/*"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/.gitignore 
new/ssdp-responder-2.0/.gitignore
--- old/ssdp-responder-1.9/.gitignore   2022-10-30 17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/.gitignore   2023-02-19 21:40:58.000000000 +0100
@@ -5,6 +5,7 @@
 GRTAGS
 GSYMS
 GTAGS
+ID
 Makefile
 Makefile.in
 aclocal.m4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/ChangeLog.md 
new/ssdp-responder-2.0/ChangeLog.md
--- old/ssdp-responder-1.9/ChangeLog.md 2022-10-30 17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/ChangeLog.md 2023-02-19 21:40:58.000000000 +0100
@@ -4,6 +4,51 @@
 All notable changes to the project are documented in this file.
 
 
+[v2.0][] - 2023-02-19
+---------------------
+
+The drop privs release.
+
+### Changes
+ - Add support for `-c NAME` to override `ssdpd` UUID cache file
+ - Add support for `-d URL` to override the UPnP description URL, a
+   single `%s` is supported to be replaced with the interface address
+ - Add support for `-m NAME` to override the `manufacturer` in the
+   default `description.xml`
+ - Add support for `-M URL` to override the `manufacturerURL` in the
+   default `description.xml`
+ - Add support for `-p URL` to override the `presentationURL` in the
+   default `description.xml`, a single `%s` is supported
+ - Add support for `-P FILE` to override the default PID file location
+   default `description.xml`, a single `%s` is supported
+ - Add support for `-u UUID` to use a custom UUID, useful when the
+   built-in micro HTTP server is disabled
+ - Add support for `-w` to disable built-in micro HTTP server, useful
+   when other, more capable, web servers are available.  Make sure to
+   have the alternate web server running on port 1901 to serve the file
+   `/description.xml`, see also `-d URL` above, which details the
+   location of the UPnP description URL
+ - Increased debug messages in `ssdp_init()`, for troubleshooting
+ - Add support for dropping root privileges after initial setup.  The
+   used `ssdp` is searched for first, with fallback to `nobody`
+ - Add support for an initial retry interval at startup, in case ssdpd
+   is started before any interface has been configured.  Configurable
+   retry count using `-R NUM` command line option
+ - Add support for an IP address monitor using Linux netlink interfaces
+   coming and going at runtime.  Meaning, `-R NUM` is unused on Linux.
+   For this to work, `ssdpd` must start as root or have `CAP_NET_RAW`
+
+### Fixes
+ - Fix #11: periodic busy loop causing intermittent 100% CPU load
+ - Fix invalid `<UDN>uuid:uuid:...</UDN>` in `description.xml`
+ - Add `Date:` and `Server:` to HTTP header in micro HTTP server
+ - Add support for HTTP HEAD requests to micro HTTP server
+ - Handle case when `/etc/os-release` does not have `VERSION_ID` fall
+   back to use `VERSION_CODENAME`
+ - Don't overwrite CPPFLAGS from the command line
+ - Portability fix to `utimensat()` replacement function
+
+
 [v1.9][] - 2022-10-30
 ---------------------
 
@@ -146,6 +191,8 @@
 Initial release
 
 
+[UNRELEASED]: https://github.com/troglobit/ssdp-responder/compare/v2.0...HEAD
+[v2.0]: https://github.com/troglobit/ssdp-responder/compare/v1.9...v2.0
 [v1.9]: https://github.com/troglobit/ssdp-responder/compare/v1.8...v1.9
 [v1.8]: https://github.com/troglobit/ssdp-responder/compare/v1.7...v1.8
 [v1.7]: https://github.com/troglobit/ssdp-responder/compare/v1.6...v1.7
@@ -155,4 +202,3 @@
 [v1.3]: https://github.com/troglobit/ssdp-responder/compare/v1.2...v1.3
 [v1.2]: https://github.com/troglobit/ssdp-responder/compare/v1.1...v1.2
 [v1.1]: https://github.com/troglobit/ssdp-responder/compare/v1.0...v1.1
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/README.md 
new/ssdp-responder-2.0/README.md
--- old/ssdp-responder-1.9/README.md    2022-10-30 17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/README.md    2023-02-19 21:40:58.000000000 +0100
@@ -15,30 +15,53 @@
 SSDP capable hosts on the network.  Take care only to use this for
 debugging since it scans the network quite aggressively.
 
+> **Note:** when `ssdpd` is started as root it will drop privileges as
+> soon as it has created its UUID cache and PID file.  It is recommended
+> to have a dedicated `ssdp` user (and group) available in the system
+> for this purpose.  As a fallback, user `nobody` (and `nogroup`) is
+> used.  When started as non-root, make sure to provide a path for the
+> cache file with the `-c FILE` option.
+
 
 Usage
 -----
 
 ```
-Usage: ssdpd [-hnsv] [-i SEC] [-l LEVEL] [-r SEC] [-t TTL] [IFACE [IFACE ...]]
-
+Usage: ssdpd [-hnsvw] [-c FILE] [-d URL] [-i SEC] [-l LEVEL] [-m NAME] [-M URL]
+                      [-p URL] [-P FILE] [-r SEC] [-R NUM] [-t TTL] [-u UUID]
+                      [IFACE [IFACE ...]]
+
+    -c FILE   Path to alternate ssdpd.cache to store and/or read the UUID
+    -d URL    Override UPnP description.xml URL in announcements.  The '%s' in
+              the URL is replaced with the IP, e.g. https://%s:1901/main.xml
     -h        This help text
     -i SEC    SSDP notify interval (30-900), default 300 sec
     -l LVL    Set log level: none, err, notice (default), info, debug
+    -m NAME   Override manufacturer in the default description.xml
+    -M URL    Override manufacturerURL in the default description.xml
     -n        Run in foreground, do not daemonize by default
     -r SEC    Interface refresh interval (5-1800), default 600 sec
+    -R NUM    Initial retries, using 10 sec refresh interval, default 3 times
+    -p URL    Override presentationURL (WebUI) in the default description.xml
+              The '%s' is replaced with the IP address.  Default: http://%s/
+    -P FILE   Override PID file location, absolute path required
     -s        Use syslog, default unless running in foreground, -n
     -t TTL    TTL for multicast frames, default 2, according to the UDA
+    -u UUID   Custom UUID instead of auto-generating one
     -v        Show program version
+    -w        Disable built-in micro HTTP server on port 1901
 
 Bug report address : https://github.com/troglobit/ssdp-responder/issues
 Project homepage   : https://github.com/troglobit/ssdp-responder
 ```
 
-See `configure --help` for some build time options.
+The `-d URL` argument can contain one `%s` modifier which is replaced
+with the IP address of the interface the SSDP notification or reply is
+sent on.  For example:
 
-> **Note:** previous releases did *not* daemonize, you will have to
-> update your start scripts to include `-n` as of v1.6
+    ssdpd -d https://%s:1901/description.xml
+
+See `configure --help` for some build time options.
 
 
 Example
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/TODO.md 
new/ssdp-responder-2.0/TODO.md
--- old/ssdp-responder-1.9/TODO.md      2022-10-30 17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/TODO.md      2023-02-19 21:40:58.000000000 +0100
@@ -6,6 +6,4 @@
   - Add simple `ssdpctl show` to list neighbors
 - Icon support
   - Custom icon support
-- Support for disabling built-in web server using configure and use
-  existing web server like Apache. Nginx, or Merecat
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/configure.ac 
new/ssdp-responder-2.0/configure.ac
--- old/ssdp-responder-1.9/configure.ac 2022-10-30 17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/configure.ac 2023-02-19 21:40:58.000000000 +0100
@@ -1,4 +1,4 @@
-AC_INIT([ssdpd], [1.9], [https://github.com/troglobit/ssdp-responder/issues],
+AC_INIT([ssdpd], [2.0], [https://github.com/troglobit/ssdp-responder/issues],
        [ssdp-responder], [https://github.com/troglobit/ssdp-responder])
 AC_CONFIG_AUX_DIR(aux)
 AM_INIT_AUTOMAKE([1.11 foreign])
@@ -22,7 +22,7 @@
        ;;
     linux*)
        vardb=lib/misc
-       CPPFLAGS="-D_GNU_SOURCE"
+       CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
        ;;
     *)
        vardb=tmp
@@ -37,6 +37,9 @@
 # Check if some func is not in libc
 AC_CHECK_LIB([util], [pidfile])
 
+# Check for Linux Netlink support
+AC_CHECK_HEADERS([linux/netlink.h linux/rtnetlink.h])
+
 # Check for usually missing API's, which we can replace
 AC_REPLACE_FUNCS([pidfile strlcpy utimensat])
 AC_CONFIG_LIBOBJ_DIR([lib])
@@ -46,18 +49,18 @@
         AS_HELP_STRING([--enable-test-mode], [Enable loopback test mode]),, 
[enable_test_mode=no])
 
 AC_ARG_WITH([vendor],
-       AS_HELP_STRING([--with-vendor=VENDOR], [Set a custom vendor string]),
+       AS_HELP_STRING([--with-vendor=VENDOR], [Override vendor string, 
default: Troglobit Software Systems]),
        [vendor=$withval], [vendor="Troglobit Software Systems"])
 
 AC_ARG_WITH([vendor-url],
-       AS_HELP_STRING([--with-vendor-url=VENDOR], [Set vendor URL]))
+       AS_HELP_STRING([--with-vendor-url=VENDOR], [Set vendor URL, default 
disabled]))
 
 AC_ARG_WITH([model],
-       AS_HELP_STRING([--with-model=MODEL], [Set a custom model string]),
+       AS_HELP_STRING([--with-model=MODEL], [Override model string, default: 
Generic]),
        [model=$withval], [model="Generic"])
 
 AC_ARG_WITH([systemd],
-     [AS_HELP_STRING([--with-systemd=DIR], [Directory for systemd service 
files])],,
+     [AS_HELP_STRING([--with-systemd=DIR], [Override detected directory for 
systemd unit files])],,
      [with_systemd=auto])
 
 AS_IF([test "x$enable_test_mode" == "xyes"], [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/lib/pidfile.c 
new/ssdp-responder-2.0/lib/pidfile.c
--- old/ssdp-responder-1.9/lib/pidfile.c        2022-10-30 17:16:41.000000000 
+0100
+++ new/ssdp-responder-2.0/lib/pidfile.c        2023-02-19 21:40:58.000000000 
+0100
@@ -31,7 +31,10 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <config.h>
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
 #include <sys/stat.h>          /* utimensat() */
 #include <sys/time.h>          /* utimensat() on *BSD */
 #include <sys/types.h>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/lib/utimensat.c 
new/ssdp-responder-2.0/lib/utimensat.c
--- old/ssdp-responder-1.9/lib/utimensat.c      2022-10-30 17:16:41.000000000 
+0100
+++ new/ssdp-responder-2.0/lib/utimensat.c      2023-02-19 21:40:58.000000000 
+0100
@@ -15,7 +15,9 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#ifdef HAVE_CONFIG_H
 #include "config.h"
+#endif
 
 #include <errno.h>
 #ifdef HAVE_FCNTL_H
@@ -23,6 +25,13 @@
 #endif
 #include <sys/time.h>          /* lutimes(), utimes(), utimensat() */
 
+#ifndef TIMESPEC_TO_TIMEVAL
+# define TIMESPEC_TO_TIMEVAL(tv, ts) {         \
+        (tv)->tv_sec  = (ts)->tv_sec;          \
+        (tv)->tv_usec = (ts)->tv_nsec / 1000;  \
+}
+#endif
+
 int utimensat(int dirfd, const char *pathname, const struct timespec ts[2], 
int flags)
 {
        int ret = -1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/man/ssdpd.8 
new/ssdp-responder-2.0/man/ssdpd.8
--- old/ssdp-responder-1.9/man/ssdpd.8  2022-10-30 17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/man/ssdpd.8  2023-02-19 21:40:58.000000000 +0100
@@ -12,7 +12,7 @@
 .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.a
-.Dd Oct 22, 2022
+.Dd Feb 14, 2023
 .Dt ssdpd 8
 .Os
 .Sh NAME
@@ -20,11 +20,19 @@
 .Nd Simple Service Discovery Protocol daemon
 .Sh SYNOPSIS
 .Nm
-.Op Fl hnsv
+.Op Fl hnsvw
+.Op Fl c Ar FILE
+.Op Fl d Ar URL
 .Op Fl i Ar SEC
 .Op Fl l Ar LEVEL
+.Op Fl m Ar NAME
+.Op Fl M Ar URL
+.Op Fl p Ar URL
+.Op Fl P Ar FILE
 .Op Fl r Ar SEC
+.Op Fl R Ar NUM
 .Op Fl t Ar TTL
+.Io Fl u Ar UUID
 .Op Ar IFACE Op Ar IFACE Op Ar ...
 .Sh DESCRIPTION
 Simple Service Discovery Protocol daemon (SSDP) for networked Linux
@@ -35,7 +43,8 @@
 is a stand-alone UNIX daemon with no external dependencies but the
 standard C library.  It has a built-in web server for serving the UPnP
 XML description which Windows use to present the icon, by default an
-InternetGatewayDevice is announced.
+.Cm InternetGatewayDevice
+is announced.
 .Pp
 On Linux systems,
 .Nm
@@ -53,16 +62,61 @@
 Each SSDP capable device on a LAN has its own UUID.  This is generated
 the first time
 .Nm
-is started and stored in
-.Pa /var/lib/misc/ssdpd.cache ,
-on *BSD systems it is stored in
-.Pa /var/db/ssdpd.cache .
-Note, depending on your system this directory may be wiped on reboot, so
+is started and stored in a file called
+.Cm ssdpd.cache ,
+either in
+.Pa /var/lib/misc/
+or
+.Pa /var/db/ ,
+which is what *BSD systems use.  Depending on your system this directory
+may be wiped on reboot, so
+.Nm
+may start up with a different UUID every time.  Use
+.Fl c Ar FILE
+to work around that.
+.Pp
+As soon as the UUID is saved or read from the cache file, and the PID
+file has been created,
 .Nm
-may start up with a different UUID every time.
+drops any root privileges it may have beeen started with.  The preferred
+user to drop to is
+.Cm ssdp ,
+but if that is not set up in the system, user
+.Cm nobody
+is used as a fallback.  In the event that user is also missing,
+.Nm
+will log a warning and continue running as
+.Cm root .
 .Sh OPTIONS
-The following command line options are available:
+.Nm
+starts without any options or arguments.  By default it forks to the
+background, as a regular UNIX daemon, and runs on all interfaces that
+are up and have an IPv4 address.  New and deleted interfaces are
+discovered at runtime.  To run on a subset of interfaces, append the
+interface name as an argument.
+.Pp
+To control other aspects of
+.Nm ,
+the following command line options are available:
 .Bl -tag -width "-l LEVEL "
+.It Fl c Ar FILE
+When started as non-root it is usually not possible to write files in
+the
+.Pa /var/lib/misc/ ,
+or
+.Pa /var/db/ ,
+directory.  Use this option to specify the full path to an alternate
+cache file where
+.Nm
+can store and/or read the UUID.
+.It Fl d Ar URL
+Override the UPnP description URL in SSDP announcement messages and
+replies, default:
+.Lk http://%s:1901/description.xml .
+.Pp
+A single '%s' modifier is allowed, which is replaced by the interface's
+IP address.  Example:
+.Lk https://%s:5000/rootDesc.xml
 .It Fl h
 Show program help text
 .It Fl i Ar SEC
@@ -88,27 +142,123 @@
 .Pp
 .It Fl n
 Run in foreground, do not daemonize by default
+.It Fl m Ar NAME
+Override
+.Cm manufacturer
+in
+.Pa description.xml .
+.It Fl M Ar UIRL
+Override
+.Cm manufacturerURL
+in
+.Pa description.xml .
+.It Fl p Ar URL
+Override
+.Cm presentationURL
+in
+.Pa description.xml .
+This is usually the URL to access the device's web interface.  Default:
+.Lk http://%s/
+.Pp
+In the UPnP spec. this can be an absolute or relative URL, but the
+default
+.Pa description.xml
+does not have the
+.Cm <URLBase>
+option set.  See the
+.Fl d
+option for overriding the UPnP presentation URL entirely to support a
+relative
+.Cm presentationURL.
+.Pp
+A single '%s' modifier is allowed, which is replaced by the interface's
+IP address.  Example:
+.Lk https://%s:8080/index.php
+.It Fl P Ar FILE
+Override default PID file location, absolute path required, default:
+.Pa /run/ssdpd.pid
 .It Fl r Ar SEC
 Interface refresh interval (5-1800), default 600 sec
+.It Fl R Ar NUM
+Initial number of retries to get a valid SSDP discovery socket when
+starting up, default 3.
+.Pp
+When starting up the system may not yet have any interface up with a
+valid IP address.  By default,
+.Nm
+retries 3 times, using a hard-coded 10 sec refresh interval, before
+reverting to the regular refresh interval,
+.Fl r .
+Unused on systems with netlink interface monitoring (Linux).
 .It Fl s
 Use syslog, default unless running in foreground,
 .Fl n .
 .It Fl t Ar TTL
 TTL for multicast frames, default 2, according to the UPnP Device
 Architecture (UDA)
+.It Fl u Ar UUID
+Custom UUID to use.  By default,
+.Nm
+generates a UUID when starting up the first time.  That UUID is
+cached between restarts in the file
+.Pa /var/lib/misc/ssdpd.cache .
+When this option is given, the built-in UUID generator is disabled and
+the cache file is not used.
 .It Fl v
 Show program version
+.It Fl w
+Disable built-in micro HTTP server on port 1901.  Use this when
+you want to use another (more capable) web server to provide the
+.Pa description.xml
+file that clients request to read the IP address and, optionally,
+custom icon for your device.  See below for an example of how the
+.Pa description.xml
+file can look.
 .It Op Ar IFACE
 By default
 .Nm
 runs on all IPv4 capable interfaces, except loopback.  If interface
 names are given as arguments,
 .Nm
-will run only on them.  Interfaces are refrehed at runtime, so if
+will run only on them.  Interfaces are refreshed at runtime, so if
 an interface given on the command line does not exist at first,
 .Nm
 will add them later.
 .El
+.Sh WEB SERVER
+The built-in micro HTTP server runs on port 1901 (base SSDP port + 1).
+Its only purpose is to serve the file
+.Pa description.xml
+that clients request after receiving the SSDP announcement frames.  The
+default XML file looks like this:
+.Pp
+.Bd -unfilled -offset indent
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+ <specVersion>
+   <major>1</major>
+   <minor>0</minor>
+ </specVersion>
+ <device>
+  <deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType>
+  <friendlyName>HOSTNAME</friendlyName>
+  <manufacturer>Troglobit Software Systems</manufacturer>
+  <manufacturerURL>https://troglobit.com</manufacturerURL>
+  <modelName>Generic</modelName>
+  <UDN>uuid:12345678-coff-eede-adbe-ff00-123456654321</UDN>
+  <presentationURL>http://1.2.3.4/description.xml</presentationURL>
+ </device>
+</root>
+.Ed
+.Pp
+When
+.Nm
+is started with the
+.Fl w
+option it is imperative that the
+.Fl u Ar UUID
+is also provided.  Otherwise clients wil detect a mismatch in the
+advertised UUID and the XML file.
 .Sh FILES
 .Bl -tag -width /var/lib/misc/ssdpd.cache -compact
 .It Pa /run/ssdpd.pid
@@ -132,5 +282,7 @@
 .Xr ssdp-scan 1
 .Xr avahi-daemon 8
 .Xr lldpd 8
+.Pp
+.Lk http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf UPnP 
Device Architecture (UDA)
 .Sh AUTHORS
 .An Joachim Wiberg Aq troglo...@gmail.com
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/src/Makefile.am 
new/ssdp-responder-2.0/src/Makefile.am
--- old/ssdp-responder-1.9/src/Makefile.am      2022-10-30 17:16:41.000000000 
+0100
+++ new/ssdp-responder-2.0/src/Makefile.am      2023-02-19 21:40:58.000000000 
+0100
@@ -6,7 +6,7 @@
 bin_PROGRAMS     = ssdp-scan
 sbin_PROGRAMS    = ssdpd
 
-ssdpd_SOURCES    = ssdpd.c compat.h ssdp.c ssdp.h web.c queue.h
+ssdpd_SOURCES    = ssdpd.c compat.h netlink.c ssdp.c ssdp.h web.c queue.h
 ssdpd_LDADD      = $(LIBS) $(LIBOBJS)
 
 ssdp_scan_SOURCES = ssdp-scan.c compat.h ssdp.c ssdp.h uget.c queue.h
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/src/netlink.c 
new/ssdp-responder-2.0/src/netlink.c
--- old/ssdp-responder-1.9/src/netlink.c        1970-01-01 01:00:00.000000000 
+0100
+++ new/ssdp-responder-2.0/src/netlink.c        2023-02-19 21:40:58.000000000 
+0100
@@ -0,0 +1,133 @@
+/* Linux netlink address monitor
+ *
+ * Copyright (c) 2023  Joachim Wiberg <troglo...@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.a
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ssdp.h"
+
+#ifdef HAVE_LINUX_NETLINK_H
+#include <time.h>
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+static unsigned char buffer[4094];
+static time_t latest_change;
+
+/*
+ * On IP address change we potentially get a LOT of netlink messages.
+ * This (very) limited netlink monitor considers any change but also
+ * smooths out the changes by scheduling SIGALRM in five seconds on
+ * each change.  If more changes come in during that time, the timer
+ * is reset to five seconds for each change.
+ */
+static void parse_rta(struct nlmsghdr *nlmsg)
+{
+       struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nlmsg);
+       struct rtattr *rta = IFA_RTA(ifa);
+       int la = IFA_PAYLOAD(nlmsg);
+
+       while (la && RTA_OK(rta, la)) {
+               if (rta->rta_type == IFA_LOCAL) {
+                       alarm(5);
+                       break;
+               }
+
+               rta = RTA_NEXT(rta, la);
+       }
+}
+
+static void netlink_recv(int sd)
+{
+       struct nlmsghdr *nlmsg;
+       ssize_t len;
+
+       while ((len = recv(sd, buffer, sizeof(buffer), 0)) == -1) {
+               switch (errno) {
+               case EAGAIN:
+               case EINTR:
+                       continue;
+               default:
+                       break;
+               }
+       }
+
+       if (len == 0 || (size_t)len > sizeof(buffer))
+               return;
+
+       nlmsg = (struct nlmsghdr *)buffer;
+       if (nlmsg->nlmsg_flags & MSG_TRUNC)
+               return;
+
+       while ((NLMSG_OK(nlmsg, len)) && (nlmsg->nlmsg_type != NLMSG_DONE)) {
+               switch (nlmsg->nlmsg_type) {
+               case RTM_NEWADDR:
+               case RTM_DELADDR:
+                       parse_rta(nlmsg);
+                       break;
+               default:
+                       break;
+               }
+
+               nlmsg = NLMSG_NEXT(nlmsg, len);
+       }
+}
+
+int netlink_init(void)
+{
+       struct sockaddr_nl snl = {
+               .nl_family = AF_NETLINK,
+               .nl_groups = RTMGRP_IPV4_IFADDR, /* | RTMGRP_IPV6_IFADDR, */
+       };
+       int sd;
+
+       sd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+       if (sd == -1) {
+               logit(LOG_ERR, "Failed creating netlink socket: %s", 
strerror(errno));
+               return -1;
+       }
+
+       if (bind(sd, (struct sockaddr *)&snl, sizeof(snl)) == -1) {
+               logit(LOG_ERR, "Failed binding to netlink socket: %s", 
strerror(errno));
+               close(sd);
+               return -1;
+       }
+
+       latest_change = time(NULL);
+       if (ssdp_register(sd, NULL, (struct sockaddr *)&snl, NULL, 
netlink_recv)) {
+               close(sd);
+               return -1;
+       }
+
+       return 0;
+}
+
+#else
+int netlink_init(void)
+{
+       return -1;
+}
+#endif /* HAVE_LINUX_NETLINK_H */
+
+/**
+ * Local Variables:
+ *  indent-tabs-mode: t
+ *  c-file-style: "linux"
+ * End:
+ */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/src/ssdp.c 
new/ssdp-responder-2.0/src/ssdp.c
--- old/ssdp-responder-1.9/src/ssdp.c   2022-10-30 17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/src/ssdp.c   2023-02-19 21:40:58.000000000 +0100
@@ -243,7 +243,7 @@
        return num;
 }
 
-int ssdp_register(int sd, struct sockaddr *addr, struct sockaddr *mask, void 
(*cb)(int sd))
+int ssdp_register(int sd, char *ifname, struct sockaddr *addr, struct sockaddr 
*mask, void (*cb)(int sd))
 {
        struct sockaddr_in *address = (struct sockaddr_in *)addr;
        struct sockaddr_in *netmask = (struct sockaddr_in *)mask;
@@ -260,6 +260,8 @@
        ifs->sd   = sd;
        ifs->mod  = 1;
        ifs->cb   = cb;
+       if (ifname)
+               strlcpy(ifs->ifname, ifname, sizeof(ifs->ifname));
        if (address)
                ifs->addr = *address;
        if (mask)
@@ -277,6 +279,21 @@
                cb(ifs, arg);
 }
 
+/* Number of SSDP sockets, not counting the web socket */
+size_t ssdp_num_sockets(void)
+{
+       struct ifsock *ifs;
+       size_t num = 0;
+
+       LIST_FOREACH(ifs, &il, link) {
+               if (ifs->ifname[0])
+                       num++;
+       }
+       logit(LOG_DEBUG, "We have %zd sockets open.", num);
+
+       return num;
+}
+
 static int socket_open(char *ifname, struct sockaddr *addr, int ttl, int srv)
 {
        struct sockaddr_in sin, *address = (struct sockaddr_in *)addr;
@@ -320,21 +337,6 @@
        return sd;
 }
 
-int ssdp_exit(void)
-{
-       struct ifsock *ifs, *tmp;
-       int ret = 0;
-
-       LIST_FOREACH_SAFE(ifs, &il, link, tmp) {
-               LIST_REMOVE(ifs, link);
-               if (ifs->sd != -1)
-                       ret |= close(ifs->sd);
-               free(ifs);
-       }
-
-       return ret;
-}
-
 /*
  * This one differs between BSD and Linux in that on BSD this
  * disables looping multicast back to all *other* sockets on
@@ -373,6 +375,7 @@
 {
        struct ifaddrs *ifaddrs, *ifa;
        int modified;
+       int dnum = 0;
        size_t i;
 
        logit(LOG_INFO, "Updating interfaces ...");
@@ -404,6 +407,8 @@
        for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
                int sd;
 
+               logit(LOG_DEBUG, "Got %s, preparing ...", ifa->ifa_name);
+
                /* Interface filtering, optional command line argument */
                if (filter_iface(ifa->ifa_name, iflist, num)) {
                        logit(LOG_DEBUG, "Skipping %s, not in iflist.", 
ifa->ifa_name);
@@ -411,8 +416,10 @@
                }
 
                /* Do we have another in the same subnet? */
-               if (filter_addr(ifa->ifa_addr))
+               if (filter_addr(ifa->ifa_addr)) {
+                       logit(LOG_DEBUG, "Skipping %s, filtered.", 
ifa->ifa_name);
                        continue;
+               }
 
                /* OpenVPN workaround, issue #6 */
                if ((ifa->ifa_flags & IFF_POINTOPOINT) && 
!strncmp(ifa->ifa_name, "tun", 3)) {
@@ -426,8 +433,11 @@
                }
 
                sd = socket_open(ifa->ifa_name, ifa->ifa_addr, ttl, srv);
-               if (sd < 0)
+               if (sd < 0) {
+                       logit(LOG_DEBUG, "Failed opening socket on %s, error 
%d: %s",
+                             ifa->ifa_name, errno, strerror(errno));
                        continue;
+               }
 
 #ifdef __linux__
                multicast_loop(sd);
@@ -436,20 +446,37 @@
                if (!multicast_join(sd, ifa->ifa_addr))
                        logit(LOG_DEBUG, "Joined group %s on interface %s", 
MC_SSDP_GROUP, ifa->ifa_name);
 
-               if (ssdp_register(sd, ifa->ifa_addr, ifa->ifa_netmask, cb)) {
+               if (ssdp_register(sd, ifa->ifa_name, ifa->ifa_addr, 
ifa->ifa_netmask, cb)) {
                        close(sd);
                        break;
                }
 
-               logit(LOG_DEBUG, "Registered socket %d with ssd_recv() 
callback", sd);
+               logit(LOG_DEBUG, "Registered socket %d with ssdp_recv() 
callback", sd);
                modified++;
+               dnum++;
        }
 
        freeifaddrs(ifaddrs);
+       logit(LOG_DEBUG, "Set up %d interfaces.", dnum);
 
        return modified;
 }
 
+int ssdp_exit(void)
+{
+       struct ifsock *ifs, *tmp;
+       int ret = 0;
+
+       LIST_FOREACH_SAFE(ifs, &il, link, tmp) {
+               LIST_REMOVE(ifs, link);
+               if (ifs->sd != -1)
+                       ret |= close(ifs->sd);
+               free(ifs);
+       }
+
+       return ret;
+}
+
 /**
  * Local Variables:
  *  indent-tabs-mode: t
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/src/ssdp.h 
new/ssdp-responder-2.0/src/ssdp.h
--- old/ssdp-responder-1.9/src/ssdp.h   2022-10-30 17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/src/ssdp.h   2023-02-19 21:40:58.000000000 +0100
@@ -17,7 +17,10 @@
 #ifndef SSDP_H_
 #define SSDP_H_
 
+#ifdef HAVE_CONFIG_H
 #include "config.h"
+#endif
+
 #include <ctype.h>
 #include <err.h>
 #include <errno.h>
@@ -78,11 +81,12 @@
 struct ifsock {
        LIST_ENTRY(ifsock) link;
 
-       int stale;
-       int mod;
+       char ifname[16];
+       int  stale;
+       int  mod;
 
        /* Interface socket, one per interface address */
-       int sd;
+       int  sd;
 
        /* Interface address and netmask */
        struct sockaddr_in addr;
@@ -93,19 +97,30 @@
 
 extern int log_level;
 extern int log_opts;
+extern char uuid[42];
+extern char url[128];
+extern char mfrurl[128];
+extern char mfrnm[128];
+extern int  ttl;
+
+extern char **ifs;
+extern size_t ifnum;
 
 void log_init(int enable);
 void log_exit(void);
 int log_str2lvl(char *level);
 void logit(int severity, const char *format, ...);
 
+int netlink_init(void);
+
 struct ifsock *ssdp_find(struct sockaddr *sa);
 void ssdp_foreach(void (*cb)(struct ifsock *, int), int arg);
+size_t ssdp_num_sockets(void);
 
 int ssdp_init(int ttl, int srv, char *iflist[], size_t num, void (*cb)(int 
sd));
 int ssdp_exit(void);
 
-int ssdp_register(int sd, struct sockaddr *addr, struct sockaddr *mask, void 
(*cb)(int sd));
+int ssdp_register(int sd, char *ifname, struct sockaddr *addr, struct sockaddr 
*mask, void (*cb)(int sd));
 int ssdp_poll(int timeout);
 
 #endif /* SSDP_H_ */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/src/ssdpd.c 
new/ssdp-responder-2.0/src/ssdpd.c
--- old/ssdp-responder-1.9/src/ssdpd.c  2022-10-30 17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/src/ssdpd.c  2023-02-19 21:40:58.000000000 +0100
@@ -16,13 +16,29 @@
  */
 
 #include <sys/utsname.h>       /* uname() for !__linux__ */
+#include <sys/types.h>
+#include <pwd.h>
+
 #include "ssdp.h"
 
 char  server_string[64] = "POSIX UPnP/1.0 " PACKAGE_NAME "/" PACKAGE_VERSION;
+char  location[128];
 char  hostname[64];
+int   ttl = MC_TTL_DEFAULT;
+char *cachefn = NULL;
 char *ver = NULL;
 char *os  = NULL;
+#ifdef MANUFACTURER_URL
+char  mfrurl[128] = MANUFACTURER_URL;
+#else
+char  mfrurl[128];
+#endif
+char  mfrnm[128] = MANUFACTURER;
 char  uuid[42];
+char  url[128] = "http://%s/";;
+
+char **ifs;
+size_t ifnum;
 
 static char *supported_types[] = {
        SSDP_ST_ALL,
@@ -33,6 +49,7 @@
 };
 
 volatile sig_atomic_t running = 1;
+volatile sig_atomic_t recheck = 1;
 
 extern void web_init(void);
 
@@ -45,6 +62,15 @@
        sin->sin_addr.s_addr = inet_addr(group);
 }
 
+static char *compose_location(char *host)
+{
+       const char *fmt = location;
+       static char buf[256];
+
+       snprintf(buf, sizeof(buf), fmt, host);
+       return buf;
+}
+
 static void compose_response(char *type, char *host, char *buf, size_t len)
 {
        char usn[256];
@@ -68,7 +94,7 @@
        snprintf(buf, len, "HTTP/1.1 200 OK\r\n"
                 "Server: %s\r\n"
                 "Date: %s\r\n"
-                "Location: http://%s:%d%s\r\n";
+                "Location: %s\r\n"
                 "ST: %s\r\n"
                 "EXT: \r\n"
                 "USN: %s\r\n"
@@ -76,7 +102,7 @@
                 "\r\n",
                 server_string,
                 date,
-                host, LOCATION_PORT, LOCATION_DESC,
+                compose_location(host),
                 type,
                 usn,
                 CACHE_TIMEOUT);
@@ -101,7 +127,7 @@
        snprintf(buf, len, "NOTIFY * HTTP/1.1\r\n"
                 "Host: %s:%d\r\n"
                 "Server: %s\r\n"
-                "Location: http://%s:%d%s\r\n";
+                "Location: %s\r\n"
                 "NT: %s\r\n"
                 "NTS: ssdp:alive\r\n"
                 "USN: %s\r\n"
@@ -109,7 +135,7 @@
                 "\r\n",
                 MC_SSDP_GROUP, MC_SSDP_PORT,
                 server_string,
-                host, LOCATION_PORT, LOCATION_DESC,
+                compose_location(host),
                 type,
                 usn,
                 CACHE_TIMEOUT);
@@ -232,20 +258,21 @@
 
 static void wait_message(time_t tmo)
 {
-       while (1) {
-               int timeout;
+       int timeout = tmo - time(NULL);
 
-               timeout = tmo - time(NULL);
+       do {
                if (timeout < 0)
                        break;
 
                if (ssdp_poll(timeout * 1000) == -1) {
-                       if (errno == EINTR)
-                               break;
-
-                       err(1, "Unrecoverable error");
+                       if (errno != EINTR)
+                               err(1, "Unrecoverable error");
+                       break;
                }
-       }
+
+               timeout = tmo - time(NULL);
+
+       } while (timeout);
 }
 
 static void announce(struct ifsock *ifs, int mod)
@@ -263,6 +290,40 @@
        }
 }
 
+void ssdpd_refresh(void)
+{
+       if (ssdp_init(ttl, 1, ifs, ifnum, ssdp_recv) > 0) {
+               logit(LOG_INFO, "Sending SSDP NOTIFY on new interfaces ...");
+               ssdp_foreach(announce, 1);
+       }
+}
+
+static void drop_privs(void)
+{
+       struct passwd *pw;
+
+       if (chdir("/"))
+               logit(LOG_WARNING, "Failed 'cd /': %s", strerror(errno));
+
+       if (getuid())
+               return;
+
+       pw = getpwnam("ssdp");
+       if (!pw)
+               pw = getpwnam("nobody");
+       if (!pw) {
+       fail:
+               logit(LOG_WARNING, "Failed dropping root privileges: %s", 
strerror(errno));
+               return;
+       }
+
+       logit(LOG_NOTICE, "Dropping privileges to user %s (%d:%d)", 
pw->pw_name, pw->pw_uid, pw->pw_gid);
+       if (setgid(pw->pw_gid))
+               goto fail;
+       if (setuid(pw->pw_uid))
+               goto fail;
+}
+
 static char *strip_quotes(char *str)
 {
        char *ptr;
@@ -310,6 +371,12 @@
                                free(ver);
                        ver = strdup(strip_quotes(++ptr));
                }
+
+               if (!strncmp(line, "VERSION_CODENAME", 16) && (ptr = 
strchr(line, '='))) {
+                       logit(LOG_DEBUG, "Found VERSION_CODENAME:%s", ptr + 1);
+                       if (!ver)
+                               ver = strdup(strip_quotes(++ptr));
+               }
        }
 
        logit(LOG_DEBUG, "Found os:%s ver:%s", os, ver);
@@ -391,6 +458,11 @@
 {
        FILE *fp;
 
+       if (cachefn) {
+               strlcpy(fn, cachefn, len);
+               return fopen(fn, mode);
+       }
+
        snprintf(fn, len, _CACHEDIR "/" PACKAGE_NAME ".cache");
        fp = fopen(fn, mode);
        if (!fp) {
@@ -408,12 +480,17 @@
        char buf[42];
        FILE *fp;
 
+       if (uuid[0])
+               goto custom;
+
        fp = fopen_cache("r", file, sizeof(file));
        if (!fp) {
        generate:
                fp = fopen_cache("w", file, sizeof(file));
                if (!fp)
                        logit(LOG_WARNING, "Cannot create UUID cache, %s: %s", 
file, strerror(errno));
+               else
+                       logit(LOG_DEBUG, "Opened cache file %s for saving 
UUID", file);
 
                srand(time(NULL));
                snprintf(buf, sizeof(buf), 
"uuid:%8.8x-%4.4x-%4.4x-%4.4x-%6.6x%6.6x",
@@ -429,6 +506,7 @@
                        fclose(fp);
                }
        } else {
+               logit(LOG_DEBUG, "Opened UUID cache file %s", file);
                if (!fgets(buf, sizeof(buf), fp)) {
                        fclose(fp);
                        goto generate;
@@ -438,38 +516,65 @@
        }
 
        strcpy(uuid, buf);
+custom:
        logit(LOG_DEBUG, "URN: %s", uuid);
+       if (cachefn) {
+               free(cachefn);
+               cachefn = NULL;
+       }
 }
 
-static void exit_handler(int signo)
+static void sighandler(int signo)
 {
-       (void)signo;
-       running = 0;
+       switch (signo) {
+       case SIGALRM:
+               recheck = 1;
+               break;
+       default:
+               running = 0;
+               break;
+       }
 }
 
 static void signal_init(void)
 {
-       signal(SIGTERM, exit_handler);
-       signal(SIGINT,  exit_handler);
-       signal(SIGHUP,  exit_handler);
-       signal(SIGQUIT, exit_handler);
+       signal(SIGTERM, sighandler);
+       signal(SIGINT,  sighandler);
+       signal(SIGHUP,  sighandler);
+       signal(SIGQUIT, sighandler);
        signal(SIGPIPE, SIG_IGN); /* get EPIPE instead */
+       signal(SIGALRM, sighandler);
 }
 
 static int usage(int code)
 {
-       printf("Usage: %s [-hnsv] [-i SEC] [-l LEVEL] [-r SEC] [-t TTL] [IFACE 
[IFACE ...]]\n"
+       printf("Usage: %s [-hnsvw] [-c FILE] [-d URL] [-i SEC] [-l LEVEL] [-m 
NAME] [-M URL]\n"
+              "                      [-p URL] [-P FILE] [-r SEC] [-R NUM] [-t 
TTL] [-u UUID]\n"
+              "                      [IFACE [IFACE ...]]\n"
               "\n"
+              "    -c FILE   Path to alternate ssdpd.cache to store and/or 
read the UUID\n"
+              "    -d URL    Override UPnP description.xml URL in 
announcements.  The '%%s' in\n"
+              "              the URL is replaced with the IP, e.g. 
https://%%s:1901/main.xml\n";
               "    -h        This help text\n"
               "    -i SEC    SSDP notify interval (30-900), default %d sec\n"
               "    -l LVL    Set log level: none, err, notice (default), info, 
debug\n"
+              "    -m NAME   Override manufacturer in the default 
description.xml\n"
+              "    -M URL    Override manufacturerURL in the default 
description.xml\n"
               "    -n        Run in foreground, do not daemonize by default\n"
               "    -r SEC    Interface refresh interval (5-1800), default %d 
sec\n"
+              "    -R NUM    Initial retries, using 10 sec refresh interval, 
default 3 times\n"
+              "              Note: unused on systems with netlink interface 
monitoring.\n"
+              "    -p URL    Override presentationURL (WebUI) in the default 
description.xml\n"
+              "              The '%%s' is replaced with the IP address.  
Default: http://%%s/\n";
+              "    -P FILE   Override PID file location, absolute path 
required\n"
               "    -s        Use syslog, default unless running in foreground, 
-n\n"
               "    -t TTL    TTL for multicast frames, default 2, according to 
the UDA\n"
+              "    -u UUID   Custom UUID instead of auto-generating one\n"
               "    -v        Show program version\n"
+              "    -w        Disable built-in micro HTTP server on port %d\n"
               "\n"
-              "Bug report address : %s\n", PACKAGE_NAME, NOTIFY_INTERVAL, 
REFRESH_INTERVAL, PACKAGE_BUGREPORT);
+              "Bug report address : %s\n", PACKAGE_NAME, NOTIFY_INTERVAL, 
REFRESH_INTERVAL,
+              LOCATION_PORT, PACKAGE_BUGREPORT);
 #ifdef PACKAGE_URL
         printf("Project homepage   : %s\n", PACKAGE_URL);
 #endif
@@ -479,16 +584,29 @@
 
 int main(int argc, char *argv[])
 {
-       time_t now, rtmo = 0, itmo = 0;
+       time_t itmo = 0;
        int background = 1;
        int interval = NOTIFY_INTERVAL;
        int refresh = REFRESH_INTERVAL;
-       int ttl = MC_TTL_DEFAULT;
+       int initial = 10;
+       int inicnt = 3;
+       char *description = NULL;
+       char *pidfn = PACKAGE_NAME;
        int do_syslog = 1;
+       int do_web = 1;
+       int nlmon = 0;
        int c;
 
-       while ((c = getopt(argc, argv, "hi:l:nr:st:v")) != EOF) {
+       while ((c = getopt(argc, argv, "c:d:hi:l:m:M:np:P:r:R:st:u:vw")) != 
EOF) {
                switch (c) {
+               case 'c':
+                       cachefn = strdup(optarg);
+                       break;
+
+               case 'd':
+                       description = optarg;
+                       break;
+
                case 'h':
                        return usage(0);
 
@@ -504,17 +622,37 @@
                                return usage(1);
                        break;
 
+               case 'm':
+                       strlcpy(mfrnm, optarg, sizeof(mfrnm));
+                       break;
+
+               case 'M':
+                       strlcpy(mfrurl, optarg, sizeof(mfrurl));
+                       break;
+
                case 'n':
                        background = 0;
                        do_syslog--;
                        break;
 
+               case 'p':
+                       strlcpy(url, optarg, sizeof(url));
+                       break;
+
+               case 'P':
+                       pidfn = optarg;
+                       break;
+
                case 'r':
                        refresh = atoi(optarg);
                        if (refresh < 5 || refresh > 1800)
                                errx(1, "Invalid refresh interval (5-1800).");
                        break;
 
+               case 'R':
+                       inicnt = atoi(optarg);
+                       break;
+
                case 's':
                        do_syslog++;
                        break;
@@ -525,10 +663,18 @@
                                errx(1, "Invalid TTL (1-255).");
                        break;
 
+               case 'u':
+                       snprintf(uuid, sizeof(uuid), "uuid:%s", optarg);
+                       break;
+
                case 'v':
                        puts(PACKAGE_VERSION);
                        return 0;
 
+               case 'w':
+                       do_web = 0;
+                       break;
+
                default:
                        break;
                }
@@ -544,18 +690,42 @@
        log_init(do_syslog);
        uuidgen();
        lsb_init();
-       web_init();
-       pidfile(PACKAGE_NAME);
+       if (do_web)
+               web_init();
+       if (description)
+               strlcpy(location, description, sizeof(location));
+       else
+               snprintf(location, sizeof(location), "http://%s:%d%s";,
+                        "%s", LOCATION_PORT, LOCATION_DESC);
+
+       if (netlink_init() == 0)
+               nlmon = 1;
+       pidfile(pidfn);
+       drop_privs();
+
+       ifnum = argc - optind;
+       ifs = calloc(ifnum, sizeof(char *));
+       for (size_t i = 0; i < ifnum; i++)
+               ifs[i] = argv[optind + i];
 
        while (running) {
-               now = time(NULL);
+               time_t now = time(NULL);
 
-               if (rtmo <= now) {
-                       if (ssdp_init(ttl, 1, &argv[optind], argc - optind, 
ssdp_recv) > 0) {
-                               logit(LOG_INFO, "Sending SSDP NOTIFY on new 
interfaces ...");
-                               ssdp_foreach(announce, 1);
-                       }
-                       rtmo = now + refresh;
+               if (recheck) {
+                       ssdpd_refresh();
+
+                       /*
+                        * If we haven't got any sockets open yet, or we've
+                        * just started up.  Handle things differently, we may
+                        * start well before we get an initial DHCP lease.
+                        */
+                       if (!nlmon && !ssdp_num_sockets() && inicnt > 0) {
+                               alarm(initial);
+                               inicnt--;
+                       } else
+                               alarm(refresh);
+
+                       recheck = 0;
                }
 
                if (itmo <= now) {
@@ -564,11 +734,13 @@
                        itmo = now + interval;
                }
 
-               wait_message(MIN(rtmo, itmo));
+               wait_message(itmo);
        }
 
        lsb_exit();
        log_exit();
+       if (ifnum)
+               free(ifs);
 
        return ssdp_exit();
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ssdp-responder-1.9/src/web.c 
new/ssdp-responder-2.0/src/web.c
--- old/ssdp-responder-1.9/src/web.c    2022-10-30 17:16:41.000000000 +0100
+++ new/ssdp-responder-2.0/src/web.c    2023-02-19 21:40:58.000000000 +0100
@@ -15,7 +15,10 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.a
  */
 
+#ifdef HAVE_CONFIG_H
 #include "config.h"
+#endif
+
 #include <errno.h>
 #include <fcntl.h>
 #include <ifaddrs.h>
@@ -25,6 +28,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <time.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 #include <net/if.h>
@@ -46,14 +50,12 @@
        "  <friendlyName>%s</friendlyName>\r\n"
        "  <manufacturer>%s</manufacturer>\r\n%s"
        "  <modelName>%s</modelName>\r\n"
-       "  <UDN>uuid:%s</UDN>\r\n"
-       "  <presentationURL>http://%s</presentationURL>\r\n"
+       "  <UDN>%s</UDN>\r\n"
+       "  <presentationURL>%s</presentationURL>\r\n"
        " </device>\r\n"
        "</root>\r\n"
        "\r\n";
 
-extern char uuid[];
-
 /* Peek into SOCK_STREAM on accepted client socket to figure out inbound 
interface */
 static struct sockaddr_in *stream_peek(int sd, char *ifname, size_t iflen)
 {
@@ -89,18 +91,54 @@
         return &sin;
 }
 
+static char *compose_url(char *addr)
+{
+       const char *fmt = url;
+       static char buf[192];
+
+       snprintf(buf, sizeof(buf), fmt, addr);
+       return buf;
+}
+
+static char *compose_time(void)
+{
+       const char *rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT";
+       static char buf[100];
+       time_t now;
+
+       now = time(NULL);
+       strftime(buf, sizeof(buf), rfc1123fmt, gmtime(&now));
+
+       return buf;
+}
+
+static int validate_http(int sd, char *http)
+{
+       if (!http || (strncmp(http, "HTTP/1.0", 8) != 0 &&
+                     strncmp(http, "HTTP/1.1", 8) != 0)) {
+               if (write(sd, "HTTP/1.1 400 Bad Request\r\n", 26) < 0)
+                       logit(LOG_WARNING, "Failed returning status 400 to 
client: %s", strerror(errno));
+               return -1;
+       }
+
+       return 0;
+}
+
 static int respond(int sd, struct sockaddr_in *sin)
 {
        struct pollfd pfd = {
                .fd = sd,
                .events = POLLIN,
        };
-       char *head = "HTTP/1.1 200 OK\r\n"
+       const char *head = "HTTP/1.1 200 OK\r\n"
+               "Date: %s\r\n"
+               "Server: ssdp-responder/%s\r\n"
                "Content-Type: text/xml\r\n"
                "Connection: close\r\n"
                "\r\n";
-       char hostname[64], url[128] = "";
+       char manufacturer_url[192] = "";
        char mesg[1024], *reqline[3];
+       char hostname[64];
        int rc, rcvd;
 
        /* Check for early disconnect or client timeout */
@@ -122,39 +160,44 @@
 
        logit(LOG_DEBUG, "%s", mesg);
        reqline[0] = strtok(mesg, " \t\n");
-       if (strncmp(reqline[0], "GET", 4) == 0) {
-               reqline[1] = strtok(NULL, " \t");
-               reqline[2] = strtok(NULL, " \t\n");
-               if (strncmp(reqline[2], "HTTP/1.0", 8) != 0 && 
strncmp(reqline[2], "HTTP/1.1", 8) != 0) {
-                       if (write(sd, "HTTP/1.1 400 Bad Request\r\n", 26) < 0)
-                               logit(LOG_WARNING, "Failed returning status 400 
to client: %s", strerror(errno));
+       reqline[1] = strtok(NULL, " \t");
+       reqline[2] = strtok(NULL, " \t\n");
+
+       if (!reqline[0]) {
+               return -1;
+       } else if (strncmp(reqline[0], "HEAD", 5) == 0) {
+               if (validate_http(sd, reqline[2]))
+                       return -1;
+
+               snprintf(mesg, sizeof(mesg), head, compose_time(), VERSION);
+               if (send(sd, mesg, strlen(mesg), 0) < 0)
+                       return -1;
+       } else if (strncmp(reqline[0], "GET", 4) == 0) {
+               if (validate_http(sd, reqline[2]))
                        return -1;
-               }
 
                /* XXX: Add support for icon as well */
-               if (!strstr(reqline[1], LOCATION_DESC)) {
+               if (!reqline[1] || !strstr(reqline[1], LOCATION_DESC)) {
                        if (write(sd, "HTTP/1.1 404 Not Found\r\n", 24) < 0)
                                logit(LOG_WARNING, "Failed returning status 404 
to client: %s", strerror(errno));
                        return -1;
                }
 
                gethostname(hostname, sizeof(hostname));
-#ifdef MANUFACTURER_URL
-               snprintf(url, sizeof(url), "  
<manufacturerURL>%s</manufacturerURL>\r\n", MANUFACTURER_URL);
-#endif
-               logit(LOG_DEBUG, "Sending XML reply ...");
-               if (send(sd, head, strlen(head), 0) < 0)
-                       goto fail;
+               if (mfrurl[0])
+                       snprintf(manufacturer_url, sizeof(manufacturer_url),
+                                "  <manufacturerURL>%s</manufacturerURL>\r\n", 
mfrurl);
 
-               snprintf(mesg, sizeof(mesg), xml,
+               logit(LOG_DEBUG, "Sending XML reply ...");
+               rc = snprintf(mesg, sizeof(mesg), head, compose_time(), 
VERSION);
+               snprintf(&mesg[rc], sizeof(mesg) - rc, xml,
                         hostname,
-                        MANUFACTURER,
-                        url,
+                        mfrnm,
+                        manufacturer_url,
                         MODEL,
                         uuid,
-                        inet_ntoa(sin->sin_addr));
+                        compose_url(inet_ntoa(sin->sin_addr)));
                if (send(sd, mesg, strlen(mesg), 0) < 0) {
-               fail:
                        logit(LOG_WARNING, "Failed sending file to client: %s", 
strerror(errno));
                        return -1;
                }
@@ -163,7 +206,7 @@
        return 0;
 }
 
-void web_recv(int sd)
+static void web_recv(int sd)
 {
        int client;
        char ifname[IF_NAMESIZE + 1] = "UNKNOWN";
@@ -217,7 +260,7 @@
                return;
        }
 
-       if (!ssdp_register(sd, (struct sockaddr *)&sin, NULL, web_recv)) {
+       if (!ssdp_register(sd, NULL, (struct sockaddr *)&sin, NULL, web_recv)) {
                char host[20];
 
                inet_ntop(AF_INET, &sin.sin_addr, host, sizeof(host));

Reply via email to