mirror of
https://github.com/inspircd/inspircd.git
synced 2025-03-09 10:39:02 -04:00
290 lines
8.8 KiB
C++
290 lines
8.8 KiB
C++
/*
|
|
* InspIRCd -- Internet Relay Chat Daemon
|
|
*
|
|
* Copyright (C) 2021 Dominic Hamon
|
|
* Copyright (C) 2019-2020 Matt Schatz <genius3000@g3k.solutions>
|
|
* Copyright (C) 2013-2016 Attila Molnar <attilamolnar@hush.com>
|
|
* Copyright (C) 2013, 2016-2024 Sadie Powell <sadie@witchery.services>
|
|
* Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org>
|
|
* Copyright (C) 2012 Robby <robby@chatbelgie.be>
|
|
* Copyright (C) 2012 ChrisTX <xpipe@hotmail.de>
|
|
* Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
|
|
* Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
|
|
*
|
|
* This file is part of InspIRCd. InspIRCd is free software: you can
|
|
* redistribute it and/or modify it under the terms of the GNU General Public
|
|
* License as published by the Free Software Foundation, version 2.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include "inspircd.h"
|
|
#include "iohook.h"
|
|
|
|
#ifndef _WIN32
|
|
# include <netinet/tcp.h>
|
|
# include <sys/stat.h>
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
// Removes a dead UNIX socket so we can bind over it.
|
|
bool RemoveSocket(ListenSocket* ls)
|
|
{
|
|
const bool replace = ls->bind_tag->getBool("replace", true);
|
|
if (!replace || !irc::sockets::isunix(ls->bind_sa.str()))
|
|
return true;
|
|
|
|
return unlink(ls->bind_sa.str().c_str()) != -1;
|
|
}
|
|
|
|
int SetDeferAccept(ListenSocket* ls)
|
|
{
|
|
// Default defer to on for TLS listeners because in TLS the client always speaks first.
|
|
unsigned int timeoutdef = ls->bind_tag->getString("sslprofile").empty() ? 0 : 5;
|
|
int timeout = static_cast<int>(ls->bind_tag->getDuration("defer", timeoutdef, 0, 60));
|
|
if (!timeout)
|
|
return 0;
|
|
|
|
#if defined TCP_DEFER_ACCEPT
|
|
return SocketEngine::SetOption(ls, IPPROTO_TCP, TCP_DEFER_ACCEPT, timeout);
|
|
#elif defined SO_ACCEPTFILTER
|
|
struct accept_filter_arg afa;
|
|
memset(&afa, 0, sizeof(afa));
|
|
strcpy(afa.af_name, "dataready");
|
|
return SocketEngine::SetOption(ls, SOL_SOCKET, SO_ACCEPTFILTER, afa);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
// Allows binding to an IP address which is not available yet.
|
|
int SetFreeBind(ListenSocket* ls)
|
|
{
|
|
#if defined IP_FREEBIND // Linux 2.4+
|
|
return SocketEngine::SetOption<int>(ls, SOL_IP, IP_FREEBIND, 1);
|
|
#elif defined IP_BINDANY // FreeBSD
|
|
return SocketEngine::SetOption<int>(ls, IPPROTO_IP, IP_BINDANY, 1);
|
|
#elif defined SO_BINDANY // NetBSD, OpenBSD
|
|
return SocketEngine::SetOption<int>(ls, SOL_SOCKET, SO_BINDANY, 1);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
// Sets the filesystem permissions for a UNIX socket.
|
|
int SetPermissions(ListenSocket* ls)
|
|
{
|
|
const std::string permissionstr = ls->bind_tag->getString("permissions");
|
|
unsigned long permissions = strtoul(permissionstr.c_str(), nullptr, 8);
|
|
if (!permissions || permissions > 07777)
|
|
return 0;
|
|
|
|
// This cast is safe thanks to the above check.
|
|
return chmod(ls->bind_sa.str().c_str(), static_cast<int>(permissions));
|
|
}
|
|
|
|
// Allow binding on IPv4 with an IPv6 socket.
|
|
void SetIPv6Only(ListenSocket* ls)
|
|
{
|
|
#ifdef IPV6_V6ONLY
|
|
/* This OS supports IPv6 sockets that can also listen for IPv4
|
|
* connections. If listening on all interfaces we enable both v4 and v6
|
|
* to allow for simpler configuration on dual-stack hosts. Otherwise,
|
|
* if it is "::" or an IPv6 address we disable support so that an IPv4
|
|
* bind will work on the same port (by us or another application).
|
|
*/
|
|
const std::string address = ls->bind_tag->getString("address");
|
|
|
|
// IMPORTANT: This must be >= sizeof(DWORD) on Windows.
|
|
const int enable = (address.empty() || address == "*") ? 0 : 1;
|
|
|
|
// Intentionally ignore the result of this so we can fall back to default behaviour.
|
|
SocketEngine::SetOption(ls, IPPROTO_IPV6, IPV6_V6ONLY, enable);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
ListenSocket::ListenSocket(const std::shared_ptr<ConfigTag>& tag, const irc::sockets::sockaddrs& bind_to, sa_family_t protocol)
|
|
: bind_tag(tag)
|
|
, bind_sa(bind_to)
|
|
, bind_protocol(protocol)
|
|
{
|
|
if (bind_to.family() == AF_UNIX && !RemoveSocket(this))
|
|
return;
|
|
|
|
SetFd(socket(bind_to.family(), SOCK_STREAM, protocol));
|
|
if (!HasFd())
|
|
return;
|
|
|
|
// Its okay if these fails.
|
|
if (bind_to.family() == AF_INET6)
|
|
SetIPv6Only(this);
|
|
SocketEngine::SetOption<int>(this, SOL_SOCKET, SO_REUSEADDR, 1);
|
|
|
|
int rv = 0;
|
|
if (bind_to.is_ip() && tag->getBool("free"))
|
|
rv = SetFreeBind(this);
|
|
|
|
if (rv != -1)
|
|
rv = SocketEngine::Bind(this, bind_to);
|
|
|
|
if (rv != -1)
|
|
rv = SocketEngine::Listen(this, ServerInstance->Config->MaxConn);
|
|
|
|
if (rv != -1)
|
|
{
|
|
if (bind_to.family() == AF_UNIX)
|
|
rv = SetPermissions(this);
|
|
|
|
else if (bind_to.is_ip() && protocol == IPPROTO_TCP)
|
|
rv = SetDeferAccept(this);
|
|
}
|
|
|
|
if (rv == -1)
|
|
{
|
|
#ifdef _WIN32
|
|
int errstore = WSAGetLastError();
|
|
#else
|
|
int errstore = errno;
|
|
#endif
|
|
SocketEngine::Shutdown(this, 2);
|
|
SocketEngine::Close(GetFd());
|
|
SetFd(-1);
|
|
#ifdef _WIN32
|
|
WSASetLastError(errstore);
|
|
#else
|
|
errno = errstore;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
SocketEngine::NonBlocking(GetFd());
|
|
SocketEngine::AddFd(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
|
|
|
|
this->ResetIOHookProvider();
|
|
}
|
|
}
|
|
|
|
ListenSocket::~ListenSocket()
|
|
{
|
|
if (this->HasFd())
|
|
{
|
|
ServerInstance->Logs.Debug("SOCKET", "Shut down listener on fd {}", GetFd());
|
|
SocketEngine::Shutdown(this, 2);
|
|
|
|
if (SocketEngine::Close(this) != 0)
|
|
ServerInstance->Logs.Warning("SOCKET", "Failed to close listener: {}", strerror(errno));
|
|
|
|
if (bind_sa.family() == AF_UNIX && unlink(bind_sa.un.sun_path))
|
|
ServerInstance->Logs.Warning("SOCKET", "Failed to unlink UNIX socket: {}", strerror(errno));
|
|
}
|
|
}
|
|
|
|
void ListenSocket::OnEventHandlerRead()
|
|
{
|
|
irc::sockets::sockaddrs client(false);
|
|
socklen_t length = sizeof(client);
|
|
int incomingfd = SocketEngine::Accept(this, &client.sa, &length);
|
|
if (incomingfd < 0)
|
|
{
|
|
ServerInstance->Logs.Debug("SOCKET", "Refused connection to {}: {}",
|
|
bind_sa.str(), strerror(errno));
|
|
ServerInstance->Stats.Refused++;
|
|
return;
|
|
}
|
|
|
|
ServerInstance->Logs.Debug("SOCKET", "Accepted connection to {} with fd {}",
|
|
bind_sa.str(), incomingfd);
|
|
|
|
irc::sockets::sockaddrs server(bind_sa);
|
|
length = sizeof(server);
|
|
if (getsockname(incomingfd, &server.sa, &length))
|
|
{
|
|
ServerInstance->Logs.Debug("SOCKET", "Unable to get peer name for fd {}: {}",
|
|
incomingfd, strerror(errno));
|
|
}
|
|
|
|
if (client.family() == AF_INET6)
|
|
{
|
|
/*
|
|
* This case is the be all and end all patch to catch and nuke 4in6
|
|
* instead of special-casing shit all over the place and wreaking merry
|
|
* havoc with crap, instead, we just recreate sockaddr and strip ::ffff: prefix
|
|
* if it's a 4in6 IP.
|
|
*
|
|
* This is, of course, much improved over the older way of handling this
|
|
* (pretend it doesn't exist + hack around it -- yes, both were done!)
|
|
*
|
|
* Big, big thanks to danieldg for his work on this.
|
|
* -- w00t
|
|
*/
|
|
static constexpr unsigned char prefix4in6[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
|
|
if (!memcmp(prefix4in6, &client.in6.sin6_addr, 12))
|
|
{
|
|
// recreate as a sockaddr_in using the IPv4 IP
|
|
in_port_t sport = client.in6.sin6_port;
|
|
client.in4.sin_family = AF_INET;
|
|
client.in4.sin_port = sport;
|
|
memcpy(&client.in4.sin_addr.s_addr, client.in6.sin6_addr.s6_addr + 12, sizeof(uint32_t));
|
|
|
|
sport = server.in6.sin6_port;
|
|
server.in4.sin_family = AF_INET;
|
|
server.in4.sin_port = sport;
|
|
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(incomingfd);
|
|
|
|
ModResult res;
|
|
FIRST_MOD_RESULT(OnAcceptConnection, res, (incomingfd, this, client, server));
|
|
if (res == MOD_RES_ALLOW)
|
|
{
|
|
ServerInstance->Stats.Accept++;
|
|
return;
|
|
}
|
|
|
|
ServerInstance->Stats.Refused++;
|
|
ServerInstance->Logs.Normal("SOCKET", "Refusing connection on {} - {}", bind_sa.str(),
|
|
res == MOD_RES_DENY ? "Connection refused by module" : "Module for this port not found");
|
|
SocketEngine::Close(incomingfd);
|
|
}
|
|
|
|
void ListenSocket::ResetIOHookProvider()
|
|
{
|
|
iohookprovs[0].SetProvider(bind_tag->getString("hook"));
|
|
|
|
// Check that all non-last hooks support being in the middle
|
|
for (IOHookProvList::iterator i = iohookprovs.begin(); i != iohookprovs.end()-1; ++i)
|
|
{
|
|
IOHookProvRef& curr = *i;
|
|
// Ignore if cannot be in the middle
|
|
if ((curr) && (!curr->IsMiddle()))
|
|
curr.ClearProvider();
|
|
}
|
|
|
|
std::string provname = bind_tag->getString("sslprofile");
|
|
if (!provname.empty())
|
|
provname.insert(0, "ssl/");
|
|
|
|
// TLS should be the last
|
|
iohookprovs.back().SetProvider(provname);
|
|
}
|