unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
history.c (22881B)
1 /* 2 * modules/chanmodes/history - Channel History 3 * (C) Copyright 2009-2019 Bram Matthys (Syzop) and the UnrealIRCd team 4 * License: GPLv2 or later 5 */ 6 #include "unrealircd.h" 7 8 ModuleHeader MOD_HEADER 9 = { 10 "chanmodes/history", 11 "1.0", 12 "Channel Mode +H", 13 "UnrealIRCd Team", 14 "unrealircd-6", 15 }; 16 17 typedef struct ConfigHistoryExt ConfigHistoryExt; 18 struct ConfigHistoryExt { 19 int lines; /**< number of lines */ 20 long time; /**< seconds */ 21 }; 22 typedef struct cfgstruct cfgstruct; 23 struct cfgstruct { 24 ConfigHistoryExt playback_on_join; /**< Maximum number of lines & time to playback on-join */ 25 ConfigHistoryExt max_storage_per_channel_registered; /**< Maximum number of lines & time to record for +r channels*/ 26 ConfigHistoryExt max_storage_per_channel_unregistered; /**< Maximum number of lines & time to record for -r channels */ 27 }; 28 29 typedef struct HistoryChanMode HistoryChanMode; 30 struct HistoryChanMode { 31 unsigned int max_lines; /**< Maximum number of messages to record */ 32 unsigned long max_time; /**< Maximum number of time (in seconds) to record */ 33 }; 34 35 /* Global variables */ 36 Cmode_t EXTMODE_HISTORY = 0L; 37 static cfgstruct cfg; 38 static cfgstruct test; 39 40 #define HistoryEnabled(channel) (channel->mode.mode & EXTMODE_HISTORY) 41 42 /* Forward declarations */ 43 static void init_config(cfgstruct *cfg); 44 int history_config_test(ConfigFile *, ConfigEntry *, int, int *); 45 int history_config_posttest(int *); 46 int history_config_run(ConfigFile *, ConfigEntry *, int); 47 int history_chanmode_change(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel); 48 static int compare_history_modes(HistoryChanMode *a, HistoryChanMode *b); 49 int history_chanmode_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what); 50 void *history_chanmode_put_param(void *r_in, const char *param); 51 const char *history_chanmode_get_param(void *r_in); 52 const char *history_chanmode_conv_param(const char *param, Client *client, Channel *channel); 53 int history_chanmode_free_param(void *r, int soft); 54 void *history_chanmode_dup_struct(void *r_in); 55 int history_chanmode_sjoin_check(Channel *channel, void *ourx, void *theirx); 56 int history_channel_destroy(Channel *channel, int *should_destroy); 57 int history_chanmsg(Client *client, Channel *channel, int sendflags, const char *prefix, const char *target, MessageTag *mtags, const char *text, SendType sendtype); 58 int history_join(Client *client, Channel *channel, MessageTag *mtags); 59 CMD_OVERRIDE_FUNC(override_mode); 60 61 MOD_TEST() 62 { 63 init_config(&test); 64 HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, history_config_test); 65 HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, history_config_posttest); 66 67 return MOD_SUCCESS; 68 } 69 70 MOD_INIT() 71 { 72 CmodeInfo creq; 73 ModDataInfo mreq; 74 75 MARK_AS_OFFICIAL_MODULE(modinfo); 76 77 memset(&creq, 0, sizeof(creq)); 78 creq.paracount = 1; 79 creq.is_ok = history_chanmode_is_ok; 80 creq.letter = 'H'; 81 creq.put_param = history_chanmode_put_param; 82 creq.get_param = history_chanmode_get_param; 83 creq.conv_param = history_chanmode_conv_param; 84 creq.free_param = history_chanmode_free_param; 85 creq.dup_struct = history_chanmode_dup_struct; 86 creq.sjoin_check = history_chanmode_sjoin_check; 87 CmodeAdd(modinfo->handle, creq, &EXTMODE_HISTORY); 88 89 init_config(&cfg); 90 91 HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, history_config_run); 92 HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CHANMODE, 0, history_chanmode_change); 93 HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CHANMODE, 0, history_chanmode_change); 94 HookAdd(modinfo->handle, HOOKTYPE_LOCAL_JOIN, 0, history_join); 95 HookAdd(modinfo->handle, HOOKTYPE_CHANMSG, 0, history_chanmsg); 96 HookAdd(modinfo->handle, HOOKTYPE_CHANNEL_DESTROY, 1000000, history_channel_destroy); 97 return MOD_SUCCESS; 98 } 99 100 MOD_LOAD() 101 { 102 CommandOverrideAdd(modinfo->handle, "MODE", 0, override_mode); 103 CommandOverrideAdd(modinfo->handle, "SVSMODE", 0, override_mode); 104 CommandOverrideAdd(modinfo->handle, "SVS2MODE", 0, override_mode); 105 CommandOverrideAdd(modinfo->handle, "SAMODE", 0, override_mode); 106 CommandOverrideAdd(modinfo->handle, "SJOIN", 0, override_mode); 107 return MOD_SUCCESS; 108 } 109 110 MOD_UNLOAD() 111 { 112 return MOD_SUCCESS; 113 } 114 115 static void init_config(cfgstruct *cfg) 116 { 117 /* Set default values */ 118 memset(cfg, 0, sizeof(cfgstruct)); 119 cfg->playback_on_join.lines = 15; 120 cfg->playback_on_join.time = 86400; 121 cfg->max_storage_per_channel_unregistered.lines = 200; 122 cfg->max_storage_per_channel_unregistered.time = 86400*31; 123 cfg->max_storage_per_channel_registered.lines = 5000; 124 cfg->max_storage_per_channel_registered.time = 86400*31; 125 } 126 127 int history_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) 128 { 129 int errors = 0; 130 ConfigEntry *cep, *cepp, *cep4, *cep5; 131 int on_join_lines=0, maximum_storage_lines_registered=0, maximum_storage_lines_unregistered=0; 132 long on_join_time=0L, maximum_storage_time_registered=0L, maximum_storage_time_unregistered=0L; 133 134 /* We only care about set::history */ 135 if ((type != CONFIG_SET) || strcmp(ce->name, "history")) 136 return 0; 137 138 for (cep = ce->items; cep; cep = cep->next) 139 { 140 if (!strcmp(cep->name, "channel")) 141 { 142 for (cepp = cep->items; cepp; cepp = cepp->next) 143 { 144 if (!strcmp(cepp->name, "playback-on-join")) 145 { 146 for (cep4 = cepp->items; cep4; cep4 = cep4->next) 147 { 148 if (!strcmp(cep4->name, "lines")) 149 { 150 int v; 151 CheckNull(cep4); 152 v = atoi(cep4->value); 153 if ((v < 0) || (v > 1000)) 154 { 155 config_error("%s:%i: set::history::channel::playback-on-join::lines must be between 0 and 1000. " 156 "Recommended values are 10-50. Got: %d.", 157 cep4->file->filename, cep4->line_number, v); 158 errors++; 159 continue; 160 } 161 test.playback_on_join.lines = v; 162 } else 163 if (!strcmp(cep4->name, "time")) 164 { 165 long v; 166 CheckNull(cep4); 167 v = config_checkval(cep4->value, CFG_TIME); 168 if (v < 0) 169 { 170 config_error("%s:%i: set::history::channel::playback-on-join::time must be zero or more.", 171 cep4->file->filename, cep4->line_number); 172 errors++; 173 continue; 174 } 175 test.playback_on_join.time = v; 176 } else 177 { 178 config_error_unknown(cep4->file->filename, 179 cep4->line_number, "set::history::channel::playback-on-join", cep4->name); 180 errors++; 181 } 182 } 183 } else 184 if (!strcmp(cepp->name, "max-storage-per-channel")) 185 { 186 for (cep4 = cepp->items; cep4; cep4 = cep4->next) 187 { 188 if (!strcmp(cep4->name, "registered")) 189 { 190 for (cep5 = cep4->items; cep5; cep5 = cep5->next) 191 { 192 if (!strcmp(cep5->name, "lines")) 193 { 194 int v; 195 CheckNull(cep5); 196 v = atoi(cep5->value); 197 if (v < 1) 198 { 199 config_error("%s:%i: set::history::channel::max-storage-per-channel::registered::lines must be a positive number.", 200 cep5->file->filename, cep5->line_number); 201 errors++; 202 continue; 203 } 204 test.max_storage_per_channel_registered.lines = v; 205 } else 206 if (!strcmp(cep5->name, "time")) 207 { 208 long v; 209 CheckNull(cep5); 210 v = config_checkval(cep5->value, CFG_TIME); 211 if (v < 1) 212 { 213 config_error("%s:%i: set::history::channel::max-storage-per-channel::registered::time must be a positive number.", 214 cep5->file->filename, cep5->line_number); 215 errors++; 216 continue; 217 } 218 test.max_storage_per_channel_registered.time = v; 219 } else 220 { 221 config_error_unknown(cep5->file->filename, 222 cep5->line_number, "set::history::channel::max-storage-per-channel::registered", cep5->name); 223 errors++; 224 } 225 } 226 } else 227 if (!strcmp(cep4->name, "unregistered")) 228 { 229 for (cep5 = cep4->items; cep5; cep5 = cep5->next) 230 { 231 if (!strcmp(cep5->name, "lines")) 232 { 233 int v; 234 CheckNull(cep5); 235 v = atoi(cep5->value); 236 if (v < 1) 237 { 238 config_error("%s:%i: set::history::channel::max-storage-per-channel::unregistered::lines must be a positive number.", 239 cep5->file->filename, cep5->line_number); 240 errors++; 241 continue; 242 } 243 test.max_storage_per_channel_unregistered.lines = v; 244 } else 245 if (!strcmp(cep5->name, "time")) 246 { 247 long v; 248 CheckNull(cep5); 249 v = config_checkval(cep5->value, CFG_TIME); 250 if (v < 1) 251 { 252 config_error("%s:%i: set::history::channel::max-storage-per-channel::unregistered::time must be a positive number.", 253 cep5->file->filename, cep5->line_number); 254 errors++; 255 continue; 256 } 257 test.max_storage_per_channel_unregistered.time = v; 258 } else 259 { 260 config_error_unknown(cep5->file->filename, 261 cep5->line_number, "set::history::channel::max-storage-per-channel::unregistered", cep5->name); 262 errors++; 263 } 264 } 265 } else 266 { 267 config_error_unknown(cep->file->filename, 268 cep->line_number, "set::history::max-storage-per-channel", cep->name); 269 errors++; 270 } 271 } 272 } else 273 { 274 /* hmm.. I don't like this method. but I just quickly copied it from CONFIG_ALLOW for now... */ 275 int used = 0; 276 Hook *h; 277 for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next) 278 { 279 int value, errs = 0; 280 if (h->owner && !(h->owner->flags & MODFLAG_TESTING) 281 && !(h->owner->options & MOD_OPT_PERM)) 282 continue; 283 value = (*(h->func.intfunc))(cf, cepp, CONFIG_SET_HISTORY_CHANNEL, &errs); 284 if (value == 2) 285 used = 1; 286 if (value == 1) 287 { 288 used = 1; 289 break; 290 } 291 if (value == -1) 292 { 293 used = 1; 294 errors += errs; 295 break; 296 } 297 if (value == -2) 298 { 299 used = 1; 300 errors += errs; 301 } 302 } 303 if (!used) 304 { 305 config_error_unknown(cepp->file->filename, 306 cepp->line_number, "set::history::channel", cepp->name); 307 errors++; 308 } 309 } 310 } 311 } else { 312 config_error_unknown(cep->file->filename, 313 cep->line_number, "set::history", cep->name); 314 errors++; 315 } 316 } 317 318 *errs = errors; 319 return errors ? -1 : 1; 320 } 321 322 int history_config_posttest(int *errs) 323 { 324 int errors = 0; 325 326 /* We could check here for on join lines / on join time being bigger than max storage but.. 327 * not really important. 328 */ 329 330 *errs = errors; 331 return errors ? -1 : 1; 332 } 333 334 int history_config_run(ConfigFile *cf, ConfigEntry *ce, int type) 335 { 336 ConfigEntry *cep, *cepp, *cep4, *cep5; 337 338 if ((type != CONFIG_SET) || strcmp(ce->name, "history")) 339 return 0; 340 341 for (cep = ce->items; cep; cep = cep->next) 342 { 343 if (!strcmp(cep->name, "channel")) 344 { 345 for (cepp = cep->items; cepp; cepp = cepp->next) 346 { 347 if (!strcmp(cepp->name, "playback-on-join")) 348 { 349 for (cep4 = cepp->items; cep4; cep4 = cep4->next) 350 { 351 if (!strcmp(cep4->name, "lines")) 352 { 353 cfg.playback_on_join.lines = atoi(cep4->value); 354 } else 355 if (!strcmp(cep4->name, "time")) 356 { 357 cfg.playback_on_join.time = config_checkval(cep4->value, CFG_TIME); 358 } 359 } 360 } else 361 if (!strcmp(cepp->name, "max-storage-per-channel")) 362 { 363 for (cep4 = cepp->items; cep4; cep4 = cep4->next) 364 { 365 if (!strcmp(cep4->name, "registered")) 366 { 367 for (cep5 = cep4->items; cep5; cep5 = cep5->next) 368 { 369 if (!strcmp(cep5->name, "lines")) 370 { 371 cfg.max_storage_per_channel_registered.lines = atoi(cep5->value); 372 } else 373 if (!strcmp(cep5->name, "time")) 374 { 375 cfg.max_storage_per_channel_registered.time = config_checkval(cep5->value, CFG_TIME); 376 } 377 } 378 } else 379 if (!strcmp(cep4->name, "unregistered")) 380 { 381 for (cep5 = cep4->items; cep5; cep5 = cep5->next) 382 { 383 if (!strcmp(cep5->name, "lines")) 384 { 385 cfg.max_storage_per_channel_unregistered.lines = atoi(cep5->value); 386 } else 387 if (!strcmp(cep5->name, "time")) 388 { 389 cfg.max_storage_per_channel_unregistered.time = config_checkval(cep5->value, CFG_TIME); 390 } 391 } 392 } 393 } 394 } else 395 { 396 Hook *h; 397 for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next) 398 { 399 int value = (*(h->func.intfunc))(cf, cepp, CONFIG_SET_HISTORY_CHANNEL); 400 if (value == 1) 401 break; 402 } 403 } 404 } 405 } 406 } 407 408 return 0; /* Retval 0 = trick so other modules can see the same configuration */ 409 } 410 411 /** Helper function for .is_ok(), .conv_param() and .put_param(). 412 * @param param: The mode parameter. 413 * @param lines: The number of lines (the X in +H X:Y) 414 * @param t: The time value (the Y in +H X:Y) 415 */ 416 int history_parse_chanmode(Channel *channel, const char *param, int *lines, long *t) 417 { 418 char buf[64], *p, *q; 419 char contains_non_digit = 0; 420 421 /* Work on a copy */ 422 strlcpy(buf, param, sizeof(buf)); 423 424 /* Initialize, to be safe */ 425 *lines = 0; 426 *t = 0; 427 428 p = strchr(buf, ':'); 429 if (!p) 430 return 0; 431 432 /* Parse lines */ 433 *p++ = '\0'; 434 *lines = atoi(buf); 435 436 /* Parse time value */ 437 /* If it is all digits then it is in minutes */ 438 for (q=p; *q; q++) 439 { 440 if (!isdigit(*q)) 441 { 442 contains_non_digit = 1; 443 break; 444 } 445 } 446 if (contains_non_digit) 447 *t = config_checkval(p, CFG_TIME); 448 else 449 *t = atoi(p) * 60; 450 451 /* Sanity checking... */ 452 if (*lines < 1) 453 return 0; 454 455 if (*t < 60) 456 return 0; 457 458 /* Check imposed configuration limits... */ 459 if (!channel || has_channel_mode(channel, 'r')) 460 { 461 if (*lines > cfg.max_storage_per_channel_registered.lines) 462 *lines = cfg.max_storage_per_channel_registered.lines; 463 464 if (*t > cfg.max_storage_per_channel_registered.time) 465 *t = cfg.max_storage_per_channel_registered.time; 466 } else { 467 if (*lines > cfg.max_storage_per_channel_unregistered.lines) 468 *lines = cfg.max_storage_per_channel_unregistered.lines; 469 470 if (*t > cfg.max_storage_per_channel_unregistered.time) 471 *t = cfg.max_storage_per_channel_unregistered.time; 472 } 473 return 1; 474 } 475 476 /** Channel Mode +H check: 477 * Does the user have rights to add/remove this channel mode? 478 * Is the supplied mode parameter ok? 479 */ 480 int history_chanmode_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what) 481 { 482 if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR)) 483 { 484 if (IsUser(client) && check_channel_access(client, channel, "oaq")) 485 return EX_ALLOW; 486 if (type == EXCHK_ACCESS_ERR) /* can only be due to being halfop */ 487 sendnumeric(client, ERR_NOTFORHALFOPS, 'H'); 488 return EX_DENY; 489 } else 490 if (type == EXCHK_PARAM) 491 { 492 int lines = 0; 493 long t = 0L; 494 495 if (!history_parse_chanmode(channel, param, &lines, &t)) 496 { 497 sendnumeric(client, ERR_CANNOTCHANGECHANMODE, 'H', "Invalid syntax for MODE +H. Use +H lines:period. The period must be in minutes (eg: 10) or a time value (eg: 1h)."); 498 return EX_DENY; 499 } 500 /* Don't bother about lines/t limits here, we will auto-convert in .conv_param */ 501 502 return EX_ALLOW; 503 } 504 505 /* fallthrough -- should not be used */ 506 return EX_DENY; 507 } 508 509 static void history_chanmode_helper(char *buf, size_t bufsize, int lines, long t) 510 { 511 if ((t % 86400) == 0) 512 { 513 /* Can be represented in full days, eg "1d" */ 514 snprintf(buf, bufsize, "%d:%ldd", lines, t / 86400); 515 } else 516 if ((t % 3600) == 0) 517 { 518 /* Can be represented in hours, eg "8h" */ 519 snprintf(buf, bufsize, "%d:%ldh", lines, t / 3600); 520 } else 521 { 522 /* Otherwise, stick to minutes */ 523 snprintf(buf, bufsize, "%d:%ldm", lines, t / 60); 524 } 525 } 526 527 /** Convert channel parameter to something proper. 528 * NOTE: client may be NULL if called for e.g. set::modes-playback-on-join 529 */ 530 const char *history_chanmode_conv_param(const char *param, Client *client, Channel *channel) 531 { 532 static char buf[64]; 533 int lines = 0; 534 long t = 0L; 535 536 if (!history_parse_chanmode(channel, param, &lines, &t)) 537 return NULL; 538 539 history_chanmode_helper(buf, sizeof(buf), lines, t); 540 return buf; 541 } 542 543 /** Store the +H x:y channel mode */ 544 void *history_chanmode_put_param(void *mode_in, const char *param) 545 { 546 HistoryChanMode *h = (HistoryChanMode *)mode_in; 547 int lines = 0; 548 long t = 0L; 549 550 if (!history_parse_chanmode(NULL, param, &lines, &t)) 551 return NULL; 552 553 if (!h) 554 { 555 /* Need to create one */ 556 h = safe_alloc(sizeof(HistoryChanMode)); 557 } 558 559 h->max_lines = lines; 560 h->max_time = t; 561 562 return (void *)h; 563 } 564 565 /** Retrieve the +H settings (the X:Y string) */ 566 const char *history_chanmode_get_param(void *h_in) 567 { 568 HistoryChanMode *h = (HistoryChanMode *)h_in; 569 static char buf[64]; 570 571 if (!h_in) 572 return NULL; 573 574 history_chanmode_helper(buf, sizeof(buf), h->max_lines, h->max_time); 575 return buf; 576 } 577 578 /** Free channel mode */ 579 int history_chanmode_free_param(void *r, int soft) 580 { 581 safe_free(r); 582 return 0; 583 } 584 585 /** Duplicate the channel mode +H settings */ 586 void *history_chanmode_dup_struct(void *r_in) 587 { 588 HistoryChanMode *r = (HistoryChanMode *)r_in; 589 HistoryChanMode *w = safe_alloc(sizeof(HistoryChanMode)); 590 591 memcpy(w, r, sizeof(HistoryChanMode)); 592 return (void *)w; 593 } 594 595 /** If two servers with an identical creation time stamp connect, 596 * we have to deal with merging the settings on different sides 597 * (if they differ at all). That's what we do here. 598 */ 599 int history_chanmode_sjoin_check(Channel *channel, void *ourx, void *theirx) 600 { 601 HistoryChanMode *our = (HistoryChanMode *)ourx; 602 HistoryChanMode *their = (HistoryChanMode *)theirx; 603 604 if ((our->max_lines == their->max_lines) && (our->max_time == their->max_time)) 605 return EXSJ_SAME; 606 607 our->max_lines = MAX(our->max_lines, their->max_lines); 608 our->max_time = MAX(our->max_time, their->max_time); 609 610 return EXSJ_MERGE; 611 } 612 613 /** On channel mode change, communicate the +H limits to the history backend layer */ 614 int history_chanmode_change(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel) 615 { 616 HistoryChanMode *settings; 617 618 /* Did anything change, with regards to channel mode H ? */ 619 if (!strchr(modebuf, 'H')) 620 return 0; 621 622 /* If so, grab the settings, and communicate them */ 623 settings = (HistoryChanMode *)GETPARASTRUCT(channel, 'H'); 624 if (settings) 625 history_set_limit(channel->name, settings->max_lines, settings->max_time); 626 else 627 history_destroy(channel->name); 628 629 return 0; 630 } 631 632 /** Channel is destroyed (or is it?) */ 633 int history_channel_destroy(Channel *channel, int *should_destroy) 634 { 635 if (*should_destroy == 0) 636 return 0; /* channel will not be destroyed */ 637 638 history_destroy(channel->name); 639 640 return 0; 641 } 642 643 int history_chanmsg(Client *client, Channel *channel, int sendflags, const char *prefix, const char *target, MessageTag *mtags, const char *text, SendType sendtype) 644 { 645 char buf[512]; 646 char source[64]; 647 HistoryChanMode *settings; 648 649 if (!HistoryEnabled(channel)) 650 return 0; 651 652 /* Filter out CTCP / CTCP REPLY */ 653 if ((*text == '\001') && strncmp(text+1, "ACTION", 6)) 654 return 0; 655 656 /* Filter out TAGMSG */ 657 if (sendtype == SEND_TYPE_TAGMSG) 658 return 0; 659 660 /* Lazy: if any prefix is addressed (eg: @#channel) then don't record it. 661 * This so we don't have to check privileges during history playback etc. 662 */ 663 if (prefix) 664 return 0; 665 666 if (IsUser(client)) 667 snprintf(source, sizeof(source), "%s!%s@%s", client->name, client->user->username, GetHost(client)); 668 else 669 strlcpy(source, client->name, sizeof(source)); 670 671 snprintf(buf, sizeof(buf), ":%s %s %s :%s", 672 source, 673 sendtype_to_cmd(sendtype), 674 channel->name, 675 text); 676 677 history_add(channel->name, mtags, buf); 678 679 return 0; 680 } 681 682 int history_join(Client *client, Channel *channel, MessageTag *mtags) 683 { 684 /* Only for +H channels */ 685 if (!HistoryEnabled(channel) || !cfg.playback_on_join.lines || !cfg.playback_on_join.time) 686 return 0; 687 688 /* No history-on-join for clients that implement CHATHISTORY, 689 * they will pull history themselves if they need it. 690 */ 691 if (HasCapability(client, "draft/chathistory") /*|| HasCapability(client, "chathistory")*/) 692 return 0; 693 694 if (MyUser(client) && can_receive_history(client)) 695 { 696 HistoryFilter filter; 697 HistoryResult *r; 698 memset(&filter, 0, sizeof(filter)); 699 filter.cmd = HFC_SIMPLE; 700 filter.last_lines = cfg.playback_on_join.lines; 701 filter.last_seconds = cfg.playback_on_join.time; 702 r = history_request(channel->name, &filter); 703 if (r) 704 { 705 history_send_result(client, r); 706 free_history_result(r); 707 } 708 } 709 710 return 0; 711 } 712 713 /** Check if a channel went from +r to -r and adjust +H if needed. 714 * This does not only override "MODE" but also "SAMODE", "SJOIN" and more. 715 */ 716 CMD_OVERRIDE_FUNC(override_mode) 717 { 718 Channel *channel; 719 int had_r = 0; 720 721 /* We only bother checking for this corner case if the -r 722 * comes from a server directly linked to us, this normally 723 * means: we are the server that services are linked to. 724 */ 725 if ((IsServer(client) && client->local) || 726 (IsUser(client) && client->uplink && client->uplink->local)) 727 { 728 /* Now check if the channel is currently +r */ 729 if ((parc >= 2) && !BadPtr(parv[1]) && ((channel = find_channel(parv[1]))) && 730 has_channel_mode(channel, 'r')) 731 { 732 had_r = 1; 733 } 734 } 735 CALL_NEXT_COMMAND_OVERRIDE(); 736 737 /* If.. 738 * - channel was +r 739 * - re-lookup the channel and check that it still 740 * exists (as it may have been destroyed) 741 * - and is now -r 742 * - and has +H set 743 * then... 744 */ 745 if (had_r && 746 ((channel = find_channel(parv[1]))) && 747 !has_channel_mode(channel, 'r') && 748 HistoryEnabled(channel)) 749 { 750 /* Check if limit is higher than allowed for unregistered channels */ 751 HistoryChanMode *settings = (HistoryChanMode *)GETPARASTRUCT(channel, 'H'); 752 int changed = 0; 753 754 if (!settings) 755 return; /* Weird */ 756 757 if (settings->max_lines > cfg.max_storage_per_channel_unregistered.lines) 758 { 759 settings->max_lines = cfg.max_storage_per_channel_unregistered.lines; 760 changed = 1; 761 } 762 763 if (settings->max_time > cfg.max_storage_per_channel_unregistered.time) 764 { 765 settings->max_time = cfg.max_storage_per_channel_unregistered.time; 766 changed = 1; 767 } 768 769 if (changed) 770 { 771 MessageTag *mtags = NULL; 772 const char *params = history_chanmode_get_param(settings); 773 char modebuf[BUFSIZE], parabuf[BUFSIZE]; 774 int destroy_channel = 0; 775 776 if (!params) 777 return; /* Weird */ 778 779 strlcpy(modebuf, "+H", sizeof(modebuf)); 780 strlcpy(parabuf, params, sizeof(modebuf)); 781 782 new_message(&me, NULL, &mtags); 783 784 sendto_channel(channel, &me, &me, 0, 0, SEND_LOCAL, mtags, 785 ":%s MODE %s %s %s", 786 me.name, channel->name, modebuf, parabuf); 787 sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s %lld", 788 me.id, channel->name, modebuf, parabuf, 789 (long long)channel->creationtime); 790 791 /* Activate this hook just like cmd_mode.c */ 792 RunHook(HOOKTYPE_REMOTE_CHANMODE, &me, channel, mtags, modebuf, parabuf, 0, 0, &destroy_channel); 793 794 free_message_tags(mtags); 795 796 *modebuf = *parabuf = '\0'; 797 } 798 } 799 }