From 5f31f7a5ccddc7517fa2d52bdb63ffb6c34854ec Mon Sep 17 00:00:00 2001 From: Bram Matthys Date: Sat, 14 Aug 2021 16:42:33 +0200 Subject: [PATCH] Add unreal_server_compat module which rewrites named extbans in server to server traffic to be letter extbans. Yeah this is a tad ugly, but the alternative was worse, see header of the file for the full story. Module is loaded by default (obviously). Still to do: only do this for non-U6 servers (add some PROTOCTL) And probably alter clean_ban_mask because I don't like the magic on NULL client at the moment. --- doc/conf/modules.default.conf | 1 + include/modules.h | 1 + src/api-extban.c | 2 +- src/channel.c | 4 + src/modules/Makefile.in | 7 +- src/modules/unreal_server_compat.c | 217 +++++++++++++++++++++++++++++ 6 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 src/modules/unreal_server_compat.c diff --git a/doc/conf/modules.default.conf b/doc/conf/modules.default.conf index 7cf3fc9e9..79c8728aa 100644 --- a/doc/conf/modules.default.conf +++ b/doc/conf/modules.default.conf @@ -121,6 +121,7 @@ loadmodule "umode2"; loadmodule "sinfo"; loadmodule "require-module"; loadmodule "slog"; +loadmodule "unreal_server_compat"; // Services commands // You could disable these if you don't use Services diff --git a/include/modules.h b/include/modules.h index e82efc16e..5beaa43b0 100644 --- a/include/modules.h +++ b/include/modules.h @@ -375,6 +375,7 @@ typedef struct { int what; /**< MODE_ADD or MODE_DEL (for is_ok) */ int what2; /**< EXBTYPE_BAN or EXBTYPE_EXCEPT (for is_ok) */ int is_ok_checktype; /**< One of EXBCHK_* (for is_ok) */ + int write_letter_bans; /**< If set to 1 then we will always use letter extbans */ } BanContext; typedef struct Extban Extban; diff --git a/src/api-extban.c b/src/api-extban.c index 3318000c2..3eed31a8c 100644 --- a/src/api-extban.c +++ b/src/api-extban.c @@ -337,7 +337,7 @@ char *prefix_with_extban(char *remainder, BanContext *b, Extban *extban, char *b if (remainder == NULL) return NULL; - if (iConf.named_extended_bans) + if (iConf.named_extended_bans && !b->write_letter_bans) snprintf(buf, buflen, "~%s:%s", extban->name, remainder); else snprintf(buf, buflen, "~%c:%s", extban->letter, remainder); diff --git a/src/channel.c b/src/channel.c index ec97c182c..2ab04cc5d 100644 --- a/src/channel.c +++ b/src/channel.c @@ -863,6 +863,9 @@ char *clean_ban_mask(char *mask, int what, Client *client) b->client = client; b->what = what; b->banstr = nextbanstr; + /* Hmm.. i may regret this :D */ + if (client == NULL) + b->write_letter_bans = 1; ret = extban->conv_param(b, extban); ret = prefix_with_extban(ret, b, extban, retbuf, sizeof(retbuf)); safe_free(b); @@ -1255,6 +1258,7 @@ int parse_chanmode(ParseMode *pm, char *modebuf_in, char *parabuf_in) strlcpy(pm->buf, start, sizeof(pm->buf)); pm->parabuf = pm->parabuf + strlen(pm->parabuf); /* point to \0 at end */ } + stripcrlf(pm->buf); /* needed for unreal_server_compat.c */ pm->param = pm->buf; } else { pm->modebuf++; diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in index 1ae9f0f9b..f95f8f4e5 100644 --- a/src/modules/Makefile.in +++ b/src/modules/Makefile.in @@ -75,7 +75,8 @@ R_MODULES= \ typing-indicator.so \ ident_lookup.so history.so chathistory.so \ targetfloodprot.so clienttagdeny.so watch-backend.so \ - monitor.so slog.so + monitor.so slog.so \ + unreal_server_compat.so MODULES=cloak.so $(R_MODULES) MODULEFLAGS=@MODULEFLAGS@ @@ -670,6 +671,10 @@ slog.so: slog.c $(INCLUDES) $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \ -o slog.so slog.c +unreal_server_compat.so: unreal_server_compat.c $(INCLUDES) + $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \ + -o unreal_server_compat.so unreal_server_compat.c + ############################################################################# # capabilities ############################################################################# diff --git a/src/modules/unreal_server_compat.c b/src/modules/unreal_server_compat.c new file mode 100644 index 000000000..9c2deaf08 --- /dev/null +++ b/src/modules/unreal_server_compat.c @@ -0,0 +1,217 @@ +/* + * unreal_server_compat - Compatibility with pre-U6 servers + * (C) Copyright 2016-2021 Bram Matthys (Syzop) + * License: GPLv2 + * + * Currently the only purpose of this module is to rewrite + * MODE lines to older servers so any bans/exempts/invex + * will show up with their single letter syntax, + * eg "MODE #test +b ~account:someacc" will be rewritten + * as "MODE #test +b ~a:someacc". + * It uses rather complex mode reparsing techniques to + * achieve this, but this was deemed to be the only way + * that we could achieve this in a doable way. + * The alternative was complicating the mode.c code with + * creating multiple strings for multiple clients, and + * doing the same in any other MODE change routine. + * That would have caused rather intrussive compatibility + * code, so I don't want that. + * With this we can just rip out the module at some point + * that we no longer want to support pre-U6 protocol. + * -- Syzop + */ + +#include "unrealircd.h" + +ModuleHeader MOD_HEADER + = { + "unreal_server_compat", + "1.0.0", + "Provides compatibility with non-U6 servers", + "Bram Matthys (Syzop)", + "unrealircd-6" + }; + +/* Forward declarations */ +int usc_packet(Client *from, Client *to, Client *intended_to, char **msg, int *length); +int usc_reparsemode(char **msg, char *p, int *length); +void skip_spaces(char **p); +void read_until_space(char **p); +int eat_parameter(char **p); + +MOD_INIT() +{ + MARK_AS_OFFICIAL_MODULE(modinfo); + HookAdd(modinfo->handle, HOOKTYPE_PACKET, 0, usc_packet); + return MOD_SUCCESS; +} + +MOD_LOAD() +{ + return MOD_SUCCESS; +} + + +MOD_UNLOAD() +{ + return MOD_SUCCESS; +} + +int usc_packet(Client *from, Client *to, Client *intended_to, char **msg, int *length) +{ + char *p, *buf = *msg; + + /* We are only interested in outgoing data. Also ircops get to see everything as-is */ + if (IsMe(to) || !IsServer(to) || !buf || !length || !*length) + return 0; + + buf[*length] = '\0'; /* safety */ + + p = *msg; + + skip_spaces(&p); + /* Skip over message tags */ + if (*p == '@') + { + read_until_space(&p); + if (*p == '\0') + return 0; /* unexpected ending */ + p++; + } + + skip_spaces(&p); + if (*p == '\0') + return 0; + + /* Skip origin */ + if (*p == ':') + { + read_until_space(&p); + if (*p == '\0') + return 0; /* unexpected ending */ + } + + skip_spaces(&p); + if (*p == '\0') + return 0; + + if (!strncmp(p, "MODE ", 5)) + { + read_until_space(&p); + skip_spaces(&p); + if (*p == '\0') + return 0; /* unexpected */ + /* p now points to #channel */ + + /* Now it gets interesting... we have to re-parse and re-write the entire MODE line. */ + return usc_reparsemode(msg, p, length); + } + + return 0; +} + +int usc_reparsemode(char **msg, char *p, int *length) +{ + static char obuf[8192]; + char modebuf[512], *mode_buf_p, *para_buf_p; + char *channel_name; + int i; + int n; + ParseMode pm; + int modes_processed = 0; + + channel_name = p; + if (!eat_parameter(&p)) + return 0; + + mode_buf_p = p; + if (!eat_parameter(&p)) + return 0; + *modebuf = '\0'; + strlncat(modebuf, mode_buf_p, sizeof(modebuf), p - mode_buf_p); // FIXME: verify that length calculation (last arg) is correct and doesnt need +1 or -1 etc. + + /* If we get here then it is (for example) a + * MODE #channel +b nick!user@host + * So, has at least one parameter (nick!user@host in the example). + * p now points exactly to the 'n' from nick!user@host. + * + * Now, what we will do: + * everything BEFORE p is the 'header' that we will + * send exactly as-is. + * The only thing we may (potentially) change is + * everything AFTER p! + */ + + /* Fill 'obuf' with that 'header' */ + *obuf = '\0'; // we should really get strlncpy ;D + strlncat(obuf, *msg, sizeof(obuf), p - *msg); // FIXME: verify that p-msg is correct and should not be -1 or +1 or anything :D + para_buf_p = p; + + /* Now parse the modes */ + for (n = parse_chanmode(&pm, modebuf, para_buf_p); n; n = parse_chanmode(&pm, NULL, NULL)) + { + /* We only rewrite the parameters, so don't care about paramless modes.. */ + if (!pm.param) + continue; + + if ((pm.modechar == 'b') || (pm.modechar == 'e') || (pm.modechar == 'I')) + { + char *result = clean_ban_mask(pm.param, pm.what, NULL); + strlcat(obuf, result?result:"", sizeof(obuf)); + strlcat(obuf, " ", sizeof(obuf)); + } else + { + /* as-is */ + strlcat(obuf, pm.param, sizeof(obuf)); + strlcat(obuf, " ", sizeof(obuf)); + } + modes_processed++; + } + + /* Send line as-is */ + if (modes_processed == 0) + return 0; + + /* Strip final whitespace */ + if (obuf[strlen(obuf)-1] == ' ') + obuf[strlen(obuf)-1] = '\0'; + + if (pm.parabuf && *pm.parabuf) + { + strlcat(obuf, " ", sizeof(obuf)); + strlcat(obuf, pm.parabuf, sizeof(obuf)); + } + + /* Add CRLF */ + if (obuf[strlen(obuf)-1] != '\n') + strlcat(obuf, "\r\n", sizeof(obuf)); + + /* Line modified, use it! */ + *msg = obuf; + *length = strlen(obuf); + + return 0; +} + +/** Skip space(s), if any. */ +void skip_spaces(char **p) +{ + for (; **p == ' '; *p = *p + 1); +} + +/** Keep reading until we hit space. */ +void read_until_space(char **p) +{ + for (; **p && (**p != ' '); *p = *p + 1); +} + +int eat_parameter(char **p) +{ + read_until_space(p); + if (**p == '\0') + return 0; /* was just a "MODE #channel" query - wait.. that's weird we are a server sending this :D */ + skip_spaces(p); + if (**p == '\0') + return 0; // impossible + return 1; +}