Merge branch 'insp3' into master.

This commit is contained in:
Sadie Powell 2021-01-07 13:13:58 +00:00
commit 4f68d162cc
19 changed files with 189 additions and 74 deletions

4
.github/SECURITY.md vendored
View File

@ -2,7 +2,7 @@
## Supported Versions
Currently the v2 (old stable) and v3 (stable) branches are actively receiving security fixes. Support for v2 will end on 2021-01-01.
Currently the v3 (stable) branch is actively receiving security fixes. Support for v2 ended at the end of 2020.
The v4 branch is still early in development and only receives security fixes when they are synced from the v3 branch.
@ -10,7 +10,7 @@ Version | Supported
------- | ---------
4.x.y | :warning:
3.x.y | :white_check_mark:
2.0.x | :warning:
2.0.x | :x:
1.2.y | :x:
1.1.y | :x:
1.0.y | :x:

View File

@ -132,13 +132,17 @@
# to this bind section.
type="clients"
# ssl: If you want the port(s) in this bind tag to use TLS (SSL), set this to
# the name of a custom <sslprofile> tag that you have defined. See the
# docs page for the TLS (SSL) module you are using for more details.
# ssl: If you want the port(s) in this bind tag to use TLS (SSL), set this
# to the name of a custom <sslprofile> tag that you have defined. See the
# docs page for the TLS (SSL) module you are using for more details:
#
# GnuTLS: https://docs.inspircd.org/3/modules/ssl_gnutls#sslprofile
# mbedTLS: https://docs.inspircd.org/3/modules/ssl_mbedtls#sslprofile
# OpenSSL: https://docs.inspircd.org/3/modules/ssl_openssl#sslprofile
#
# You will need to load the ssl_openssl module for OpenSSL, ssl_gnutls
# for GnuTLS and ssl_mbedtls for mbedTLS.
ssl="gnutls"
ssl="Clients"
# defer: When this is non-zero, connections will not be handed over to
# the daemon from the operating system before data is ready.

View File

@ -10,7 +10,7 @@
<bind address="1.2.3.4"
port="7005"
type="servers"
ssl="gnutls">
ssl="Servers">
# Plaintext listener that binds on a TCP/IP endpoint:
<bind address=""
@ -56,14 +56,18 @@
# failover (see above).
timeout="5m"
# ssl: If defined, this states the name of the TLS (SSL) profile that will
# be used when making an outbound connection to the server. See the docs
# page for the TLS (SSL) module you are using for more details.
# ssl: If defined, this states the TLS (SSL) profile that will be used
# when making an outbound connection to the server. See the docs page for
# the TLS (SSL) module you are using for more details:
#
# GnuTLS: https://docs.inspircd.org/3/modules/ssl_gnutls#sslprofile
# mbedTLS: https://docs.inspircd.org/3/modules/ssl_mbedtls#sslprofile
# OpenSSL: https://docs.inspircd.org/3/modules/ssl_openssl#sslprofile
#
# You will need to load the ssl_openssl module for OpenSSL, ssl_gnutls
# for GnuTLS and ssl_mbedtls for mbedTLS. The server port that you
# connect to must be capable of accepting this type of connection.
ssl="gnutls"
ssl="Servers"
# fingerprint: If defined, this option will force servers to be
# authenticated using TLS (SSL) certificate fingerprints. See
@ -96,7 +100,7 @@
port="7000"
allowmask="203.0.113.0/24 127.0.0.0/8 2001:db8::/32"
timeout="5m"
ssl="gnutls"
ssl="Servers"
bind="1.2.3.4"
statshidden="no"
hidden="no"

View File

@ -343,14 +343,14 @@
# message to the server on connection. For more details please read the
# IRCv3 WebIRC specification at: https://ircv3.net/specs/extensions/webirc.html
#
# When using this method you must specify a wildcard mask or CIDR range
# to allow gateway connections from and at least one of either a TLS (SSL)
# client certificate fingerprint for the gateway or a password to be
# sent in the WEBIRC command.
# When using this method you must specify one or more wildcard masks
# or CIDR ranges to allow gateway connections from and at least one of
# either a TLS (SSL) client certificate fingerprint for the gateway or
# a password to be sent in the WEBIRC command.
#
# <cgihost type="webirc"
# fingerprint="bd90547b59c1942b85f382bc059318f4c6ca54c5"
# mask="192.0.2.0/24">
# mask="192.0.2.0/24 198.51.100.*">
# <cgihost type="webirc"
# password="$2a$10$WEUpX9GweJiEF1WxBDSkeODBstIBMlVPweQTG9cKM8/Vd58BeM5cW"
# hash="bcrypt"
@ -361,13 +361,14 @@
# address in the ident sent by the user. This is not recommended as it
# only works with IPv4 connections.
#
# When using this method you must specify a wildcard mask or CIDR range to allow
# gateway connections from. You can also optionally configure the static value
# that replaces the IP in the ident to avoid leaking the real IP address of
# gateway clients (defaults to "gateway" if not set).
# When using this method you must specify one or more wildcard masks
# or CIDR ranges to allow gateway connections from. You can also
# optionally configure the static value that replaces the IP in the
# ident to avoid leaking the real IP address of gateway clients
# (defaults to "gateway" if not set).
#
# <cgihost type="ident"
# mask="198.51.100.0/24"
# mask="198.51.100.0/24 203.0.113.*"
# newident="wibble">
# <cgihost type="ident"
# mask="*.ident.gateway.example.com"
@ -836,7 +837,17 @@
#<module name="dnsbl">
# #
# For configuration options please see the docs page for dnsbl at #
# https://docs.inspircd.org/3/modules/dnsbl #
# https://docs.inspircd.org/3/modules/dnsbl. You can also use one or #
# more of the following example configs for popular DNSBLs: #
# #
# DroneBL (https://dronebl.org) #
#<include file="examples/providers/dronebl.conf.example">
# #
# EFnet RBL (https://rbl.efnetrbl.org) #
#<include file="examples/providers/efnet-rbl.conf.example">
# #
# dan.me.uk Tor exit node DNSBL (https://www.dan.me.uk/dnsbl) #
#<include file="examples/providers/torexit.conf.example">
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
# Exempt channel operators module: Provides support for allowing #
@ -1953,8 +1964,6 @@
# securelist blocking these sites from listing, define exception tags #
# as shown below: #
#<securehost exception="*@*.netsplit.de">
#<securehost exception="*@*.ircdriven.com">
#<securehost exception="*@*.ircs.me">
# #
# exemptregistered - Whether the waiting period applies to users who #
# are logged in to a services account. #

View File

@ -0,0 +1,11 @@
# This file contains configuration for using the dnsbl module with
# the DroneBL DNSBL. See https://dronebl.org/ for more information on
# DroneBL.
<dnsbl name="DroneBL"
domain="dnsbl.dronebl.org"
type="record"
records="3,5,6,7,8,9,10,11,13,14,15,16,17,19"
action="zline"
duration="7d"
reason="You are listed in DroneBL. Please visit https://dronebl.org/lookup.do?ip=%ip% for more information.">

View File

@ -0,0 +1,11 @@
# This file contains configuration for using the dnsbl module with
# the EFnet RBL. See https://rbl.efnetrbl.org/ for more information
# on the EFnet RBL.
<dnsbl name="EFnet RBL"
domain="rbl.efnetrbl.org"
type="record"
records="1,2,3,4,5"
action="zline"
duration="7d"
reason="You are listed in the EFnet RBL. Please visit https://rbl.efnetrbl.org/?i=%ip% for more information.">

View File

@ -0,0 +1,11 @@
# This file contains configuration for using the dnsbl module with
# the dan.me.uk Tor exit node DNSBL. See https://www.dan.me.uk/dnsbl
# for more information on the dan.me.uk Tor exit node DNSBL.
<dnsbl name="torexit.dan.me.uk"
domain="torexit.dan.me.uk"
type="record"
records="100"
action="zline"
duration="7d"
reason="Tor exit nodes are not allowed on this network. See https://metrics.torproject.org/rs.html#search/%ip% for more information.">

View File

@ -33,9 +33,27 @@ struct ParseStack
ParseStack(ServerConfig* conf)
: output(conf->config_data), FilesOutput(conf->Files), errstr(conf->errstr)
{
vars["amp"] = "&";
// Special character escapes.
vars["newline"] = "\n";
vars["nl"] = "\n";
// XML escapes.
vars["amp"] = "&";
vars["apos"] = "'";
vars["gt"] = ">";
vars["lt"] = "<";
vars["quot"] = "\"";
vars["newline"] = vars["nl"] = "\n";
// IRC formatting codes.
vars["irc.bold"] = "\x02";
vars["irc.color"] = "\x03";
vars["irc.colour"] = "\x03";
vars["irc.italic"] = "\x1D";
vars["irc.monospace"] = "\x11";
vars["irc.reset"] = "\x0F";
vars["irc.reverse"] = "\x16";
vars["irc.strikethrough"] = "\x1E";
vars["irc.underline"] = "\x1F";
}
bool ParseFile(const std::string& name, int flags, const std::string& mandatory_tag = std::string(), bool isexec = false);
void DoInclude(std::shared_ptr<ConfigTag> includeTag, int flags);

View File

@ -194,7 +194,7 @@ inline bool Events::ModuleEventProvider::ElementComp::operator()(Events::ModuleE
template<typename Class, typename... FunArgs, typename... FwdArgs>
inline void Events::ModuleEventProvider::Call(void (Class::*function)(FunArgs...), FwdArgs&&... args) const
{
if (!GetModule() || GetModule()->dying)
if (GetModule() && GetModule()->dying)
return;
for (const auto& subscriber : subscribers)
@ -211,7 +211,7 @@ inline void Events::ModuleEventProvider::Call(void (Class::*function)(FunArgs...
template<typename Class, typename... FunArgs, typename... FwdArgs>
inline ModResult Events::ModuleEventProvider::FirstResult(ModResult (Class::*function)(FunArgs...), FwdArgs&&... args) const
{
if (!GetModule() || GetModule()->dying)
if (GetModule() && GetModule()->dying)
return MOD_RES_PASSTHRU;
ModResult result;

View File

@ -49,6 +49,7 @@ enum
RPL_MAP = 15, // ircu
RPL_ENDMAP = 17, // ircu
RPL_MAPUSERS = 18, // insp-specific
RPL_SAVENICK = 43, // From irc2.
RPL_STATS = 210, // From aircd.
RPL_UMODEIS = 221,

View File

@ -34,6 +34,7 @@ use File::Spec::Functions qw(rel2abs);
our @EXPORT = qw(command
execute_command
console_format
print_format
print_error
print_warning
@ -59,20 +60,23 @@ struct 'command' => {
'description' => '$',
};
sub __console_format($$) {
my ($name, $data) = @_;
return $data unless -t STDOUT;
return $FORMAT_CODES{uc $name} . $data . $FORMAT_CODES{DEFAULT};
sub console_format($) {
my $message = shift;
while ($message =~ /(<\|(\S+)\s(.*?)\|>)/) {
my ($match, $type, $text) = ($1, uc $2, $3);
if (-t STDOUT && exists $FORMAT_CODES{$type}) {
$message =~ s/\Q$match\E/$FORMAT_CODES{$type}$text$FORMAT_CODES{DEFAULT}/;
} else {
$message =~ s/\Q$match\E/$text/;
}
}
return $message;
}
sub print_format($;$) {
my $message = shift;
my $stream = shift // *STDOUT;
while ($message =~ /(<\|(\S+)\s(.*?)\|>)/) {
my $formatted = __console_format $2, $3;
$message =~ s/\Q$1\E/$formatted/;
}
print { $stream } $message;
print { $stream } console_format $message;
}
sub print_error {

View File

@ -172,7 +172,7 @@ sub cmd_rehash()
{
if (getstatus() == 1) {
my $pid = getprocessid();
system("kill -HUP $pid >/dev/null 2>&1");
kill HUP => $pid;
print "InspIRCd rehashed (pid: $pid).\n";
exit GENERIC_EXIT_SUCCESS;
} else {

View File

@ -39,16 +39,19 @@ enum
RPL_WHOISGATEWAY = 350
};
// One or more hostmask globs or CIDR ranges.
typedef std::vector<std::string> MaskList;
// Encapsulates information about an ident host.
class IdentHost
{
private:
std::string hostmask;
MaskList hostmasks;
std::string newident;
public:
IdentHost(const std::string& mask, const std::string& ident)
: hostmask(mask)
IdentHost(const MaskList& masks, const std::string& ident)
: hostmasks(masks)
, newident(ident)
{
}
@ -60,10 +63,19 @@ class IdentHost
bool Matches(LocalUser* user) const
{
if (!InspIRCd::Match(user->GetRealHost(), hostmask, ascii_case_insensitive_map))
return false;
for (MaskList::const_iterator iter = hostmasks.begin(); iter != hostmasks.end(); ++iter)
{
// Does the user's hostname match this hostmask?
if (InspIRCd::Match(user->GetRealHost(), *iter, ascii_case_insensitive_map))
return true;
return InspIRCd::MatchCIDR(user->GetIPString(), hostmask, ascii_case_insensitive_map);
// Does the user's IP address match this hostmask?
if (InspIRCd::MatchCIDR(user->GetIPString(), *iter, ascii_case_insensitive_map))
return true;
}
// The user didn't match any hostmasks.
return false;
}
};
@ -71,14 +83,14 @@ class IdentHost
class WebIRCHost
{
private:
std::string hostmask;
MaskList hostmasks;
std::string fingerprint;
std::string password;
std::string passhash;
public:
WebIRCHost(const std::string& mask, const std::string& fp, const std::string& pass, const std::string& hash)
: hostmask(mask)
WebIRCHost(const MaskList& masks, const std::string& fp, const std::string& pass, const std::string& hash)
: hostmasks(masks)
, fingerprint(fp)
, password(pass)
, passhash(hash)
@ -96,26 +108,22 @@ class WebIRCHost
if (!fingerprint.empty() && !InspIRCd::TimingSafeCompare(fp, fingerprint))
return false;
// Does the user's hostname match our hostmask?
if (InspIRCd::Match(user->GetRealHost(), hostmask, ascii_case_insensitive_map))
return true;
for (MaskList::const_iterator iter = hostmasks.begin(); iter != hostmasks.end(); ++iter)
{
// Does the user's hostname match this hostmask?
if (InspIRCd::Match(user->GetRealHost(), *iter, ascii_case_insensitive_map))
return true;
// Does the user's IP address match our hostmask?
return InspIRCd::MatchCIDR(user->GetIPString(), hostmask, ascii_case_insensitive_map);
// Does the user's IP address match this hostmask?
if (InspIRCd::MatchCIDR(user->GetIPString(), *iter, ascii_case_insensitive_map))
return true;
}
// The user didn't match any hostmasks.
return false;
}
};
/*
* WEBIRC
* This is used for the webirc method of CGIIRC auth, and is (really) the best way to do these things.
* Syntax: WEBIRC password gateway hostname ip
* Where password is a shared key, gateway is the name of the WebIRC gateway and version (e.g. cgiirc), hostname
* is the resolved host of the client issuing the command and IP is the real IP of the client.
*
* How it works:
* To tie in with the rest of cgiirc module, and to avoid race conditions, /webirc is only processed locally
* and simply sets metadata on the user, which is later decoded on full connect to give something meaningful.
*/
class CommandWebIRC : public SplitCommand
{
public:
@ -287,9 +295,13 @@ class ModuleCgiIRC
for (auto& [_, tag] : ServerInstance->Config->ConfTags("cgihost"))
{
MaskList masks;
irc::spacesepstream maskstream(tag->getString("mask"));
for (std::string mask; maskstream.GetToken(mask); )
masks.push_back(mask);
// Ensure that we have the <cgihost:mask> parameter.
const std::string mask = tag->getString("mask");
if (mask.empty())
if (masks.empty())
throw ModuleException("<cgihost:mask> is a mandatory field, at " + tag->source.str());
// Determine what lookup type this host uses.
@ -298,7 +310,7 @@ class ModuleCgiIRC
{
// The IP address should be looked up from the hex IP address.
const std::string newident = tag->getString("newident", "gateway", ServerInstance->IsIdent);
identhosts.push_back(IdentHost(mask, newident));
identhosts.push_back(IdentHost(masks, newident));
}
else if (stdalgo::string::equalsci(type, "webirc"))
{
@ -317,7 +329,7 @@ class ModuleCgiIRC
tag->source.str().c_str());
}
webirchosts.push_back(WebIRCHost(mask, fingerprint, password, passwordhash));
webirchosts.push_back(WebIRCHost(masks, fingerprint, password, passwordhash));
}
else
{

View File

@ -24,16 +24,28 @@
class ClassExtBan
: public ExtBan::MatchingBase
{
private:
std::string space;
std::string underscore;
public:
ClassExtBan(Module* Creator)
: ExtBan::MatchingBase(Creator, "class", 'n')
, space(" ")
, underscore("_")
{
}
bool IsMatch(User* user, Channel* channel, const std::string& text) override
{
LocalUser* luser = IS_LOCAL(user);
return luser && InspIRCd::Match(luser->GetClass()->name, text);
if (!luser)
return false;
// Replace spaces with underscores as they're prohibited in mode parameters.
std::string classname(luser->GetClass()->name);
stdalgo::string::replace_all(classname, space, underscore);
return InspIRCd::Match(classname, text);
}
};

View File

@ -76,6 +76,21 @@ class ModuleCodepage
hashmap.swap(newhash);
}
void CheckDuplicateNick()
{
insp::flat_set<std::string, irc::insensitive_swo> duplicates;
const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
for (UserManager::LocalList::const_iterator iter = list.begin(); iter != list.end(); ++iter)
{
LocalUser* user = *iter;
if (user->nick != user->uuid && !duplicates.insert(user->nick).second)
{
user->WriteNumeric(RPL_SAVENICK, user->uuid, "Your nickname is no longer available.");
user->ChangeNick(user->uuid);
}
}
}
void CheckInvalidNick()
{
const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
@ -83,7 +98,10 @@ class ModuleCodepage
{
LocalUser* user = *iter;
if (user->nick != user->uuid && !ServerInstance->IsNick(user->nick))
{
user->WriteNumeric(RPL_SAVENICK, user->uuid, "Your nickname is no longer valid.");
user->ChangeNick(user->uuid);
}
}
}
@ -113,6 +131,7 @@ class ModuleCodepage
ServerInstance->Config->CaseMapping = origcasemapname;
national_case_insensitive_map = origcasemap;
CheckDuplicateNick();
CheckRehash(casemap);
}

View File

@ -53,11 +53,7 @@ class OperExtBan
if (!user->IsOper())
return false;
// Check whether the oper's type matches the ban.
if (InspIRCd::Match(user->oper->name, text))
return true;
// If the oper's type contains spaces recheck with underscores.
// Replace spaces with underscores as they're prohibited in mode parameters.
std::string opername(user->oper->name);
stdalgo::string::replace_all(opername, space, underscore);
return InspIRCd::Match(opername, text);

View File

@ -36,6 +36,7 @@
CommandMap::CommandMap(Module* Creator)
: Command(Creator, "MAP", 0, 1)
{
allow_empty_last_param = false;
Penalty = 2;
}

View File

@ -67,6 +67,7 @@ CmdResult CommandSVSNick::Handle(User* user, Params& parameters)
if (!u->ChangeNick(nick, NickTS))
{
// Changing to 'nick' failed (it may already be in use), change to the uuid
u->WriteNumeric(RPL_SAVENICK, u->uuid, "Your nickname is in use by an older user on a new server.");
u->ChangeNick(u->uuid);
}
}

View File

@ -661,6 +661,7 @@ bool QLine::Matches(User *u)
void QLine::Apply(User* u)
{
/* Force to uuid on apply of Q-line, no need to disconnect anymore :) */
u->WriteNumeric(RPL_SAVENICK, u->uuid, "Your nickname has been Q-lined.");
u->ChangeNick(u->uuid);
}