pissircd/src/modules/chanmodes/censor.c

339 lines
7.7 KiB
C

/*
* Channel Mode +G
* (C) Copyright 2005-current Bram Matthys and The UnrealIRCd team.
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"chanmodes/censor",
"4.2",
"Channel Mode +G",
"UnrealIRCd Team",
"unrealircd-6",
};
Cmode_t EXTMODE_CENSOR = 0L;
#define IsCensored(x) ((x)->mode.mode & EXTMODE_CENSOR)
int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
const char *censor_pre_local_part(Client *client, Channel *channel, const char *text);
const char *censor_pre_local_quit(Client *client, const char *text);
int censor_stats_badwords_channel(Client *client, const char *para);
int censor_config_test(ConfigFile *, ConfigEntry *, int, int *);
int censor_config_run(ConfigFile *, ConfigEntry *, int);
ModuleInfo *ModInfo = NULL;
ConfigItem_badword *conf_badword_channel = NULL;
MOD_TEST()
{
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, censor_config_test);
return MOD_SUCCESS;
}
MOD_INIT()
{
CmodeInfo req;
ModInfo = modinfo;
MARK_AS_OFFICIAL_MODULE(modinfo);
memset(&req, 0, sizeof(req));
req.paracount = 0;
req.is_ok = extcmode_default_requirechop;
req.letter = 'G';
CmodeAdd(modinfo->handle, req, &EXTMODE_CENSOR);
HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, censor_can_send_to_channel);
HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, censor_pre_local_part);
HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, 0, censor_pre_local_quit);
HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, censor_stats_badwords_channel);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, censor_config_run);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
ConfigItem_badword *badword, *next;
for (badword = conf_badword_channel; badword; badword = next)
{
next = badword->next;
DelListItem(badword, conf_badword_channel);
badword_config_free(badword);
}
return MOD_SUCCESS;
}
int censor_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
int errors = 0;
ConfigEntry *cep;
char has_word = 0, has_replace = 0, has_action = 0, action = 'r';
if (type != CONFIG_MAIN)
return 0;
if (!ce || !ce->name || strcmp(ce->name, "badword"))
return 0; /* not interested */
if (!ce->value)
{
config_error("%s:%i: badword without type",
ce->file->filename, ce->line_number);
*errs = 1;
return -1;
}
else if (strcmp(ce->value, "channel") &&
strcmp(ce->value, "quit") && strcmp(ce->value, "all")) {
/* config_error("%s:%i: badword with unknown type",
ce->file->filename, ce->line_number); -- can't do that.. */
return 0; /* unhandled */
}
if (!strcmp(ce->value, "quit"))
{
config_error("%s:%i: badword quit has been removed. We just use the bad words from "
"badword channel { } instead.",
ce->file->filename, ce->line_number);
return 0; /* pretend unhandled.. ok not just pretend.. ;) */
}
for (cep = ce->items; cep; cep = cep->next)
{
if (config_is_blankorempty(cep, "badword"))
{
errors++;
continue;
}
if (!strcmp(cep->name, "word"))
{
const char *errbuf;
if (has_word)
{
config_warn_duplicate(cep->file->filename,
cep->line_number, "badword::word");
continue;
}
has_word = 1;
if ((errbuf = badword_config_check_regex(cep->value,1,1)))
{
config_error("%s:%i: badword::%s contains an invalid regex: %s",
cep->file->filename,
cep->line_number,
cep->name, errbuf);
errors++;
}
}
else if (!strcmp(cep->name, "replace"))
{
if (has_replace)
{
config_warn_duplicate(cep->file->filename,
cep->line_number, "badword::replace");
continue;
}
has_replace = 1;
}
else if (!strcmp(cep->name, "action"))
{
if (has_action)
{
config_warn_duplicate(cep->file->filename,
cep->line_number, "badword::action");
continue;
}
has_action = 1;
if (!strcmp(cep->value, "replace"))
action = 'r';
else if (!strcmp(cep->value, "block"))
action = 'b';
else
{
config_error("%s:%d: Unknown badword::action '%s'",
cep->file->filename, cep->line_number,
cep->value);
errors++;
}
}
else
{
config_error_unknown(cep->file->filename, cep->line_number,
"badword", cep->name);
errors++;
}
}
if (!has_word)
{
config_error_missing(ce->file->filename, ce->line_number,
"badword::word");
errors++;
}
if (has_action)
{
if (has_replace && action == 'b')
{
config_error("%s:%i: badword::action is block but badword::replace exists",
ce->file->filename, ce->line_number);
errors++;
}
}
*errs = errors;
return errors ? -1 : 1;
}
int censor_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
ConfigEntry *cep, *word = NULL;
ConfigItem_badword *ca;
if (type != CONFIG_MAIN)
return 0;
if (!ce || !ce->name || strcmp(ce->name, "badword"))
return 0; /* not interested */
if (strcmp(ce->value, "channel") && strcmp(ce->value, "all"))
return 0; /* not for us */
ca = safe_alloc(sizeof(ConfigItem_badword));
ca->action = BADWORD_REPLACE;
for (cep = ce->items; cep; cep = cep->next)
{
if (!strcmp(cep->name, "action"))
{
if (!strcmp(cep->value, "block"))
{
ca->action = BADWORD_BLOCK;
}
}
else if (!strcmp(cep->name, "replace"))
{
safe_strdup(ca->replace, cep->value);
} else
if (!strcmp(cep->name, "word"))
{
word = cep;
}
}
badword_config_process(ca, word->value);
if (!strcmp(ce->value, "channel"))
AddListItem(ca, conf_badword_channel);
else if (!strcmp(ce->value, "all"))
{
AddListItem(ca, conf_badword_channel);
return 0; /* pretend we didn't see it, so other modules can handle 'all' as well */
}
return 1;
}
const char *stripbadwords_channel(const char *str, int *blocked)
{
return stripbadwords(str, conf_badword_channel, blocked);
}
int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
{
int blocked;
Hook *h;
int i;
if (!IsCensored(channel))
return HOOK_CONTINUE;
for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
{
i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_CENSOR);
if (i == HOOK_ALLOW)
return HOOK_CONTINUE; /* bypass censor restriction */
if (i != HOOK_CONTINUE)
break;
}
*msg = stripbadwords_channel(*msg, &blocked);
if (blocked)
{
*errmsg = "Swearing is not permitted in this channel";
return HOOK_DENY;
}
return HOOK_CONTINUE;
}
const char *censor_pre_local_part(Client *client, Channel *channel, const char *text)
{
int blocked;
if (!text)
return NULL;
if (!IsCensored(channel))
return text;
text = stripbadwords_channel(text, &blocked);
return blocked ? NULL : text;
}
/** Is any channel where the user is in +G? */
static int IsAnyChannelCensored(Client *client)
{
Membership *lp;
for (lp = client->user->channel; lp; lp = lp->next)
if (IsCensored(lp->channel))
return 1;
return 0;
}
const char *censor_pre_local_quit(Client *client, const char *text)
{
int blocked = 0;
if (!text)
return NULL;
if (IsAnyChannelCensored(client))
text = stripbadwords_channel(text, &blocked);
return blocked ? NULL : text;
}
int censor_stats_badwords_channel(Client *client, const char *para)
{
ConfigItem_badword *words;
if (!para || !(!strcmp(para, "b") || !strcasecmp(para, "badword")))
return 0;
for (words = conf_badword_channel; words; words = words->next)
{
sendtxtnumeric(client, "c %c %s%s%s %s", words->type & BADW_TYPE_REGEX ? 'R' : 'F',
(words->type & BADW_TYPE_FAST_L) ? "*" : "", words->word,
(words->type & BADW_TYPE_FAST_R) ? "*" : "",
words->action == BADWORD_REPLACE ? (words->replace ? words->replace : "<censored>") : "");
}
return 1;
}