This commit adds a release notes entry about the reformat, and this
commit message also contains migration instructions for people who
maintain a fork or maintain a set of patches. See further below.
== BACKGROUND ==
In July 2026 the entire UnrealIRCd 6 source tree was reformatted with
clang-format. The main commit is:
548d66d26c
That commit is exactly what 'make format' produces with clang-format
version 21.1.8, with nothing else mixed in. It only changes whitespace:
we verified that the code itself is exactly the same as before, down to
the last character. It was preceded by a few small preparation commits
(the first being 2d8df60e14) and followed
by one more commit with whitespace fixes
(b785d69309).
== GIT BLAME ==
All the commits mentioned above are listed in .git-blame-ignore-revs,
so 'git blame' and the blame view on GitHub ignore them, giving a
more usable blame view. To get that behavior locally, run this once
in your working copy:
git config blame.ignoreRevsFile .git-blame-ignore-revs
== MIGRATION INSTRUCTIONS FOR FORKS AND LOCAL PATCH SETS ==
The following instructions were suggested by Claude (AI) and are
completely untested (as I, Syzop, don't maintain a fork myself).
Do not merge straight across the reformat: you would get whitespace
conflicts in nearly every file you ever touched. Instead, first bring
your own tree into the same formatting, as described below. You need
clang-format major version 21 (upstream used 21.1.8); other versions
format slightly differently. Check with: clang-format --version
If your fork is a branch that merges upstream from time to time:
1. Merge upstream up to just BEFORE the reformat (note the ^):
git fetch upstream
git merge 548d66d26c3e8e52ada71de84f07569fcee33f69^
Resolve ordinary conflicts as usual.
2. Reformat your own tree with the same tool and commit it:
make format
git commit -a -m "Reformat to match upstream clang-format"
After this, all code that you share with upstream is formatted
exactly the same as upstream, so git has nothing left to fight
over there.
3. Merge the rest, which is now nearly conflict-free:
git merge upstream/unreal60_dev
4. Add YOUR reformat commit hash from step 2 to
.git-blame-ignore-revs and commit that.
5. Verify: 'make format' followed by 'git diff' must show nothing.
If your fork is a rebased patch series instead: rebase onto
548d66d26c3e8e52ada71de84f07569fcee33f69^ first and fix ordinary
conflicts there, then continue with:
git rebase -X ignore-space-change
onto the current upstream tip, run 'make format', and fold the result
into your patches or keep it as one extra commit at the end of the
series. Then verify here too: 'make format' plus 'git diff' must show
nothing.
From then on, run 'make format' before committing (or use your
editor's clang-format integration with the in-tree .clang-format
config) and your merges will contain less whitespace noise than ever.
When not using version 302, such as with "CAP LS", the specification does not
allow us to use continuation lines. This means all advertised caps must fit
into one line. That is no longer always the case, especially if you load 3rd
party capabilities. So we need to scratch advertising some capabilities to
<302 clients.
"CAP LS 302" is unaffected. Note that version 302 in the specification exists
since at least November 2017, so most clients use that one.
According to https://ircv3.net/software/clients the following clients are
affected by this change:
Desktop Clients
* KVIrc
* Circe
* catgirl
* BitchX
* Pidgin
* LimeChat
Mobile Clients
* IRC for Android
* LimeChat
And various older versions of other clients (obviously).
NOTE: The source is only that IRCv3 page. I did not check manually.
For this particular commit. We filter out various unrealircd.org informative
CAPs and the vendor specific json-log. So that isn't much of a problem.
However, in the future we may be forced to filter out more capabilities to
make room. It would be much better if all clients are on >=302. Also, I
should mention we are not the only IRCd out there, so I can't vouch on what
other IRCds (will) do when hitting this non-302-limit.
Reported by ProgVal in https://bugs.unrealircd.org/view.php?id=6630
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
`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";
// }
// }
//}
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
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.
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.
* `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).
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.
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 :)
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%)
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
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)
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)
Only for ~/unrealircd/lib/ we had this ommision, and for ~/unrealircd itself.
I doubt this means a change for users, as all subdirs were already 0700
so then tightening of ~/unrealircd is not very important.
And only upsides... making things safer..
and refer to this as well.
Suggested by PeGaSuS in https://bugs.unrealircd.org/view.php?id=6610
This also moves extras/tls.cnf to doc/conf/tls/tls.cnf which
also gets installed in ~/unrealircd/conf/tls/ (or whatever CONFDIR is)
And just to be clear: this means you can run "./unrealircd makecert"
without needing to go into BUILDDIR (or even having it at all).
At the same time, the generation commands have been modified slightly
so two warnings during certificate generation are no longer there.
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.
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).
Version 1.0.21 which we shipped with 6.2.3 has this bug, reported
by PhotoJim at https://bugs.unrealircd.org/view.php?id=6615.
And yes, libsodium also has this weird -stable thing, which does
have the fix, but that's basically just a snapshot of their git
version, it's a .tar.gz that gets updated every X time and it does
not have a GPG signature, while I have the policy nowadays to
verify GPG signatures for libraries we ship. So I am option to just
downgrade a version, for now, which is fine since we shipped with
1.0.20 for quite some time until recently.
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
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.