connect-flood and maxperip module. This so they actually take
set::default-ipv6-clone-mask into account.
This also changes the maxperip module to a more simple method of
just freeing all entries and rebuilding the hash table on load.
That's necessary since now set::default-ipv6-clone-mask can change.
The OOB write did not happen on file-backed downloads, such as remote
includes. It only happened for memory-backed requests, which are only
these 4 in standard UnrealIRCd: centralblocklist, central spam report,
other spamreport blocks (eg to dronebl) and the log block with
destination webhook. All those 4 cases are very likely to be trusted
web servers, given the nature of the data you are sending to them.
The fix was to extend the size fields everywhere to 64 bits. It was
applied to both URL backends: url_unreal.c and url_curl.c.
The new API feature is a 'max_size' in OutgoingWebRequest, which
defaults to 1MB. This is only used for memory-backed responses,
so not for real file downloads. This fixes not only the reported
bug but also the case where a rogue webserver was unbounded in
terms of what response it could send back, potentially filling
up gigabytes of server memory.
Reported by Link420.
There is no hard cap on batch reference length, so we had to make one up.
It is now a clear #define MAXBATCHREFLEN 48, which should be plenty.
No sane client is going to use like a 64 byte batch reference :D
So we did use 48, but we also accidentally used BATCHLEN at another
place. BATCHLEN is 22 and refers to how many bytes we generate, so
that is not appropritate.
Thanks to Valware for spotting this.
This wasn't done before, because optimizing stuff can always introduce
nice new issues. But is kinda necessary now since the previous way was
very inefficient. This now builds all the necessary buffers for multiline
clients and for non-multiline clients. And then iterates through both
types of clients, sending what they need. Instead of doing it the other
way around.
I had the dillema to either expose the linecache API and have everything
in multiline.c. Or, i do not expose linecache, and we do everything in
send.c. The downside of the latter is that if there is mistake then we
can't simply reload (or unload) the module to solve it. So, I have chosen
to expose the linecache API (sure, less clean) since that leaves us with
options if we screw up, plus it means everything related to multiline
sending is nicely in multiline.c, which is i guess just as good as an
argument as well ;)
Add a little fake lag based on history result: 400ms for 50 lines
under normal conditions where 50 lines = 50 lines. But this can go
up to 5000ms for worst-case amplification attacks where requesting
50 lines actually returns 50*15=750 lines when each line is a multiline
with max-lines, which gets you close to 350k+. This would only happen
if someone on the channel is doing evil stuff (with presumably consent
of the ops).
Also guard against hiting max sendq. If we are too close, then we
reject the CHATHISTORY request rather than quiting with "Max SendQ
exceeded". This protects against an attack where someone would be
tricked into joining a channel with amplified history (as explained
in previous paragraph), their client would do an automatic CHATHISTORY
request and then the victim would exceed max sendq and thus be killed.
And yes, this and maaaaany other multiline + history interactions
and many "buts" and security/flood concerns are why this implemtnation
took (and still takes) a lot of hours to get right :D.
For +H we now temporarily allow overshooting. This only matters for low limits.
Multiline batches are atomic so we have to choose to keep them as a whole
or remove the complete batch. So if +H 5:1h and the last message was a 15-line
multiline event, what do we do? We allow temporary overshooting to store the
15 lines. As said, the alternative would be to store 0 lines which would be
worse in terms of functionality, and the small overshoot is defensible.
For higher limits (where the +H line limit is bigger than multiline max-lines),
we always stay under the +H limit. Eg if all history in a channel consists
of 15 line multiline events and we have +H 100 then we will store 90, not 105.
It's only for +H linelimit < max-lines that this matters, because there the
zero-lines consequence sucks too much ;)
This way you can limit the number of pastes going on in a channel, as
this is from everyone in that channel (like 'm') not individual (like 't').
If it is exceeded then we will simply reject the BATCH, similar to
how action d(rop) works for some other subtypes. You won't see the paste
on the channel, only the sending user receives an error (MULTILINE_PASTE_LIMIT).
Small note: a multiline BATCH of just 2 lines is not considered a paste.
We consider a multiline of 3+ lines as a paste. I think that is reasonable,
since a two-line-multiline is not that much of a paste ;).
In the default anti-flood profile (+F normal) we also set 2p per 15s,
so this means channels are by default limited to 2 pastes per 15s max.
Of course, you can override this with +f [4p]:15 or whatever you like.
In terms of +F profiles, the defaults are (maximum x pastes per 15 seconds):
very-strict: 1p
strict: 1p
normal: 2p
relaxed: 2p
very-relaxed: 3p
and 7 for unknown-users (with max-bytes 5250 and 1500 respectively). This
allows pasting a short snippet of code, config file, text from a site, etc.
With multiline you have the guarantee that:
1) You will see the entire text with no delay between lines
2) You won't see another persons chat half-way through such a paste
3) For multiline supporting clients it is now clear that all the text
belongs to each other, which can make selecting/copying it easier.
This basically means short snippets/pastes like that can be completely on
IRC again. No need for a pastebin for it. Though, you may still need such
a service if you are pasting more lines.
Regarding the implementation in UnrealIRCd:
* Clients without multiline get individual fallback lines (concat lines
merged, blank lines skipped, as per spec). And we know that clients like
weechat - which does support multiline - also shows all lines and not
only a few plus snippet style "[.."]. That is another reason for only
allowing 15 lines by default and not something much more. Otherwise all
those clients would get a big wall of text, which just sucks.
* Spamfilter (also) runs on the full text of all lines together, so
splitting a phrase across lines does not evade spamfilter.
* Fakelag: a client can send the BATCH start+PRIVMSG (or NOTICE)+BATCH end
at full speed. We impose no fake lag there. Also, the multiline default
max-lines and max-bytes are lower than the example class::recvq of 8000,
so should be perfectly safe. If the entire BATCH is accepted then we
will impose fake-lag afterwards, with a cap of 15 seconds maximum.
If the BATCH is rejected, we impose half the fakelag plus 2sec.
* If the time between BATCH start and BATCH end is more than 15 seconds
then the BATCH is rejected (set::multiline::batch-timeout).
* The BATCH is atomic (either you see it all, or you see none of it):
* When the client sends it to server, it is buffered first.
* Only after the batch close the server indicates if it is accepted
or rejected. This has various reasons, two of them are: 1) The client
is going to send everything in one go anyway and not wait for a
response between each PRIVMSG, and 2) we can't do many checks in the
buffering stage and skip those after, that would cause a TOCTOU
problem (eg. a banned user still being able to speak).
* If any line gets rejected due to spamfilter or other case
(eg +c, +b ~text with block, etc etc), the entire batch is rejected
* Locally we deliver all or nothing (as said)
* S2S we buffer the batch as well, so if a server splits after having
received 10 lines out of 15, then clients will not see anything.
* We send max-lines and max-bytes, this is the hard upper limit.
* A multiline can still be limited more tight if:
* +f with 't' or 'm' restricts to fewer lines,
eg +f [5t]:15, which means max 5 lines per 15 seconds,
means the max accepted multiline is 5 for that channel.
* +F works the same, except that default +F normal does not
have a 't' at the moment and 'm' is very high (50) so
practically not limited by default.
* There will be a future +f flood subtype for some more control
TODO: we will send CAP NEW on unknown-users <-> known-users to
indicate the new max-lines value if you transition security groups
TODO: chat history does not yet include multiline batches.
As usual, this is mostly for configuration templates that you use for
multiple servers, that sort of things, eg.
@if !environment("ADMIN")
@error "Environment variable ADMIN is not set"
@endif
This also adds a change in conf.c so @define, @error and
@warning are skipped in @if blocks that evaluate to false
(that's obviously what everyone wants :D). So that fixes a
previous bug with @define in @if.
to check environment variables.
This also means functions can now return values, so some changes
under the hood. This also moves the <=, >=, <, > ops code.
loadmodule + set config items
This checks the file on-disk, which is slightly different than
@if module-loaded("third/coolmod") which checks if it is loaded.
geoip_classic and geoip_mmdb in modules.default.conf with Conditional
Config, a dynamic loadmodule line, and auto-updates.
Somewhere in a later version, probably 6.2.5, we will default to mmdb
for all cases.
This is mainly due to licensing. The libmaxminddb library uses the
Apache license, which meant if we would compile it in by default it
would effectively transform our "GPLv2 or later" to "GPLv3 or later".
Our implementation is ISC licensed, so we can include and enable it
by default and keep things at "GPLv2 or later". This is also why we
used geoip_classic in the first place as default and compiled in,
and not the mmdb variant.
The mmdb.c is based on the specification, using the Go implementation
as a reference during development (ISC licensed), initially implemented
with the help of Claude Opus 4.6. After that substantial changes were
made to make it match UnrealIRCd's style and to make things less error
prone: C style changes, allocation and zero termination of strings in
the library, auto-NULL in variadic functions so the caller cannot
forget NULL there (similar to our unreal_log/do_unreal_log), using
enums as the return type instead of int (similar to curl), adding
doxygen docs, etc.
This also means the old mmdb library dependency has been dropped,
including from configure/autoconf.
At the moment we still use the geoip classic library by default,
including those DB files. The idea is we will switch over sometime
later after this current new MMDB stuff has received more testing.
This also makes us more flexible, since .mmdb files have become the
de-facto standard for pretty much all geoip vendors.
even if it costs an extra round-trip due to HRR (Hello Retry Request).
This is IRC after all, where connections live minutes, hours, days,
so that extra round trip is worth it if it means better security.
The TL;DR is: we try harder to use X25519MLKEM768.
The longer story is as follows:
In TLSv1.3, the client will indicate which groups it supports (eg
a list of 4 items) and which ones it speculates to be used (very
often just 2 items). Some TLS clients may not include X25519MLKEM768
in this initial speculation, but only f.e. X25519 and prime256v1
even though X25519MLKEM768 is communicated via their "supported" list.
Without this patch, we would then settle with one of those 2.
With this patch, we will send a Hello Retry Request, allowing to
use X25519MLKEM768.
This is rare, though, most TLS client implementations that have
X25519MLKEM768 will bet on it to be used (the 2 they bet on is
often X25519MLKEM768 & X25519). That's many browsers like Chrome,
OpenSSL, Go, etc.
GnuTLS usually will do this as well, but under some configurations
it may bet on 2 classic crypto to be used. For that specific (type
of) situation, this patch will help to use X25519MLKEM768.
This can be tested with OpenSSL to simulate such an implementation:
openssl s_client -connect 127.0.0.1:6697 -groups X25519MLKEM768:*X25519
Before this patch, it would result in X25519 (because that is the
speculated group, with the asterisk). After this patch it will
cause X25519MLKEM768 to be used.
The tuple syntax is in 3.5.0+ and our UNREALIRCD_DEFAULT_TLS_GROUPS_PRIMARY
with X25519MLKEM768 also requires 3.5.0+ so this is an easy change.
Oh and, this commit comment is rather long for a 1 byte change :D
For the extbans that we ship, no problem, as this isn't used in
any of our extbans, but for third party it may matter, or for us
in the future.
Just something we came across while looking into the issue from
previous commit.
in NameList, Tag, Watch and HistoryLogLine.
This does mean the allocation routines need a +1 everywhere, but
I think I got all of them. I also don't see them being used directly
in such a way in 3rd party modules (which is logical, as they
should use the API and not allocate such structs directly).
Also, SpamExcept has been removed as it was not used anywhere.
the particular extended ban module if you don't want it.
For example, if you include the default modules.default.conf and, say,
you don't want ~quiet extbans then you add this in your unrealircd.conf:
blacklist-module "extbans/quiet";
This shouldn't be needed except for some corner cases, like if some
third party module does not limit their stuff properly, in S2S
or if channeldb contains some weird long entry or something.
This one has DNS caching enabled[*], which makes sense for this case.
[*] If using c-ares 1.31.0 or later. That version was released in June 2024.
The shipped-with-UnrealIRCd library version is 1.34.6, so qualifies.
However, if using system c-ares (which is automatically the case, if detected)
then many systems don't have it. The first Linux distro versions that qualify:
* Fedora 40
* Debian 13
* Ubuntu 25.04 (non-LTS) and future Ubuntu 26.04 (LTS)
* Etc...
since the message/notice would not make it through either.
This also means someone can no longer iterate through users to see who
is +D/+R by sending a "silent" TAGMSG. (Silent in the sense that the
end-user usually would not have noticed)
Suggested in https://bugs.unrealircd.org/view.php?id=6579 by zw32h (I think)
This also means HOOKTYPE_CAN_SEND_TO_USER now allows you to NOT to
set errmsg, to silently drop a message. Previously we would crash
deliberately on such a situation to enforce that all modules would
set a proper errmsg.
user is in known-users or in unknown-users. Not used anywhere yet.
Every 2 minutes we rescore all users. Or more specifically: every
5 seconds we rescore 1/24th of all users. That's the slow update path.
On certain events that cause a likely/possible transition, we update
the cache immediately. At the moment that is on IP change and account
login/logout. More will be added later.
* Add some missing fields, such as destination, but mostly in the
exclude- area where a bunch were missing (some of those are a bit
far fetched, but hey, they exist, so should be shown if in use).
* Re-order fields to more closely match the struct (still not 100%)
* Extended fields, such as "account" and "country", now show up
directly under the security group, just like the other fields,
such as "reputation_score". This is also how they show up in the
config file, so hide the the fact that internally in the struct it
is stored differently.
* Add a comment in SecurityGroup struct in include/struct.h to make
it clear you have to add/update stuff at 7 places if you are adding
something new.
New RPC methods:
- security_group.list: List all security groups
- security_group.get: Get details of a specific security group
- connthrottle.status: Get full connection throttle status, counters, and config
- connthrottle.set: Enable/disable connection throttling
- connthrottle.reset: Reset connection throttling counts
This also adds json_expand_mask_list(), json_expand_name_list(), and
json_expand_nvplist() to src/json.c for reuse by RPC modules.