]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Auth webserver Unix socket support
authorGeorg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
Mon, 10 Mar 2025 01:10:59 +0000 (02:10 +0100)
committerGeorg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
Mon, 17 Mar 2025 12:10:01 +0000 (13:10 +0100)
This introduces support for binding to a Unix instead of a TCP/IP
socket, which is useful in applications where binding to a TCP/IP socket
is not desired due to security and/or performance considerations or
constraints of the surrounding system. Closes #8677.

Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
docs/http-api/index.rst
docs/settings.rst
pdns/auth-main.cc
pdns/iputils.hh
pdns/logger.cc
pdns/logger.hh
pdns/sstuff.hh
pdns/webserver.cc
pdns/webserver.hh

index 100a48a7ce031258bc88234f3a24c8c14154839e..2051947955cc9645a8dce3781b0a29ee49539159 100644 (file)
@@ -15,10 +15,10 @@ The webserver does not allow remote management of the daemon, but allows control
 The following webserver related configuration items are available:
 
 * :ref:`setting-webserver`: If set to anything but 'no', a webserver is launched.
-* :ref:`setting-webserver-address`: Address to bind the webserver to. Defaults to 127.0.0.1, which implies that only the local computer is able to connect to the nameserver! To allow remote hosts to connect, change to 0.0.0.0 or the physical IP address of your nameserver.
+* :ref:`setting-webserver-address`: IP address or UNIX domain socket path to bind the webserver to. Defaults to 127.0.0.1, which implies that only the local computer is able to connect to the nameserver! To allow remote hosts to connect, change to 0.0.0.0 or the physical IP address of your nameserver.
 * :ref:`setting-webserver-password`: If set, viewers will have to enter this password in order to gain access to the statistics, in addition to entering the configured API key on the index page.
-* :ref:`setting-webserver-port`: Port to bind the webserver to.
-* :ref:`setting-webserver-allow-from`: Netmasks that are allowed to connect to the webserver
+* :ref:`setting-webserver-port`: Port to bind the webserver to (not relevant if :ref:`setting-webserver-address` is set to a UNIX domain socket).
+* :ref:`setting-webserver-allow-from`: Netmasks that are allowed to connect to the webserver (not relevant if :ref:`setting-webserver-address` is set to a UNIX domain socket).
 * :ref:`setting-webserver-max-bodysize`: Maximum request/response body size in megabytes
 * :ref:`setting-webserver-connection-timeout`: Request/response timeout in seconds
 
index 25b5dc8d7fa07998b407e4fd8c2184419e78f7a6..f4c729a7cd71c242dbc56aaaf26238dbcd4643c3 100644 (file)
@@ -2035,7 +2035,7 @@ Start a webserver for monitoring. See :doc:`performance`".
 -  IP Address
 -  Default: 127.0.0.1
 
-IP Address for webserver/API to listen on.
+IP Address or path to UNIX domain socket for webserver/API to listen on.
 
 .. _setting-webserver-allow-from:
 
@@ -2046,6 +2046,7 @@ IP Address for webserver/API to listen on.
 -  Default: 127.0.0.1,::1
 
 Webserver/API access is only allowed from these subnets.
+Ignored if ``webserver-address`` is set to a UNIX domain socket.
 
 .. _setting-webserver-hash-plaintext-credentials:
 
@@ -2141,6 +2142,7 @@ Password required to access the webserver. Since 4.6.0 the password can be hashe
 -  Default: 8081
 
 The port where webserver/API will listen on.
+Ignored if ``webserver-address`` is set to a UNIX domain socket.
 
 .. _setting-webserver-print-arguments:
 
index d1eb4a4d4fc277bfce1955abda19347b3e17416f..2f276179e87ad0c099dbe929db224f94f579c29a 100644 (file)
@@ -239,7 +239,7 @@ static void declareArguments()
 
   ::arg().setSwitch("webserver", "Start a webserver for monitoring (api=yes also enables the HTTP listener)") = "no";
   ::arg().setSwitch("webserver-print-arguments", "If the webserver should print arguments") = "no";
-  ::arg().set("webserver-address", "IP Address of webserver/API to listen on") = "127.0.0.1";
+  ::arg().set("webserver-address", "IP Address or path to UNIX domain socket for webserver/API to listen on") = "127.0.0.1";
   ::arg().set("webserver-port", "Port of webserver/API to listen on") = "8081";
   ::arg().set("webserver-password", "Password required for accessing the webserver") = "";
   ::arg().set("webserver-allow-from", "Webserver/API access is only allowed from these subnets") = "127.0.0.1,::1";
index 48e963ae1ac706240311d899c1ba3e7b4ad7bed4..b237d47809c0ddb75c02ed9bc68f6d4d09206713 100644 (file)
@@ -31,6 +31,7 @@
 #include "misc.hh"
 #include <netdb.h>
 #include <sstream>
+#include <sys/un.h>
 
 #include "namespaces.hh"
 
@@ -489,6 +490,134 @@ union ComboAddress
   };
 };
 
+union SockaddrWrapper
+{
+  sockaddr_in sin4{};
+  sockaddr_in6 sin6;
+  sockaddr_un sinun;
+
+  [[nodiscard]] socklen_t getSocklen() const
+  {
+    if (sin4.sin_family == AF_INET) {
+      return sizeof(sin4);
+    }
+    if (sin6.sin6_family == AF_INET6) {
+      return sizeof(sin6);
+    }
+    if (sinun.sun_family == AF_UNIX) {
+      return sizeof(sinun);
+    }
+    return 0;
+  }
+
+  SockaddrWrapper()
+  {
+    sin4.sin_family = AF_INET;
+    sin4.sin_addr.s_addr = 0;
+    sin4.sin_port = 0;
+  }
+
+  SockaddrWrapper(const struct sockaddr* socketAddress, socklen_t salen)
+  {
+    setSockaddr(socketAddress, salen);
+  };
+
+  SockaddrWrapper(const struct sockaddr_in6* socketAddress)
+  {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    setSockaddr(reinterpret_cast<const struct sockaddr*>(socketAddress), sizeof(struct sockaddr_in6));
+  };
+
+  SockaddrWrapper(const struct sockaddr_in* socketAddress)
+  {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    setSockaddr(reinterpret_cast<const struct sockaddr*>(socketAddress), sizeof(struct sockaddr_in));
+  };
+
+  SockaddrWrapper(const struct sockaddr_un* socketAddress)
+  {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    setSockaddr(reinterpret_cast<const struct sockaddr*>(socketAddress), sizeof(struct sockaddr_un));
+  };
+
+  void setSockaddr(const struct sockaddr* socketAddress, socklen_t salen)
+  {
+    if (salen > sizeof(struct sockaddr_un)) {
+      throw PDNSException("ComboAddress can't handle other than sockaddr_in, sockaddr_in6 or sockaddr_un");
+    }
+    memcpy(this, socketAddress, salen);
+  }
+
+  explicit SockaddrWrapper(const string& str, uint16_t port = 0)
+  {
+    memset(&sinun, 0, sizeof(sinun));
+    sin4.sin_family = AF_INET;
+    sin4.sin_port = 0;
+    if (str == "\"\"" || str == "''") {
+      throw PDNSException("Stray quotation marks in address.");
+    }
+    if (makeIPv4sockaddr(str, &sin4) != 0) {
+      sin6.sin6_family = AF_INET6;
+      if (makeIPv6sockaddr(str, &sin6) < 0) {
+        sinun.sun_family = AF_UNIX;
+        // only attempt Unix socket address if address candidate does not contain a port
+        if (str.find(':') != string::npos || makeUNsockaddr(str, &sinun) < 0) {
+          throw PDNSException("Unable to convert presentation address '" + str + "'");
+        }
+      }
+    }
+    if (sinun.sun_family != AF_UNIX && sin4.sin_port == 0) { // 'str' overrides port!
+      sin4.sin_port = htons(port);
+    }
+  }
+
+  [[nodiscard]] bool isIPv6() const
+  {
+    return sin4.sin_family == AF_INET6;
+  }
+  [[nodiscard]] bool isIPv4() const
+  {
+    return sin4.sin_family == AF_INET;
+  }
+  [[nodiscard]] bool isUnixSocket() const
+  {
+    return sin4.sin_family == AF_UNIX;
+  }
+
+  [[nodiscard]] string toString() const
+  {
+    if (sinun.sun_family == AF_UNIX) {
+      return sinun.sun_path;
+    }
+    std::array<char, 1024> host{};
+    if (sin4.sin_family != 0) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      int retval = getnameinfo(reinterpret_cast<const struct sockaddr*>(this), getSocklen(), host.data(), host.size(), nullptr, 0, NI_NUMERICHOST);
+      if (retval == 0) {
+        return host.data();
+      }
+      return "invalid " + string(gai_strerror(retval));
+    }
+    return "invalid";
+  }
+
+  [[nodiscard]] string toStringWithPort() const
+  {
+    if (sinun.sun_family == AF_UNIX) {
+      return toString();
+    }
+    if (sin4.sin_family == AF_INET) {
+      return toString() + ":" + std::to_string(ntohs(sin4.sin_port));
+    }
+    return "[" + toString() + "]:" + std::to_string(ntohs(sin4.sin_port));
+  }
+
+  void reset()
+  {
+    memset(&sinun, 0, sizeof(sinun));
+  }
+};
+
 /** This exception is thrown by the Netmask class and by extension by the NetmaskGroup class */
 class NetmaskException : public PDNSException
 {
index 52a5f08f72fe632e1b24422cb8898a5aadbcb59f..d01bf6a647e53ec307acc51ae50e8d815a8ebdcf 100644 (file)
@@ -217,6 +217,12 @@ Logger& Logger::operator<<(const ComboAddress& ca)
   return *this;
 }
 
+Logger& Logger::operator<<(const SockaddrWrapper& sockaddr)
+{
+  *this << sockaddr.toString();
+  return *this;
+}
+
 void addTraceTS(const timeval& start, ostringstream& str)
 {
   const auto& content = str.str();
index d1581809c4f313248142d246e52cf6b9fe84d655..7944038db5980390ba4b3aa1f698f45d990ee7fd 100644 (file)
@@ -109,6 +109,7 @@ public:
   Logger& operator<<(const string& s); //!< log a string
   Logger& operator<<(const DNSName&);
   Logger& operator<<(const ComboAddress&); //!< log an address
+  Logger& operator<<(const SockaddrWrapper&); //!< log an address
   Logger& operator<<(Urgency); //!< set the urgency, << style
 
   Logger& operator<<(const QType& qtype)
index e673c320dde6dde394ebd9f8af10066e489149bb..50a81622dbac22640aa6115e589bf16cf0d9bec8 100644 (file)
@@ -162,7 +162,8 @@ public:
   }
 
   //! Bind the socket to a specified endpoint
-  void bind(const ComboAddress& local, bool reuseaddr = true) const
+  template <typename T>
+  void bind(const T& local, bool reuseaddr = true) const
   {
     int tmp = 1;
     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
index ae3deb010f94645dc17cbb17a6ad29b0be37f5f2..c65bce3ef0f9e76d88c02b4a4aa6ebd9b7048290 100644 (file)
 #include <yahttp/router.hpp>
 #include <algorithm>
 #include <bitset>
+#include <unistd.h>
+#include <filesystem>
+
+namespace filesystem = std::filesystem;
 
 json11::Json HttpRequest::json()
 {
@@ -626,10 +630,25 @@ WebServer::WebServer(string listenaddress, int port) :
 
 void WebServer::bind()
 {
+  if (filesystem::is_socket(d_listenaddress.c_str())) {
+    int err=unlink(d_listenaddress.c_str());
+    if(err < 0 && errno!=ENOENT) {
+      SLOG(g_log<<Logger::Error<<d_logprefix<<"Listening on HTTP socket failed, unable to remove existing socket at "<<d_listenaddress<<endl,
+           d_slog->error(Logr::Error, e.what(), "Listening on HTTP socket failed, unable to remove existing socket", "exception", d_listenaddress));
+      d_server = nullptr;
+      return;
+    }
+  }
+
   try {
     d_server = createServer();
-    SLOG(g_log<<Logger::Warning<<d_logprefix<<"Listening for HTTP requests on "<<d_server->d_local.toStringWithPort()<<endl,
-         d_slog->info(Logr::Info, "Listening for HTTP requests", "address", Logging::Loggable(d_server->d_local)));
+    if (d_server->d_local.isUnixSocket()) {
+      SLOG(g_log<<Logger::Warning<<d_logprefix<<"Listening for HTTP requests on "<<d_listenaddress<<endl,
+           d_slog->info(Logr::Info, "Listening for HTTP requests", "path", Logging::Loggable(d_listenaddress)));
+    } else {
+        SLOG(g_log<<Logger::Warning<<d_logprefix<<"Listening for HTTP requests on "<<d_server->d_local.toStringWithPort()<<endl,
+             d_slog->info(Logr::Info, "Listening for HTTP requests", "address", Logging::Loggable(d_server->d_local)));
+    }
   }
   catch(NetworkError &e) {
     SLOG(g_log<<Logger::Error<<d_logprefix<<"Listening on HTTP socket failed: "<<e.what()<<endl,
@@ -651,7 +670,7 @@ void WebServer::go()
         if (!client) {
           continue;
         }
-        if (client->acl(d_acl)) {
+        if (d_server->d_local.isUnixSocket() || client->acl(d_acl)) {
           std::thread webHandler(WebServerConnectionThreadStart, this, client);
           webHandler.detach();
         } else {
index 767f986d3ec0d0d13e5f22fc1dc5259859fbfaa1..12d8aec1269800f2c62a49af8edc3a58b515772a 100644 (file)
@@ -179,7 +179,7 @@ public:
   }
   virtual ~Server() = default;
 
-  ComboAddress d_local;
+  SockaddrWrapper d_local;
 
   std::shared_ptr<Socket> accept() {
     return std::shared_ptr<Socket>(d_server_socket.accept());