unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

unreal_server_compat.c (8112B)

      1 /*
      2  * unreal_server_compat - Compatibility with pre-U6 servers
      3  * (C) Copyright 2016-2021 Bram Matthys (Syzop)
      4  * License: GPLv2 or later
      5  *
      6  * Currently the only purpose of this module is to rewrite MODE
      7  * and SJOIN lines to older servers so any bans/exempts/invex
      8  * will show up with their single letter syntax,
      9  * eg "MODE #test +b ~account:someacc" will be rewritten
     10  * as "MODE #test +b ~a:someacc".
     11  * It uses rather complex mode reparsing techniques to
     12  * achieve this, but this was deemed to be the only way
     13  * that we could achieve this in a doable way.
     14  * The alternative was complicating the mode.c code with
     15  * creating multiple strings for multiple clients, and
     16  * doing the same in any other MODE change routine.
     17  * That would have caused rather intrussive compatibility
     18  * code, so I don't want that.
     19  * With this we can just rip out the module at some point
     20  * that we no longer want to support pre-U6 protocol.
     21  * For SJOIN we do something similar, though in that case
     22  * it would have been quite doable to handle it in there.
     23  * Just figured I would stuff it in here as well, since
     24  * it is basically the same case.
     25  * -- Syzop
     26  */
     27 
     28 #include "unrealircd.h"
     29 
     30 ModuleHeader MOD_HEADER
     31   = {
     32 	"unreal_server_compat",
     33 	"1.0.0",
     34 	"Provides compatibility with non-U6 servers",
     35 	"Bram Matthys (Syzop)",
     36 	"unrealircd-6"
     37     };
     38 
     39 /* Forward declarations */
     40 int usc_packet(Client *from, Client *to, Client *intended_to, char **msg, int *length);
     41 int usc_reparse_mode(char **msg, char *p, int *length);
     42 int usc_reparse_sjoin(char **msg, char *p, int *length);
     43 void skip_spaces(char **p);
     44 void read_until_space(char **p);
     45 int eat_parameter(char **p);
     46 
     47 MOD_INIT()
     48 {
     49 	MARK_AS_OFFICIAL_MODULE(modinfo);
     50 	HookAdd(modinfo->handle, HOOKTYPE_PACKET, 0, usc_packet);
     51 	return MOD_SUCCESS;
     52 }
     53 
     54 MOD_LOAD()
     55 {
     56 	return MOD_SUCCESS;
     57 }
     58 
     59 
     60 MOD_UNLOAD()
     61 {
     62 	return MOD_SUCCESS;
     63 }
     64 
     65 int usc_packet(Client *from, Client *to, Client *intended_to, char **msg, int *length)
     66 {
     67 	char *p, *buf = *msg;
     68 
     69 	/* We are only interested in outgoing servers
     70 	 * that do not support PROTOCTL NEXTBANS
     71 	 */
     72 	if (IsMe(to) || !IsServer(to) || SupportNEXTBANS(to) || !buf || !length || !*length)
     73 		return 0;
     74 
     75 	buf[*length] = '\0'; /* safety */
     76 
     77 	p = *msg;
     78 
     79 	skip_spaces(&p);
     80 	/* Skip over message tags */
     81 	if (*p == '@')
     82 	{
     83 		read_until_space(&p);
     84 		if (*p == '\0')
     85 			return 0; /* unexpected ending */
     86 		p++;
     87 	}
     88 
     89 	skip_spaces(&p);
     90 	if (*p == '\0')
     91 		return 0;
     92 
     93 	/* Skip origin */
     94 	if (*p == ':')
     95 	{
     96 		read_until_space(&p);
     97 		if (*p == '\0')
     98 			return 0; /* unexpected ending */
     99 	}
    100 
    101 	skip_spaces(&p);
    102 	if (*p == '\0')
    103 		return 0;
    104 
    105 	if (!strncmp(p, "MODE ", 5)) /* MODE #channel */
    106 	{
    107 		if (!eat_parameter(&p))
    108 			return 0;
    109 		/* p now points to #channel */
    110 
    111 		/* Now it gets interesting... we have to re-parse and re-write the entire MODE line. */
    112 		return usc_reparse_mode(msg, p, length);
    113 	}
    114 
    115 	if (!strncmp(p, "SJOIN ", 6)) /* SJOIN timestamp #channel */
    116 	{
    117 		if (!eat_parameter(&p) || !eat_parameter(&p))
    118 			return 0;
    119 		/* p now points to #channel */
    120 
    121 		/* Now it gets interesting... we have to re-parse and re-write the entire SJOIN line. */
    122 		return usc_reparse_sjoin(msg, p, length);
    123 	}
    124 
    125 	return 0;
    126 }
    127 
    128 int usc_reparse_mode(char **msg, char *p, int *length)
    129 {
    130 	static char obuf[8192];
    131 	char modebuf[512], *mode_buf_p, *para_buf_p;
    132 	char *channel_name;
    133 	int i;
    134 	int n;
    135 	ParseMode pm;
    136 	int modes_processed = 0;
    137 
    138 	channel_name = p;
    139 	if (!eat_parameter(&p))
    140 		return 0;
    141 
    142 	mode_buf_p = p;
    143 	if (!eat_parameter(&p))
    144 		return 0;
    145 	strlncpy(modebuf, mode_buf_p, sizeof(modebuf), p - mode_buf_p);
    146 
    147 	/* If we get here then it is (for example) a
    148 	 * MODE #channel +b nick!user@host
    149 	 * So, has at least one parameter (nick!user@host in the example).
    150 	 * p now points exactly to the 'n' from nick!user@host.
    151 	 *
    152 	 * Now, what we will do:
    153 	 * everything BEFORE p is the 'header' that we will
    154 	 * send exactly as-is.
    155 	 * The only thing we may (potentially) change is
    156 	 * everything AFTER p!
    157 	 */
    158 
    159 	/* Fill 'obuf' with that 'header' */
    160 	strlncpy(obuf, *msg, sizeof(obuf), p - *msg);
    161 	para_buf_p = p;
    162 
    163 	/* Now parse the modes */
    164 	for (n = parse_chanmode(&pm, modebuf, para_buf_p); n; n = parse_chanmode(&pm, NULL, NULL))
    165 	{
    166 		/* We only rewrite the parameters, so don't care about paramless modes.. */
    167 		if (!pm.param)
    168 			continue;
    169 
    170 		if ((pm.modechar == 'b') || (pm.modechar == 'e') || (pm.modechar == 'I'))
    171 		{
    172 			const char *result = clean_ban_mask(pm.param, pm.what, &me, 1);
    173 			strlcat(obuf, result?result:"<invalid>", sizeof(obuf));
    174 			strlcat(obuf, " ", sizeof(obuf));
    175 		} else
    176 		{
    177 			/* as-is */
    178 			strlcat(obuf, pm.param, sizeof(obuf));
    179 			strlcat(obuf, " ", sizeof(obuf));
    180 		}
    181 		modes_processed++;
    182 	}
    183 
    184 	/* Send line as-is */
    185 	if (modes_processed == 0)
    186 		return 0;
    187 
    188 	/* Strip final whitespace */
    189 	if (obuf[strlen(obuf)-1] == ' ')
    190 		obuf[strlen(obuf)-1] = '\0';
    191 
    192 	if (pm.parabuf && *pm.parabuf)
    193 	{
    194 		strlcat(obuf, " ", sizeof(obuf));
    195 		strlcat(obuf, pm.parabuf, sizeof(obuf));
    196 	}
    197 
    198 	/* Add CRLF */
    199 	if (obuf[strlen(obuf)-1] != '\n')
    200 		strlcat(obuf, "\r\n", sizeof(obuf));
    201 
    202 	/* Line modified, use it! */
    203 	*msg = obuf;
    204 	*length = strlen(obuf);
    205 
    206 	return 0;
    207 }
    208 
    209 int usc_reparse_sjoin(char **msg, char *p, int *length)
    210 {
    211 	static char obuf[8192];
    212 	char parabuf[512];
    213 	char *save = NULL;
    214 	char *s;
    215 
    216 	/* Skip right to the last parameter, the only one we care about */
    217 	p = strstr(p, " :");
    218 	if (!p)
    219 		return 0;
    220 	p += 2;
    221 
    222 	/* Save everything before p, put it in obuf... */
    223 
    224 	/* Fill 'obuf' with that 'header' */
    225 	strlncpy(obuf, *msg, sizeof(obuf), p - *msg);
    226 
    227 	/* Put parameters in parabuf so we can trash it :D */
    228 	strlcpy(parabuf, p, sizeof(parabuf));
    229 
    230 	/* Now parse the SJOIN */
    231 	for (s = strtoken(&save, parabuf, " "); s; s = strtoken(&save, NULL, " "))
    232 	{
    233 		if (*s == '<')
    234 		{
    235 			/* SJSBY */
    236 			char *next = strchr(s, '>');
    237 			const char *result;
    238 			if (!next)
    239 			{
    240 				unreal_log(ULOG_WARNING, "unreal_server_compat", "USC_REPARSE_SJOIN_FAILURE", NULL,
    241 				           "[unreal_server_compat] usc_reparse_sjoin(): sjoin data '$ban' seemed like a SJSBY but was not??",
    242 				           log_data_string("ban", s));
    243 				continue;
    244 			}
    245 			if (!strchr("&\"\\", next[1]))
    246 				goto fallback_usc_reparse_sjoin;
    247 			*next++ = '\0';
    248 			result = clean_ban_mask(next+1, MODE_ADD, &me, 1);
    249 			if (!result)
    250 			{
    251 				unreal_log(ULOG_WARNING, "unreal_server_compat", "USC_REPARSE_SJOIN_FAILURE", NULL,
    252 				           "[unreal_server_compat] usc_reparse_sjoin(): ban '$ban' could not be converted",
    253 				           log_data_string("ban", s+1));
    254 				continue;
    255 			}
    256 			strlcat(obuf, s, sizeof(obuf)); /* "<123,nick" */
    257 			strlcat(obuf, ">", sizeof(obuf)); /* > */
    258 			strlncat(obuf, next, sizeof(obuf), 1); /* & or \" or \\ */
    259 			strlcat(obuf, result, sizeof(obuf)); /* the converted result */
    260 			strlcat(obuf, " ", sizeof(obuf));
    261 		} else
    262 		if (strchr("&\"\\", *s))
    263 		{
    264 			/* +b / +e / +I */
    265 			const char *result = clean_ban_mask(s+1, MODE_ADD, &me, 1);
    266 			if (!result)
    267 			{
    268 				unreal_log(ULOG_WARNING, "unreal_server_compat", "USC_REPARSE_SJOIN_FAILURE", NULL,
    269 				           "[unreal_server_compat] usc_reparse_sjoin(): ban '$ban' could not be converted",
    270 				           log_data_string("ban", s+1));
    271 				continue;
    272 			}
    273 			strlncat(obuf, s, sizeof(obuf), 1);
    274 			strlcat(obuf, result, sizeof(obuf));
    275 			strlcat(obuf, " ", sizeof(obuf));
    276 		} else {
    277 fallback_usc_reparse_sjoin:
    278 			strlcat(obuf, s, sizeof(obuf));
    279 			strlcat(obuf, " ", sizeof(obuf));
    280 		}
    281 	}
    282 
    283 	/* Strip final whitespace */
    284 	if (obuf[strlen(obuf)-1] == ' ')
    285 		obuf[strlen(obuf)-1] = '\0';
    286 
    287 	/* Add CRLF */
    288 	if (obuf[strlen(obuf)-1] != '\n')
    289 		strlcat(obuf, "\r\n", sizeof(obuf));
    290 
    291 	/* And use it! */
    292 	*msg = obuf;
    293 	*length = strlen(obuf);
    294 
    295 	return 0;
    296 }
    297 
    298 /** Skip space(s), if any. */
    299 void skip_spaces(char **p)
    300 {
    301 	for (; **p == ' '; *p = *p + 1);
    302 }
    303 
    304 /** Keep reading until we hit space. */
    305 void read_until_space(char **p)
    306 {
    307 	for (; **p && (**p != ' '); *p = *p + 1);
    308 }
    309 
    310 int eat_parameter(char **p)
    311 {
    312 	read_until_space(p);
    313 	if (**p == '\0')
    314 		return 0; /* was just a "MODE #channel" query - wait.. that's weird we are a server sending this :D */
    315 	skip_spaces(p);
    316 	if (**p == '\0')
    317 		return 0; // impossible
    318 	return 1;
    319 }