1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-04 11:33:13 +02:00
Files
unrealircd/src/modules/batch.c
T
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

143 lines
3.6 KiB
C

/*
* IRC - Internet Relay Chat, src/modules/batch.c
* (C) 2019 Syzop & The UnrealIRCd Team
*
* See file AUTHORS in IRC package for additional names of
* the programmers.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 1, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"batch",
"5.0",
"Batch CAP",
"UnrealIRCd Team",
"unrealircd-6",
};
/* Forward declarations */
CMD_FUNC(cmd_batch);
/* Variables */
long CAP_BATCH = 0L;
int batch_mtag_is_ok(Client *client, const char *name, const char *value);
MOD_INIT()
{
ClientCapabilityInfo cap;
ClientCapability *c;
MessageTagHandlerInfo mtag;
MARK_AS_OFFICIAL_MODULE(modinfo);
memset(&cap, 0, sizeof(cap));
cap.name = "batch";
c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_BATCH);
memset(&mtag, 0, sizeof(mtag));
mtag.name = "batch";
mtag.is_ok = batch_mtag_is_ok;
mtag.clicap_handler = c;
MessageTagHandlerAdd(modinfo->handle, &mtag);
CommandAdd(modinfo->handle, "BATCH", cmd_batch, MAXPARA, CMD_USER|CMD_SERVER);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
/* BATCH:
* As an intra-server command:
* :sender BATCH target +xxxxx [etc etc etc]
*/
CMD_FUNC(cmd_batch)
{
Client *target;
char buf[512];
if (MyUser(client))
{
/* No module claimed this client-initiated batch */
if (parc >= 2 && parv[1][0] == '+')
{
if (!valid_batch_reference_tag(parv[1] + 1))
sendto_one(client, NULL, ":%s FAIL BATCH INVALID_REFTAG %s :Invalid batch reference tag", me.name, parv[1] + 1);
else
sendto_one(client, NULL, ":%s FAIL BATCH UNKNOWN_TYPE :Unknown batch type", me.name);
}
return;
}
if (parc < 3)
return;
if (parv[2][0] != '+' && parv[2][0] != '-')
return;
if (!valid_batch_reference_tag(parv[2] + 1))
return;
target = find_client(parv[1], NULL);
if (!target)
return; /* race condition */
/* If the recipient does not support message tags or
* does not support batch, then don't do anything.
*/
if (MyConnect(target) && !IsServer(target) && !HasCapability(target, "batch"))
return;
if (MyUser(target))
{
/* Send the batch message to the client */
parv[1] = "BATCH";
concat_params(buf, sizeof(buf), parc, parv);
sendto_prefix_one(target, client, recv_mtags, ":%s %s", client->name, buf);
} else {
/* Relay the batch message to the server */
concat_params(buf, sizeof(buf), parc, parv);
sendto_prefix_one(target, client, recv_mtags, ":%s BATCH %s", client->name, buf);
}
}
/** This function verifies if the client sending
* 'batch' is permitted to do so and uses a permitted
* syntax.
* We allow batch from servers (with any syntax) and from
* local users (so modules handling client-initiated batches,
* like draft/multiline, can see the tag on recv_mtags).
*/
int batch_mtag_is_ok(Client *client, const char *name, const char *value)
{
if (IsServer(client))
return 1;
if (MyUser(client) && !BadPtr(value) && valid_batch_reference_tag(value))
return 1;
return 0;
}