Skip to content

Merge upstream unrealircd 6.2.6-rc1#20

Merged
matheusfillipe merged 45 commits into
unreal60_devfrom
chore/merge-upstream-6.2.x
Jun 24, 2026
Merged

Merge upstream unrealircd 6.2.6-rc1#20
matheusfillipe merged 45 commits into
unreal60_devfrom
chore/merge-upstream-6.2.x

Conversation

@matheusfillipe

@matheusfillipe matheusfillipe commented Jun 23, 2026

Copy link
Copy Markdown

44 upstream commits. Fork gains the server_flood_count()/total_channel_flood_count() crules the test harness now requires, so Linux CI passes without pinning the harness.

TKL hits/id taken from upstream; version kept at ObbyIRCd 6.2.6.

Summary by CodeRabbit

  • New Features

    • Added unique ID tracking for server bans and spamfilters with persistent hit counters
    • Introduced new crule flood-counter functions for dynamic rule evaluation
    • Extended CAP version negotiation support with minimum version constraints
    • Added chat history end-of-pagination signaling
  • Changes

    • TLS certificate generation now uses ./unrealircd mkcert command instead of make pem
    • Server-linking TLS configuration options now available for certificate management
    • Base directory permissions tightened with restrictive file modes
  • Fixes

    • Improved URL handling for HTTPS downloads and JSON-RPC operations
    • Enhanced flood protection tracking by connection type

syzop and others added 30 commits June 5, 2026 09:43
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.
…dirs.

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..
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)
…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)
…hashes.

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
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%)
…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 :)
…ace.

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.
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.
… 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).
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
and JSON-RPC.

This exposes the newly added flood counters from
4384f11 and
029675f 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.
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.
…ssages.

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
  `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";
//              }
//      }
//}
…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
print copy-pastable config blocks. Previously we used add_name_list(),
which uses insert at beginning, which would reverse the order.

Also changed duplicate_name_list() to preserve order. Previously
it reversed the order of all items.
(we should actually remove this one day :D)
Not in UnrealIRCd itself: it is only used in one place, STATS with a
big buffer. And unrealircd-contrib 3rd party modules has no consumers.
syzop and others added 15 commits June 18, 2026 19:55
…issue.

This is unreachable in current code paths, but could be some day.
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).
…rcd#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.
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.
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.
And attach draft/chathistory-end when exactly 'limit' targets exist
and nothing more.
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).
Eg on failed oper attempts, that sort of things. Previously it was still
adding fake lag. This complicated unrealircd-tests :).

As always, nofakelag should never be used in normal conditions, it
disables the most important protection we have (fake lag bumping).
If you want lower lag for a group of users, the right tool is
set::anti-flood::name-of-security-group::lag-penalty and ::lag-penalty-bytes
See https://www.unrealircd.org/docs/Special_users
And a minor README.md update to add a few more links.
1) obviously only provide ASan report if relevant (eg memory issue),
   not for like priv escalation :)
2) "If you are submitting issues and fail to follow the procedure above"
   was in the AI/tooling paragraph, but just in case someone reads it
   out of that context we now scope AGAIN it to that ONLY.
   This so normal users (that have nothing to do with AI/tooling)
   are not scared off in reporting real issues.

[skip ci]
… unaffected.

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
TKL hits/id taken from upstream (dropping the fork's parallel
reimplementation); account-aware banexception kept. Version stays
ObbyIRCd 6.2.6.
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR prepares UnrealIRCd 6.2.6-rc1 with the following changes: replaces make pem with unrealircd mkcert for TLS certificate generation; introduces dedicated set::server-linking::tls-options with SPKIFP caching and a post-configuration consistency checker; adds network-wide unique IDs and persistent hit counters for TKL entries with tkldb schema 6260; adds per-session flood-block counters exposed via new crule functions and deferred rule-only spamfilter execution; implements IRCv3 draft/chathistory-end pagination signaling; gates server-specific capabilities behind CAP LS 302; extends JSON expansion with flood counts and optional log fields; improves JSON-RPC remote routing; and fixes multiline fallback delivery and message-tag buffer bounds.

Changes

UnrealIRCd 6.2.6-rc1 feature release

Layer / File(s) Summary
Build: mkcert replaces make pem, directory permissions
Config, Makefile.in, configure, configure.ac, obbyircd.in, extras/build-tests/nix/build, src/windows/makecert.bat, src/windows/unrealinst.iss, doc/conf/tls/tls.cnf
obbyircd mkcert generates EC self-signed TLS certificates, replacing the deprecated make pem target. Makefile.in's pem recipe now prints a deprecation notice. Base/private-lib directories gain chmod 0700. Windows installer drops signonce flags and updates tls.cnf source path.
Public API contracts: headers and data structures
include/config.h, include/struct.h, include/h.h, include/modules.h, include/dynconf.h, include/numeric.h, src/api-efunctions.c
struct TKL gains id[16], spamfilter_id[16], hits, and lasthit. FloodCounter adds blocked. TLSOptions adds spkifp. Configuration adds three server-linking TLS fields. LogData adds label and two new optional field types. New efunction types, HOOKTYPE_POSTCONF, HistoryResult.reached_end, ClientCapabilityInfo.minimum_cap_version, and updated unreal_match/banned_client signatures are all declared.
TLS context selection and server-linking TLS options
src/tls.c, src/conf.c, src/socket.c, src/modules/starttls.c, src/modules/stats.c, src/unrealircdctl.c
tls.c adds tls_ctx_for_listener, tls_ctx_for_outgoing_link, tls_options_for_* helpers and builds dedicated ctx_link_server/ctx_link_client contexts from set::server-linking::tls-options. init_ctx() computes and caches SPKIFP lists. conf.c wires the new TLS inheritance model into listener, link, and SNI block parsing, and link_generator() emits per-fingerprint spkifp passwords.
Server-linking certificate consistency checker
src/modules/server.c, src/conf.c
check_server_linking_cert_consistency() runs at HOOKTYPE_POSTCONF, scans all serversonly listeners and outgoing links, compares SPKIFP sets for mismatches, and logs SERVER_LINKING_CERT_MISMATCH or SERVER_LINKING_CA_CERTIFICATE with a generated configuration fix. set::server-linking now accepts tls-options, mixed-certificates, and allow-ca-certificate.
TKL unique IDs and S2S propagation
src/modules/tkl.c, include/struct.h, src/misc.c
tkl.c generates stable IDs (id_prefix per type), serializes them via s2s-tkl/id and s2s-tkl/spamfilter_id message tags in tkl_sync_send_entry(), and merges them with tie-break rules in cmd_tkl_add. Stats output, oper notices, expiry logging, and /SPAMFILTER del all use the new IDs. spamfilter_fallback_id() handles legacy entries.
TKL hit-counter persistence across rehash
src/modules/tkl.c, src/modules/tkldb.c, src/conf.c
config_tkl_hits_snapshot() stashes hit counts before config/central TKLs are deleted; _config_tkl_hits_restore() copies them back onto rebuilt entries. tkldb.c bumps schema to 6260, persisting id, spamfilter_id, hits, lasthit, hits_except, and lasthit_except. _tkl_hit() is the new central hit-recording function used by ban, shun, zline, and Q-line paths.
Per-session flood-block counters and crule functions
src/user.c, src/modules/chanmodes/floodprot.c, src/modules/jointhrottle.c, src/modules/crule.c, src/parse.c, src/misc.c
FloodCounter.blocked is incremented via flood_blocked_increment() whenever server-level or channel-level flood protection denies an action. Two new crule functions server_flood_count(type) and total_channel_flood_count(type) expose these counts. bump_tag_serial() wraps at INT_MAX. add_fake_lag() gains IsNoFakeLag guards.
Deferred rule-only spamfilters and PCRE2 limits
src/match.c, src/ircd.c, src/modules/tkl.c, src/parse.c, include/config.h
init_match() creates a shared pcre2_match_context with configurable match/depth limits. unreal_match() now accepts a const char **error parameter and populates it on PCRE2 failures. _run_deferred_rule_only_spamfilters() evaluates ::rule-only spamfilters after tag-serial changes. parse.c calls it after each queued packet.
banned_client tklid parameter and callers
src/modules/quit.c, src/modules/chgname.c, src/modules/setname.c, src/modules/nick.c, src/aliases.c
_banned_client() gains a tklid parameter that appends [ID: ...] to ban rejection messages. The efunction signature and all callers are updated. take_action_ex() carries the spamfilter ID into derived *LINE/SHUN actions.
IRCv3 chathistory-end pagination
src/api-history-backend.c, src/modules/history_backend_mem.c, src/modules/chathistory.c, src/modules/chanmodes/history.c, src/modules/history.c
The memory backend sets r->reached_end when results are not truncated. history_send_result() attaches a draft/chathistory-end tag to the batch open when end_of_pagination is true. chathistory.c pre-counts targets for CHATHISTORY TARGETS pagination and registers the draft/chathistory-end message-tag handler (server-only).
CAP LS 302 minimum_cap_version gating
src/api-clicap.c, src/modules/cap.c, src/modules/json-log-tag.c, src/modules/link-security.c, src/modules/plaintext-policy.c, src/modules/no-implicit-names.c, src/modules/reply-tag.c, src/modules/join.c, doc/conf/modules.default.conf
ClientCapabilityAdd stores minimum_cap_version; clicap_generate() skips capabilities below the client's negotiated CAP version. Server-specific capabilities set minimum_cap_version = 302. Both draft and ratified no-implicit-names capabilities are now registered. The +reply tag handler is activated. join.c checks both capability names.
JSON flood expansion, TKL JSON fields, and optional log fields
src/json.c, src/log.c, src/modules/central-blocklist.c, src/modules/spamreport.c, src/support.c
json_expand_client() at detail≥5 appends flood counters. json_expand_tkl() emits id, spamfilter_id, hits, last_hit_at, and last_hit_except_at. log.c adds optional log-field constructors with space-eating text rendering. buildlogstring() accepts optional_keys. buildvarstring_nvp removes trailing spaces for empty vars unless BUILDVARSTRING_KEEP_SPACE_FOR_EMPTY_VAR is set.
JSON-RPC: remote user.get, rrpc v1.0.5, stats fix
src/modules/rpc/rpc.c, src/modules/rpc/user.c, src/modules/rpc/stats.c, src/modules/rpc/spamfilter.c
RPC module bumps to v1.0.5 with recursive RRPC path validation requiring min version 1.0.5. user.get gains object_remote_fetch (default true at detail≥5) to forward requests to the user's home server. Temporary remote clients are marked SetRPC. stats.c reads tkl->hits directly. rpc.add_timer rejects remote requests.
Message tag bounds-checking and multiline fallback fix
src/modules/message-tags.c, src/modules/multiline.c, src/url_unreal.c, src/list.c
message_tag_escape() gains an outsize parameter to prevent buffer overruns. multiline.c removes the pre-built fallback_lines cache and iterates batch->lines directly, fixing blank-line handling. url_unreal.c only saves the cut-off header pointer before the end-of-header marker. _append_name_list() and duplicate_name_list() are added/rewritten.
Config validation fixes and misc cleanup
src/conf.c
_test_allow_channel() and _test_deny channel reorder checks with blank/empty guards. display_path() is added. chmode_str() gains size guards. config_item_allowed_for_config_file() gets an explicit NULL guard. Default K/G-line reject messages include $banid.
Documentation, release notes, and example configs
doc/RELEASE-NOTES.md, SECURITY.md, doc/Config.header, doc/conf/examples/example.conf, doc/conf/help/help.fr.conf, src/unrealircdctl.c
Release notes fully populated for 6.2.6-rc1 covering all enhancements, changes, fixes, and developer/protocol notes. SECURITY.md clarifies PR-reporting prohibition. example.conf adds recommended server-linking TLS block. French help updates snomask descriptions and adds Eline/Tline sections.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 A rabbit hops through certs and bans,
With IDs unique across the lands!
mkcert blooms where make pem fell,
Flood counters count each tale to tell.
The spamfilters wait on deferred rule,
While PCRE2 keeps regex cool.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly summarizes the main change: merging upstream unrealircd 6.2.6-rc1 into the ObbyIRCd fork, which is accurate and clearly conveys the primary purpose of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/merge-upstream-6.2.x

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🧹 Nitpick comments (2)
src/conf.c (1)

7725-7726: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use block comments in C sources.

These new // comments violate the C comment-style rule; switch them to /* ... */.

As per coding guidelines, "**/*.{c,h}: Use only /* */ comments in C code, never // comments".

Proposed fix
-		// certificate_files: done at end of function
-		// key_files: done at end of function
+		/* certificate_files: done at end of function */
+		/* key_files: done at end of function */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/conf.c` around lines 7725 - 7726, Convert the two C++ style comments
(using //) to C style block comments (using /* */) in src/conf.c. Specifically,
replace the comments about "certificate_files: done at end of function" and
"key_files: done at end of function" by wrapping each comment text with /* and
*/ delimiters instead of using the // prefix. This ensures compliance with the
coding guideline that C source files must use only /* */ style comments, never
// comments.

Source: Coding guidelines

src/modules/tkl.c (1)

197-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the new C declarations/comments in project style.

The new struct typedefs use K&R braces, and Line 6055 adds a // comment. Please switch these to Allman braces and /* */ comments.

Proposed style cleanup
-struct ConfigTKLHits {
+struct ConfigTKLHits
+{
 	ConfigTKLHits *prev, *next;
 	char key[256];
 	long long hits;
 	time_t lasthit;
 	long long hits_except;    /* spamfilter only */
 	time_t lasthit_except;    /* spamfilter only */
 };

-typedef struct {
+typedef struct
+{
 	const char *name;
 	const char *(*serialize)(TKL *tkl);              /**< value to send, or NULL to omit the tag */
 	void (*unserialize)(TKL *tkl, const char *value);
 } TKLS2SField;
-				// Yeah we are re-running the text analysis.
+				/* Yeah we are re-running the text analysis. */

As per coding guidelines, **/*.{c,h}: “Use only /* */ comments in C code, never // comments” and “Use Allman-style braces for C code (opening brace on new line), never K&R style.”

Also applies to: 6055-6055

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/tkl.c` around lines 197 - 215, Update the code style for the new
declarations to match project guidelines. For the ConfigTKLHits struct and the
TKLS2SField typedef, change from K&R-style braces (opening brace on the same
line) to Allman-style braces (opening brace on a new line). Additionally,
convert any C++ style comments (using //) to C-style comments (using /* */) as
required by the project coding guidelines. This applies to the struct
ConfigTKLHits definition, the typedef struct definition, and the comment on line
6055.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Config`:
- Around line 53-58: Quote all the path variables in the mkdir and chmod
commands (around lines 53-58) by wrapping $BASEPATH, $TMPDIR, and $CONFDIR in
double quotes to prevent word-splitting and glob expansion. Additionally, add
fail-checks after each mkdir command by appending || exit 1 to ensure the script
exits if any directory creation fails, rather than continuing with invalid
paths.
- Line 194: The ./unrealircd mkcert command does not have its exit status
checked before printing a success message, meaning a failed certificate
generation will still appear successful. After executing the ./unrealircd mkcert
command, check its exit status and only print the success message if the command
succeeds. If the command fails, the script should handle the error appropriately
(typically by exiting with a non-zero status or printing an error message).

In `@configure.ac`:
- Around line 561-565: The mkdir and chmod commands in the private library
directory creation block do not cause the configure script to exit if they fail,
which can lead to confusing errors later during bundled library installation.
Add error handling to the line containing mkdir -p "$PRIVATELIBDIR" && chmod
0700 "$PRIVATELIBDIR" so that the configure script immediately exits with a
fatal error if either the directory creation or permission change operation
fails. This ensures failures are caught early and reported clearly rather than
silently allowing the script to continue with an improperly configured
directory.

In `@doc/conf/help/help.fr.conf`:
- Around line 920-923: Fix the spelling errors in the ELINE bantype descriptions
block where handshake is misspelled as "handhsake", Exception is misspelled as
"Exeptmion", and restriction is misspelled as "restricion". Locate these three
typos in the lines containing the d, m, and r bantype entries respectively and
correct them to their proper spellings to ensure clarity in this
security-sensitive help documentation for French-speaking operators.

In `@doc/conf/modules.default.conf`:
- Line 261: Remove the duplicate loadmodule directive for "multiline" in the
configuration file. The multiline module is already being loaded earlier in the
configuration, so the second loadmodule "multiline" statement causes a duplicate
module configuration error during fresh installs. Delete the entire line
containing the duplicate loadmodule "multiline" directive with the
draft/multiline comment.

In `@doc/Config.header`:
- Line 10: The Config banner in doc/Config.header displays version 6.2.6-rc1
while the actual project version in configure.ac is declared as 6.2.6, causing a
mismatch that can confuse operators. Update the version string in the
Config.header banner where it says "for UnrealIRCd 6.2.6-rc1" to match the
official release version 6.2.6 declared in configure.ac, ensuring consistency
across the configuration files.

In `@doc/RELEASE-NOTES.md`:
- Around line 4-5: The release status text in doc/RELEASE-NOTES.md (lines 4-5
starting with "This is the Release Candidate for future version 6.2.6")
conflicts with the maintained 6.2.6 version referenced elsewhere in the PR.
Update this release status text to accurately reflect the current release status
of version 6.2.6 according to the versioning policy, removing the "RC for
future" language and replacing it with the appropriate status for the current
6.2.6 release.
- Line 12: The ATX style headings (### Enhancements:, and the same pattern at
lines 65, 107, and 122) need to be converted to setext style headings with
underlines to satisfy markdownlint requirements. Convert each of these four
headings by removing the ### prefix and adding an underline of dashes directly
below the heading text, maintaining consistent setext formatting throughout the
file to ensure they are properly recognized as one-level increments from the
top-level section.

In `@obbyircd.in`:
- Around line 321-338: The issue is that if the req command (certificate
generation) fails after the ecparam command (key generation) succeeds, the live
key will be overwritten while the certificate remains old, creating a mismatched
TLS pair. To fix this, modify the code to generate both the key and certificate
into temporary files using temp file names (e.g., appending .tmp to the paths)
instead of directly outputting to the $KEY and $CERT variables. After both the
ecparam and req commands complete successfully (which they already do with ||
exit 1), then move the temporary files to their final locations using mv
commands. This ensures atomicity where either both files are updated together or
neither is updated if any command fails.

In `@src/api-efunctions.c`:
- Line 444: The efunc_init_function call for
EFUNC_RUN_DEFERRED_RULE_ONLY_SPAMFILTERS is registered with NULL as the default
handler (third parameter), which can cause NULL pointer dereference when parse.c
calls run_deferred_rule_only_spamfilters from the core packet loop if the
provider is not loaded. Replace the NULL parameter with a reference to a no-op
default handler function that safely handles the case when no provider is
available, ensuring the function has a safe default behavior instead of being
NULL.

In `@src/list.c`:
- Around line 566-570: The _append_name_list function uses strcpy to copy the
name parameter, which violates the repository's C safety guidelines that require
bounded string copies. Replace the strcpy(e->name, name) call in
_append_name_list with strlcpy(e->name, name, strlen(name)+1) to use bounded
string copying. Additionally, locate and fix the similar strcpy call at lines
591-593 with the same strlcpy replacement. Ensure all unsafe string operations
use the repository's standard bounded copy functions (strlcpy, strlcat, or
ircsnprintf) as required by the coding guidelines.

In `@src/modules/tkl.c`:
- Line 6119: The assignment statement resetting
stop_processing_general_spamfilters and stop_processing_central_spamfilters
unconditionally erases any stop state that was set during normal spamfilter
processing when rule-only filters are subsequently executed. Modify the reset
logic to only reset these flags on the initial call before processing normal
spamfilters, and preserve the existing stop-processing state if it has already
been set by a prior stop action or stop-on-first-match condition. Apply the same
preservation logic at lines 6408-6411 where this pattern also occurs.
- Around line 1566-1570: The `client` parameter in the `_tkl_hit()` function is
unused and triggers compiler warnings. Add an explicit `(void) client;`
statement at the beginning of the `_tkl_hit()` function body to intentionally
mark the parameter as unused. Similarly, locate the `config_tkl_hits_free()`
function and add `(void) m;` at its beginning to mark the unused `m` parameter
in the same way. These explicit void casts inform the compiler that the unused
parameters are intentional and will suppress the warnings.
- Line 2753: The condition in the spamfilter deletion check is using strcmp()
for ID comparison, which is case-sensitive, but find_tkl_by_id() performs
case-insensitive matching. To fix this inconsistency and allow case-insensitive
deletion via the /SPAMFILTER del command, replace both strcmp() calls in the
condition (the one comparing tk->id with id and the one comparing
spamfilter_fallback_id(tk) with id) with strcasecmp() to ensure case-insensitive
string comparison.
- Around line 3304-3315: The issue is that valid local config spamfilter IDs are
being silently replaced with a fallback hash because tkl_config_run_spamfilter()
clears the id parameter for non-central spamfilters before calling this
function. Either ensure that the id parameter is preserved and passed through
correctly from tkl_config_run_spamfilter() for local config spamfilters (so the
condition checking !BadPtr(id) in the current code block actually receives the
intended id), or alternatively add validation logic outside this code block to
explicitly reject id values that are provided for non-central config spamfilters
to prevent silent discarding of user-provided IDs.
- Around line 1423-1438: The functions tkl_s2s_set_id and
tkl_s2s_set_spamfilter_id currently only validate the length of the input values
but do not validate the actual characters, allowing spaces and control
characters that can corrupt STATS/log fields. Locate the existing ID character
validation function that is used for config IDs (this validation likely checks
for valid ID characters), and apply that same validation check to both
tkl_s2s_set_id and tkl_s2s_set_spamfilter_id before calling strlcpy to copy the
value into tkl->id and tkl->spamfilter_id respectively. Ensure the value is only
copied if both the length check and the character validation pass.

In `@src/modules/tkldb.c`:
- Around line 569-583: For database entries with version less than 6260, the
tkl->id field is never populated and remains empty, which causes empty IDs to be
persisted when entries are later rewritten at lines 810-811. Add an else block
after the version check (after the closing brace of the if statement that checks
version >= 6260) to generate a fresh ID for tkl entries when the version is less
than 6260. This ensures that existing persistent server bans and name bans
receive the new operator-visible TKL identity before being saved as version 6260
during the first database rewrite.

In `@src/tls.c`:
- Around line 858-881: The server-linking TLS contexts are being activated
immediately after each context is built separately. If the second init_ctx call
fails to build the client context, the server context is already activated,
creating an inconsistent state where incoming server links use the new context
but outgoing links keep the old one. Instead, build both the server context and
client context using init_ctx into temporary variables first (without activating
them), verify that both init_ctx calls succeed, and only then free the old
contexts and assign the new temporaries to ctx_link_server and ctx_link_client.
This ensures both contexts are successfully built before any activation occurs.

In `@src/user.c`:
- Around line 1163-1168: The issue in the tags_serial management code is that
the increment operation (client->local->tags_serial++) occurs before the wrap
check, causing signed integer overflow if tags_serial already equals INT_MAX. To
fix this, reverse the order of operations by checking whether tags_serial is >=
INT_MAX before incrementing it, ensuring the reset to zero happens first to
prevent the overflow from occurring.

---

Nitpick comments:
In `@src/conf.c`:
- Around line 7725-7726: Convert the two C++ style comments (using //) to C
style block comments (using /* */) in src/conf.c. Specifically, replace the
comments about "certificate_files: done at end of function" and "key_files: done
at end of function" by wrapping each comment text with /* and */ delimiters
instead of using the // prefix. This ensures compliance with the coding
guideline that C source files must use only /* */ style comments, never //
comments.

In `@src/modules/tkl.c`:
- Around line 197-215: Update the code style for the new declarations to match
project guidelines. For the ConfigTKLHits struct and the TKLS2SField typedef,
change from K&R-style braces (opening brace on the same line) to Allman-style
braces (opening brace on a new line). Additionally, convert any C++ style
comments (using //) to C-style comments (using /* */) as required by the project
coding guidelines. This applies to the struct ConfigTKLHits definition, the
typedef struct definition, and the comment on line 6055.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 38529733-bc8d-4e38-aadb-f348fa152309

📥 Commits

Reviewing files that changed from the base of the PR and between e9a2806 and 9b3919b.

📒 Files selected for processing (70)
  • Config
  • Makefile.in
  • SECURITY.md
  • configure
  • configure.ac
  • doc/Config.header
  • doc/RELEASE-NOTES.md
  • doc/conf/examples/example.conf
  • doc/conf/help/help.fr.conf
  • doc/conf/modules.default.conf
  • doc/conf/tls/tls.cnf
  • extras/build-tests/nix/build
  • include/config.h
  • include/dynconf.h
  • include/h.h
  • include/modules.h
  • include/numeric.h
  • include/struct.h
  • obbyircd.in
  • src/aliases.c
  • src/api-clicap.c
  • src/api-efunctions.c
  • src/api-history-backend.c
  • src/conf.c
  • src/ircd.c
  • src/json.c
  • src/list.c
  • src/log.c
  • src/match.c
  • src/misc.c
  • src/modules/cap.c
  • src/modules/central-blocklist.c
  • src/modules/chanmodes/floodprot.c
  • src/modules/chanmodes/history.c
  • src/modules/chathistory.c
  • src/modules/chgname.c
  • src/modules/crule.c
  • src/modules/history.c
  • src/modules/history_backend_mem.c
  • src/modules/join.c
  • src/modules/jointhrottle.c
  • src/modules/json-log-tag.c
  • src/modules/link-security.c
  • src/modules/message-tags.c
  • src/modules/multiline.c
  • src/modules/nick.c
  • src/modules/no-implicit-names.c
  • src/modules/plaintext-policy.c
  • src/modules/quit.c
  • src/modules/reply-tag.c
  • src/modules/rpc/rpc.c
  • src/modules/rpc/spamfilter.c
  • src/modules/rpc/stats.c
  • src/modules/rpc/user.c
  • src/modules/server.c
  • src/modules/setname.c
  • src/modules/spamreport.c
  • src/modules/starttls.c
  • src/modules/stats.c
  • src/modules/tkl.c
  • src/modules/tkldb.c
  • src/parse.c
  • src/socket.c
  • src/support.c
  • src/tls.c
  • src/unrealircdctl.c
  • src/url_unreal.c
  • src/user.c
  • src/windows/makecert.bat
  • src/windows/unrealinst.iss
💤 Files with no reviewable changes (1)
  • doc/conf/tls/tls.cnf

Comment thread Config
Comment on lines +53 to +58
mkdir -p $BASEPATH
mkdir -p $TMPDIR
mkdir -p $PRIVATELIBDIR
mkdir -p $CONFDIR
chmod 0700 $BASEPATH
chmod 0700 $TMPDIR
chmod 0700 $CONFDIR

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Quote and fail-check directory path operations.

Line 53-Line 58 use unquoted path variables, so paths with spaces/globs can create or chmod unintended targets.

Suggested patch
-mkdir -p $BASEPATH
-mkdir -p $TMPDIR
-mkdir -p $CONFDIR
-chmod 0700 $BASEPATH
-chmod 0700 $TMPDIR
-chmod 0700 $CONFDIR
+mkdir -p "$BASEPATH" || exit 1
+mkdir -p "$TMPDIR" || exit 1
+mkdir -p "$CONFDIR" || exit 1
+chmod 0700 "$BASEPATH" || exit 1
+chmod 0700 "$TMPDIR" || exit 1
+chmod 0700 "$CONFDIR" || exit 1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mkdir -p $BASEPATH
mkdir -p $TMPDIR
mkdir -p $PRIVATELIBDIR
mkdir -p $CONFDIR
chmod 0700 $BASEPATH
chmod 0700 $TMPDIR
chmod 0700 $CONFDIR
mkdir -p "$BASEPATH" || exit 1
mkdir -p "$TMPDIR" || exit 1
mkdir -p "$CONFDIR" || exit 1
chmod 0700 "$BASEPATH" || exit 1
chmod 0700 "$TMPDIR" || exit 1
chmod 0700 "$CONFDIR" || exit 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Config` around lines 53 - 58, Quote all the path variables in the mkdir and
chmod commands (around lines 53-58) by wrapping $BASEPATH, $TMPDIR, and $CONFDIR
in double quotes to prevent word-splitting and glob expansion. Additionally, add
fail-checks after each mkdir command by appending || exit 1 to ensure the script
exits if any directory creation fails, rather than continuing with invalid
paths.

Comment thread Config
echo "Press ENTER to continue"
read cc
$MAKE pem
./unrealircd mkcert

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop on certificate generation failure before printing success.

Line 194 runs ./unrealircd mkcert without checking status, then always prints success. A failed cert bootstrap currently looks successful.

Suggested patch
-			./unrealircd mkcert
-			echo "Certificate created successfully."
+			if ./unrealircd mkcert; then
+				echo "Certificate created successfully."
+			else
+				echo "Certificate generation failed."
+				exit 1
+			fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Config` at line 194, The ./unrealircd mkcert command does not have its exit
status checked before printing a success message, meaning a failed certificate
generation will still appear successful. After executing the ./unrealircd mkcert
command, check its exit status and only print the success message if the command
succeeds. If the command fails, the script should handle the error appropriately
(typically by exiting with a non-zero status or printing an error message).

Comment thread configure.ac
Comment on lines +561 to +565
# Create the private library directory now with restrictive permissions.
# This must happen here rather than in the Makefile because the bundled
# libraries are installed into it during configure. It must also work when
# configure is run directly without ./Config having created it first.
mkdir -p "$PRIVATELIBDIR" && chmod 0700 "$PRIVATELIBDIR"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make private-libdir creation failure fatal during configure.

Line 565 currently continues even if mkdir/chmod fails, which can cause later bundled-library install steps to fail in less obvious ways.

Suggested patch
-		mkdir -p "$PRIVATELIBDIR" && chmod 0700 "$PRIVATELIBDIR"
+		mkdir -p "$PRIVATELIBDIR" && chmod 0700 "$PRIVATELIBDIR" || AC_MSG_ERROR([Unable to initialize private library directory: $PRIVATELIBDIR])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Create the private library directory now with restrictive permissions.
# This must happen here rather than in the Makefile because the bundled
# libraries are installed into it during configure. It must also work when
# configure is run directly without ./Config having created it first.
mkdir -p "$PRIVATELIBDIR" && chmod 0700 "$PRIVATELIBDIR"
# Create the private library directory now with restrictive permissions.
# This must happen here rather than in the Makefile because the bundled
# libraries are installed into it during configure. It must also work when
# configure is run directly without ./Config having created it first.
mkdir -p "$PRIVATELIBDIR" && chmod 0700 "$PRIVATELIBDIR" || AC_MSG_ERROR([Unable to initialize private library directory: $PRIVATELIBDIR])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configure.ac` around lines 561 - 565, The mkdir and chmod commands in the
private library directory creation block do not cause the configure script to
exit if they fail, which can lead to confusing errors later during bundled
library installation. Add error handling to the line containing mkdir -p
"$PRIVATELIBDIR" && chmod 0700 "$PRIVATELIBDIR" so that the configure script
immediately exits with a fatal error if either the directory creation or
permission change operation fails. This ensures failures are caught early and
reported clearly rather than silently allowing the script to continue with an
improperly configured directory.

Comment on lines +920 to +923
" d | handshake flood | Exception de test de flood sur les données handhsake ";
" | | (pas de ZLINE si flood avant enregistrement) ";
" m | maxperip | Exeptmion de restricion allow::maxperip restriction ";
" r | antirandom | Exception sur le module antirandom ";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix typographical errors in the new ELINE bantype descriptions.

There are multiple spelling issues in this block (eg. handhsake, Exeptmion, restricion) that reduce operator clarity in a security-sensitive help section.

Suggested wording cleanup
-        "     d   | handshake flood | Exception de test de flood sur les données handhsake  ";
+        "     d   | handshake flood | Exception de test de flood sur les données handshake   ";
         "         |                 |  (pas de ZLINE si flood avant enregistrement)         ";
-        "     m   |     maxperip    | Exeptmion de restricion allow::maxperip restriction   ";
+        "     m   |     maxperip    | Exception à la restriction allow::maxperip            ";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
" d | handshake flood | Exception de test de flood sur les données handhsake ";
" | | (pas de ZLINE si flood avant enregistrement) ";
" m | maxperip | Exeptmion de restricion allow::maxperip restriction ";
" r | antirandom | Exception sur le module antirandom ";
" d | handshake flood | Exception de test de flood sur les données handshake ";
" | | (pas de ZLINE si flood avant enregistrement) ";
" m | maxperip | Exception à la restriction allow::maxperip ";
" r | antirandom | Exception sur le module antirandom ";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doc/conf/help/help.fr.conf` around lines 920 - 923, Fix the spelling errors
in the ELINE bantype descriptions block where handshake is misspelled as
"handhsake", Exception is misspelled as "Exeptmion", and restriction is
misspelled as "restricion". Locate these three typos in the lines containing the
d, m, and r bantype entries respectively and correct them to their proper
spellings to ensure clarity in this security-sensitive help documentation for
French-speaking operators.

loadmodule "standard-replies"; /* Standard Replies */
loadmodule "no-implicit-names"; /* Opt out of receiving implicit NAMES when joining a channel */
loadmodule "extended-isupport"; /* draft/extended-isupport */
loadmodule "multiline"; /* draft/multiline */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the duplicate multiline load.

multiline is already loaded on Line 241, so this second default loadmodule can trigger duplicate-module configuration failures for fresh installs.

Proposed fix
-loadmodule "multiline"; /* draft/multiline */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doc/conf/modules.default.conf` at line 261, Remove the duplicate loadmodule
directive for "multiline" in the configuration file. The multiline module is
already being loaded earlier in the configuration, so the second loadmodule
"multiline" statement causes a duplicate module configuration error during fresh
installs. Delete the entire line containing the duplicate loadmodule "multiline"
directive with the draft/multiline comment.

Comment thread src/modules/tkl.c
Comment on lines +3304 to +3315
/* Config (and central) spamfilters are local per server, so they never receive
* a random network id. Give them a deterministic id instead, identical on every
* server: the config/central id label if set, otherwise a content hash. This is
* what lets a central spamfilter be referenced by the same id on every server.
*/
if (flags & (TKL_FLAG_CONFIG | TKL_FLAG_CENTRAL_SPAMFILTER))
{
if (!BadPtr(id))
strlcpy(tkl->id, id, sizeof(tkl->id));
else
strlcpy(tkl->id, spamfilter_fallback_id(tkl), sizeof(tkl->id));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don’t silently discard local config spamfilter IDs.

This branch says config spamfilters use the provided id, but tkl_config_run_spamfilter() still clears id for non-central spamfilters before calling here. That means a valid local spamfilter::id is accepted, then replaced with the fallback hash; either pass it through or reject id outside central configs.

Proposed direction
-	} else {
-		id = NULL;
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/tkl.c` around lines 3304 - 3315, The issue is that valid local
config spamfilter IDs are being silently replaced with a fallback hash because
tkl_config_run_spamfilter() clears the id parameter for non-central spamfilters
before calling this function. Either ensure that the id parameter is preserved
and passed through correctly from tkl_config_run_spamfilter() for local config
spamfilters (so the condition checking !BadPtr(id) in the current code block
actually receives the intended id), or alternatively add validation logic
outside this code block to explicitly reject id values that are provided for
non-central config spamfilters to prevent silent discarding of user-provided
IDs.

Comment thread src/modules/tkl.c
TKL *tkl;
crule_context context;

*stop_processing_general_spamfilters = *stop_processing_central_spamfilters = 0; /* reset */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve existing stop-processing state for rule-only filters.

Line 6119 resets the stop flags even when _match_spamfilter() calls this after normal spamfilters. A prior stop action or stop-on-first-match can therefore be ignored for the rule-only pass.

Proposed fix
-	*stop_processing_general_spamfilters = *stop_processing_central_spamfilters = 0; /* reset */
 	for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)

Also applies to: 6408-6411

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/tkl.c` at line 6119, The assignment statement resetting
stop_processing_general_spamfilters and stop_processing_central_spamfilters
unconditionally erases any stop state that was set during normal spamfilter
processing when rule-only filters are subsequently executed. Modify the reset
logic to only reset these flags on the initial call before processing normal
spamfilters, and preserve the existing stop-processing state if it has already
been set by a prior stop action or stop-on-first-match condition. Apply the same
preservation logic at lines 6408-6411 where this pattern also occurs.

Comment thread src/modules/tkldb.c
Comment on lines +569 to +583
/* id and spamfilter_id were added in TKLDB_VERSION 6260 */
if (version >= 6260)
{
R_SAFE(unrealdb_read_str(db, &str));
strlcpy(tkl->id, str, sizeof(tkl->id));
safe_free(str);
R_SAFE(unrealdb_read_str(db, &str));
strlcpy(tkl->spamfilter_id, str, sizeof(tkl->spamfilter_id));
safe_free(str);
/* Hit-stat fields for all TKL types (hits, lasthit). */
R_SAFE(unrealdb_read_int64(db, &v));
tkl->hits = v;
R_SAFE(unrealdb_read_int64(db, &v));
tkl->lasthit = v;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Backfill IDs when upgrading pre-6260 tkldb entries.

For version < 6260, tkl->id remains empty, and Lines 810-811 copy that empty value into the restored entry. Those entries will then be saved as 6260 without an ID, so existing persistent server bans/name bans never receive the new operator-visible TKL identity. Generate a fresh ID for added when the DB lacks one before the first 6260 rewrite.

Also applies to: 808-813

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/tkldb.c` around lines 569 - 583, For database entries with
version less than 6260, the tkl->id field is never populated and remains empty,
which causes empty IDs to be persisted when entries are later rewritten at lines
810-811. Add an else block after the version check (after the closing brace of
the if statement that checks version >= 6260) to generate a fresh ID for tkl
entries when the version is less than 6260. This ensures that existing
persistent server bans and name bans receive the new operator-visible TKL
identity before being saved as version 6260 during the first database rewrite.

Comment thread src/tls.c
Comment on lines +858 to +881
/* set::server-linking::tls-options (free+NULL if no longer configured) */
if (iConf.server_linking_tls_options)
{
tmp = init_ctx(iConf.server_linking_tls_options, 1);
if (!tmp)
{
unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
"TLS Reload failed at set::server-linking::tls-options. See previous errors.");
return 0;
}
if (ctx_link_server)
SSL_CTX_free(ctx_link_server);
ctx_link_server = tmp; /* activate */

tmp = init_ctx(iConf.server_linking_tls_options, 0);
if (!tmp)
{
unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
"TLS Reload failed at set::server-linking::tls-options (client). See previous errors.");
return 0;
}
if (ctx_link_client)
SSL_CTX_free(ctx_link_client);
ctx_link_client = tmp; /* activate */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Swap server-linking TLS contexts only after both rebuilds succeed.

Line 870 activates ctx_link_server before the client context is built. If Line 872 then fails, rehash reports failure while incoming server-linking TLS already uses the new context and outgoing links keep the old/null one. Build both contexts into temporaries first, then swap after both succeed.

Proposed reload shape
 	if (iConf.server_linking_tls_options)
 	{
-		tmp = init_ctx(iConf.server_linking_tls_options, 1);
-		if (!tmp)
+		SSL_CTX *tmp_link_server = init_ctx(iConf.server_linking_tls_options, 1);
+		SSL_CTX *tmp_link_client = NULL;
+
+		if (!tmp_link_server)
 		{
 			unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
 				   "TLS Reload failed at set::server-linking::tls-options. See previous errors.");
 			return 0;
 		}
-		if (ctx_link_server)
-			SSL_CTX_free(ctx_link_server);
-		ctx_link_server = tmp; /* activate */
 
-		tmp = init_ctx(iConf.server_linking_tls_options, 0);
-		if (!tmp)
+		tmp_link_client = init_ctx(iConf.server_linking_tls_options, 0);
+		if (!tmp_link_client)
 		{
+			SSL_CTX_free(tmp_link_server);
 			unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
 				   "TLS Reload failed at set::server-linking::tls-options (client). See previous errors.");
 			return 0;
 		}
+
+		if (ctx_link_server)
+			SSL_CTX_free(ctx_link_server);
+		if (ctx_link_client)
+			SSL_CTX_free(ctx_link_client);
+		ctx_link_server = tmp_link_server;
+		ctx_link_client = tmp_link_client;
-		if (ctx_link_client)
-			SSL_CTX_free(ctx_link_client);
-		ctx_link_client = tmp; /* activate */
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* set::server-linking::tls-options (free+NULL if no longer configured) */
if (iConf.server_linking_tls_options)
{
tmp = init_ctx(iConf.server_linking_tls_options, 1);
if (!tmp)
{
unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
"TLS Reload failed at set::server-linking::tls-options. See previous errors.");
return 0;
}
if (ctx_link_server)
SSL_CTX_free(ctx_link_server);
ctx_link_server = tmp; /* activate */
tmp = init_ctx(iConf.server_linking_tls_options, 0);
if (!tmp)
{
unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
"TLS Reload failed at set::server-linking::tls-options (client). See previous errors.");
return 0;
}
if (ctx_link_client)
SSL_CTX_free(ctx_link_client);
ctx_link_client = tmp; /* activate */
/* set::server-linking::tls-options (free+NULL if no longer configured) */
if (iConf.server_linking_tls_options)
{
SSL_CTX *tmp_link_server = init_ctx(iConf.server_linking_tls_options, 1);
SSL_CTX *tmp_link_client = NULL;
if (!tmp_link_server)
{
unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
"TLS Reload failed at set::server-linking::tls-options. See previous errors.");
return 0;
}
tmp_link_client = init_ctx(iConf.server_linking_tls_options, 0);
if (!tmp_link_client)
{
SSL_CTX_free(tmp_link_server);
unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
"TLS Reload failed at set::server-linking::tls-options (client). See previous errors.");
return 0;
}
if (ctx_link_server)
SSL_CTX_free(ctx_link_server);
if (ctx_link_client)
SSL_CTX_free(ctx_link_client);
ctx_link_server = tmp_link_server;
ctx_link_client = tmp_link_client;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tls.c` around lines 858 - 881, The server-linking TLS contexts are being
activated immediately after each context is built separately. If the second
init_ctx call fails to build the client context, the server context is already
activated, creating an inconsistent state where incoming server links use the
new context but outgoing links keep the old one. Instead, build both the server
context and client context using init_ctx into temporary variables first
(without activating them), verify that both init_ctx calls succeed, and only
then free the old contexts and assign the new temporaries to ctx_link_server and
ctx_link_client. This ensures both contexts are successfully built before any
activation occurs.

Comment thread src/user.c
Comment on lines +1163 to 1168
/* This is only ever compared with != (a change counter), so we just need a
* wrap point high enough that it never realistically aliases within a session.
*/
client->local->tags_serial++;
if (client->local->tags_serial > 10000)
if (client->local->tags_serial >= INT_MAX)
client->local->tags_serial = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check for wrap before incrementing tags_serial.

Line 1166 increments first, so if tags_serial is already INT_MAX, the signed addition overflows before Line 1167 can reset it.

Proposed fix
-	client->local->tags_serial++;
-	if (client->local->tags_serial >= INT_MAX)
+	if (client->local->tags_serial >= INT_MAX - 1)
 		client->local->tags_serial = 0;
+	else
+		client->local->tags_serial++;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* This is only ever compared with != (a change counter), so we just need a
* wrap point high enough that it never realistically aliases within a session.
*/
client->local->tags_serial++;
if (client->local->tags_serial > 10000)
if (client->local->tags_serial >= INT_MAX)
client->local->tags_serial = 0;
/* This is only ever compared with != (a change counter), so we just need a
* wrap point high enough that it never realistically aliases within a session.
*/
if (client->local->tags_serial >= INT_MAX - 1)
client->local->tags_serial = 0;
else
client->local->tags_serial++;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/user.c` around lines 1163 - 1168, The issue in the tags_serial management
code is that the increment operation (client->local->tags_serial++) occurs
before the wrap check, causing signed integer overflow if tags_serial already
equals INT_MAX. To fix this, reverse the order of operations by checking whether
tags_serial is >= INT_MAX before incrementing it, ensuring the reset to zero
happens first to prevent the overflow from occurring.

@matheusfillipe
matheusfillipe merged commit 44ed5e9 into unreal60_dev Jun 24, 2026
7 checks passed
@matheusfillipe
matheusfillipe deleted the chore/merge-upstream-6.2.x branch June 24, 2026 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants