]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Initial support for listening on UNIX socket endpoints.
authorPeter Powell <petpow@saberuk.com>
Fri, 13 Jul 2018 09:28:28 +0000 (10:28 +0100)
committerPeter Powell <petpow@saberuk.com>
Wed, 18 Jul 2018 18:22:17 +0000 (19:22 +0100)
docs/conf/inspircd.conf.example
include/inspircd.h
include/socket.h
src/coremods/core_hostname_lookup.cpp
src/listensocket.cpp
src/modules/m_cloaking.cpp
src/modules/m_dnsbl.cpp
src/modules/m_ident.cpp
src/modules/m_spanningtree/main.cpp
src/socket.cpp

index d06989551e4332536250ad0b580c8775ab6d004f..d97837d39f2df8aed874c81c521cbdcc749306b6 100644 (file)
 # module).
 #<bind address="" port="7002" type="clients" hook="websocket">
 
+# EXPERIMENTAL: Listener that binds on a UNIX endpoint instead of a TCP/IP endpoint:
+#<bind path="/tmp/inspircd.sock" type="clients">
+
 # You can define a custom <sslprofile> tag which defines the SSL configuration
 # for this listener. See the wiki page for the SSL module you are using for
 # more details.
index 447fb844138edec56df5ed899d6e54f8cf1f4e06..b028280f7c661309bfb48dfb3916b06a61900657 100644 (file)
@@ -344,6 +344,13 @@ class CoreExport InspIRCd
         */
        static void DefaultGenRandom(char* output, size_t max);
 
+       /** Bind to a specific port from a config tag.
+        * @param Tag the tag that contains bind information.
+        * @param sa The endpoint to listen on.
+        * @params old_ports Previously listening ports that may be on the same endpoint.
+        */
+       bool BindPort(ConfigTag* tag, const irc::sockets::sockaddrs& sa, std::vector<ListenSocket*>& old_ports);
+
        /** Bind all ports specified in the configuration file.
         * @return The number of ports bound without error
         */
index 6e9a2051840a558328bedc8b75aa9ed806329ceb..47e89070f0fb034dc21f165c3050e2a3501bfbe4 100644 (file)
@@ -32,6 +32,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
+#include <sys/un.h>
 #include <netinet/in.h>
 #include <unistd.h>
 #include <fcntl.h>
@@ -59,6 +60,7 @@ namespace irc
                        struct sockaddr sa;
                        struct sockaddr_in in4;
                        struct sockaddr_in6 in6;
+                       struct sockaddr_un un;
                        /** Return the family of the socket (e.g. AF_INET). */
                        int family() const;
                        /** Return the size of the structure for syscall passing */
index bf882abf63eb03742eb4d904315a74376637939a..ec93732b124c732b7569f36454f6fdde55b3b762 100644 (file)
@@ -199,6 +199,13 @@ class ModuleHostnameLookup : public Module
                        return;
                }
 
+               // Clients can't have a DNS hostname if they aren't connected via IPv4 or IPv6.
+               if (user->client_sa.family() != AF_INET && user->client_sa.family() != AF_INET6)
+               {
+                       user->WriteNotice("*** Skipping host resolution (connected via a non-IP socket)");
+                       return;
+               }
+
                user->WriteNotice("*** Looking up your hostname...");
 
                UserResolver* res_reverse = new UserResolver(*this->DNS, this, user, user->GetIPString(), DNS::QUERY_PTR);
index 71364238b4f420bf3806c60141e245fffd1d5408..5ecaab460c4850949e689bff6b5baa77afedfee5 100644 (file)
@@ -108,8 +108,12 @@ ListenSocket::~ListenSocket()
        {
                ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Shut down listener on fd %d", this->fd);
                SocketEngine::Shutdown(this, 2);
+
                if (SocketEngine::Close(this) != 0)
                        ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Failed to cancel listener: %s", strerror(errno));
+
+               if (bind_sa.family() == AF_UNIX && unlink(bind_sa.un.sun_path))
+                       ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Failed to unlink UNIX socket: %s", strerror(errno));
        }
 }
 
@@ -163,6 +167,14 @@ void ListenSocket::OnEventHandlerRead()
                        memcpy(&server.in4.sin_addr.s_addr, server.in6.sin6_addr.s6_addr + 12, sizeof(uint32_t));
                }
        }
+       else if (client.family() == AF_UNIX)
+       {
+               // Clients connecting via UNIX sockets don't have paths so give them
+               // the server path as defined in RFC 1459 section 8.1.1.
+               //
+               // strcpy is safe here because sizeof(sockaddr_un.sun_path) is equal on both.
+               strcpy(client.un.sun_path, server.un.sun_path);
+       }
 
        SocketEngine::NonBlocking(incomingSockfd);
 
index 87ff14a9d15c4e25b83ba464ebdd13100193b641..75dc889f9ec6ae388070966ae017072231a2003b 100644 (file)
@@ -395,6 +395,10 @@ class ModuleCloaking : public Module
                if (cloak)
                        return;
 
+               // TODO: decide how we are going to cloak AF_UNIX hostnames.
+               if (dest->client_sa.family() != AF_INET && dest->client_sa.family() != AF_INET6)
+                       return;
+
                cu.ext.set(dest, GenCloak(dest->client_sa, dest->GetIPString(), dest->GetRealHost()));
        }
 };
index 95913c235a8f69d48dfd4e456ad4efe5a20ad053..10b0e272840cfc6c024e4a4b825cf8e8f5772fe6 100644 (file)
@@ -339,6 +339,10 @@ class ModuleDNSBL : public Module, public Stats::EventListener
                if ((user->exempt) || !DNS)
                        return;
 
+               // Clients can't be in a DNSBL if they aren't connected via IPv4 or IPv6.
+               if (user->client_sa.family() != AF_INET && user->client_sa.family() != AF_INET6)
+                       return;
+
                if (user->MyClass)
                {
                        if (!user->MyClass->config->getBool("usednsbl", true))
index f645a77ffb74eb7ce83a8c3ace29bd862f9d4d4d..ca12a9ba3bf836182c16a55f704555178c338027 100644 (file)
@@ -277,6 +277,10 @@ class ModuleIdent : public Module
 
        void OnUserInit(LocalUser *user) CXX11_OVERRIDE
        {
+               // The ident protocol requires that clients are connecting over a protocol with ports.
+               if (user->client_sa.family() != AF_INET && user->client_sa.family() != AF_INET6)
+                       return;
+
                ConfigTag* tag = user->MyClass->config;
                if (!tag->getBool("useident", true))
                        return;
index 0b311a0bd7f16e2760edeb222965d40eba25744d..8bc3bfd9cb573b14170e163f6b75267fcede2263 100644 (file)
@@ -203,7 +203,13 @@ void ModuleSpanningTree::ConnectServer(Link* x, Autoconnect* y)
                return;
        }
 
-       if (strchr(x->IPAddr.c_str(),':'))
+       if (x->IPAddr.find('/') != std::string::npos)
+       {
+               struct stat sb;
+               if (stat(x->IPAddr.c_str(), &sb) == -1 || !S_ISSOCK(sb.st_mode))
+                       ipvalid = false;
+       }
+       if (x->IPAddr.find(':') != std::string::npos)
        {
                in6_addr n;
                if (inet_pton(AF_INET6, x->IPAddr.c_str(), &n) < 1)
index 5252e01e9a6085701a275667e025f3f075b73ff8..1d110323567a589ca39d3a55c89a2c1884363540 100644 (file)
 
 #include "inspircd.h"
 
-int InspIRCd::BindPorts(FailedPortList &failed_ports)
+bool InspIRCd::BindPort(ConfigTag* tag, const irc::sockets::sockaddrs& sa, std::vector<ListenSocket*>& old_ports)
+{
+       for (std::vector<ListenSocket*>::iterator n = old_ports.begin(); n != old_ports.end(); ++n)
+       {
+               if ((**n).bind_sa == sa)
+               {
+                       // Replace tag, we know addr and port match, but other info (type, ssl) may not.
+                       ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "Replacing listener on %s from old tag at %s with new tag from %s",
+                               sa.str().c_str(), (*n)->bind_tag->getTagLocation().c_str(), tag->getTagLocation().c_str());
+                       (*n)->bind_tag = tag;
+                       (*n)->ResetIOHookProvider();
+
+                       old_ports.erase(n);
+                       return true;
+               }
+       }
+       
+       ListenSocket* ll = new ListenSocket(tag, sa);
+       if (ll->GetFd() < 0)
+       {
+               ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "Failed to listen on %s from tag at %s: %s",
+                       sa.str().c_str(), tag->getTagLocation().c_str(), strerror(errno));
+               delete ll;
+               return false;
+       }
+
+       ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "Added a listener on %s from tag at %s", sa.str().c_str(), tag->getTagLocation().c_str());
+       ports.push_back(ll);
+       return true;
+}
+
+int InspIRCd::BindPorts(FailedPortList& failed_ports)
 {
        int bound = 0;
        std::vector<ListenSocket*> old_ports(ports.begin(), ports.end());
 
        ConfigTagList tags = ServerInstance->Config->ConfTags("bind");
-       for(ConfigIter i = tags.first; i != tags.second; ++i)
+       for (ConfigIter i = tags.first; i != tags.second; ++i)
        {
                ConfigTag* tag = i->second;
-               std::string porttag = tag->getString("port");
-               std::string Addr = tag->getString("address");
-
-               if (strncasecmp(Addr.c_str(), "::ffff:", 7) == 0)
-                       this->Logs->Log("SOCKET", LOG_DEFAULT, "Using 4in6 (::ffff:) isn't recommended. You should bind IPv4 addresses directly instead.");
 
-               irc::portparser portrange(porttag, false);
-               int portno = -1;
-               while (0 != (portno = portrange.GetToken()))
+               // Are we creating a TCP/IP listener?
+               const std::string address = tag->getString("address");
+               const std::string portlist = tag->getString("port");
+               if (!address.empty() || !portlist.empty())
                {
-                       irc::sockets::sockaddrs bindspec;
-                       if (!irc::sockets::aptosa(Addr, portno, bindspec))
-                               continue;
+                       // InspIRCd supports IPv4 and IPv6 natively; no 4in6 required.
+                       if (strncasecmp(address.c_str(), "::ffff:", 7) == 0)
+                               this->Logs->Log("SOCKET", LOG_DEFAULT, "Using 4in6 (::ffff:) isn't recommended. You should bind IPv4 addresses directly instead.");
 
-                       bool skip = false;
-                       for (std::vector<ListenSocket*>::iterator n = old_ports.begin(); n != old_ports.end(); ++n)
-                       {
-                               if ((**n).bind_sa == bindspec)
-                               {
-                                       (*n)->bind_tag = tag; // Replace tag, we know addr and port match, but other info (type, ssl) may not
-                                       (*n)->ResetIOHookProvider();
-
-                                       skip = true;
-                                       old_ports.erase(n);
-                                       break;
-                               }
-                       }
-                       if (!skip)
+                       // A TCP listener with no ports is not very useful.
+                       if (portlist.empty())
+                               this->Logs->Log("SOCKET", LOG_DEFAULT, "TCP listener on %s at %s has no ports specified!",
+                                       address.empty() ? "*" : address.c_str(), tag->getTagLocation().c_str());
+
+                       irc::portparser portrange(portlist, false);
+                       for (int port; (port = portrange.GetToken()); )
                        {
-                               ListenSocket* ll = new ListenSocket(tag, bindspec);
+                               irc::sockets::sockaddrs bindspec;
+                               if (!irc::sockets::aptosa(address, port, bindspec))
+                                       continue;
 
-                               if (ll->GetFd() > -1)
-                               {
-                                       bound++;
-                                       ports.push_back(ll);
-                               }
-                               else
-                               {
+                               if (!BindPort(tag, bindspec, old_ports))
                                        failed_ports.push_back(std::make_pair(bindspec, errno));
-                                       delete ll;
-                               }
+                               else
+                                       bound++;
+                       }
+                       continue;
+               }
+
+               // Are we creating a UNIX listener?
+               const std::string path = tag->getString("path");
+               if (!path.empty())
+               {
+                       // UNIX socket paths are length limited to less than PATH_MAX.
+                       irc::sockets::sockaddrs bindspec;
+                       if (path.length() > std::min(ServerInstance->Config->Limits.MaxHost, sizeof(bindspec.un.sun_path)))
+                       {
+                               this->Logs->Log("SOCKET", LOG_DEFAULT, "UNIX listener on %s at %s specified a path that is too long!",
+                                       path.c_str(), tag->getTagLocation().c_str());
+                               continue;
                        }
+
+                       // Create the bindspec manually (aptosa doesn't work with AF_UNIX yet).
+                       memset(&bindspec, 0, sizeof(bindspec));
+                       bindspec.un.sun_family = AF_UNIX;
+                       stpncpy(bindspec.un.sun_path, path.c_str(), sizeof(bindspec.un.sun_path) - 1);
+
+                       if (!BindPort(tag, bindspec, old_ports))
+                               failed_ports.push_back(std::make_pair(bindspec, errno));
+                       else
+                               bound++;
                }
        }
 
@@ -138,57 +179,89 @@ int irc::sockets::sockaddrs::family() const
 
 int irc::sockets::sockaddrs::port() const
 {
-       if (family() == AF_INET)
-               return ntohs(in4.sin_port);
-       if (family() == AF_INET6)
-               return ntohs(in6.sin6_port);
-       return -1;
+       switch (family())
+       {
+               case AF_INET:
+                       return ntohs(in4.sin_port);
+
+               case AF_INET6:
+                       return ntohs(in6.sin6_port);
+
+               case AF_UNIX:
+                       return 0;
+       }
+
+       // If we have reached this point then we have encountered a bug.
+       ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::sockaddrs::port(): socket type %d is unknown!", family());
+       return 0;
 }
 
 std::string irc::sockets::sockaddrs::addr() const
 {
-       char addrv[INET6_ADDRSTRLEN+1];
-       if (family() == AF_INET)
-       {
-               if (!inet_ntop(AF_INET, (void*)&in4.sin_addr, addrv, sizeof(addrv)))
-                       return "";
-               return addrv;
-       }
-       else if (family() == AF_INET6)
+       switch (family())
        {
-               if (!inet_ntop(AF_INET6, (void*)&in6.sin6_addr, addrv, sizeof(addrv)))
-                       return "";
-               return addrv;
+               case AF_INET:
+                       char ip4addr[INET_ADDRSTRLEN];
+                       if (!inet_ntop(AF_INET, (void*)&in4.sin_addr, ip4addr, sizeof(ip4addr)))
+                               return "0.0.0.0";
+                       return ip4addr;
+
+               case AF_INET6:
+                       char ip6addr[INET6_ADDRSTRLEN];
+                       if (!inet_ntop(AF_INET6, (void*)&in6.sin6_addr, ip6addr, sizeof(ip6addr)))
+                               return "0:0:0:0:0:0:0:0";
+                       return ip6addr;
+
+               case AF_UNIX:
+                       return un.sun_path;
        }
-       return "";
+
+       // If we have reached this point then we have encountered a bug.
+       ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::sockaddrs::addr(): socket type %d is unknown!", family());
+       return "<unknown>";
 }
 
 std::string irc::sockets::sockaddrs::str() const
 {
-       if (family() == AF_INET)
+       switch (family())
        {
-               char ipaddr[INET_ADDRSTRLEN];
-               inet_ntop(AF_INET, (void*)&in4.sin_addr, ipaddr, sizeof(ipaddr));
-               return InspIRCd::Format("%s:%u", ipaddr, ntohs(in4.sin_port));
+               case AF_INET:
+                       char ip4addr[INET_ADDRSTRLEN];
+                       if (!inet_ntop(AF_INET, (void*)&in4.sin_addr, ip4addr, sizeof(ip4addr)))
+                               strcpy(ip4addr, "0.0.0.0");
+                       return InspIRCd::Format("%s:%u", ip4addr, ntohs(in4.sin_port));
+
+               case AF_INET6:
+                       char ip6addr[INET6_ADDRSTRLEN];
+                       if (!inet_ntop(AF_INET6, (void*)&in6.sin6_addr, ip6addr, sizeof(ip6addr)))
+                               strcpy(ip6addr, "0:0:0:0:0:0:0:0");
+                       return InspIRCd::Format("[%s]:%u", ip6addr, ntohs(in6.sin6_port));
+
+               case AF_UNIX:
+                       return un.sun_path;
        }
 
-       if (family() == AF_INET6)
-       {
-               char ipaddr[INET6_ADDRSTRLEN];
-               inet_ntop(AF_INET6, (void*)&in6.sin6_addr, ipaddr, sizeof(ipaddr));
-               return InspIRCd::Format("[%s]:%u", ipaddr, ntohs(in6.sin6_port));
-       }
-
-       // This should never happen.
+       // If we have reached this point then we have encountered a bug.
+       ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::sockaddrs::str(): socket type %d is unknown!", family());
        return "<unknown>";
 }
 
 socklen_t irc::sockets::sockaddrs::sa_size() const
 {
-       if (family() == AF_INET)
-               return sizeof(in4);
-       if (family() == AF_INET6)
-               return sizeof(in6);
+       switch (family())
+       {
+               case AF_INET:
+                       return sizeof(in4);
+
+               case AF_INET6:
+                       return sizeof(in6);
+
+               case AF_UNIX:
+                       return sizeof(un);
+       }
+
+       // If we have reached this point then we have encountered a bug.
+       ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::sockaddrs::sa_size(): socket type %d is unknown!", family());
        return 0;
 }
 
@@ -196,10 +269,21 @@ bool irc::sockets::sockaddrs::operator==(const irc::sockets::sockaddrs& other) c
 {
        if (family() != other.family())
                return false;
-       if (family() ==  AF_INET)
-               return (in4.sin_port == other.in4.sin_port) && (in4.sin_addr.s_addr == other.in4.sin_addr.s_addr);
-       if (family() ==  AF_INET6)
-               return (in6.sin6_port == other.in6.sin6_port) && !memcmp(in6.sin6_addr.s6_addr, other.in6.sin6_addr.s6_addr, 16);
+
+       switch (family())
+       {
+               case AF_INET:
+                       return (in4.sin_port == other.in4.sin_port) && (in4.sin_addr.s_addr == other.in4.sin_addr.s_addr);
+
+               case AF_INET6:
+                       return (in6.sin6_port == other.in6.sin6_port) && !memcmp(in6.sin6_addr.s6_addr, other.in6.sin6_addr.s6_addr, 16);
+
+               case AF_UNIX:
+                       return !strcmp(un.sun_path, other.un.sun_path);
+       }
+
+       // If we have reached this point then we have encountered a bug.
+       ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::sockaddrs::operator==(): socket type %d is unknown!", family());
        return !memcmp(this, &other, sizeof(*this));
 }
 
@@ -207,31 +291,38 @@ static void sa2cidr(irc::sockets::cidr_mask& cidr, const irc::sockets::sockaddrs
 {
        const unsigned char* base;
        unsigned char target_byte;
-       cidr.type = sa.family();
 
        memset(cidr.bits, 0, sizeof(cidr.bits));
 
-       if (cidr.type == AF_INET)
-       {
-               target_byte = sizeof(sa.in4.sin_addr);
-               base = (unsigned char*)&sa.in4.sin_addr;
-               if (range > 32)
-                       range = 32;
-       }
-       else if (cidr.type == AF_INET6)
-       {
-               target_byte = sizeof(sa.in6.sin6_addr);
-               base = (unsigned char*)&sa.in6.sin6_addr;
-               if (range > 128)
-                       range = 128;
-       }
-       else
+       cidr.type = sa.family();
+       switch (cidr.type)
        {
-               cidr.length = 0;
-               return;
+               case AF_UNIX:
+                       // XXX: UNIX sockets don't support CIDR. This fix is non-ideal but I can't
+                       // really think of another way to handle it.
+                       cidr.length = 0;
+                       return;
+
+               case AF_INET:
+                       cidr.length = range > 32 ? 32 : range;
+                       target_byte = sizeof(sa.in4.sin_addr);
+                       base = (unsigned char*)&sa.in4.sin_addr;
+                       break;
+
+               case AF_INET6:
+                       cidr.length = range > 128 ? 128 : range;
+                       target_byte = sizeof(sa.in6.sin6_addr);
+                       base = (unsigned char*)&sa.in6.sin6_addr;
+                       break;
+
+               default:
+                       // If we have reached this point then we have encountered a bug.
+                       ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: sa2cidr(): socket type %d is unknown!", cidr.type);
+                       cidr.length = 0;
+                       return;
        }
-       cidr.length = range;
-       unsigned int border = range / 8;
+
+       unsigned int border = cidr.length / 8;
        unsigned int bitmask = (0xFF00 >> (range & 7)) & 0xFF;
        for(unsigned int i=0; i < target_byte; i++)
        {
@@ -271,22 +362,31 @@ std::string irc::sockets::cidr_mask::str() const
 {
        irc::sockets::sockaddrs sa;
        sa.sa.sa_family = type;
+
        unsigned char* base;
        size_t len;
-       if (type == AF_INET)
-       {
-               base = (unsigned char*)&sa.in4.sin_addr;
-               len = 4;
-       }
-       else if (type == AF_INET6)
+       switch (type)
        {
-               base = (unsigned char*)&sa.in6.sin6_addr;
-               len = 16;
+               case AF_INET:
+                       base = (unsigned char*)&sa.in4.sin_addr;
+                       len = 4;
+                       break;
+
+               case AF_INET6:
+                       base = (unsigned char*)&sa.in6.sin6_addr;
+                       len = 16;
+
+               case AF_UNIX:
+                       return sa.un.sun_path;
+
+               default:
+                       // If we have reached this point then we have encountered a bug.
+                       ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::cidr_mask::str(): socket type %d is unknown!", type);
+                       return "<unknown>";
        }
-       else
-               return "";
+
        memcpy(base, bits, len);
-       return sa.addr() + "/" + ConvToStr((int)length);
+       return sa.addr() + "/" + ConvToStr(length);
 }
 
 bool irc::sockets::cidr_mask::operator==(const cidr_mask& other) const