1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-04 06:03:12 +02:00
Commit Graph

3448 Commits

Author SHA1 Message Date
Bram Matthys 3fafd32067 Fix end marker missing for 0 result in some CHATHISTORY BETWEEN. 2026-06-20 09:35:16 +02:00
Bram Matthys 09a732e2c1 Redo draft/chathistory-end from yesterday in a different way.
The previous mechanism (from yesterday) was a bit too simple at the
chathistory.c where returned_lines < limit would set the end tag but
it would not deal with the situation where returned_lines == limit
which is ambigious. So we had to move up a layer (or is it down?),
don't handle this in chathistory.c but in the backend. A new struct
field r->reached_end was added for this (set by backend).
2026-06-20 08:32:28 +02:00
Bram Matthys 570c32ea67 Fix CHATHISTORY TARGETS sending one target too little if limit is hit
And attach draft/chathistory-end when exactly 'limit' targets exist
and nothing more.
2026-06-19 21:33:49 +02:00
Bram Matthys 37977fcfe6 Don't send draft/chathistory-end for AROUND. As around does not have
a directorion, but is a midpoint, and we send X lines above Y under,
so end does not make sense there anyway (which of the two ends?).

We simply avoid sending it.
2026-06-19 21:28:15 +02:00
Valerie Liu 59d497726b chathistory: implement draft/chathistory-end tag (PR #337)
Signals to the client that it has reached the end of the history and
there are no more messages to fetch. The tag is attached to the BATCH
opener when the server returns the last page of results.

Only sent to clients that negotiated the draft/chathistory capability.
2026-06-19 21:19:10 +02:00
Valerie Liu f5d59dd152 Support ratified tags for reply-tag and no-implicit-names (PR #336)
The IRCv3 specifications for these have been ratified:
- https://ircv3.net/specs/client-tags/reply
- https://ircv3.net/specs/extensions/no-implicit-names

Both the draft and ratified names are supported during a transition period.
2026-06-19 20:17:02 +02:00
Bram Matthys ecde1b6479 Add bounds checking to message_tag_escape().
This fixes an OOB write that cannot be reached by users. Only a
hostile server could cause it in some situations. Even then, in
my tests this did not cause a crash (it goes into bss too, not
heap or stack).
2026-06-19 19:43:13 +02:00
Bram Matthys ce6f078262 Deal better with multiple spkifp, such as ECC + ML-DSA. We now cache them
and "./unrealircd genlinkblock" outputs multiple password ".." { spkifp; }
lines in such a case.

Other than that some cleaning up of recently-added-functions that are
now no longer needed: we now create ctx_link_server and ctx_link_client
that represent set::server-linking::tls-options for incoming and outgoing
links. Which can be NULL, and then we use ctx_server / ctx_client (set::tls).
Also add proper documentation on this.

When using ./unrealircd spkifp, tell ./unrealircd genblock is cooler.
Nah.. it takes more factors into account, genlinkblock, so is preferred :D
2026-06-17 15:45:01 +02:00
Bram Matthys 1162da4a9e * Server linking and certificates: we now treat listener blocks that are
`serversonly` (such as port 6900 in the example.conf) and link { } blocks
  in a different way than regular listen { } blocks:
  * If there are different certificates used in the serversonly listen block
    vs link blocks, then this is almost always means server linking is broken,
    so we now print a warning on boot and rehash.
  * We also print an 'advice' if any of these are not using (long-lived)
    self-signed certificate. This is because CA issued certificates are
    typically not suitable because they typically rotate keys and thus change
    the `spkifp`. Changing spkifp breaks server linking. We will now print
    an advice along with command and config block instructions to fix it.
  * We now use `set::server-linking::tls-options` for link { } blocks
    and listen { } blocks that are `serversonly`. All the rest uses the
    `set::tls` settings by default (eg the regular listen { } block on 6697).
    * This means our guide on
      [Using Let's Encrypt with UnrealIRCd](https://www.unrealircd.org/docs/Using_Let's_Encrypt_with_UnrealIRCd)
      and generic usage is more intuitive. You just set both set settings
      and then no longer need to use any tls-options in listen blocks or link
      blocks. The example conf has also been updated with this.
    * If `set::server-linking::tls-options` is not configured, it defaults
      to `set::tls`, so there is no unexpected behavior change for anyone.
  * In a future release we will make server linking with `spkifp` mandatory,
    so all of this helps with getting people ready for that, making such
    a future transition smooth.

TODO: Update wiki, better wording in release notes, etc.

This also changes the default example conf:

/* RECOMMENDED:
 * Everyone should be using IRC over SSL/TLS on port 6697. However, to use
 * it properly, you have to get a "real" certificate instead of the
 * self-signed default certificate that was generated by the installer.
 * The Let's Encrypt initiative allows you to get a free certificate that is
 * issued by a trusted Certificate Authority. Instructions are at:
 * https://www.unrealircd.org/docs/Using_Let's_Encrypt_with_UnrealIRCd
 *
 * When you follow that guide you will have a "dual certificate" setup:
 * set::tls:
 *   Your trusted CA certificate, served to clients on port 6697.
 *   (key and certificate change and renew every xx days automatically)
 * set::server-linking::tls-options
 *   A long-lived self-signed certificate for server linking, with
 *   a stable 'spkifp' signature that you use in link blocks.
 *   This certificate is used automatically in "serversonly" listen blocks
 *   (port 6900 in this configuration file) and automatically used for all
 *   link { } blocks.
 *
 */
//set {
//      tls {
//              certificate "/etc/letsencrypt/live/irc.example.org/fullchain.pem";
//              key "/etc/letsencrypt/live/irc.example.org/privkey.pem";
//      }
//      server-linking {
//              tls-options {
//                      certificate "tls/server.cert.pem";
//                      key "tls/server.key.pem";
//              }
//      }
//}
2026-06-16 20:50:56 +02:00
Bram Matthys 8d783204dd JSON-RPC: Remote RPC was broken and causing "not authorized" error messages.
This was used by `server.rehash` and `server.module_list`. Plus,
this release `user.get` under some circumstances. This is now
fixed but requires the target server to be on UnrealIRCd 6.2.6.
If the target server does not meet this condition then we error
telling the server "does not support remote JSON-RPC".

This was first reported by AdmiraL- in https://bugs.unrealircd.org/view.php?id=6611
2026-06-13 16:04:43 +02:00
Bram Matthys 2089aa4ec4 In RPC_CALL_ERROR show the actual error 2026-06-13 14:49:11 +02:00
Bram Matthys 7667307b0e JSON-RPC user.get can now expose more fields by forwarding the request
to the server where the user is actually on. Think of idle time etc.

* JSON-RPC: We can now route `user.get` requests to the server that user is
  on. This so we can fetch all fields for that user (including flood
  counters, idle time, snomask) that are normally not available remotely.
  * We do this automatically in `user.get` when `object_detail_level` is 5+.
  * You can force this explicitly with `object_remote_fetch` set to `true`.
    So you can also use it with detail level 2 if you want, e.g. if you
    don't need the flood counters but do want the idle time.
  * When RRPC is not available we answer ourselves (so safe fallback, but
    you won't have the local-only fields).

Oh and we deliberately don't do this in `user.list`, as doing it there
would mean a single request could result in hundreds of semi-`user.get`
calls across multiple servers.
2026-06-13 12:40:44 +02:00
Bram Matthys 65f918e8e9 Add json_expand_flood_counts() and make available in Central Spamreport
and JSON-RPC.

This exposes the newly added flood counters from
4384f1127b and
029675f867 in JSON.

I didn't want to put it in every JSON log message. So right now it
is only in:
* JSON-RPC with object_detail_level >= 5.
* Central Spamreport

I may expand it later to one or a few other areas.
2026-06-13 12:09:09 +02:00
Bram Matthys 3000381493 Fix multiline-concat behavior for fallback clients.
We were merging draft/multiline-concat lines together server-side before
sending them to non-multiline clients. This could truncate oversized merged
lines. We now simply send them as separate lines.

Reported by ProgVal in https://bugs.unrealircd.org/view.php?id=6628
2026-06-13 10:29:46 +02:00
Bram Matthys 029675f867 Similar to previous, add total_channel_flood_count() for +f/+F limits exceeded
* `total_channel_flood_count('..setting..')` returns the number of
  times `+f`/`+F` limits were exceeded by that user in all channels
  the user is or was in. Available are: `nick`, `join`, `knock`, `msg`,
  `ctcp`, `text`, `repeat` and `paste` (and `all` for the sum).
2026-06-13 07:46:51 +02:00
Bram Matthys 4384f1127b Crule: new server_flood_count() for nick, away, join etc floods.
Suggested by westid in https://bugs.unrealircd.org/view.php?id=6477

* New [crule function](https://www.unrealircd.org/docs/Crule) that return
  the number of times a flood was blocked for that user. For example,
  `server_flood_count('away')` returns the number of time away-flood
  was exceeded. Aslo available: `nick`, `join`, `invite`, `knock`,
  `vhost` and `conversations`. Plus, there is `all` for a total of all.
  * This can be used in a security-group::rule or spamfilter::rule.
    Eg: `spamfilter { rule "server_flood_count('nick')>4"; action gline; }`

This also - internally - adds a mechanism to run spamfilter rule-only-
filters after the command handler, whenever a tag value or other thing
changed. That's part of this commit.
2026-06-12 17:43:51 +02:00
Bram Matthys 57ca415c26 Add whitespace deletion in buildvarstring() so template can have a space.
Basically if a $variable is empty, and there is a space before it in the
template string then we delete that space.

May seem (or is) a bit over the top but this way the template stays clean,
and it may be used/useful in other places as well.

This is a behavior change, but I think we can live with it. One can opt-
out via BUILDVARSTRING_KEEP_SPACE_FOR_EMPTY_VAR.
2026-06-11 19:19:53 +02:00
Bram Matthys 5850ec9434 Show TKL IDs (and related spamfilter TKL ID, if any) in TKL_ADD, TKL_DEL,
TKL_EXPIRE and SPAMFILTER_MATCH messages.

This uses the newly added functions log_data_optional_string() and
log_data_optional_name_value(). The first shows the optional string
like "abc" and the second expands to "[name: value]". What's also new
is that both of these will swallow a preceding space if there is no value.
This so you can just use "Something. $optional_string" and it will
expand to "Something." if $optional_string is empty. This makes things
less hacky and more human readable :)
2026-06-10 19:48:38 +02:00
Bram Matthys 62f3cda8f2 Make spamfilter IDs start with "SPAM" to be more visible. And this also
means shun IDs now start with "H". Update release notes.

This, after i realized that for like *LINEs that are added by spamfilter
the two ID fields in "STATS gline" are a bit confusing as to which ID is
what. Now the spamfilter one starts with "SPAM" so there can be no
confusion. The gline one still starts with "G" as before.

Since I kept the generated ID length the same, this means there is less
bits available for the spamfilter ID, but there are rarely more than 1000
spamfilters, and in that scenario there's just as little birthday attack
collision % as with 200k glines, just to illustrate (~0.0015% vs ~0.0018%)
2026-06-10 15:37:20 +02:00
Bram Matthys faecdd66cd Config-file based *LINES/Spamfilter: preserve hit counters between rehashes.
Unlike non-config-based TKLs - which go through tkldb - they are still not
preserved through restarts. But at least they are not lost due to REHASH.
This is done via a save+restore, a bit complicated, but we have little
choice (other than not doing this at all).

This also moves remove_config_tkls() from conf.c to tkl.c
2026-06-10 14:30:39 +02:00
Bram Matthys d5b799d3de Server bans and Spamfilters now track how often they are hit and the time
of the last hit, eg in `STATS gline` for GLINEs. These counts happen on
each individual server and are not network-wide. This allows IRCOps to see
which entries never get any hits and can potentially be removed.
* Important exception: config-based spamfilters/bans lose their counters
  on `REHASH` and restart atm.
* For non-config TKLs, the hit count and last hit timestamp are preserved
  across reboots (via tkldb).
* Again, see *Developers and protocol* for the exact STATS field.

The spamfilter hits already existed but all the rest is new.

Suggested by BlackBishop in https://bugs.unrealircd.org/view.php?id=6304
(in particular, time of the last hit)
2026-06-08 13:44:00 +02:00
Bram Matthys 27a086b03a Add TKL IDs via message tags in S2S.
By default - assuming you don't set set::reject-message things by yourself -
the *LINE id is appended at the end of the rejection that is shown to the
user, like: [ID: G7K2MP9WQX3].

Also new is spamfilter to *LINE mapping, so you can see which *LINE was
set by which SPAMFILTER. For this STATS gline and friends were enhanced.
In fact, multiple fields were added there, including some that are 0
(zero) placeholders at the moment. These will be set in a future commit.
Some things were combined here so we only have to break STATS and tkldb
database format once (unless i made a mistake, then the follow up commit
will correct that i guess :D).

This was requested by Hero in https://bugs.unrealircd.org/view.php?id=4397
in 2015. Again by musk in https://bugs.unrealircd.org/view.php?id=4397
in 2022. And on IRC by Chris and others.

As you can see it was not SUPER easy and a lot of thought went into this
(and in terms of S2S traffic it is part of something bigger too)
2026-06-07 17:19:00 +02:00
Bram Matthys cbc9213d5e Similarly to previous, fix allow channel::except and spamfilter::except
so they actually work.
2026-06-05 10:36:46 +02:00
Bram Matthys dee26e2e12 Add const to third argument of unreal_create_match() 2026-06-05 10:00:14 +02:00
Bram Matthys f0c0feff4f Set PCRE2 limits explicitly (to more sensible defaults), reported by Link420. 2026-06-05 09:43:22 +02:00
Bram Matthys caa01c9c8c Call update_known_user_cache() right before "Client connecting" log.
This is after PRE_LOCAL_CONNECT hook and can be useful in case some
module in there did something to the user that made them known-users.

And mention explicitly to module devs if they have things like
authentication mods that may move users between known<->unknown
that they should update the cache.
2026-05-20 10:16:29 +02:00
Bram Matthys 75bd6e87d3 Fix set::antimixedutf8::except not working
Reported by Le_Coyote in https://bugs.unrealircd.org/view.php?id=6625
2026-05-15 09:23:19 +02:00
Bram Matthys 69b2116826 Fix typo in linking message, mentioned by Gottem. 2026-05-14 11:24:49 +02:00
Bram Matthys 0f62b20972 Bump maxperip and connthrottle module version to 2.0.0 2026-05-13 15:40:50 +02:00
Bram Matthys 0007ccda47 Connthrottle has a start delay, but this makes no sense for the ipv6 stuff.
The start delay is there for the rate limit (since lots of users may
connect after starting the server). The IPv6 is not a ratelimit but a limit.
2026-05-13 13:34:21 +02:00
Bram Matthys 4af3695347 Show BUG_CT_NEGATIVE_COUNTER also in non-DEBUGMODE and limit to 5:60.
Not only that one, but all BUG_CT_* connthrottle "something isn't
right here" messages.
2026-05-13 13:08:38 +02:00
Bram Matthys 31b43dcb08 Fix CONNTHROTTLE_CHECK and use <addr>/<prefix> in 'STATS maxperip'
just like we do in 'STATS connthrottle'.
2026-05-13 08:30:45 +02:00
Bram Matthys a4361b7c90 Add set::known-cloud-services [yes|no] (enabled by default)
Install default maxperip/connect-flood exception for IRC platforms
that are so big that they are known to trip default maxperip restrictions
(per IPv4 IP or per IPv6 /64: 3 local users, 4 network-wide users)
on dozens of networks and that publish a stable list of IP ranges.
Currently only IRCCloud qualifies for this.

IRCCloud is in example conf since May 2023 (commit 82dbc4a297) as:
except ban { mask *.irccloud.com; type { maxperip; connect-flood; } }.
Unfortunately DNS sometimes fails to resolve. We have seen this happen
during an outage or server restart. People then mass-connect, but DNS
is not fully working (yet), leading to unresolved hostnames.

Recent stricter maxperip treatment for /64 IPv6 and the new /56, /48
and /32 restrictions in connthrottle make this problem worse. Without
these IP exceptions it would cause unwanted rejections.

If you don't want this, use: set { known-cloud-services no; }
(And then presumably you also don't want the except ban block
 that example conf has been shipping since 2023)
2026-05-07 09:15:36 +02:00
Bram Matthys 05ef211900 For connthrottle rate limiting (new-users) now check except tkl type 'c'
(connect-flood). Those users are exempt and not counted towards new users.

And the new ipv6-unknown-users-limit in connthrottle (which has nothing
do with rates, but counts, similar to maxperip, but only on unknown-users)
now checks tkl type 'm' (maxperip). Those are counted as "except unknowns".

This is more of what the admin would expect.
2026-05-06 18:54:10 +02:00
Bram Matthys 3e6f9f06e2 set::connthrottle::disabled-when::reputation-gathering default of 1 week
was stated in docs at https://www.unrealircd.org/docs/Connthrottle but
if this item was not there then the default was actually zero (0).
Now, that isn't too common, since we ship with example.conf with the
connthrottle block as shown there, so lots of users have the proper
default, but just in case someone hand-writes or removed that connthrottle
settings block ("because they are the default)"... :)
2026-05-06 09:39:40 +02:00
Bram Matthys f765905b15 New snomask 'x' (set by default): maxperip/connthrottle connect rejections
When a client is rejected by maxperip (not new) or connthrottle
ipv6-unknown-users-limit (that one is new), a notice to +s +x will be sent.

maxperip ipv4 example:
*** Client testuser4 with IP 1.2.3.4 rejected: maxperip limit exceeded (4 global, max 3)

maxperip ipv6 with /64 example:
*** Client testuser4 with IP 2001:dbe:0:0:0:0:0:4 rejected: maxperip limit exceeded for 2001:dbe::/64 (4 local, max 3)

connthrottle example where /56 limit is exceeded:
*** Client testuser5 with IP 2001:db8:cafe:abcd:0:0:0:5 rejected:
    connthrottle ipv6-unknown-users-limit (cidr-56, max 4) exceeded for
    2001:db8:cafe::/56 (5 unknown / 0 excepted / 0 known)

Oh and this commit also fixes a typo in existing CONNTHROTTLE events,
which previously were CONNTHROTLE (a missing T).
2026-05-05 16:33:19 +02:00
Bram Matthys 0940ed5d13 Update the messages regarding too many (new) connections.
Changed "Too many connections from your IP" to have "[maxperip]" at the end.
Also create new setting and swap it with existing-one-during-development.

Long story short, we now have 3 different messages for these limits:

set::reject-message::too-many-connections
 "Too many connections from your IP [maxperip]"

set::reject-message::too-many-connections-ipv6-range
 "Too many connections from your IPv6 range ($prefix_addr/$prefix_len) [maxperip]"

set::reject-message::too-many-new-connections-ipv6-range
 "Too many new connections from this IPv6 range ($prefix_addr/$prefix_len) [connthrottle]"

So we explicitly mention whether it is maxperip or connthrottle limiting the
user, that should provide enough clue to the IRCOp if the user pastes the
message to them.
2026-05-05 13:24:01 +02:00
Bram Matthys 32e7dbfb3c Add connthrottle self-test that (only) runs in DEBUGMODE.
This verifies state every second. Obviously not for production.
2026-05-05 10:03:26 +02:00
Bram Matthys 2ae69be391 Implement IPv6 CIDR restrictions for unknown-users
Will do more in follow-up commits.
2026-05-05 10:03:25 +02:00
Bram Matthys 3a429dbd42 Add helper functions and start the IPv6 /128 to /64 transition in
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.
2026-05-05 10:03:22 +02:00
Bram Matthys c0f68bfd08 Deprecate link::verify-certificate, as 'Client Authentication EKU' is being
dropped by public certificate authorities (as per Chrome Root Program).

The fix is to simply use 'spkifp'. The config warning has all the details.
2026-05-01 19:47:28 +02:00
Bram Matthys dbc3182462 Update -DTESTSUITE +f/+F exemption.
The "not setting +F" stuff didn't work, as due to netmerge - which
can even happen without a split when joining clients on both sides -
this would revert to +F normal basically.

So we just explicitly exempt in the join and msg code.

All this is for unrealircd-tests.
2026-04-08 17:50:16 +02:00
Bram Matthys babb86818f S2S: Fix memory leak on RRPC with wrong source (either rogue server or very rare) 2026-04-07 17:59:07 +02:00
Bram Matthys 281d0cce9b multiline: mv CommandOverrideAdd() to MOD_LOAD so module order doesn't matter 2026-04-04 08:51:25 +02:00
Bram Matthys dc6740bfb7 Small code cleanup (identical branches) 2026-04-04 07:06:18 +02:00
Bram Matthys 781aecf95a Fix batch reference length. We had two with different sizes.
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.
2026-04-03 16:38:34 +02:00
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