unrealircd

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

labeled-response.c (11857B)

      1 /*
      2  *   IRC - Internet Relay Chat, src/modules/labeled-response.c
      3  *   (C) 2019 Syzop & The UnrealIRCd Team
      4  *
      5  *   See file AUTHORS in IRC package for additional names of
      6  *   the programmers.
      7  *
      8  *   This program is free software; you can redistribute it and/or modify
      9  *   it under the terms of the GNU General Public License as published by
     10  *   the Free Software Foundation; either version 1, or (at your option)
     11  *   any later version.
     12  *
     13  *   This program is distributed in the hope that it will be useful,
     14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  *   GNU General Public License for more details.
     17  *
     18  *   You should have received a copy of the GNU General Public License
     19  *   along with this program; if not, write to the Free Software
     20  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     21  */
     22 
     23 #include "unrealircd.h"
     24 
     25 ModuleHeader MOD_HEADER
     26   = {
     27 	"labeled-response",
     28 	"5.0",
     29 	"Labeled response CAP",
     30 	"UnrealIRCd Team",
     31 	"unrealircd-6",
     32 	};
     33 
     34 /* Data structures */
     35 typedef struct LabeledResponseContext LabeledResponseContext;
     36 struct LabeledResponseContext {
     37 	Client *client; /**< The client who issued the original command with a label */
     38 	char label[256]; /**< The label attached to this command */
     39 	char batch[BATCHLEN+1]; /**< The generated batch id */
     40 	int responses; /**< Number of lines sent back to client */
     41 	int sent_remote; /**< Command has been sent to remote server */
     42 	char firstbuf[MAXLINELENGTH]; /**< First buffered response */
     43 };
     44 
     45 /* Forward declarations */
     46 int lr_pre_command(Client *from, MessageTag *mtags, const char *buf);
     47 int lr_post_command(Client *from, MessageTag *mtags, const char *buf);
     48 int lr_close_connection(Client *client);
     49 int lr_packet(Client *from, Client *to, Client *intended_to, char **msg, int *len);
     50 void *_labeled_response_save_context(void);
     51 void _labeled_response_set_context(void *ctx);
     52 void _labeled_response_force_end(void);
     53 
     54 /* Our special version of SupportBatch() assumes that remote servers always handle it */
     55 #define SupportBatch(x)		(MyConnect(x) ? HasCapability((x), "batch") : 1)
     56 #define SupportLabel(x)		(HasCapabilityFast((x), CAP_LABELED_RESPONSE))
     57 
     58 /* Variables */
     59 static LabeledResponseContext currentcmd;
     60 static long CAP_LABELED_RESPONSE = 0L;
     61 
     62 static char packet[MAXLINELENGTH*2];
     63 
     64 int labeled_response_mtag_is_ok(Client *client, const char *name, const char *value);
     65 
     66 MOD_TEST()
     67 {
     68 	MARK_AS_OFFICIAL_MODULE(modinfo);
     69 	EfunctionAddPVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_SAVE_CONTEXT, _labeled_response_save_context);
     70 	EfunctionAddVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_SET_CONTEXT, _labeled_response_set_context);
     71 	EfunctionAddVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_FORCE_END, _labeled_response_force_end);
     72 
     73 	return MOD_SUCCESS;
     74 }
     75 
     76 MOD_INIT()
     77 {
     78 	ClientCapabilityInfo cap;
     79 	ClientCapability *c;
     80 	MessageTagHandlerInfo mtag;
     81 
     82 	MARK_AS_OFFICIAL_MODULE(modinfo);
     83 
     84 	memset(&currentcmd, 0, sizeof(currentcmd));
     85 
     86 	memset(&cap, 0, sizeof(cap));
     87 	cap.name = "labeled-response";
     88 	c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_LABELED_RESPONSE);
     89 
     90 	memset(&mtag, 0, sizeof(mtag));
     91 	mtag.name = "label";
     92 	mtag.is_ok = labeled_response_mtag_is_ok;
     93 	mtag.clicap_handler = c;
     94 	MessageTagHandlerAdd(modinfo->handle, &mtag);
     95 
     96 	HookAdd(modinfo->handle, HOOKTYPE_PRE_COMMAND, -1000000000, lr_pre_command);
     97 	HookAdd(modinfo->handle, HOOKTYPE_POST_COMMAND, 1000000000, lr_post_command);
     98 	HookAdd(modinfo->handle, HOOKTYPE_CLOSE_CONNECTION, 1000000000, lr_close_connection);
     99 	HookAdd(modinfo->handle, HOOKTYPE_PACKET, 1000000000, lr_packet);
    100 
    101 	return MOD_SUCCESS;
    102 }
    103 
    104 MOD_LOAD()
    105 {
    106 	return MOD_SUCCESS;
    107 }
    108 
    109 MOD_UNLOAD()
    110 {
    111 	return MOD_SUCCESS;
    112 }
    113 
    114 int lr_pre_command(Client *from, MessageTag *mtags, const char *buf)
    115 {
    116 	memset(&currentcmd, 0, sizeof(currentcmd));
    117 	labeled_response_inhibit = labeled_response_inhibit_end = labeled_response_force = 0;
    118 
    119 	if (IsServer(from))
    120 		return 0;
    121 
    122 	for (; mtags; mtags = mtags->next)
    123 	{
    124 		if (!strcmp(mtags->name, "label") && mtags->value)
    125 		{
    126 			strlcpy(currentcmd.label, mtags->value, sizeof(currentcmd.label));
    127 			currentcmd.client = from;
    128 			break;
    129 		}
    130 	}
    131 
    132 	return 0;
    133 }
    134 
    135 char *gen_start_batch(void)
    136 {
    137 	static char buf[512];
    138 
    139 	generate_batch_id(currentcmd.batch);
    140 
    141 	if (MyConnect(currentcmd.client))
    142 	{
    143 		/* Local connection */
    144 		snprintf(buf, sizeof(buf), "@label=%s :%s BATCH +%s labeled-response",
    145 			currentcmd.label,
    146 			me.name,
    147 			currentcmd.batch);
    148 	} else {
    149 		/* Remote connection: requires intra-server BATCH syntax */
    150 		snprintf(buf, sizeof(buf), "@label=%s :%s BATCH %s +%s labeled-response",
    151 			currentcmd.label,
    152 			me.name,
    153 			currentcmd.client->name,
    154 			currentcmd.batch);
    155 	}
    156 	return buf;
    157 }
    158 
    159 int lr_post_command(Client *from, MessageTag *mtags, const char *buf)
    160 {
    161 	/* ** IMPORTANT **
    162 	 * Take care NOT to return here, use 'goto done' instead
    163 	 * as some variables need to be cleared.
    164 	 */
    165 
    166 	/* We may have to send a response or end a BATCH here, if all of
    167 	 * the following is true:
    168 	 * 1. The client is still online (from is not NULL)
    169 	 * 2. A "label" was attached
    170 	 * 3. The client supports BATCH (or is remote)
    171 	 * 4. The command has not been forwarded to a remote server
    172 	 *    (in which case they would be handling it, and not us)
    173 	 * 5. Unless labeled_response_force is set, in which case
    174 	 *    we are assumed to have handled it anyway (necessary for
    175 	 *    commands like PRIVMSG, quite rare).
    176 	 */
    177 	if (from && currentcmd.client &&
    178 	    !(currentcmd.sent_remote && !currentcmd.responses && !labeled_response_force))
    179 	{
    180 		Client *savedptr;
    181 
    182 		if (currentcmd.responses == 0)
    183 		{
    184 			MessageTag *m = safe_alloc(sizeof(MessageTag));
    185 			safe_strdup(m->name, "label");
    186 			safe_strdup(m->value, currentcmd.label);
    187 			memset(&currentcmd, 0, sizeof(currentcmd));
    188 			sendto_one(from, m, ":%s ACK", me.name);
    189 			free_message_tags(m);
    190 			goto done;
    191 		} else
    192 		if (currentcmd.responses == 1)
    193 		{
    194 			/* We have buffered this response earlier,
    195 			 * now we will send it
    196 			 */
    197 			int more_tags = currentcmd.firstbuf[0] == '@';
    198 			currentcmd.client = NULL; /* prevent lr_packet from interfering */
    199 			snprintf(packet, sizeof(packet)-3,
    200 				 "@label=%s%s%s",
    201 				 currentcmd.label,
    202 				 more_tags ? ";" : " ",
    203 				 more_tags ? currentcmd.firstbuf+1 : currentcmd.firstbuf);
    204 			/* Format the IRC message correctly here, so we can take the
    205 			 * quick path through sendbufto_one().
    206 			 */
    207 			strlcat(packet, "\r\n", sizeof(packet));
    208 			sendbufto_one(from, packet, strlen(packet));
    209 			goto done;
    210 		}
    211 
    212 		/* End the batch */
    213 		if (!labeled_response_inhibit_end)
    214 		{
    215 			savedptr = currentcmd.client;
    216 			currentcmd.client = NULL;
    217 			if (MyConnect(savedptr))
    218 				sendto_one(from, NULL, ":%s BATCH -%s", me.name, currentcmd.batch);
    219 			else
    220 				sendto_one(from, NULL, ":%s BATCH %s -%s", me.name, savedptr->name, currentcmd.batch);
    221 		}
    222 	}
    223 done:
    224 	memset(&currentcmd, 0, sizeof(currentcmd));
    225 	labeled_response_inhibit = labeled_response_inhibit_end = labeled_response_force = 0;
    226 	return 0;
    227 }
    228 
    229 int lr_close_connection(Client *client)
    230 {
    231 	/* Flush all data before closing connection */
    232 	lr_post_command(client, NULL, NULL);
    233 	return 0;
    234 }
    235 
    236 /** Helper function for lr_packet() to skip the message tags prefix,
    237  * and possibly @batch as well.
    238  */
    239 char *skip_tags(char *msg)
    240 {
    241 	if (*msg != '@')
    242 		return msg;
    243 	if (!strncmp(msg, "@batch", 6))
    244 	{
    245 		char *p;
    246 		for (p = msg; *p; p++)
    247 			if ((*p == ';') || (*p == ' '))
    248 				return p;
    249 	}
    250 	return msg+1; /* just skip the '@' */
    251 }
    252 
    253 int lr_packet(Client *from, Client *to, Client *intended_to, char **msg, int *len)
    254 {
    255 	if (currentcmd.client && !labeled_response_inhibit)
    256 	{
    257 		/* Labeled response is active */
    258 		if (currentcmd.client == intended_to)
    259 		{
    260 			/* Add the label */
    261 			if (currentcmd.responses == 0)
    262 			{
    263 				int n = *len;
    264 				if (n > sizeof(currentcmd.firstbuf))
    265 					n = sizeof(currentcmd.firstbuf);
    266 				strlcpy(currentcmd.firstbuf, *msg, n);
    267 				/* Don't send anything -- yet */
    268 				*msg = NULL;
    269 				*len = 0;
    270 			} else
    271 			if (currentcmd.responses == 1)
    272 			{
    273 				/* Start the batch now, normally this would be a sendto_one()
    274 				 * but doing so is not possible since we are in the sending code :(
    275 				 * The code below is almost unbearable to see, but the alternative
    276 				 * is to use an intermediate buffer or pointer jugling, of which
    277 				 * the former is slower than this implementation and with the latter
    278 				 * it is easy to make a mistake and create an overflow issue.
    279 				 * So guess I'll stick with this...
    280 				 */
    281 				char *batchstr = gen_start_batch();
    282 				int more_tags_one = currentcmd.firstbuf[0] == '@';
    283 				int more_tags_two = **msg == '@';
    284 
    285 				if (!strncmp(*msg, "@batch", 6))
    286 				{
    287 					/* Special case: current message (*msg) already contains a batch */
    288 					snprintf(packet, sizeof(packet),
    289 						 "%s\r\n"
    290 						 "@batch=%s%s%s\r\n"
    291 						 "%s",
    292 						 batchstr,
    293 						 currentcmd.batch,
    294 						 more_tags_one ? ";" : " ",
    295 						 more_tags_one ? currentcmd.firstbuf+1 : currentcmd.firstbuf,
    296 						 *msg);
    297 				} else
    298 				{
    299 					/* Regular case: current message (*msg) contains no batch yet, add one.. */
    300 					snprintf(packet, sizeof(packet),
    301 						 "%s\r\n"
    302 						 "@batch=%s%s%s\r\n"
    303 						 "@batch=%s%s%s",
    304 						 batchstr,
    305 						 currentcmd.batch,
    306 						 more_tags_one ? ";" : " ",
    307 						 more_tags_one ? currentcmd.firstbuf+1 : currentcmd.firstbuf,
    308 						 currentcmd.batch,
    309 						 more_tags_two ? ";" : " ",
    310 						 more_tags_two ? *msg+1 : *msg);
    311 				}
    312 				*msg = packet;
    313 				*len = strlen(*msg);
    314 			} else {
    315 				/* >2 responses.... the first 2 have already been sent */
    316 				if (!strncmp(*msg, "@batch", 6))
    317 				{
    318 					/* No buffer change needed, already contains a (now inner) batch */
    319 				} else {
    320 					int more_tags = **msg == '@';
    321 					snprintf(packet, sizeof(packet), "@batch=%s%s%s",
    322 						currentcmd.batch,
    323 						more_tags ? ";" : " ",
    324 						more_tags ? *msg+1 : *msg);
    325 					*msg = packet;
    326 					*len = strlen(*msg);
    327 				}
    328 			}
    329 			currentcmd.responses++;
    330 		}
    331 		else if (IsServer(to) || !MyUser(to))
    332 		{
    333 			currentcmd.sent_remote = 1;
    334 		}
    335 	}
    336 
    337 	return 0;
    338 }
    339 
    340 /** This function verifies if the client sending the
    341  * tag is permitted to do so and uses a permitted syntax.
    342  */
    343 int labeled_response_mtag_is_ok(Client *client, const char *name, const char *value)
    344 {
    345 	if (BadPtr(value))
    346 		return 0;
    347 
    348 	if (IsServer(client))
    349 		return 1;
    350 
    351 	/* Ignore the label if the client does not support both
    352 	 * (draft/)labeled-response and batch. Yeah, batch too,
    353 	 * it's too much hassle to support labeled-response without
    354 	 * batch and the end result is quite broken too.
    355 	 */
    356 	if (MyUser(client) && (!SupportLabel(client) || !SupportBatch(client)))
    357 		return 0;
    358 
    359 	/* Do some basic sanity checking for non-servers */
    360 	if (strlen(value) <= 64)
    361 		return 1;
    362 
    363 	return 0;
    364 }
    365 
    366 /** Save current context for later use in labeled-response.
    367  * Currently used in /LIST. Is not planned for other places tbh.
    368  */
    369 void *_labeled_response_save_context(void)
    370 {
    371 	LabeledResponseContext *ctx = safe_alloc(sizeof(LabeledResponseContext));
    372 	memcpy(ctx, &currentcmd, sizeof(LabeledResponseContext));
    373 	return (void *)ctx;
    374 }
    375 
    376 /** Set previously saved context 'ctx', or clear the context.
    377  * @param ctx    The context, or NULL to clear the context.
    378  * @note The client from the previously saved context should be
    379  *       the same. Don't save one context when processing
    380  *       client A and then restore it when processing client B (duh).
    381  */
    382 void _labeled_response_set_context(void *ctx)
    383 {
    384 	if (ctx == NULL)
    385 	{
    386 		/* This means: clear the current context */
    387 		memset(&currentcmd, 0, sizeof(currentcmd));
    388 	} else {
    389 		/* Set the current context to the provided one */
    390 		memcpy(&currentcmd, ctx, sizeof(LabeledResponseContext));
    391 	}
    392 }
    393 
    394 /** Force an end of the labeled-response (only used in /LIST atm) */
    395 void _labeled_response_force_end(void)
    396 {
    397 	if (currentcmd.client)
    398 		lr_post_command(currentcmd.client, NULL, NULL);
    399 }