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(¤tcmd, 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(¤tcmd, 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(¤tcmd, 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(¤tcmd, 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, ¤tcmd, 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(¤tcmd, 0, sizeof(currentcmd)); 388 } else { 389 /* Set the current context to the provided one */ 390 memcpy(¤tcmd, 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 }