1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-05 17:53:13 +02:00
Commit Graph

224 Commits

Author SHA1 Message Date
Bram Matthys b0dba4bede Add draft/multiline support with a default max-lines of 15 for known-users
and 7 for unknown-users (with max-bytes 5250 and 1500 respectively). This
allows pasting a short snippet of code, config file, text from a site, etc.

With multiline you have the guarantee that:
1) You will see the entire text with no delay between lines
2) You won't see another persons chat half-way through such a paste
3) For multiline supporting clients it is now clear that all the text
   belongs to each other, which can make selecting/copying it easier.
This basically means short snippets/pastes like that can be completely on
IRC again. No need for a pastebin for it. Though, you may still need such
a service if you are pasting more lines.

Regarding the implementation in UnrealIRCd:
* Clients without multiline get individual fallback lines (concat lines
  merged, blank lines skipped, as per spec). And we know that clients like
  weechat - which does support multiline - also shows all lines and not
  only a few plus snippet style "[.."]. That is another reason for only
  allowing 15 lines by default and not something much more. Otherwise all
  those clients would get a big wall of text, which just sucks.
* Spamfilter (also) runs on the full text of all lines together, so
  splitting a phrase across lines does not evade spamfilter.
* Fakelag: a client can send the BATCH start+PRIVMSG (or NOTICE)+BATCH end
  at full speed. We impose no fake lag there. Also, the multiline default
  max-lines and max-bytes are lower than the example class::recvq of 8000,
  so should be perfectly safe. If the entire BATCH is accepted then we
  will impose fake-lag afterwards, with a cap of 15 seconds maximum.
  If the BATCH is rejected, we impose half the fakelag plus 2sec.
* If the time between BATCH start and BATCH end is more than 15 seconds
  then the BATCH is rejected (set::multiline::batch-timeout).
* The BATCH is atomic (either you see it all, or you see none of it):
  * When the client sends it to server, it is buffered first.
  * Only after the batch close the server indicates if it is accepted
    or rejected. This has various reasons, two of them are: 1) The client
    is going to send everything in one go anyway and not wait for a
    response between each PRIVMSG, and 2) we can't do many checks in the
    buffering stage and skip those after, that would cause a TOCTOU
    problem (eg. a banned user still being able to speak).
  * If any line gets rejected due to spamfilter or other case
    (eg +c, +b ~text with block, etc etc), the entire batch is rejected
  * Locally we deliver all or nothing (as said)
  * S2S we buffer the batch as well, so if a server splits after having
    received 10 lines out of 15, then clients will not see anything.
* We send max-lines and max-bytes, this is the hard upper limit.
* A multiline can still be limited more tight if:
  * +f with 't' or 'm' restricts to fewer lines,
    eg +f [5t]:15, which means max 5 lines per 15 seconds,
    means the max accepted multiline is 5 for that channel.
  * +F works the same, except that default +F normal does not
    have a 't' at the moment and 'm' is very high (50) so
    practically not limited by default.
  * There will be a future +f flood subtype for some more control

TODO: we will send CAP NEW on unknown-users <-> known-users to
      indicate the new max-lines value if you transition security groups

TODO: chat history does not yet include multiline batches.
2026-03-30 13:16:48 +02:00
Bram Matthys b93cb14623 Fix crash due to fix from a few hours ago (5580b294f4) 2026-02-21 16:04:50 +01:00
Bram Matthys 5580b294f4 Fix memory leak if using spamfilter::except. 2026-02-21 13:20:17 +01:00
Bram Matthys be479aa890 The buffer in spamfilter_build_user_string() was too small causing cut off.
This affects the spamfilter 'u' target. It didn't overflow but was cut off,
potentially causing a NON-MATCH where it could have been a MATCH instead.
2026-02-21 13:18:30 +01:00
Bram Matthys 2ac09de148 Fix central spamfilter with "stop" action, due to using same &var twice. 2026-02-21 13:13:15 +01:00
Bram Matthys 4e3989f304 Add ban user { ....; soft yes; } as an easy way to add a soft-ban from
the config file, without having to resort to things like mask %~asn:XXX;
Now you can just use:
ban user {
	asn { 11111; 22222; 33333; 44444; }
	soft yes;
	reason "This ASN is not allowed. If you have an account you can still bypass";
}

Requested by nobody but sounds like a good idea :)
2026-01-03 19:59:52 +01:00
Bram Matthys 8c26cec5fc Fix 'const' in various functions: various arguments were const char *
in the EFunction but not in the actual function. That's bad since it
means the "const guarantee" got lost. And one or two similar cases with
incorrect parameter types and mismatching return types. This was
found with some analyzer, we had no bugreports with regards to this.
2025-09-14 15:01:39 +02:00
alice 2c7bcebaca Make spamfilter:input-conversion accept deconfuse and deconfused for confusables (#316) 2025-08-01 07:39:43 +00:00
Bram Matthys 93980ee004 Include TextAnalysis in antimixedutf8 hit as well. And use "text_analysis"
and not "textanalysis" for the JSON, to keep naming of multi-word stuff
consistent.

Example:
--snip--
  "text_analysis": {
    "antimixedutf8_points": 20,
    "unicode_blocks": 9,
    "num_bytes": 55,
    "num_unicode_characters": 20,
    "deconfused": "Valware is ualwaring",
    "deconfused": "This is a testtestte",
    "unicode_blockmap": {
      "Basic Latin": 2,
      "Latin Extended-B": 2,
      "IPA Extensions": 1,
      "Greek and Coptic": 1,
      "Latin Extended Additional": 2,
      "Greek Extended": 1,
      "Number Forms": 1,
      "Tifinagh": 1,
      "Mathematical Alphanumeric Symbols": 7
    }
  },
2025-07-14 18:41:04 +02:00
Bram Matthys d135e687c3 Add TextAnalysis on spamfilter hit in the JSON logs. 2025-07-14 18:11:59 +02:00
Bram Matthys cc75840189 Add unicode_count() crule, e.g. unicode_count('Emoticons')
This will return the number of characters that are in the unicode block
with that name.

spamfilter {
	rule "unicode_count('Emoticons')>2";
	target { private; channel; private-notice; channel-notice; }
	action block;
	reason "Too much emotion";
}

In this commit we also make it so we pass the ClientContext (including
clictx->textanalysis) in crule_context.
2025-03-23 18:14:32 +01:00
Bram Matthys 74e17b7a26 Make SPAMINFO show the UTF8 block names a text uses.
Example output:
*** SPAMINFO ***
This will show the original text and the deconfused text which can be used in a spamfilter block with input-conversion deconfused;
Original spam text: ẔŽŽẐ𝞕ȤℤΖℨℨ𝒁𝓩ẒŹƵᏃŻẒŽℨŹ𝒵𝛧Ż𝝛𝛧ℨℤ𝜡Ƶ𝞕𝘡ŹẐ𝑍ẔẐẐΖ𝜡Ẕ𝜡Ẕ𝞕ꓜ𝚭ᏃẐẔ𝙕
Deconfused spam text: ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
AntiMixedUTF8 points: 64
Number of Unicode characters in total: 50
Number of different Unicode blocks used: 8
Unicode Block breakdown (name: bytes [capped at 255]):
- Latin Extended-A: 8
- Latin Extended-B: 3
- Greek and Coptic: 2
- Cherokee: 2
- Latin Extended Additional: 12
- Letterlike Symbols: 6
- Lisu: 1
- Mathematical Alphanumeric Symbols: 16
2025-03-23 13:03:58 +01:00
Bram Matthys 9b89166280 Add deconfused to TextAnalysis. Add ClientContext * to match_spamfilter().
Make match_spamfilter use the clictx->textanalysis->deconfused rather than
calculating its own. The latter will probably disappear altogether.

Unrelated but also fixed: properly set e->unicode_blocks.
2025-03-23 12:13:38 +01:00
Bram Matthys 2c33103d28 Fix OOB read, write and NULL dereference code from yesterday. 2025-03-23 07:21:00 +01:00
Bram Matthys e1fac402d5 Add spamfilter { input-conversion confusables; ..... } for UTF8 conversion
of lookalike characters to simple latin characters.

Also add SPAMINFO command so you can see the result of the conversion.
2025-03-22 08:31:22 +01:00
Bram Matthys d15c82346e Pass ClientContext in CMD_FUNC() and friends. So extra arg. Breaking change.
It now passes 'clictx' which at the moment only has clictx->cmd which
points to the command handler. So only useful in very few cases where
you have like a generic command handler and thus have no idea for which
command you are being called. In the future, with this new ClientContext
struct, we can simply add new fields to the struct without breaking
things in the core and in (third party) modules.

If you use the magic functions in your modules CMD_FUNC(cmd_mycmd),
OVERRIDE_FUNC(myoverride), CALL_NEXT_COMMAND_OVERRIDE() and such then
you shouldn't have any compile errors as these will use the correct
prototypes and variable names automatically. In a few cases you can't
use these, in which case you will need to update your modules.
2025-03-21 15:40:42 +01:00
Bram Matthys 094efeee25 Add spamfilter::show-message-content-on-hit to override on a spamfilter basis.
This works the same as set::spamfilter::show-message-content-on-hit
https://www.unrealircd.org/docs/Set_block#set::spamfilter::show-message-content-on-hit
but per spamfilter { } in the conf.

Indirectly suggested in https://bugs.unrealircd.org/view.php?id=6437
2025-02-15 12:14:44 +01:00
Bram Matthys ae166bd99e Add spamfilter::input-conversion none; to not use StripControlChars()
for matching. Docs and release notes text will follow later.
2025-02-15 11:05:37 +01:00
Bram Matthys 985a591df2 Previous commit broke "GLINE *@1.2.3.4 0 test" and had a memory leak.
The former was fixed by merging the 'if'. The latter by getting rid
of dynamic memory allocation, long live the stack!
2024-10-16 10:21:16 +02:00
Valerie Liu 8e47aff2cf Make *LINE behave smarter if missing reason or time value (#304)
Now this works like:
if the time param exists, even without a reason, it will be checked if it's a time param. if it's not a time param, it'll be considered to be the reason (or the first part of it anyway)

Reported by PeGaSuS in https://bugs.unrealircd.org/view.php?id=6105
2024-10-16 08:01:12 +00:00
Bram Matthys afbb0c283b Accept multiple masks in ban ip { } and ban nick { } such as:
ban ip {
	mask { 1.1.1.1; 2.2.2.2; 3.3.3.3; }
	reason "Go away";
}

Or the alternate form:

ban ip {
	mask 1.1.1.1;
	mask 2.2.2.2;
	mask 3.3.3.3;
	reason "Go away";
}

Suggested by magic000 in https://bugs.unrealircd.org/view.php?id=4599

Note that this is not a Mask item, these are special, hence the
special code.
2024-09-23 12:29:35 +02:00
Valerie Liu ae8b039831 Fix $nick log string in debug message re spamfilter tag (#294) 2024-09-08 14:37:26 +00:00
Bram Matthys 99bc061a74 Fix require authentication { } not allowing SASL users in.
It was behaving like a ban user { } block.

Reported by Jellis in https://bugs.unrealircd.org/view.php?id=6464
2024-08-30 20:01:20 +02:00
Bram Matthys 4f3e524602 Add function set_client_ip() and call HOOKTYPE_IP_CHANGE there if needed.
This to replace the scattered IP setting. It is very important to always
use set_client_ip() from this point. Everywhere!

Also, in addition to client->ip, this adds client->rawip that contains
the IP in network byte order. In older UnrealIRCd versions we always had
the raw IP but not the IP as a string, so we moved to IP as a string,
but it can be useful to have both in terms of optimizations.
Of course, then the client->ip and client->rawip always need to 100% match,
hence the set_client_ip().

This also changes IsIPV6() to do A BUGFIX, it changes it from:
* if local user is the user connected over IPv6? Otherwise, does it have ':' in the IP?
To:
* check if the IPv6 flag is set (which is set if IP contains ':')
This may seem insignificant but it means that for spoofed IP addresses,
such as WEBIRC or transparant proxy, we use the correct transport.
Previously, if the proxy was IPv6 then even if the spoofed user was using
IPv4, the ident check would still be tried over IPv6. That sort of fun.
From now in, in such a situation client->local->socket_type will be
SOCKET_TYPE_IPV6 but since client->ip (and rawip) will contain IPv4
the IsIPV6() will actually return false, as it should be.

Also, in the HOOKTYPE_IP_CHANGE, enforce that if HOOK_DENY is returned,
the the user is killed by dead_link(). The user must be killed because
that is what we expect, and you cannot use exit_client() because from
some code paths that would be too much freed structures / hassle,
as a comment in src/modules/connect-flood.c correctly states:
/* There are two reasons why we can't use exit_client() here:
 * 1) Because the HOOKTYPE_IP_CHANGE call may be too deep.
 *    Eg: read_packet -> webserver_packet_in ->
 *    webserver_handle_request_header -> webserver_handle_request ->
 *    RunHook().... and then returning without touching anything
 *    after an exit_client() would not be feasible.
 * 2) Because in HOOKTYPE_ACCEPT we always need to use dead_socket
 *    if we want to print a friendly message to TLS users.
 */
2024-07-20 12:22:26 +02:00
Bram Matthys 9cc2918d5f Make set::spamfilter::except a Mask item
* [set::spamfilter::except](https://www.unrealircd.org/docs/Set_block#set::spamfilter::except)
  is now a [Mask item](https://www.unrealircd.org/docs/Mask_item) instead of
  only a list of exempted targets. A warning is created to existing users
  along with a suggestion of how to use the new syntax. Technically, this is
  not really new functionality as all this was already possible via
  the [Except ban block](https://www.unrealircd.org/docs/Except_ban_block)
  with type spamfilter, but it is more visible/logical to have this also.
2024-07-06 10:09:35 +02:00
Bram Matthys e03a5dfd5f Support ::destination and ::exclude-destination in security groups / mask items
at selected places (there needs to be explicit code in place to handle this).
At the moment it is supported at two places only:
* For spamfilters (was already possible via crules via ::rule with
  a destination('xyz') but now non-crule destination "#xyz"; works as well, eg:
  spamfilter {
          ...
          except {
                  destination "#main";
          }
  }
  Note that if you want to exempt a destination in all spamfilters,
  we already have set::spamfilter::except for that!
* In restrict commands for like channel-message and such:
  set {
          restrict-commands {
                  channel-message {
                          except {
                                  connect-time 600;
                                  destination "#test";
                          }
                  }
           }
  }

Allow passing a crule_context via user_allowed_by_security_group_context()
and make user_allowed_by_security_group() call that.

Actually document spamfilter::except online in the docs (yeah you
won't see it in this commit, just mentioning...)

And yeah, by now i wonder if we should really call it crule_context
since it is more like a security group matching context, but.. whatever.
2024-07-06 09:16:53 +02:00
Bram Matthys c12864f81b Fix crash in server_ban.list JSON-RPC call as well.
Hmm... we should probably use json_expand_tkl() differently for match items
instead of returning "<match-item>" literally. Consider this a TODO item :D
This only happens for config-based bans that can't be removed anyway, so..
2024-06-30 19:59:41 +02:00
Bram Matthys 58d7a274f6 Fix crash in new ban user { } code, as predicted two commits ago. 2024-06-30 19:47:04 +02:00
Bram Matthys 53d97e020f Fix for last commit: except ban { } was not checked for ban user { } blocks 2024-06-30 19:26:02 +02:00
Bram Matthys bc7c69dd20 Make ban user::mask and require authentication::mask a Mask item. Finally.
As requested in
https://bugs.unrealircd.org/view.php?id=6159 by PeGaSuS
https://bugs.unrealircd.org/view.php?id=6319 by BlackBishop
https://bugs.unrealircd.org/view.php?id=6397 by Valware

The mask item https://www.unrealircd.org/docs/Mask_item
means you can use all the power of mask items and security groups and
multiple matching criteria.

This requires a bit more testing as username/hostname are NULL now
so some code paths may have to be adjusted. The function call to add
server bans has changed too. And, really need to check that soft bans
are not broken... because they might be ;D
2024-06-30 19:06:37 +02:00
Bram Matthys 037889d7ac Add safety rollback of spamfilter if it doesn't compile. Should not be needed
but we (I) tend to screw up in other areas :D
[skip ci]
2024-01-17 09:48:47 +01:00
Bram Matthys 48d3673a02 Only do slow spamfilter detection for regexes, not for 'simple' */?
Since it is pointless and this saves some CPU :)
2023-12-22 15:43:11 +01:00
Bram Matthys 96b18946ca Include oper name on /SPAMREPORT (for central spamreport) 2023-12-01 07:58:01 +01:00
Bram Matthys 25d1bdfbf5 Make central spamfilters show in STATS spamfilter as "-centralspamfilter-"
rather than "-config-". Suggested by Lord255.
[skip ci]
2023-10-06 08:29:19 +02:00
Bram Matthys 45002eeb6f Fix STATS output for config-based spamfilters with reasons with spaces.
For config-based spamfilters, the reason was not escaped, meaning that
spaces and underscores did not work as expected.
For example, in "STATS spamfilter" the spaces were displayed as-is
which means that the numeric output was not really parsable.

Apparently this bug exists since UnrealIRCd 5 already...
2023-10-06 07:36:26 +02:00
Bram Matthys 43240e4557 Don't allow central spamfilter without 'reason' 2023-10-06 07:00:44 +02:00
Bram Matthys 97630b4717 Allow setting reputation in https://www.unrealircd.org/docs/Actions via
action { set REPUTATION--; } and similar.

Also enhancement to reputation S2S traffic, to support decreasing:
  *
+ * Since UnrealIRCd 6.0.2+ there is now also asterisk-score-asterisk:
+ * :server REPUTATION 1.2.3.4 *2*
+ * The leading asterisk means no reply will be sent back, ever, and the
+ * trailing asterisk will mean it is a "FORCED SET", which means that
+ * servers should set the reputation to that value, even if it is lower.
+ * This way reputation can be reduced and the reducation can be synced
+ * across servers, which was not possible before 6.0.2.
+ *

So if you are actually decreasing reputation, you need all servers on
6.0.2 or higher for it to work properly, otherwise the other servers
don't decrease it, and next connect the highest wins again, etc.
2023-09-17 09:39:55 +02:00
Bram Matthys 50753b4678 Make central spamfilters require an 'id', and ignore for non-central.
At least for now...
2023-07-21 12:26:02 +02:00
Bram Matthys cd19198e3b Spamfilter fixes: prevent actions that are currently config-only from
being added by other servers and being able to spread to areas of
which the code is currently not ready for ('set', 'report', 'stop').
2023-07-20 14:50:40 +02:00
Bram Matthys 937236126f Add new spamfilter type 'raw' which matches against a raw command/protocol line.
SPAMFILTER add -simple R block - Hi_there! LIST*

Though it is more useful in complex spamfilter rules in the conf, presumably.
2023-07-16 19:47:43 +02:00
Bram Matthys b272b6700a Add security-group::rule support, see https://www.unrealircd.org/docs/Crule 2023-07-16 12:09:01 +02:00
Bram Matthys 59c6c99ba3 spamfilter::rule: add destination('#xyz') support (supports wildcards) 2023-07-16 11:29:53 +02:00
Bram Matthys b325f88795 crule/spamfilter: pass text in crule context, not used yet, but could
be useful in some future crule function.
[skip ci]
2023-07-16 10:46:39 +02:00
Bram Matthys a153a2cce3 Change definition of parse_ban_action_config(), was too easy to leak memory.
Often you have default values for the config, and then a subsequent config
parsing run would overwrite the return value (= memory leak), merging/appending
would make no sense either, so it would force a free in all code before
calling us, well... let's just deal with it ourselves instead then ;)
2023-07-14 08:08:47 +02:00
Bram Matthys 4c3d2a6d6d Fix write bug in tkldb and add spamfilter::action stop.
The spamfilter::action stop ill prevent processing other spamfilters.
This would normally be a bit unusual, and potentially dangerous when you
do exclude things this way, but can be useful in some circumstances.

Stopping only affects the same type of spamfilters (general or central
spamfilters), so they don't interfere.

The tkldb write DB bug had to do with that it was processing
central spamfilters, which should be skipped just like config
based spamfilters were already skipped.
2023-07-11 14:32:11 +02:00
Bram Matthys 32701e6f99 Central spamfilter: don't stop processing on 1 bad spamfilter block. 2023-07-11 13:34:28 +02:00
Bram Matthys 018efd8366 Fix crash in spamfilter { } block handling due to unitialized variable 2023-07-11 12:15:01 +02:00
Bram Matthys f333aa0c09 New option set::spamfilter::show-message-content-on-hit:
you can now configure to hide the message content in spamfilter hit
messages. Generally it is very useful to see if a spamfilter hit is
correct or not, so the default is 'always', but it also has privacy
implications so there is now this option to disable it.

Suggested by alice, quite a while ago.

https://www.unrealircd.org/docs/Set_block#set::spamfilter::show-message-content-on-hit

Also as mentioned there:
UnrealIRCd has the following spying countermeasure (for many years) to help
that spamfilters are not abused for spying. When a spamfilter hit happens
that has an action like gline or blocking, it is visible to the user that an
action was taken. There is also the action 'warn', which means: take no
action and only warn IRCOps, that one would be easy to use as a spy tool, so
when this happens and message content was revealed, numeric 659
(RPL_SPAMCMDFWD) is sent to the client to indicate that the message is
allowed through but IRCOps were informed.
With this new set::spamfilter::show-message-content-on-hit feature, when
the message content was hidden due to this setting (eg due to 'never' or
'channel-only'), the warn message will not be sent as there is no need to
inform the user in such a case.
2023-07-11 12:11:26 +02:00
Bram Matthys 4df6ed7f9a Get rid of duplicate "spamfilter hit" code. 2023-07-11 11:42:06 +02:00
Bram Matthys f277880fb3 Add set::central-spamfilter::limit-ban-action and ::limit-ban-time
to limit actions to limit-ban-action as the highest, and limit
ban times to limit-ban-time the highest, see
https://www.unrealircd.org/docs/Central_Spamfilter

This also changes highest_spamfilter_action() to highest_ban_action().
2023-07-11 10:17:51 +02:00