From 9c92a3b52cb5a397c8f739cb975ecf1fac6d8798 Mon Sep 17 00:00:00 2001 From: pk910 Date: Fri, 21 Oct 2011 00:05:42 +0200 Subject: [PATCH] added joinflood scanner --- src/ChanNode.c | 3 +- src/bot_NeonSpam.c | 65 +++++++++++++- src/bot_NeonSpam.h | 11 +++ src/event_neonspam_chanmsg.c | 10 --- src/event_neonspam_join.c | 167 +++++++++++++++++++++++++++++++++++ 5 files changed, 243 insertions(+), 13 deletions(-) create mode 100644 src/event_neonspam_join.c diff --git a/src/ChanNode.c b/src/ChanNode.c index af8a5bb..480baed 100644 --- a/src/ChanNode.c +++ b/src/ChanNode.c @@ -20,6 +20,7 @@ #include "BanNode.h" #include "modcmd.h" #include "ModeNode.h" +#include "bot_NeonSpam.h" static struct ChanNode **chanList; @@ -224,7 +225,7 @@ void freeChanNode(struct ChanNode* chan) { if(chan->bans) removeChannelBans(chan); if(chan->spam_settings) - free(chan->spam_settings); + freeNeonSpamSettings(chan->spam_settings); free(chan); } diff --git a/src/bot_NeonSpam.c b/src/bot_NeonSpam.c index d4a10d4..5a200c1 100644 --- a/src/bot_NeonSpam.c +++ b/src/bot_NeonSpam.c @@ -46,9 +46,21 @@ static const struct default_language_entry msgtab[] = { static int loadNeonSpamSettings(struct ChanNode *chan); static void createSpamNode(struct ChanUser *chanuser); +static void freeJoinNode(struct NeonSpamJoinNode *joinnode); +static struct NeonSpamJoinNode *getNeonSpamJoinNode(struct ChanUser *chanuser); + +#define SPAMSERV_CHECK_IGNORE 0 +#define SPAMSERV_CHECK_WARN 1 +#define SPAMSERV_CHECK_PUNISH 2 + +#define SPAMSERV_MSG_SPAM "Spamming" +#define SPAMSERV_MSG_FLOOD "Flooding the channel/network" +#define SPAMSERV_MSG_ADV "Advertising" +#define SPAMSERV_MSG_JOINFLOOD "Join flooding the channel" +#define SPAMSERV_MSG_WARNING "%s is against the channel rules" //EVENTS -//#include "event_neonspam_join.c" +#include "event_neonspam_join.c" #include "event_neonspam_chanmsg.c" static void neonspam_bot_ready(struct ClientSocket *client) { @@ -126,6 +138,7 @@ static int loadNeonSpamSettings(struct ChanNode *chan) { } MYSQL_RES *res; MYSQL_ROW row, defaults = NULL; + loadChannelSettings(chan); printf_mysql_query("SELECT `channel_scanstate`, `channel_maxrepeat`, `channel_maxflood`, `channel_floodtime`, `channel_maxjoin`, `channel_jointime`, `channel_scanexcept` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id); res = mysql_use(); row = mysql_fetch_row(res); @@ -141,10 +154,58 @@ static int loadNeonSpamSettings(struct ChanNode *chan) { settings->join_amount = atoi(row[4] ? row[4] : defaults[4]); settings->join_time = atoi(row[5] ? row[5] : defaults[5]); settings->exceptlevel = atoi(row[6] ? row[6] : defaults[6]); + settings->join_nodes = NULL; chan->spam_settings = settings; return 1; } +void freeNeonSpamSettings(struct NeonSpamSettings *settings) { + struct NeonSpamJoinNode *joinnode, *nextjoinnode; + for(joinnode = settings->join_nodes; joinnode; joinnode = nextjoinnode) { + nextjoinnode = joinnode->next; + freeJoinNode(joinnode); + } + free(settings); +} + +static void freeJoinNode(struct NeonSpamJoinNode *joinnode) { + free(joinnode->ident); + free(joinnode->host); + free(joinnode); +} + +static struct NeonSpamJoinNode *getNeonSpamJoinNode(struct ChanUser *chanuser) { + struct NeonSpamJoinNode *joinnode, *prevjoinnode = NULL, *nextjoinnode, *result = NULL; + for(joinnode = chanuser->chan->spam_settings->join_nodes; joinnode; joinnode = nextjoinnode) { + nextjoinnode = joinnode->next; + if(!stricmp(joinnode->ident, chanuser->user->ident) && !stricmp(joinnode->host, chanuser->user->host)) { + prevjoinnode = joinnode; + result = joinnode; + } else if(time(0) - joinnode->last_penalty_update > MAX_JOIN_TIME) { + freeJoinNode(joinnode); + if(prevjoinnode) + prevjoinnode->next = nextjoinnode; + else + chanuser->chan->spam_settings->join_nodes = nextjoinnode; + } else + prevjoinnode = joinnode; + } + if(result) + return result; + joinnode = malloc(sizeof(*joinnode)); + if(!joinnode) { + perror("malloc() failed"); + return NULL; + } + joinnode->ident = strdup(chanuser->user->ident); + joinnode->host = strdup(chanuser->user->host); + joinnode->last_penalty_update = time(0); + joinnode->joinpenalty = 0; + joinnode->next = chanuser->chan->spam_settings->join_nodes; + chanuser->chan->spam_settings->join_nodes = joinnode; + return joinnode; +} + static void createSpamNode(struct ChanUser *chanuser) { struct NeonSpamNode *spamnode = malloc(sizeof(*spamnode)); if(!spamnode) { @@ -186,7 +247,7 @@ void init_NeonSpam() { //register events bind_bot_ready(neonspam_bot_ready); - //bind_join(neonspam_event_join); + bind_join(neonspam_event_join); bind_chanmsg(neonspam_event_chanmsg); bind_privctcp(general_event_privctcp); diff --git a/src/bot_NeonSpam.h b/src/bot_NeonSpam.h index d318682..838a570 100644 --- a/src/bot_NeonSpam.h +++ b/src/bot_NeonSpam.h @@ -42,6 +42,7 @@ struct NeonSpamSettings { unsigned char join_amount; unsigned char join_time; unsigned int exceptlevel : 10; + struct NeonSpamJoinNode *join_nodes; }; /* PENALTY SYSTEM * user gets MAX_FLOOD_TIME points per message @@ -60,8 +61,18 @@ struct NeonSpamNode { time_t last_penalty_update; }; +struct NeonSpamJoinNode { + char *ident; + char *host; + int joinpenalty; + time_t last_penalty_update; + struct NeonSpamJoinNode *next; +}; + void init_NeonSpam(); void loop_NeonSpam(); void free_NeonSpam(); +void freeNeonSpamSettings(struct NeonSpamSettings *settings); + #endif \ No newline at end of file diff --git a/src/event_neonspam_chanmsg.c b/src/event_neonspam_chanmsg.c index 13e849b..f55635a 100644 --- a/src/event_neonspam_chanmsg.c +++ b/src/event_neonspam_chanmsg.c @@ -15,16 +15,6 @@ * along with this program. If not, see . */ -#define SPAMSERV_CHECK_IGNORE 0 -#define SPAMSERV_CHECK_WARN 1 -#define SPAMSERV_CHECK_PUNISH 2 - -#define SPAMSERV_MSG_SPAM "Spamming" -#define SPAMSERV_MSG_FLOOD "Flooding the channel/network" -#define SPAMSERV_MSG_ADV "Advertising" -#define SPAMSERV_MSG_JOINFLOOD "Join flooding the channel" -#define SPAMSERV_MSG_WARNING "%s is against the channel rules" - static int neonspam_spamscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message); static int neonspam_floodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser); diff --git a/src/event_neonspam_join.c b/src/event_neonspam_join.c new file mode 100644 index 0000000..86c02d0 --- /dev/null +++ b/src/event_neonspam_join.c @@ -0,0 +1,167 @@ +/* event_neonspam_join.c - NeonServ v5.1 + * Copyright (C) 2011 Philipp Kreil (pk910) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +static USERAUTH_CALLBACK(neonspam_event_join_nick_lookup); +static void neonspam_event_join_punish(struct ClientSocket *client, struct ChanUser *chanuser, struct NeonSpamSettings *settings, int action, char *reason, char *reaction); +static int neonspam_joinfloodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser); + +struct neonspam_event_join_cache { + struct ClientSocket *client; + struct ChanUser *chanuser; + struct NeonSpamSettings *settings; + int action; + char *reason; + char *reaction; +}; + +static void neonspam_event_join(struct ChanUser *chanuser) { + struct ClientSocket *client = getChannelBot(chanuser->chan, BOTID); + if(!client) return; //we can't "see" this event + loadNeonSpamSettings(chanuser->chan); + struct NeonSpamSettings *settings = chanuser->chan->spam_settings; + if(!settings) return; + if(settings->exceptlevel == 0) return; + //scan the message + int result = 0; + int action = SPAMSERV_CHECK_IGNORE; + char reason[MAXLEN]; + char *reaction = NULL; + if(action != SPAMSERV_CHECK_PUNISH && (settings->flags & SPAMSETTINGS_FLOODSCAN)) { + result = neonspam_joinfloodscan(settings, chanuser); + switch(result) { + case SPAMSERV_CHECK_IGNORE: + break; + case SPAMSERV_CHECK_WARN: + if(action == SPAMSERV_CHECK_IGNORE) { + action = result; + sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_FLOOD); + } + break; + case SPAMSERV_CHECK_PUNISH: + if(action != SPAMSERV_CHECK_PUNISH) { + action = result; + sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_FLOOD); + reaction = "channel_joinreaction"; + } + break; + } + } + //some other checks? + if(action != SPAMSERV_CHECK_IGNORE) { + //whois the user to check against exceptlevel + if((chanuser->user->flags & USERFLAG_ISAUTHED) || settings->exceptlevel == 501) { + neonspam_event_join_punish(client, chanuser, settings, action, reason, reaction); + } else { + struct neonspam_event_join_cache *cache = malloc(sizeof(*cache)); + if (!cache) { + perror("malloc() failed"); + return; + } + cache->client = client; + cache->chanuser = chanuser; + cache->settings = settings; + cache->action = action; + cache->reason = strdup(reason); + cache->reaction = reaction; + get_userauth(chanuser->user, neonspam_event_join_nick_lookup, cache); + } + + } +} + +static USERAUTH_CALLBACK(neonspam_event_join_nick_lookup) { + struct neonspam_event_join_cache *cache = data; + neonspam_event_join_punish(cache->client, cache->chanuser, cache->settings, cache->action, cache->reason, cache->reaction); + free(cache->reason); + free(cache); +} + +static void neonspam_event_join_punish(struct ClientSocket *client, struct ChanUser *chanuser, struct NeonSpamSettings *settings, int action, char *reason, char *reaction) { + int uaccess = 0; + if(chanuser->user->flags & USERFLAG_ISAUTHED) + uaccess = getChannelAccess(chanuser->user, chanuser->chan, 0); + if(uaccess >= settings->exceptlevel) return; + if(action == SPAMSERV_CHECK_WARN) { + reply(client, chanuser->user, "%s", reason); + } else if(action == SPAMSERV_CHECK_PUNISH) { + MYSQL_RES *res; + MYSQL_ROW row; + loadChannelSettings(chanuser->chan); + printf_mysql_query("SELECT `%s` FROM `channels` WHERE `channel_id` = '%d'", reaction, chanuser->chan->channel_id); + res = mysql_use(); + row = mysql_fetch_row(res); + if(!row[0]) { + printf_mysql_query("SELECT `%s` FROM `channels` WHERE `channel_name` = 'defaults'", reaction); + res = mysql_use(); + row = mysql_fetch_row(res); + } + int duration = 0; + char banmaskBuf[NICKLEN+USERLEN+HOSTLEN+3]; + char *banmask = NULL; + switch (atoi(row[0])) { + case 2: //TIMEBAN: 3min + duration = 180; + case 3: //TIMEBAN: 1h + if(!duration) + duration = 3600; + banmask = generate_banmask(chanuser->user, banmaskBuf); + printf_mysql_query("INSERT INTO `bans` (`ban_channel`, `ban_mask`, `ban_triggered`, `ban_timeout`, `ban_owner`, `ban_reason`) VALUES ('%d', '%s', UNIX_TIMESTAMP(), '%lu', '%d', '%s')", chanuser->chan->channel_id, escape_string(banmask), (unsigned long) (time(0) + duration), 0, escape_string(reason)); + int banid = (int) mysql_insert_id(mysql_conn); + char nameBuf[MAXLEN]; + char banidBuf[20]; + sprintf(nameBuf, "ban_%d", banid); + sprintf(banidBuf, "%d", banid); + timeq_add_name(nameBuf, duration, channel_ban_timeout, strdup(banidBuf)); + case 1: //KICKBAN + if(!banmask) + banmask = generate_banmask(chanuser->user, banmaskBuf); + putsock(client, "MODE %s +b %s", chanuser->chan->name, banmask); + case 0: //KICK + putsock(client, "KICK %s %s :%s", chanuser->chan->name, chanuser->user->nick, reason); + break; + } + } +} + +static int neonspam_update_join_penalty(struct NeonSpamSettings *settings, struct NeonSpamJoinNode *joinnode, int addjoin) { + int last_update = time(0) - joinnode->last_penalty_update; + if(last_update) { + if(last_update < MAX_JOIN_TIME && joinnode->joinpenalty) { + joinnode->joinpenalty -= last_update * (MAX_JOIN_TIME / settings->join_time); + if(joinnode->joinpenalty < 0) + joinnode->joinpenalty = 0; + } else + joinnode->joinpenalty = 0; + joinnode->last_penalty_update = time(0); + } + joinnode->joinpenalty += MAX_JOIN_TIME * addjoin; + return joinnode->joinpenalty / MAX_JOIN_TIME + ((joinnode->joinpenalty % MAX_JOIN_TIME) ? 1 : 0); +} + +static int neonspam_joinfloodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser) { + if(!chanuser->spamnode) + createSpamNode(chanuser); + int joins_pending = neonspam_update_join_penalty(settings, getNeonSpamJoinNode(chanuser), 1); + if(joins_pending == settings->join_amount) + return SPAMSERV_CHECK_WARN; + else if(joins_pending > settings->join_amount) + return SPAMSERV_CHECK_PUNISH; + else + return SPAMSERV_CHECK_IGNORE; +} + + -- 2.20.1