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 }