mirror of git://git.acid.vegas/unrealircd.git
800 lines
22 KiB
C
800 lines
22 KiB
C
/*
|
|
* modules/chanmodes/history - Channel History
|
|
* (C) Copyright 2009-2019 Bram Matthys (Syzop) and the UnrealIRCd team
|
|
* License: GPLv2 or later
|
|
*/
|
|
#include "unrealircd.h"
|
|
|
|
ModuleHeader MOD_HEADER
|
|
= {
|
|
"chanmodes/history",
|
|
"1.0",
|
|
"Channel Mode +H",
|
|
"UnrealIRCd Team",
|
|
"unrealircd-6",
|
|
};
|
|
|
|
typedef struct ConfigHistoryExt ConfigHistoryExt;
|
|
struct ConfigHistoryExt {
|
|
int lines; /**< number of lines */
|
|
long time; /**< seconds */
|
|
};
|
|
typedef struct cfgstruct cfgstruct;
|
|
struct cfgstruct {
|
|
ConfigHistoryExt playback_on_join; /**< Maximum number of lines & time to playback on-join */
|
|
ConfigHistoryExt max_storage_per_channel_registered; /**< Maximum number of lines & time to record for +r channels*/
|
|
ConfigHistoryExt max_storage_per_channel_unregistered; /**< Maximum number of lines & time to record for -r channels */
|
|
};
|
|
|
|
typedef struct HistoryChanMode HistoryChanMode;
|
|
struct HistoryChanMode {
|
|
unsigned int max_lines; /**< Maximum number of messages to record */
|
|
unsigned long max_time; /**< Maximum number of time (in seconds) to record */
|
|
};
|
|
|
|
/* Global variables */
|
|
Cmode_t EXTMODE_HISTORY = 0L;
|
|
static cfgstruct cfg;
|
|
static cfgstruct test;
|
|
|
|
#define HistoryEnabled(channel) (channel->mode.mode & EXTMODE_HISTORY)
|
|
|
|
/* Forward declarations */
|
|
static void init_config(cfgstruct *cfg);
|
|
int history_config_test(ConfigFile *, ConfigEntry *, int, int *);
|
|
int history_config_posttest(int *);
|
|
int history_config_run(ConfigFile *, ConfigEntry *, int);
|
|
int history_chanmode_change(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel);
|
|
static int compare_history_modes(HistoryChanMode *a, HistoryChanMode *b);
|
|
int history_chanmode_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
|
|
void *history_chanmode_put_param(void *r_in, const char *param);
|
|
const char *history_chanmode_get_param(void *r_in);
|
|
const char *history_chanmode_conv_param(const char *param, Client *client, Channel *channel);
|
|
int history_chanmode_free_param(void *r, int soft);
|
|
void *history_chanmode_dup_struct(void *r_in);
|
|
int history_chanmode_sjoin_check(Channel *channel, void *ourx, void *theirx);
|
|
int history_channel_destroy(Channel *channel, int *should_destroy);
|
|
int history_chanmsg(Client *client, Channel *channel, int sendflags, const char *prefix, const char *target, MessageTag *mtags, const char *text, SendType sendtype);
|
|
int history_join(Client *client, Channel *channel, MessageTag *mtags);
|
|
CMD_OVERRIDE_FUNC(override_mode);
|
|
|
|
MOD_TEST()
|
|
{
|
|
init_config(&test);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, history_config_test);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, history_config_posttest);
|
|
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_INIT()
|
|
{
|
|
CmodeInfo creq;
|
|
ModDataInfo mreq;
|
|
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
|
|
memset(&creq, 0, sizeof(creq));
|
|
creq.paracount = 1;
|
|
creq.is_ok = history_chanmode_is_ok;
|
|
creq.letter = 'H';
|
|
creq.put_param = history_chanmode_put_param;
|
|
creq.get_param = history_chanmode_get_param;
|
|
creq.conv_param = history_chanmode_conv_param;
|
|
creq.free_param = history_chanmode_free_param;
|
|
creq.dup_struct = history_chanmode_dup_struct;
|
|
creq.sjoin_check = history_chanmode_sjoin_check;
|
|
CmodeAdd(modinfo->handle, creq, &EXTMODE_HISTORY);
|
|
|
|
init_config(&cfg);
|
|
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, history_config_run);
|
|
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CHANMODE, 0, history_chanmode_change);
|
|
HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CHANMODE, 0, history_chanmode_change);
|
|
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_JOIN, 0, history_join);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CHANMSG, 0, history_chanmsg);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CHANNEL_DESTROY, 1000000, history_channel_destroy);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_LOAD()
|
|
{
|
|
CommandOverrideAdd(modinfo->handle, "MODE", 0, override_mode);
|
|
CommandOverrideAdd(modinfo->handle, "SVSMODE", 0, override_mode);
|
|
CommandOverrideAdd(modinfo->handle, "SVS2MODE", 0, override_mode);
|
|
CommandOverrideAdd(modinfo->handle, "SAMODE", 0, override_mode);
|
|
CommandOverrideAdd(modinfo->handle, "SJOIN", 0, override_mode);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_UNLOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
static void init_config(cfgstruct *cfg)
|
|
{
|
|
/* Set default values */
|
|
memset(cfg, 0, sizeof(cfgstruct));
|
|
cfg->playback_on_join.lines = 15;
|
|
cfg->playback_on_join.time = 86400;
|
|
cfg->max_storage_per_channel_unregistered.lines = 200;
|
|
cfg->max_storage_per_channel_unregistered.time = 86400*31;
|
|
cfg->max_storage_per_channel_registered.lines = 5000;
|
|
cfg->max_storage_per_channel_registered.time = 86400*31;
|
|
}
|
|
|
|
int history_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
|
|
{
|
|
int errors = 0;
|
|
ConfigEntry *cep, *cepp, *cep4, *cep5;
|
|
int on_join_lines=0, maximum_storage_lines_registered=0, maximum_storage_lines_unregistered=0;
|
|
long on_join_time=0L, maximum_storage_time_registered=0L, maximum_storage_time_unregistered=0L;
|
|
|
|
/* We only care about set::history */
|
|
if ((type != CONFIG_SET) || strcmp(ce->name, "history"))
|
|
return 0;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "channel"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "playback-on-join"))
|
|
{
|
|
for (cep4 = cepp->items; cep4; cep4 = cep4->next)
|
|
{
|
|
if (!strcmp(cep4->name, "lines"))
|
|
{
|
|
int v;
|
|
CheckNull(cep4);
|
|
v = atoi(cep4->value);
|
|
if ((v < 0) || (v > 1000))
|
|
{
|
|
config_error("%s:%i: set::history::channel::playback-on-join::lines must be between 0 and 1000. "
|
|
"Recommended values are 10-50. Got: %d.",
|
|
cep4->file->filename, cep4->line_number, v);
|
|
errors++;
|
|
continue;
|
|
}
|
|
test.playback_on_join.lines = v;
|
|
} else
|
|
if (!strcmp(cep4->name, "time"))
|
|
{
|
|
long v;
|
|
CheckNull(cep4);
|
|
v = config_checkval(cep4->value, CFG_TIME);
|
|
if (v < 0)
|
|
{
|
|
config_error("%s:%i: set::history::channel::playback-on-join::time must be zero or more.",
|
|
cep4->file->filename, cep4->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
test.playback_on_join.time = v;
|
|
} else
|
|
{
|
|
config_error_unknown(cep4->file->filename,
|
|
cep4->line_number, "set::history::channel::playback-on-join", cep4->name);
|
|
errors++;
|
|
}
|
|
}
|
|
} else
|
|
if (!strcmp(cepp->name, "max-storage-per-channel"))
|
|
{
|
|
for (cep4 = cepp->items; cep4; cep4 = cep4->next)
|
|
{
|
|
if (!strcmp(cep4->name, "registered"))
|
|
{
|
|
for (cep5 = cep4->items; cep5; cep5 = cep5->next)
|
|
{
|
|
if (!strcmp(cep5->name, "lines"))
|
|
{
|
|
int v;
|
|
CheckNull(cep5);
|
|
v = atoi(cep5->value);
|
|
if (v < 1)
|
|
{
|
|
config_error("%s:%i: set::history::channel::max-storage-per-channel::registered::lines must be a positive number.",
|
|
cep5->file->filename, cep5->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
test.max_storage_per_channel_registered.lines = v;
|
|
} else
|
|
if (!strcmp(cep5->name, "time"))
|
|
{
|
|
long v;
|
|
CheckNull(cep5);
|
|
v = config_checkval(cep5->value, CFG_TIME);
|
|
if (v < 1)
|
|
{
|
|
config_error("%s:%i: set::history::channel::max-storage-per-channel::registered::time must be a positive number.",
|
|
cep5->file->filename, cep5->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
test.max_storage_per_channel_registered.time = v;
|
|
} else
|
|
{
|
|
config_error_unknown(cep5->file->filename,
|
|
cep5->line_number, "set::history::channel::max-storage-per-channel::registered", cep5->name);
|
|
errors++;
|
|
}
|
|
}
|
|
} else
|
|
if (!strcmp(cep4->name, "unregistered"))
|
|
{
|
|
for (cep5 = cep4->items; cep5; cep5 = cep5->next)
|
|
{
|
|
if (!strcmp(cep5->name, "lines"))
|
|
{
|
|
int v;
|
|
CheckNull(cep5);
|
|
v = atoi(cep5->value);
|
|
if (v < 1)
|
|
{
|
|
config_error("%s:%i: set::history::channel::max-storage-per-channel::unregistered::lines must be a positive number.",
|
|
cep5->file->filename, cep5->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
test.max_storage_per_channel_unregistered.lines = v;
|
|
} else
|
|
if (!strcmp(cep5->name, "time"))
|
|
{
|
|
long v;
|
|
CheckNull(cep5);
|
|
v = config_checkval(cep5->value, CFG_TIME);
|
|
if (v < 1)
|
|
{
|
|
config_error("%s:%i: set::history::channel::max-storage-per-channel::unregistered::time must be a positive number.",
|
|
cep5->file->filename, cep5->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
test.max_storage_per_channel_unregistered.time = v;
|
|
} else
|
|
{
|
|
config_error_unknown(cep5->file->filename,
|
|
cep5->line_number, "set::history::channel::max-storage-per-channel::unregistered", cep5->name);
|
|
errors++;
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
config_error_unknown(cep->file->filename,
|
|
cep->line_number, "set::history::max-storage-per-channel", cep->name);
|
|
errors++;
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
/* hmm.. I don't like this method. but I just quickly copied it from CONFIG_ALLOW for now... */
|
|
int used = 0;
|
|
Hook *h;
|
|
for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
|
|
{
|
|
int value, errs = 0;
|
|
if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
|
|
&& !(h->owner->options & MOD_OPT_PERM))
|
|
continue;
|
|
value = (*(h->func.intfunc))(cf, cepp, CONFIG_SET_HISTORY_CHANNEL, &errs);
|
|
if (value == 2)
|
|
used = 1;
|
|
if (value == 1)
|
|
{
|
|
used = 1;
|
|
break;
|
|
}
|
|
if (value == -1)
|
|
{
|
|
used = 1;
|
|
errors += errs;
|
|
break;
|
|
}
|
|
if (value == -2)
|
|
{
|
|
used = 1;
|
|
errors += errs;
|
|
}
|
|
}
|
|
if (!used)
|
|
{
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::history::channel", cepp->name);
|
|
errors++;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
config_error_unknown(cep->file->filename,
|
|
cep->line_number, "set::history", cep->name);
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
*errs = errors;
|
|
return errors ? -1 : 1;
|
|
}
|
|
|
|
int history_config_posttest(int *errs)
|
|
{
|
|
int errors = 0;
|
|
|
|
/* We could check here for on join lines / on join time being bigger than max storage but..
|
|
* not really important.
|
|
*/
|
|
|
|
*errs = errors;
|
|
return errors ? -1 : 1;
|
|
}
|
|
|
|
int history_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
|
|
{
|
|
ConfigEntry *cep, *cepp, *cep4, *cep5;
|
|
|
|
if ((type != CONFIG_SET) || strcmp(ce->name, "history"))
|
|
return 0;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "channel"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "playback-on-join"))
|
|
{
|
|
for (cep4 = cepp->items; cep4; cep4 = cep4->next)
|
|
{
|
|
if (!strcmp(cep4->name, "lines"))
|
|
{
|
|
cfg.playback_on_join.lines = atoi(cep4->value);
|
|
} else
|
|
if (!strcmp(cep4->name, "time"))
|
|
{
|
|
cfg.playback_on_join.time = config_checkval(cep4->value, CFG_TIME);
|
|
}
|
|
}
|
|
} else
|
|
if (!strcmp(cepp->name, "max-storage-per-channel"))
|
|
{
|
|
for (cep4 = cepp->items; cep4; cep4 = cep4->next)
|
|
{
|
|
if (!strcmp(cep4->name, "registered"))
|
|
{
|
|
for (cep5 = cep4->items; cep5; cep5 = cep5->next)
|
|
{
|
|
if (!strcmp(cep5->name, "lines"))
|
|
{
|
|
cfg.max_storage_per_channel_registered.lines = atoi(cep5->value);
|
|
} else
|
|
if (!strcmp(cep5->name, "time"))
|
|
{
|
|
cfg.max_storage_per_channel_registered.time = config_checkval(cep5->value, CFG_TIME);
|
|
}
|
|
}
|
|
} else
|
|
if (!strcmp(cep4->name, "unregistered"))
|
|
{
|
|
for (cep5 = cep4->items; cep5; cep5 = cep5->next)
|
|
{
|
|
if (!strcmp(cep5->name, "lines"))
|
|
{
|
|
cfg.max_storage_per_channel_unregistered.lines = atoi(cep5->value);
|
|
} else
|
|
if (!strcmp(cep5->name, "time"))
|
|
{
|
|
cfg.max_storage_per_channel_unregistered.time = config_checkval(cep5->value, CFG_TIME);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
Hook *h;
|
|
for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
|
|
{
|
|
int value = (*(h->func.intfunc))(cf, cepp, CONFIG_SET_HISTORY_CHANNEL);
|
|
if (value == 1)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0; /* Retval 0 = trick so other modules can see the same configuration */
|
|
}
|
|
|
|
/** Helper function for .is_ok(), .conv_param() and .put_param().
|
|
* @param param: The mode parameter.
|
|
* @param lines: The number of lines (the X in +H X:Y)
|
|
* @param t: The time value (the Y in +H X:Y)
|
|
*/
|
|
int history_parse_chanmode(Channel *channel, const char *param, int *lines, long *t)
|
|
{
|
|
char buf[64], *p, *q;
|
|
char contains_non_digit = 0;
|
|
|
|
/* Work on a copy */
|
|
strlcpy(buf, param, sizeof(buf));
|
|
|
|
/* Initialize, to be safe */
|
|
*lines = 0;
|
|
*t = 0;
|
|
|
|
p = strchr(buf, ':');
|
|
if (!p)
|
|
return 0;
|
|
|
|
/* Parse lines */
|
|
*p++ = '\0';
|
|
*lines = atoi(buf);
|
|
|
|
/* Parse time value */
|
|
/* If it is all digits then it is in minutes */
|
|
for (q=p; *q; q++)
|
|
{
|
|
if (!isdigit(*q))
|
|
{
|
|
contains_non_digit = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (contains_non_digit)
|
|
*t = config_checkval(p, CFG_TIME);
|
|
else
|
|
*t = atoi(p) * 60;
|
|
|
|
/* Sanity checking... */
|
|
if (*lines < 1)
|
|
return 0;
|
|
|
|
if (*t < 60)
|
|
return 0;
|
|
|
|
/* Check imposed configuration limits... */
|
|
if (!channel || has_channel_mode(channel, 'r'))
|
|
{
|
|
if (*lines > cfg.max_storage_per_channel_registered.lines)
|
|
*lines = cfg.max_storage_per_channel_registered.lines;
|
|
|
|
if (*t > cfg.max_storage_per_channel_registered.time)
|
|
*t = cfg.max_storage_per_channel_registered.time;
|
|
} else {
|
|
if (*lines > cfg.max_storage_per_channel_unregistered.lines)
|
|
*lines = cfg.max_storage_per_channel_unregistered.lines;
|
|
|
|
if (*t > cfg.max_storage_per_channel_unregistered.time)
|
|
*t = cfg.max_storage_per_channel_unregistered.time;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/** Channel Mode +H check:
|
|
* Does the user have rights to add/remove this channel mode?
|
|
* Is the supplied mode parameter ok?
|
|
*/
|
|
int history_chanmode_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
|
|
{
|
|
if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
|
|
{
|
|
if (IsUser(client) && check_channel_access(client, channel, "oaq"))
|
|
return EX_ALLOW;
|
|
if (type == EXCHK_ACCESS_ERR) /* can only be due to being halfop */
|
|
sendnumeric(client, ERR_NOTFORHALFOPS, 'H');
|
|
return EX_DENY;
|
|
} else
|
|
if (type == EXCHK_PARAM)
|
|
{
|
|
int lines = 0;
|
|
long t = 0L;
|
|
|
|
if (!history_parse_chanmode(channel, param, &lines, &t))
|
|
{
|
|
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).");
|
|
return EX_DENY;
|
|
}
|
|
/* Don't bother about lines/t limits here, we will auto-convert in .conv_param */
|
|
|
|
return EX_ALLOW;
|
|
}
|
|
|
|
/* fallthrough -- should not be used */
|
|
return EX_DENY;
|
|
}
|
|
|
|
static void history_chanmode_helper(char *buf, size_t bufsize, int lines, long t)
|
|
{
|
|
if ((t % 86400) == 0)
|
|
{
|
|
/* Can be represented in full days, eg "1d" */
|
|
snprintf(buf, bufsize, "%d:%ldd", lines, t / 86400);
|
|
} else
|
|
if ((t % 3600) == 0)
|
|
{
|
|
/* Can be represented in hours, eg "8h" */
|
|
snprintf(buf, bufsize, "%d:%ldh", lines, t / 3600);
|
|
} else
|
|
{
|
|
/* Otherwise, stick to minutes */
|
|
snprintf(buf, bufsize, "%d:%ldm", lines, t / 60);
|
|
}
|
|
}
|
|
|
|
/** Convert channel parameter to something proper.
|
|
* NOTE: client may be NULL if called for e.g. set::modes-playback-on-join
|
|
*/
|
|
const char *history_chanmode_conv_param(const char *param, Client *client, Channel *channel)
|
|
{
|
|
static char buf[64];
|
|
int lines = 0;
|
|
long t = 0L;
|
|
|
|
if (!history_parse_chanmode(channel, param, &lines, &t))
|
|
return NULL;
|
|
|
|
history_chanmode_helper(buf, sizeof(buf), lines, t);
|
|
return buf;
|
|
}
|
|
|
|
/** Store the +H x:y channel mode */
|
|
void *history_chanmode_put_param(void *mode_in, const char *param)
|
|
{
|
|
HistoryChanMode *h = (HistoryChanMode *)mode_in;
|
|
int lines = 0;
|
|
long t = 0L;
|
|
|
|
if (!history_parse_chanmode(NULL, param, &lines, &t))
|
|
return NULL;
|
|
|
|
if (!h)
|
|
{
|
|
/* Need to create one */
|
|
h = safe_alloc(sizeof(HistoryChanMode));
|
|
}
|
|
|
|
h->max_lines = lines;
|
|
h->max_time = t;
|
|
|
|
return (void *)h;
|
|
}
|
|
|
|
/** Retrieve the +H settings (the X:Y string) */
|
|
const char *history_chanmode_get_param(void *h_in)
|
|
{
|
|
HistoryChanMode *h = (HistoryChanMode *)h_in;
|
|
static char buf[64];
|
|
|
|
if (!h_in)
|
|
return NULL;
|
|
|
|
history_chanmode_helper(buf, sizeof(buf), h->max_lines, h->max_time);
|
|
return buf;
|
|
}
|
|
|
|
/** Free channel mode */
|
|
int history_chanmode_free_param(void *r, int soft)
|
|
{
|
|
safe_free(r);
|
|
return 0;
|
|
}
|
|
|
|
/** Duplicate the channel mode +H settings */
|
|
void *history_chanmode_dup_struct(void *r_in)
|
|
{
|
|
HistoryChanMode *r = (HistoryChanMode *)r_in;
|
|
HistoryChanMode *w = safe_alloc(sizeof(HistoryChanMode));
|
|
|
|
memcpy(w, r, sizeof(HistoryChanMode));
|
|
return (void *)w;
|
|
}
|
|
|
|
/** If two servers with an identical creation time stamp connect,
|
|
* we have to deal with merging the settings on different sides
|
|
* (if they differ at all). That's what we do here.
|
|
*/
|
|
int history_chanmode_sjoin_check(Channel *channel, void *ourx, void *theirx)
|
|
{
|
|
HistoryChanMode *our = (HistoryChanMode *)ourx;
|
|
HistoryChanMode *their = (HistoryChanMode *)theirx;
|
|
|
|
if ((our->max_lines == their->max_lines) && (our->max_time == their->max_time))
|
|
return EXSJ_SAME;
|
|
|
|
our->max_lines = MAX(our->max_lines, their->max_lines);
|
|
our->max_time = MAX(our->max_time, their->max_time);
|
|
|
|
return EXSJ_MERGE;
|
|
}
|
|
|
|
/** On channel mode change, communicate the +H limits to the history backend layer */
|
|
int history_chanmode_change(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel)
|
|
{
|
|
HistoryChanMode *settings;
|
|
|
|
/* Did anything change, with regards to channel mode H ? */
|
|
if (!strchr(modebuf, 'H'))
|
|
return 0;
|
|
|
|
/* If so, grab the settings, and communicate them */
|
|
settings = (HistoryChanMode *)GETPARASTRUCT(channel, 'H');
|
|
if (settings)
|
|
history_set_limit(channel->name, settings->max_lines, settings->max_time);
|
|
else
|
|
history_destroy(channel->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Channel is destroyed (or is it?) */
|
|
int history_channel_destroy(Channel *channel, int *should_destroy)
|
|
{
|
|
if (*should_destroy == 0)
|
|
return 0; /* channel will not be destroyed */
|
|
|
|
history_destroy(channel->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int history_chanmsg(Client *client, Channel *channel, int sendflags, const char *prefix, const char *target, MessageTag *mtags, const char *text, SendType sendtype)
|
|
{
|
|
char buf[512];
|
|
char source[64];
|
|
HistoryChanMode *settings;
|
|
|
|
if (!HistoryEnabled(channel))
|
|
return 0;
|
|
|
|
/* Filter out CTCP / CTCP REPLY */
|
|
if ((*text == '\001') && strncmp(text+1, "ACTION", 6))
|
|
return 0;
|
|
|
|
/* Filter out TAGMSG */
|
|
if (sendtype == SEND_TYPE_TAGMSG)
|
|
return 0;
|
|
|
|
/* Lazy: if any prefix is addressed (eg: @#channel) then don't record it.
|
|
* This so we don't have to check privileges during history playback etc.
|
|
*/
|
|
if (prefix)
|
|
return 0;
|
|
|
|
if (IsUser(client))
|
|
snprintf(source, sizeof(source), "%s!%s@%s", client->name, client->user->username, GetHost(client));
|
|
else
|
|
strlcpy(source, client->name, sizeof(source));
|
|
|
|
snprintf(buf, sizeof(buf), ":%s %s %s :%s",
|
|
source,
|
|
sendtype_to_cmd(sendtype),
|
|
channel->name,
|
|
text);
|
|
|
|
history_add(channel->name, mtags, buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int history_join(Client *client, Channel *channel, MessageTag *mtags)
|
|
{
|
|
/* Only for +H channels */
|
|
if (!HistoryEnabled(channel) || !cfg.playback_on_join.lines || !cfg.playback_on_join.time)
|
|
return 0;
|
|
|
|
/* No history-on-join for clients that implement CHATHISTORY,
|
|
* they will pull history themselves if they need it.
|
|
*/
|
|
if (HasCapability(client, "draft/chathistory") /*|| HasCapability(client, "chathistory")*/)
|
|
return 0;
|
|
|
|
if (MyUser(client) && can_receive_history(client))
|
|
{
|
|
HistoryFilter filter;
|
|
HistoryResult *r;
|
|
memset(&filter, 0, sizeof(filter));
|
|
filter.cmd = HFC_SIMPLE;
|
|
filter.last_lines = cfg.playback_on_join.lines;
|
|
filter.last_seconds = cfg.playback_on_join.time;
|
|
r = history_request(channel->name, &filter);
|
|
if (r)
|
|
{
|
|
history_send_result(client, r);
|
|
free_history_result(r);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Check if a channel went from +r to -r and adjust +H if needed.
|
|
* This does not only override "MODE" but also "SAMODE", "SJOIN" and more.
|
|
*/
|
|
CMD_OVERRIDE_FUNC(override_mode)
|
|
{
|
|
Channel *channel;
|
|
int had_r = 0;
|
|
|
|
/* We only bother checking for this corner case if the -r
|
|
* comes from a server directly linked to us, this normally
|
|
* means: we are the server that services are linked to.
|
|
*/
|
|
if ((IsServer(client) && client->local) ||
|
|
(IsUser(client) && client->uplink && client->uplink->local))
|
|
{
|
|
/* Now check if the channel is currently +r */
|
|
if ((parc >= 2) && !BadPtr(parv[1]) && ((channel = find_channel(parv[1]))) &&
|
|
has_channel_mode(channel, 'r'))
|
|
{
|
|
had_r = 1;
|
|
}
|
|
}
|
|
CALL_NEXT_COMMAND_OVERRIDE();
|
|
|
|
/* If..
|
|
* - channel was +r
|
|
* - re-lookup the channel and check that it still
|
|
* exists (as it may have been destroyed)
|
|
* - and is now -r
|
|
* - and has +H set
|
|
* then...
|
|
*/
|
|
if (had_r &&
|
|
((channel = find_channel(parv[1]))) &&
|
|
!has_channel_mode(channel, 'r') &&
|
|
HistoryEnabled(channel))
|
|
{
|
|
/* Check if limit is higher than allowed for unregistered channels */
|
|
HistoryChanMode *settings = (HistoryChanMode *)GETPARASTRUCT(channel, 'H');
|
|
int changed = 0;
|
|
|
|
if (!settings)
|
|
return; /* Weird */
|
|
|
|
if (settings->max_lines > cfg.max_storage_per_channel_unregistered.lines)
|
|
{
|
|
settings->max_lines = cfg.max_storage_per_channel_unregistered.lines;
|
|
changed = 1;
|
|
}
|
|
|
|
if (settings->max_time > cfg.max_storage_per_channel_unregistered.time)
|
|
{
|
|
settings->max_time = cfg.max_storage_per_channel_unregistered.time;
|
|
changed = 1;
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
MessageTag *mtags = NULL;
|
|
const char *params = history_chanmode_get_param(settings);
|
|
char modebuf[BUFSIZE], parabuf[BUFSIZE];
|
|
int destroy_channel = 0;
|
|
|
|
if (!params)
|
|
return; /* Weird */
|
|
|
|
strlcpy(modebuf, "+H", sizeof(modebuf));
|
|
strlcpy(parabuf, params, sizeof(modebuf));
|
|
|
|
new_message(&me, NULL, &mtags);
|
|
|
|
sendto_channel(channel, &me, &me, 0, 0, SEND_LOCAL, mtags,
|
|
":%s MODE %s %s %s",
|
|
me.name, channel->name, modebuf, parabuf);
|
|
sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s %lld",
|
|
me.id, channel->name, modebuf, parabuf,
|
|
(long long)channel->creationtime);
|
|
|
|
/* Activate this hook just like cmd_mode.c */
|
|
RunHook(HOOKTYPE_REMOTE_CHANMODE, &me, channel, mtags, modebuf, parabuf, 0, 0, &destroy_channel);
|
|
|
|
free_message_tags(mtags);
|
|
|
|
*modebuf = *parabuf = '\0';
|
|
}
|
|
}
|
|
}
|