1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-04 11:33:13 +02:00
Commit Graph

3402 Commits

Author SHA1 Message Date
Bram Matthys fa2f78fe94 Optimize multiline delivery to channels (use LineCache)
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 ;)
2026-04-03 09:04:33 +02:00
Bram Matthys 36baf946a3 Guard against multiline+history amplification attacks in CHATHISTORY.
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.
2026-04-03 07:59:11 +02:00
Bram Matthys a1dc459a33 Update +H limit and write release notes regarding draft/multiline support.
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 ;)
2026-04-02 20:24:21 +02:00
Bram Matthys 04ffe335f1 Send CAP NEW multiline=max-lines=.. on unknown-users<->known-users transition 2026-04-02 18:29:12 +02:00
Bram Matthys 46be05d42f Multiline: fix memory leaks and missing inner tags 2026-04-02 17:34:44 +02:00
Bram Matthys 8c0590cda2 Add multiline support in history. 2026-03-30 19:09:20 +02:00
Bram Matthys 1df465a6a5 Add +f subtype 'p' (for 'paste'). So [2p]:15 means max 2 pastes per 15s.
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
2026-03-30 14:55:03 +02:00
Bram Matthys b0dba4bede Add draft/multiline support with a default max-lines of 15 for known-users
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.
2026-03-30 13:16:48 +02:00
Bram Matthys f329a64991 The IsFloodLimit() used a hardcoded channel parameter. This was not a problem.
But is dangerous if the macro would be used where it mattered.
2026-03-26 17:28:51 +01:00
Bram Matthys 14cb15c632 Don't call -m upgrade or -m compile-all if zero src/modules/third/*.c
Reported by bss.
2026-03-25 14:01:42 +01:00
Bram Matthys 6ad7f7dccf And use binary search now that we have so many crule functions... 2026-03-24 19:37:12 +01:00
Bram Matthys ed16dad40e Add a bunch of crule functions:
* Boolean checks: is_oper, is_local, has_swhois
* Match functions: match_class, match_server, match_vhost,
  match_realhost, match_away, match_asname, match_operlogin,
  match_operclass, match_sni, match_tls_cipher
* Numeric counters: connections_from_ip, channel_count,
  channel_member_count, idle_time
* Traffic stats: messages_sent, messages_received, bytes_sent,
  bytes_received
* Text analysis: text_byte_count, text_character_count, word_count,
  uppercase_percentage, digit_percentage, non_ascii_percentage,
  max_repeat_count, mixed_utf8_score, unicode_block_count

Will do a more thorough audit and look at adding some kind of
tests tomorrow.
2026-03-24 19:33:55 +01:00
Bram Matthys f884bfe755 Another workaround for test suite. 2026-03-22 13:25:36 +01:00
Bram Matthys 806c883a7f Rename geoip_maxmind to geoip_mmdb with a backwards-compatible warn.
This is a mmdb backend which supports various GeoIP providers,
and we no longer use the maxmind library, so this makes sense.
2026-03-22 12:29:00 +01:00
Bram Matthys 172ace9750 geoip_maxmind: use our own mmdb implementation
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.
2026-03-22 12:10:18 +01:00
Bram Matthys 89bce01c31 Fix OOB write in geoip_csv if the .csv file is bad / malicious.
This module is rarely used but analysis showed that there was an
OOB write in the country name, and two small off-by-ones in code
and continent.

Again, this only matters if the CSV file you are importing is bad
or malicious. And we use stack protection in UnrealIRCd so this
should then "only" cause a crash.
2026-03-16 14:10:29 +01:00
Bram Matthys f944990c54 Fix some flagged stray semicolon in C code (;;) 2026-03-16 09:53:22 +01:00
Bram Matthys 27a3fb8d97 unreal_server_compat: fix always using EXBTYPE_BAN even for +e/+I.
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.
2026-03-14 10:20:24 +01:00
Bram Matthys 31005e18b1 Fix extbans in +I not being converted to letter bans to older servers.
This affects servers without NEXTBANS, such as anope 2.0.x series
(anope 2.1.x is not affected as it supports NEXTBANS).

Non-NEXTBANS servers only support letter extbans so we are supposed
to convert ~security-group:known-users to ~G:known-users when sending
to such a server, in unreal_server_compat. And we did this well for
the MODE command for +beI. In SJOIN we did this correctly for +b/+e
but not for +I due to a silly code mistake.

This bug is present since 6.0.0 but wasn't noticed until now.

To be a real problem you need something like:
1. Anope 2.0.x series (or other services without NEXTBANS)
2. A channel with +I extbans
3. KEEPMODES set on that channel

Then what happens is when services boot:
1. UnrealIRCd will sync with anope 2.0.x and incorrectly send
   named bans, which will confuse anope. But nothing strange
   happens yet at this point.
2. Then on next server sync (eg anope restart or unreal restart)
   anope will try to restore these but they end up with weird
   entries like +I *!*@~security-group:known-users
   (note the *!*@ prefix)

And it should be noted that this would also happen in a situation
with UnrealIRCd 5 + UnrealIRCd 6 servers, but UnrealIRCd 5 is
End Of Life anyway.

Reported by BlackBishop and Sadie two days ago. Thanks!
2026-03-13 13:57:41 +01:00
Bram Matthys 7865675917 Fix OOB write if a trusted linked server sends malicious data.
NOTE: Linked servers are considered trusted in UnrealIRCd.

This is not exploitable beyond a crash, due to -fstack-protector-all,
a hardening compiler flag we added many years ago. Even without
that flag it would be rather difficult, and i didn't manage to,
but this should never happen anyway since this flag is only
missing in gcc/clang versions that are more than 15 years old.

This issue was introduced by the move to CMD_BIGLINES in
6c5de62c18 in 6.2.2 release.
2026-03-06 07:14:10 +01:00
Bram Matthys 87e4249a09 extjwt: don't free modes/umodes, they are taken care of by payload. 2026-03-04 17:07:06 +01:00
k4be deff636c74 extjwt: Remove OpenSSL deprecation warnings 2026-03-04 09:38:05 +01:00
Bram Matthys 648a10494f Add -DTESTSUITE and use it from extras/build-tests/nix/build.
In particular, this disables default +F for #__SYNC__ channels.
The test suite has a "+F off" but when on 3 servers, each 75
clones are connecting, the MODE is too late and the join limit
is already reached sometimes. Causing tests to fail.
2026-02-28 15:26:57 +01:00
Bram Matthys 17037b0694 Fix build failing if DNS is not working. Building UnrealIRCd should never fail
because it has no internet access, like when fetching the repository
(modules.list file) of 3rd party modules.

Previously I had..
url_start_async(request);
synchronous_http_request_in_progress = 1;
.. which worked fine for the "cannot connect case", like port blocked
or timeout connecting. But if DNS fails then the step of setting
synchronous_http_request_in_progress = -1 (so failed) already happens
during the url_start_async(request); call, and then the line after it
sets 'synchronous_http_request_in_progress = 1;' so we miss that it
failed and wait in the I/O loop forever.
Simply swapping the two lines of code fixes this.

The other change is that when running the ModuleManager in "make" we should
ignore the exit code. I probably broke that while refactoring and adding
non-zero exit codes in de modulemanager past few months for this release.
2026-02-25 14:58:11 +01:00
Bram Matthys 3a96bdf6ec Add set::allow-setident (default: 'no'), set::allow-setname ('yes')
Two new settings that control the use of `SETIDENT` and `SETNAME`:
* [set::allow-setident](https://www.unrealircd.org/docs/Set_block#set::allow-setident)
  now defaults to 'no'. Previously all users were allowed to change their
  ident (taking into account
  [set::allow-userhost-change](https://www.unrealircd.org/docs/Set_block#set::allow-userhost-change)
  restrictions).
* [set::allow-setname])(https://www.unrealircd.org/docs/Set_block#set::allow-setname)
  has a default of 'yes' which matches older UnrealIRCd versions (no change).
  Perhaps some admins who use controlled (web)chats may want to set this
  to 'no' if users are not supposed to change their realname/gecos.
  This is probably rare, but they have the option now.
2026-02-23 08:58:39 +01:00
Bram Matthys 014925496b Forgot a few more of those [1] that need to be []
(see previous commit)
2026-02-22 16:24:55 +01:00
Bram Matthys 7d45e69fd2 Use C99 flexible array members, like name[], instead of name[1]
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.
2026-02-22 16:11:41 +01:00
Bram Matthys 19d17832fe Remove set::restrict-extendedbans as it didn't work. Simply don't load
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";
2026-02-22 13:07:57 +01:00
Bram Matthys 979f44bde4 Linking: upon duplicate server we could SQUIT the wrong one.
This would cause a bit of a mess, that usually would be resolved a few
seconds later, but still a mess. I had this on irc*.unrealircd.org
myself when rerouting a server from a backup-hub to primary-hub
a few months ago.
2026-02-22 11:37:09 +01:00
Bram Matthys d79161019a Clear client->local->proto for users.
This is not an issue now in all code paths, but if someone accidentally uses
SupportXYZ() without checking IsServer() then it would be an issue.

In the past we used client->local->proto for client flags as well, but this
has been split off to client->local->caps a while ago.

I guess we should rename client->local->proto to something more server-ish
in a later major release to indicate this as well.
2026-02-22 10:37:01 +01:00
Bram Matthys 371cb487b9 Fix missing "return;" in "Bad ulines" rejection of a server. 2026-02-22 10:00:32 +01:00
Bram Matthys 059abc4b56 "STATS fdtable" is mostly for debugging. Simplify read/write handler display
and callback data in non-DEBUGMODE. Also because exposing pointers like
this can defeat ASLR. These STATS are oper-only though, but hey, defense in
depth... and the pointer values don't make sense to non-devs anyway,
so why show them in the first place.
2026-02-21 19:41:56 +01:00
Bram Matthys b467e4e147 JSON-RPC: Fix missing mtag issued by in user.part
We use mtag_add_issued_by() to prepare it but then pass NULL
in do_cmd() so it was basically useless.

Also compile fix for previous (forgot to git ammend)
2026-02-21 16:22:36 +01:00
Bram Matthys ec4ccbde82 Fix memory leak on JSON-RPC log.send and fix a small auth url parse thing.
Actually that auth url method is not documented, we should probably remove it.
2026-02-21 16:18:34 +01:00
Bram Matthys b93cb14623 Fix crash due to fix from a few hours ago (5580b294f4) 2026-02-21 16:04:50 +01:00
Bram Matthys d22f65364c Make duplicate deny link::rule items an error.
(as otherwise using duplicated generates only a warning and could memleak)
2026-02-21 14:57:41 +01:00
Bram Matthys b55a4b84e0 Blacklist hit with a soft ban action: fix memory leak if multiple hits occur.
So, if the IP was on multiple blacklists.
2026-02-21 14:43:41 +01:00
Bram Matthys f20b62ea3b Fix memory leak on blacklist hit if using soft bans 2026-02-21 13:59:10 +01:00
Bram Matthys 28a8bee041 Don't use 'client' in CENTRAL_BLOCKLIST_ERROR, prolly copy-paste error.
Not really important as it is not part of the normal log message (only JSON).
2026-02-21 13:49:26 +01:00
Bram Matthys f59b937f3b Fix leak if central-blocklist returns "error" JSON string (very rare) 2026-02-21 13:45:47 +01:00
Bram Matthys 5580b294f4 Fix memory leak if using spamfilter::except. 2026-02-21 13:20:17 +01:00
Bram Matthys be479aa890 The buffer in spamfilter_build_user_string() was too small causing cut off.
This affects the spamfilter 'u' target. It didn't overflow but was cut off,
potentially causing a NON-MATCH where it could have been a MATCH instead.
2026-02-21 13:18:30 +01:00
Bram Matthys 2ac09de148 Fix central spamfilter with "stop" action, due to using same &var twice. 2026-02-21 13:13:15 +01:00
Bram Matthys c24424bb50 JSON-RPC: throttle.set did not do anything
Reported by adator in https://bugs.unrealircd.org/view.php?id=6608
2026-01-30 07:39:37 +01:00
Bram Matthys 91d5114a1e Whitespace fix
[skip ci]
2026-01-28 09:38:39 +01:00
Bram Matthys a887de92ce Add extra safety in register_user() against shunned users. 2026-01-25 12:56:52 +01:00
Bram Matthys 8467969878 Don't show confusing CENTRAL_BLOCKLIST_TIMEOUT when user is shunned.
Previously it showed this warning and said "Allowing user .. in unchecked"
when the user got shunend by CBL. Usually harmless but.. had a report
where it possibly was not (though that was an older UnrealIRCd version).
In any case, confusing, solved now!
2026-01-25 12:54:30 +01:00
Bram Matthys 91930e3631 Bleh, just use "*" in ERR_INVALIDMODEPARAM for the param.
Otherwise you get into trouble if client does things like:
MODE #test +l ::a
MODE #test +l :a b c
And I am too lazy to handle these cases :D
2026-01-23 08:48:34 +01:00
Bram Matthys d413959e57 Chanmode +l: when coming from an IRC client, reject <=0 instead of transforming.
Reject it with an ERR_INVALIDMODEPARAM, just like we do for +k.

I think the higher number transforming is fine, but this <=0 transformation
is odd as it almost never is what the user actually intended.

In S2S traffic we still transform, as rejecting there is more problematic,
(causing a desync) and transforming it there is not a major issue, anyway.

Reported by ProgVal in https://bugs.unrealircd.org/view.php?id=6602
2026-01-23 08:45:34 +01:00
Bram Matthys 2dd23d13b7 Silently drop TAGMSG to users who refuse PRIVMSG/NOTICE also (umode +D, +R),
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.
2026-01-23 08:23:22 +01:00