#define KEY_INFO "info"
#define KEY_EXCEPTLEVEL "exceptlevel"
#define KEY_EXPIRY "expiry"
+#define KEY_LASTBADWORDID "last_badword_id"
+#define KEY_BADWORDS "badwords"
+
+#define KEY_BADWORD_MASK "mask"
+#define KEY_BADWORD_TRIGGERED "count"
+#define KEY_BADWORD_ACTION "action"
+#define KEY_BADWORDID "badwordid"
#define KEY_DEBUG_CHANNEL "debug_channel"
#define KEY_OPER_CHANNEL "oper_channel"
{ "SSMSG_STATUS_MEMORY", "$bMemory Information:$b" },
{ "SSMSG_STATUS_CHANNEL_LIST", "$bRegistered Channels:$b" },
{ "SSMSG_STATUS_NO_CHANNEL", "No channels registered." },
+
+ { "SSMSG_BADWORD_ALREADY_ADDED", "$b%s$b is already added. (ID: %s)" },
+ { "SSMSG_BADWORD_ADDED", "added '$b%s$b' to the badword list with ID %s." },
+ { "SSMSG_BADWORD_SET_DONE", "Done." },
+ { "SSMSG_BADWORD_SET_INVALID", "Invalid Option for setting %s" },
+ { "SSMSG_BADWORD_SET", "Settings for BadWord entry $b%s$b" },
+ { "SSMSG_BADWORD_SET_MASK", "$bMASK$b: %s" },
+ { "SSMSG_BADWORD_SET_ACTION", "$bACTION$b: %s" },
+ { "SSMSG_BADWORD_NOT_FOUND", "badword with ID %s does not exist." },
+ { "SSMSG_BADWORD_REMOVED", "badword ID $b%s$b has been removed (mask: '%s')" },
{ NULL, NULL }
};
#define SSMSG_WARNING_2 "You are violating the network rules"
#define SSMSG_WARNING_RULES "%s is against the network rules. Read the network rules at %s"
#define SSMSG_WARNING_RULES_2 "You are violating the network rules. Read the network rules at %s"
+#define SSMSG_BADWORD_DETECTED "Your message contained a forbidden word."
#define SSMSG_CHANNEL_REGISTERED "%s (channel %s) registered by %s."
#define SSMSG_CHANNEL_UNREGISTERED "%s (channel %s) unregistered by %s."
}
}
+static struct badword*
+add_badword(struct chanInfo *cInfo, const char *badword_mask, unsigned int triggered, unsigned int action, const char *id)
+{
+ struct badword *badword;
+
+ badword = calloc(1, sizeof(*badword));
+ if (!badword)
+ return NULL;
+
+ if(!id) {
+ cInfo->last_badword_id++;
+ badword->id = strtab(cInfo->last_badword_id);
+ } else
+ badword->id = strdup(id);
+ badword->badword_mask = strdup(badword_mask);
+ badword->triggered = triggered;
+ badword->action = action;
+ dict_insert(cInfo->badwords, badword->id, badword);
+ return badword;
+}
+
/***********************************************/
/* Other Stuff */
/***********************************************/
spamserv_unregister_channel(cInfo);
- spamserv_oper_message(SSMSG_CHANNEL_UNREGISTERED, channel->name, user->handle_info->handle);
+ spamserv_oper_message(SSMSG_CHANNEL_UNREGISTERED, spamserv->nick, channel->name, user->handle_info->handle);
ss_reply("SSMSG_UNREG_SUCCESS", channel->name);
return 1;
return 1;
}
+static SPAMSERV_FUNC(cmd_addbad)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ struct userData *uData;
+
+ if(!cInfo)
+ {
+ ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+ return 0;
+ }
+
+ if(CHECK_SUSPENDED(cInfo))
+ {
+ ss_reply("SSMSG_SUSPENDED", channel->name);
+ return 0;
+ }
+
+ if(!check_user_level(channel,user,lvlSetters,1,0))
+ {
+ ss_reply("SSMSG_NO_ACCESS");
+ return 0;
+ }
+
+ dict_iterator_t it;
+ char *mask = unsplit_string(argv + 1, argc - 1, NULL);
+ for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) {
+ struct badword *badword = iter_data(it);
+ if(match_ircglob(mask,badword->badword_mask)) {
+ reply("SSMSG_BADWORD_ALREADY_ADDED", mask, badword->id);
+ return 1;
+ }
+ }
+
+ struct badword *new_badword = add_badword(cInfo, mask, 0, BADACTION_KICK, NULL);
+ for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) {
+ struct badword *badword = iter_data(it);
+ if(match_ircglob(badword->badword_mask, new_badword->badword_mask) && badword != new_badword) {
+ dict_remove(cInfo->badwords, badword->id);
+ }
+ }
+
+ reply("SSMSG_BADWORD_ADDED", new_badword->badword_mask, new_badword->id);
+ return 1;
+}
+
+static SPAMSERV_FUNC(cmd_delbad)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ struct userData *uData;
+ unsigned int n;
+
+ if(!cInfo)
+ {
+ ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+ return 0;
+ }
+
+ if(CHECK_SUSPENDED(cInfo))
+ {
+ ss_reply("SSMSG_SUSPENDED", channel->name);
+ return 0;
+ }
+
+ if(!check_user_level(channel,user,lvlSetters,1,0))
+ {
+ ss_reply("SSMSG_NO_ACCESS");
+ return 0;
+ }
+
+ for (n=1; n<argc; n++) {
+ struct badword *badword = dict_find(cInfo->badwords, argv[n], NULL);
+ if (!badword) {
+ reply("SSMSG_BADWORD_NOT_FOUND", argv[n]);
+ continue;
+ }
+ reply("SSMSG_BADWORD_REMOVED", argv[n], badword->badword_mask);
+ dict_remove(cInfo->badwords, argv[n]);
+ }
+ return 1;
+}
+
+static SPAMSERV_FUNC(cmd_setbad)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ struct userData *uData;
+ struct badword *badword;
+
+ if(!cInfo)
+ {
+ ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+ return 0;
+ }
+
+ if(CHECK_SUSPENDED(cInfo))
+ {
+ ss_reply("SSMSG_SUSPENDED", channel->name);
+ return 0;
+ }
+
+ if(!check_user_level(channel,user,lvlSetters,1,0))
+ {
+ ss_reply("SSMSG_NO_ACCESS");
+ return 0;
+ }
+
+ if ((badword = dict_find(cInfo->badwords, argv[1], NULL))) {
+ if (argc > 3) {
+ unsigned int ii;
+ char *setting = argv[2];
+ char *value = argv[3];
+ for( ii = 0; setting[ ii ]; ii++)
+ setting[ ii ] = toupper( setting[ ii ] );
+ for( ii = 0; value[ ii ]; ii++)
+ value[ ii ] = toupper( value[ ii ] );
+ if(!strcmp("MASK",setting)) {
+ free(badword->badword_mask);
+ badword->badword_mask = strdup(argv[3]);
+ badword->triggered = 0;
+ reply("SSMSG_BADWORD_SET_DONE");
+ }
+ else if(!strcmp("ACTION",setting)) {
+ if (!strcmp("1",value) || !strcmp("KICK",value)) {
+ badword->action = BADACTION_KICK;
+ reply("SSMSG_BADWORD_SET_DONE");
+ } else if (!strcmp("2",value) || !strcmp("BAN",value)) {
+ badword->action = BADACTION_BAN;
+ reply("SSMSG_BADWORD_SET_DONE");
+ } else if (!strcmp("3",value) || !strcmp("KILL",value)) {
+ badword->action = BADACTION_KILL;
+ reply("SSMSG_BADWORD_SET_DONE");
+ } else if (!strcmp("4",value) || !strcmp("GLINE",value)) {
+ badword->action = BADACTION_GLINE;
+ reply("SSMSG_BADWORD_SET_DONE");
+ } else {
+ reply("SSMSG_BADWORD_SET_INVALID", setting);
+ }
+ } else {
+ reply("SSMSG_BADWORD_SETTING_INVALID", setting);
+ }
+
+ } else {
+ reply("SSMSG_BADWORD_SET", badword->id);
+ reply("SSMSG_BADWORD_SET_MASK", badword->badword_mask);
+ switch(badword->action) {
+ case BADACTION_KICK:
+ reply("SSMSG_BADWORD_SET_ACTION", "KICK");
+ break;
+ case BADACTION_BAN:
+ reply("SSMSG_BADWORD_SET_ACTION", "BAN");
+ break;
+ case BADACTION_KILL:
+ reply("SSMSG_BADWORD_SET_ACTION", "KILL");
+ break;
+ case BADACTION_GLINE:
+ reply("SSMSG_BADWORD_SET_ACTION", "GLINE");
+ break;
+ default:
+ reply("SSMSG_BADWORD_SET_ACTION", "*undef*");
+ }
+ }
+ } else {
+ reply("SSMSG_BADWORD_NOT_FOUND", argv[1]);
+ return 0;
+ }
+ return 1;
+}
+
+int
+ss_badwords_sort(const void *pa, const void *pb)
+{
+ struct badword *a = *(struct badword**)pa;
+ struct badword *b = *(struct badword**)pb;
+
+ return strtoul(a->id, NULL, 0) - strtoul(b->id, NULL, 0);
+}
+
+static SPAMSERV_FUNC(cmd_listbad)
+{
+ struct helpfile_table tbl;
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ struct userData *uData;
+ struct badword **badwords;
+ unsigned int count = 0, ii = 0;
+
+ if(!cInfo)
+ {
+ ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+ return 0;
+ }
+
+ if(CHECK_SUSPENDED(cInfo))
+ {
+ ss_reply("SSMSG_SUSPENDED", channel->name);
+ return 0;
+ }
+
+ if(!check_user_level(channel,user,lvlSetters,1,0))
+ {
+ ss_reply("SSMSG_NO_ACCESS");
+ return 0;
+ }
+
+ dict_iterator_t it;
+ for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) {
+ count++;
+ }
+
+ tbl.length = count+1;
+ tbl.width = 4;
+ tbl.flags = 0;
+ tbl.flags = TABLE_NO_FREE;
+ tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
+ tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+ tbl.contents[0][0] = "#";
+ tbl.contents[0][1] = "Badword";
+ tbl.contents[0][2] = "Action";
+ tbl.contents[0][3] = "(Triggered)";
+ if(!count)
+ {
+ table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+ reply("MSG_NONE");
+ free(tbl.contents[0]);
+ free(tbl.contents);
+ return 0;
+ }
+ badwords = alloca(count * sizeof(badwords[0]));
+ for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) {
+ struct badword *bw = iter_data(it);
+ badwords[ii++] = bw;
+ }
+
+ qsort(badwords, count, sizeof(badwords[0]), ss_badwords_sort);
+ for (ii = 1; ii <= count; ii++) {
+ struct badword *bw = badwords[ii-1];
+ tbl.contents[ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+ tbl.contents[ii][0] = strdup(bw->id);
+ tbl.contents[ii][1] = strdup(bw->badword_mask);
+ switch(bw->action) {
+ case BADACTION_KICK:
+ tbl.contents[ii][2] = "KICK";
+ break;
+ case BADACTION_BAN:
+ tbl.contents[ii][2] = "BAN";
+ break;
+ case BADACTION_KILL:
+ tbl.contents[ii][2] = "KILL";
+ break;
+ case BADACTION_GLINE:
+ tbl.contents[ii][2] = "GLINE";
+ break;
+ default:
+ tbl.contents[ii][2] = "*undef*";
+ }
+ tbl.contents[ii][3] = strtab(bw->triggered);
+ }
+
+ table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+ for(ii = 1; ii < tbl.length; ++ii)
+ {
+ free(tbl.contents[ii]);
+ }
+ free(tbl.contents[0]);
+ free(tbl.contents);
+ return 1;
+}
+
static void
to_lower(char *message)
{
KickChannelUser(user, channel, spamserv, reason);
}
+static void
+spamserv_detected_badword(struct userNode *user, struct chanNode *chan, struct badword *badword)
+{
+ char *hostmask;
+ char *reason = SSMSG_BADWORD_DETECTED;
+ char mask[IRC_NTOP_MAX_SIZE+3] = { '*', '@', '\0' };
+ switch(badword->action) {
+ case BADACTION_BAN:
+ hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT);
+ sanitize_ircmask(hostmask);
+ if(chan->channel_info) {
+ //registered channel
+ add_channel_ban(chan->channel_info, hostmask, spamserv->nick, now, now, now + spamserv_conf.long_ban_duration, reason);
+ }
+ struct mod_chanmode change;
+ mod_chanmode_init(&change);
+ change.argc = 1;
+ change.args[0].mode = MODE_BAN;
+ change.args[0].u.hostmask = hostmask;
+ mod_chanmode_announce(spamserv, chan, &change);
+ free(hostmask);
+ break;
+ case BADACTION_KICK:
+ if(GetUserMode(chan, user))
+ KickChannelUser(user, chan, spamserv, reason);
+ break;
+ case BADACTION_KILL:
+ DelUser(user, spamserv, 1, reason);
+ break;
+ case BADACTION_GLINE:
+ irc_ntop(mask + 2, sizeof(mask) - 2, &user->ip);
+ gline_add(spamserv->nick, mask, spamserv_conf.gline_duration, reason, now, now, 0, 1);
+ break;
+ default:
+ //error?
+ break;
+ }
+}
+
void
spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text)
{
}
}
}
+
+ dict_iterator_t it;
+
+ for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) {
+ struct badword *badword = iter_data(it);
+ if(match_ircglob(text, badword->badword_mask)) {
+ spamserv_detected_badword(user, channel, badword);
+ }
+ }
if(CHECK_ADV(cInfo) && check_advertising(cInfo, text))
{
}
}
+static int
+spamserv_saxdb_read_shitlist(const char *name, void *data, void *extra)
+{
+ struct record_data *rd = data;
+ struct chanInfo *chan = extra;
+ char *badword;
+ char *triggered, *action;
+
+ if (rd->type == RECDB_OBJECT) {
+ dict_t obj = GET_RECORD_OBJECT(rd);
+ /* new style structure */
+ badword = database_get_data(obj, KEY_BADWORD_MASK, RECDB_QSTRING);
+ triggered = database_get_data(obj, KEY_BADWORD_TRIGGERED, RECDB_QSTRING);
+ action = database_get_data(obj, KEY_BADWORD_ACTION, RECDB_QSTRING);
+
+ add_badword(chan, badword, strtoul(triggered, NULL, 0), strtoul(action, NULL, 0), name);
+ }
+ return 0;
+}
+
static int
spamserv_saxdb_read(struct dict *database)
{
dict_iterator_t it;
+ struct dict *badwords;
struct record_data *hir;
struct chanNode *channel;
struct chanInfo *cInfo;
struct string_list *strlist;
- unsigned int flags,exceptlevel;
- char *str, *info;
+ unsigned int flags,exceptlevel,badwordid;
+ char *str, *info;
unsigned long expiry;
for(it = dict_first(database); it; it = iter_next(it))
else
cInfo->suspend_expiry = expiry;
cInfo->exceptlevel=exceptlevel;
+ cInfo->badwords = dict_new();
+ str = database_get_data(hir->d.object, KEY_LASTBADWORDID, RECDB_QSTRING);
+ badwordid = str ? atoi(str) : 0;
+ cInfo->last_badword_id = badwordid;
+ if ((badwords = database_get_data(hir->d.object, KEY_BADWORDS, RECDB_OBJECT)))
+ dict_foreach(badwords, spamserv_saxdb_read_shitlist, cInfo);
}
}
else
saxdb_write_int(ctx, KEY_EXCEPTLEVEL, cInfo->exceptlevel);
if(cInfo->suspend_expiry)
- saxdb_write_int(ctx, KEY_EXPIRY, cInfo->suspend_expiry);
-
+ saxdb_write_int(ctx, KEY_EXPIRY, cInfo->suspend_expiry);
+
+ saxdb_write_int(ctx, KEY_LASTBADWORDID, cInfo->last_badword_id);
+ saxdb_start_record(ctx, KEY_BADWORDS, 1);
+ dict_iterator_t itbad;
+ for (itbad = dict_first(cInfo->badwords); itbad; itbad = iter_next(itbad)) {
+ struct badword *badword = iter_data(itbad);
+ saxdb_start_record(ctx, badword->id, 1);
+ saxdb_write_string(ctx, KEY_BADWORDID, badword->id);
+ saxdb_write_string(ctx, KEY_BADWORD_MASK, badword->badword_mask);
+ saxdb_write_int(ctx, KEY_BADWORD_ACTION, badword->action);
+ saxdb_write_int(ctx, KEY_BADWORD_TRIGGERED, badword->triggered);
+ saxdb_end_record(ctx);
+ }
+ saxdb_end_record(ctx);
saxdb_end_record(ctx);
}
return 0;
modcmd_register(spamserv_module, "ADDEXCEPTION", cmd_addexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
modcmd_register(spamserv_module, "DELEXCEPTION", cmd_delexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
modcmd_register(spamserv_module, "STATUS", cmd_status, 1, 0, NULL);
+ modcmd_register(spamserv_module, "ADDBAD", cmd_addbad, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "DELBAD", cmd_delbad, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SETBAD", cmd_setbad, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "LISTBAD", cmd_listbad, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
modcmd_register(spamserv_module, "SET", cmd_set, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
modcmd_register(spamserv_module, "SET SPAMLIMIT", opt_spamlimit, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
modcmd_register(spamserv_module, "SET ADVREACTION", opt_advreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
" SET Changes various channel settings.",
" STATUS Shows general information about $X.",
" VERSION Prints the srvx and $X version information.",
+ " ADDBAD Adds a new badword with the given mask to the badwordlist of the specified channel.",
+ " DELBAD Deletes the given badword from the badwordlist of the specified channel.",
+ " SETBAD This command will set various badword options.",
+ " LISTBAD Causes $X to send you the badwordlist of the specified channel.",
"$bStaff Commands:$b",
" REGISTER Registers a new channel.",
" UNREGISTER Removes $X from a registered channel.");
"ADDEXCEPTION" ("/msg $X ADDEXCEPTION [word]",
"Without an argument, it will show all existing exceptions.",
- "With an argument, it will add the given word to the exception list.",
- "$X checks, if one of the words in the sentence of a user is in the exception list; if so, $X will not punish the user, doesn't matter, if it's a bad advertisement.",
- "This means, you have to make sure, all exceptions are adequate.",
- "$bFirst example$b: You added the word \"gamesurge.net\" to the exception list and someone posts \"www.gamesurge.net/aup\", he won't get punished.",
- "$bSecond example$b: You added the word \"support\" to the list and someone tells another person to join #support, he won't get punished.",
- "$bThird example$b: You added \"GameSurge\" to the list and someone posts \"JOIN #channelxyz on GameSurge\", he will NOT get punished, because the word \"GameSurge\" is in the sentence.",
- "If he would say \"JOIN #channelxyz\", $X would punish him.",
- "$uSee Also:$u delexception");
+ "With an argument, it will add the given word to the exception list.",
+ "$X checks, if one of the words in the sentence of a user is in the exception list; if so, $X will not punish the user, doesn't matter, if it's a bad advertisement.",
+ "This means, you have to make sure, all exceptions are adequate.",
+ "$bFirst example$b: You added the word \"gamesurge.net\" to the exception list and someone posts \"www.gamesurge.net/aup\", he won't get punished.",
+ "$bSecond example$b: You added the word \"support\" to the list and someone tells another person to join #support, he won't get punished.",
+ "$bThird example$b: You added \"GameSurge\" to the list and someone posts \"JOIN #channelxyz on GameSurge\", he will NOT get punished, because the word \"GameSurge\" is in the sentence.",
+ "If he would say \"JOIN #channelxyz\", $X would punish him.",
+ "$uSee Also:$u delexception");
"DELEXCEPTION" ("/msg $X DELEXCEPTION",
"Without an argument, it will show all existing exceptions.",
"With an argument, it will delete the given word from the exception list.",
"If you are not network staff, you must add $bCONFIRM$b to the end of your line to confirm unregistration.",
"$bSee Also:$b register");
"VERSION" ("/msg $X VERSION",
- "$bVERSION$b causes $X to send you the srvx version and some additional version information about $X.");
\ No newline at end of file
+ "$bVERSION$b causes $X to send you the srvx version and some additional version information about $X.");
+"LISTBAD" ("/msg $X <#channel> LISTBAD",
+ "$bLIST$b causes $X to send you the badwordlist of the specified channel");
+"ADDBAD" ("/msg $X <#channel> ADDBAD <MASK>",
+ "$bADDBAD$b adds a new badword with the given mask to the badwordlist of the specified channel");
+"DELBAD" ("/msg $X <#channel> DELBAD <badwordid>",
+ "$bDELBAD$b deletes the given badword from the badwordlist of the specified channel");
+"SETBAD" ("/msg $X SETBAD <#channel> <badwordid> [<parameter> [setting]]",
+ "This command will set various badword options. With no parameters, it will show the current values of all settings of the given badword.",
+ "Only channel owners and coowners may change settings.",
+ "MASK: Mask of the given badword.",
+ "ACTION: What happens when someone used the given badword.",
+ "$uSee Also:$u setbad action");
+"SETBAD ACTION" ("/msg $X SETBAD <#channel> <badwordid> ACTION <KICK|BAN|KILL|GLINE>",
+ "What happens when someone used the given badword.");
\ No newline at end of file