completely changed the NeonSpam settings / scanner management
[NeonServV5.git] / src / event_neonspam_chanmsg.c
1 /* event_neonspam_chanmsg.c - NeonServ v5.1
2  * Copyright (C) 2011  Philipp Kreil (pk910)
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License 
15  * along with this program. If not, see <http://www.gnu.org/licenses/>. 
16  */
17
18 static int neonspam_spamscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message);
19 static int neonspam_floodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser);
20 static int neonspam_botnetscan(struct ClientSocket *client, struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message);
21
22 static USERAUTH_CALLBACK(neonspam_event_chanmsg_nick_lookup);
23 static void neonspam_event_chanmsg_punish(struct ClientSocket *client, struct ChanUser *chanuser, struct NeonSpamSettings *settings, unsigned int warn, unsigned int punish);
24
25 struct neonspam_event_chanmsg_cache {
26     struct ClientSocket *client;
27     struct ChanUser *chanuser;
28     struct NeonSpamSettings *settings;
29     unsigned int warn;
30     unsigned int punish;
31 };
32
33
34
35 static void neonspam_event_chanmsg(struct UserNode *user, struct ChanNode *chan, char *message) {
36     struct ClientSocket *client = getChannelBot(chan, BOTID);
37     if(!client) return; //we can't "see" this event
38     loadNeonSpamSettings(chan);
39     struct NeonSpamSettings *settings = chan->spam_settings;
40     struct ChanUser *chanuser = getChanUser(user, chan);
41     if(!settings || !chanuser) return;
42     #define NEONSPAM_CHANMSG_DO_SCANOPS(FLAG) ((settings->flags & FLAG) || !(chanuser->flags & CHANUSERFLAG_OPPED))
43     #define NEONSPAM_CHANMSG_DO_SCANVOICE(FLAG) ((settings->flags & FLAG) || !(chanuser->flags & CHANUSERFLAG_VOICED))
44     #define NEONSPAM_CHANMSG_DO_EXCEPT(INDEX) (settings->exceptlevel[INDEX] != 0)
45     #define NEONSPAM_CHANMSG_NEED_WHO(INDEX) (settings->exceptlevel[INDEX] != 501)
46     //scan the message
47     int result = 0;
48     unsigned int warn = 0;
49     unsigned int punish = 0;
50     int needwho = 0;
51     if((settings->flags & SPAMSETTINGS_SPAMSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_SPAMSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_SPAMSCAN_VOICE) && NEONSPAM_CHANMSG_DO_EXCEPT(SPAMSETTINGS_SPAMEXCINDEX)) {
52         result = neonspam_spamscan(settings, chanuser, message);
53         switch(result) {
54             case SPAMSERV_CHECK_IGNORE:
55                 break;
56             case SPAMSERV_CHECK_WARN:
57                 warn |= SPAMSETTINGS_SPAMSCAN;
58                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_SPAMEXCINDEX))
59                     needwho = 1;
60                 break;
61             case SPAMSERV_CHECK_PUNISH:
62                 punish |= SPAMSETTINGS_SPAMSCAN;
63                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_SPAMEXCINDEX))
64                     needwho = 1;
65                 break;
66         }
67     }
68     if((settings->flags & SPAMSETTINGS_FLOODSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_FLOODSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_FLOODSCAN_VOICE) && NEONSPAM_CHANMSG_DO_EXCEPT(SPAMSETTINGS_FLOODEXCINDEX)) {
69         result = neonspam_floodscan(settings, chanuser);
70         switch(result) {
71             case SPAMSERV_CHECK_IGNORE:
72                 break;
73             case SPAMSERV_CHECK_WARN:
74                 warn |= SPAMSETTINGS_FLOODSCAN;
75                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_FLOODEXCINDEX))
76                     needwho = 1;
77                 break;
78             case SPAMSERV_CHECK_PUNISH:
79                 punish |= SPAMSETTINGS_FLOODSCAN;
80                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_FLOODEXCINDEX))
81                     needwho = 1;
82                 break;
83         }
84     }
85     if((settings->flags & SPAMSETTINGS_BOTNETSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_BOTNETSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_BOTNETSCAN_VOICE)) {
86         result = neonspam_botnetscan(client, settings, chanuser, message);
87         switch(result) {
88             case SPAMSERV_CHECK_DEAD:
89                 return;
90             case SPAMSERV_CHECK_IGNORE:
91                 break;
92         }
93     }
94     //some other checks?
95     
96     if(warn || punish) {
97         //whois the user to check against exceptlevel
98         if(!needwho || (user->flags & USERFLAG_ISAUTHED)) {
99             neonspam_event_chanmsg_punish(client, chanuser, settings, warn, punish);
100         } else {
101             struct neonspam_event_chanmsg_cache *cache = malloc(sizeof(*cache));
102             if (!cache) {
103                 perror("malloc() failed");
104                 return;
105             }
106             cache->client = client;
107             cache->chanuser = chanuser;
108             cache->settings = settings;
109             cache->warn = warn;
110             cache->punish = punish;
111             get_userauth(user, neonspam_event_chanmsg_nick_lookup, cache);
112         }
113         
114     }
115     #undef NEONSPAM_CHANMSG_DO_SCANOPS
116     #undef NEONSPAM_CHANMSG_DO_SCANVOICE
117     #undef NEONSPAM_CHANMSG_DO_EXCEPT
118     #undef NEONSPAM_CHANMSG_NEED_WHO
119 }
120
121 static USERAUTH_CALLBACK(neonspam_event_chanmsg_nick_lookup) {
122     struct neonspam_event_chanmsg_cache *cache = data;
123     neonspam_event_chanmsg_punish(cache->client, cache->chanuser, cache->settings, cache->warn, cache->punish);
124     free(cache);
125 }
126
127 static void neonspam_event_chanmsg_punish(struct ClientSocket *client, struct ChanUser *chanuser, struct NeonSpamSettings *settings, unsigned int warn, unsigned int punish) {
128     MYSQL_RES *res;
129     MYSQL_ROW row;
130     loadChannelSettings(chanuser->chan);
131     int uaccess = 0;
132     if(chanuser->user->flags & USERFLAG_ISAUTHED)
133         uaccess = getChannelAccess(chanuser->user, chanuser->chan, 0);
134     char reason[MAXLEN];
135     reason[0] = '\0';
136     int punishment = 0;
137     int punish_time = 0;
138     if(!punishment && (punish & SPAMSETTINGS_SPAMSCAN) && settings->exceptlevel[SPAMSETTINGS_SPAMEXCINDEX] > uaccess) {
139         printf_mysql_query("SELECT `channel_spam_reaction`, `channel_spam_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
140         res = mysql_use();
141         row = mysql_fetch_row(res);
142         if(!row[0]) {
143             printf_mysql_query("SELECT `channel_spam_reaction`, `channel_spam_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
144             res = mysql_use();
145             row = mysql_fetch_row(res);
146         }
147         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_SPAM);
148         punishment = atoi(row[0]) + 1;
149         punish_time = atoi(row[1]);
150     }
151     if(!punishment && (punish & SPAMSETTINGS_FLOODSCAN) && settings->exceptlevel[SPAMSETTINGS_FLOODEXCINDEX] > uaccess) {
152         printf_mysql_query("SELECT `channel_flood_reaction`, `channel_flood_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
153         res = mysql_use();
154         row = mysql_fetch_row(res);
155         if(!row[0]) {
156             printf_mysql_query("SELECT `channel_flood_reaction`, `channel_flood_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
157             res = mysql_use();
158             row = mysql_fetch_row(res);
159         }
160         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_FLOOD);
161         punishment = atoi(row[0]) + 1;
162         punish_time = atoi(row[1]);
163     }
164     if(!punishment && (warn & SPAMSETTINGS_SPAMSCAN) && settings->exceptlevel[SPAMSETTINGS_SPAMEXCINDEX] > uaccess) {
165         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_SPAM);
166     }
167     if(!punishment && (warn & SPAMSETTINGS_FLOODSCAN) && settings->exceptlevel[SPAMSETTINGS_FLOODEXCINDEX] > uaccess) {
168         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_FLOOD);
169     }
170     if(punishment) {
171         char banmaskBuf[NICKLEN+USERLEN+HOSTLEN+3];
172         char *banmask = NULL;
173         switch (punishment) {
174             case 3: //TIMEBAN: 1h
175                 banmask = generate_banmask(chanuser->user, banmaskBuf);
176                 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) (punish_time ? (time(0) + punish_time) : 0), 0, escape_string(reason));
177                 if(punish_time) {
178                     int banid = (int) mysql_insert_id(mysql_conn);
179                     char nameBuf[MAXLEN];
180                     char banidBuf[20];
181                     sprintf(nameBuf, "ban_%d", banid);
182                     sprintf(banidBuf, "%d", banid);
183                     timeq_add_name(nameBuf, punish_time, channel_ban_timeout, strdup(banidBuf));
184                 }
185             case 2: //KICKBAN
186                 if(!banmask)
187                     banmask = generate_banmask(chanuser->user, banmaskBuf);
188                 putsock(client, "MODE %s +b %s", chanuser->chan->name, banmask);
189             case 1: //KICK
190                 putsock(client, "KICK %s %s :%s", chanuser->chan->name, chanuser->user->nick, reason);
191                 break;
192         }
193     } else if(*reason)
194         reply(client, chanuser->user, "%s", reason);
195 }
196
197 static int neonspam_spamscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) {
198     //crc32 hash of the message
199     unsigned long msghash = crc32(message);
200     if(chanuser->spamnode) {
201         if(chanuser->spamnode->lastmsg == msghash) {
202             chanuser->spamnode->spamcount++;
203             if(chanuser->spamnode->spamcount == settings->spam_amount)
204                 return SPAMSERV_CHECK_WARN;
205             else if(chanuser->spamnode->spamcount > settings->spam_amount)
206                 return SPAMSERV_CHECK_PUNISH;
207             else
208                 return SPAMSERV_CHECK_IGNORE;
209         }
210     } else
211         createSpamNode(chanuser);
212     chanuser->spamnode->lastmsg = msghash;
213     chanuser->spamnode->spamcount = 1;
214     return SPAMSERV_CHECK_IGNORE;
215 }
216
217 static int neonspam_update_penalty(struct NeonSpamSettings *settings, struct ChanUser *chanuser, int addmsg) {
218     int last_update = time(0) - chanuser->spamnode->last_penalty_update;
219     if(last_update) {
220         if(last_update < MAX_FLOOD_TIME && chanuser->spamnode->floodpenalty) {
221             chanuser->spamnode->floodpenalty -= last_update * (MAX_FLOOD_TIME / settings->sensibility_time[SPAMSETTINGS_FLOODSENINDEX]);
222             if(chanuser->spamnode->floodpenalty < 0)
223                 chanuser->spamnode->floodpenalty = 0;
224         } else
225             chanuser->spamnode->floodpenalty = 0;
226         chanuser->spamnode->last_penalty_update = time(0);
227     }
228     chanuser->spamnode->floodpenalty += MAX_FLOOD_TIME * addmsg;
229     return chanuser->spamnode->floodpenalty / MAX_FLOOD_TIME + ((chanuser->spamnode->floodpenalty % MAX_FLOOD_TIME) ? 1 : 0);
230 }
231
232 static int neonspam_floodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser) {
233     if(!chanuser->spamnode)
234         createSpamNode(chanuser);
235     int messages_pending = neonspam_update_penalty(settings, chanuser, 1);
236     if(messages_pending == settings->sensibility_amount[SPAMSETTINGS_FLOODSENINDEX])
237         return SPAMSERV_CHECK_WARN;
238     else if(messages_pending > settings->sensibility_amount[SPAMSETTINGS_FLOODSENINDEX])
239         return SPAMSERV_CHECK_PUNISH;
240     else
241         return SPAMSERV_CHECK_IGNORE;
242 }
243
244 static int neonspam_botnetscan(struct ClientSocket *client, struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) {
245     //crc32 hash of the message
246     unsigned long msghash = crc32(message);
247     if((time(0) - settings->lastmsg_time) > BOTNETSCAN_TIME || settings->lastmsg != msghash) {
248         int i;
249         for(i = 0; i < BOTNETSCAN_USERS; i++) {
250             if(settings->botnicks[i]) {
251                 free(settings->botnicks[i]);
252                 settings->botnicks[i] = NULL;
253             }
254         }
255         settings->flags &= ~SPAMSETTINGS_KICKEDBOTQUEUE;
256         settings->lastmsg = msghash;
257     } else if(settings->lastmsg == msghash) {
258         int i;
259         for(i = 0; i < BOTNETSCAN_USERS; i++) {
260             if(!settings->botnicks[i]) {
261                 settings->botnicks[i] = strdup(chanuser->user->nick);
262                 break;
263             } else if(!stricmp(chanuser->user->nick, settings->botnicks[i])) {
264                 return SPAMSERV_CHECK_IGNORE;
265             }
266         }
267         if(i == BOTNETSCAN_USERS) {
268             //BOTNETSCAN_USERS exceeded
269             if(!(settings->flags & SPAMSETTINGS_KICKEDBOTQUEUE)) {
270                 for(i = 0; i < BOTNETSCAN_USERS; i++) {
271                     putsock(client, "KICK %s %s :%s", chanuser->chan->name, settings->botnicks[i], SPAMSERV_MSG_BOTNET);
272                 }
273                 settings->flags |= SPAMSETTINGS_KICKEDBOTQUEUE;
274             }
275             putsock(client, "KICK %s %s :%s", chanuser->chan->name, chanuser->user->nick, SPAMSERV_MSG_BOTNET);
276             return SPAMSERV_CHECK_DEAD;
277         }
278     }
279     settings->lastmsg_time = time(0);
280     return SPAMSERV_CHECK_IGNORE;
281 }