fb91aa5ccb3b6145c27093ad4ba98ada69356106
[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 static int neonspam_capsscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message);
22 static int neonspam_digitscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message);
23
24 static USERAUTH_CALLBACK(neonspam_event_chanmsg_nick_lookup);
25 static void neonspam_event_chanmsg_punish(struct ClientSocket *client, struct ChanUser *chanuser, struct NeonSpamSettings *settings, unsigned int warn, unsigned int punish);
26
27 struct neonspam_event_chanmsg_cache {
28     struct ClientSocket *client;
29     struct ChanUser *chanuser;
30     struct NeonSpamSettings *settings;
31     unsigned int warn;
32     unsigned int punish;
33 };
34
35
36
37 static void neonspam_event_chanmsg(struct UserNode *user, struct ChanNode *chan, char *message) {
38     struct ClientSocket *client = getChannelBot(chan, BOTID);
39     if(!client) return; //we can't "see" this event
40     loadNeonSpamSettings(chan);
41     struct NeonSpamSettings *settings = chan->spam_settings;
42     struct ChanUser *chanuser = getChanUser(user, chan);
43     if(!settings || !chanuser) return;
44     #define NEONSPAM_CHANMSG_DO_SCANOPS(FLAG) ((settings->flags & FLAG) || !(chanuser->flags & CHANUSERFLAG_OPPED))
45     #define NEONSPAM_CHANMSG_DO_SCANVOICE(FLAG) ((settings->flags & FLAG) || !(chanuser->flags & CHANUSERFLAG_VOICED))
46     #define NEONSPAM_CHANMSG_DO_EXCEPT(INDEX) (settings->exceptlevel[INDEX] != 0)
47     #define NEONSPAM_CHANMSG_NEED_WHO(INDEX) (settings->exceptlevel[INDEX] != 501)
48     //scan the message
49     int result = 0;
50     unsigned int warn = 0;
51     unsigned int punish = 0;
52     int needwho = 0;
53     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)) {
54         result = neonspam_spamscan(settings, chanuser, message);
55         switch(result) {
56             case SPAMSERV_CHECK_IGNORE:
57                 break;
58             case SPAMSERV_CHECK_WARN:
59                 warn |= SPAMSETTINGS_SPAMSCAN;
60                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_SPAMEXCINDEX))
61                     needwho = 1;
62                 break;
63             case SPAMSERV_CHECK_PUNISH:
64                 punish |= SPAMSETTINGS_SPAMSCAN;
65                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_SPAMEXCINDEX))
66                     needwho = 1;
67                 break;
68         }
69     }
70     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)) {
71         result = neonspam_floodscan(settings, chanuser);
72         switch(result) {
73             case SPAMSERV_CHECK_IGNORE:
74                 break;
75             case SPAMSERV_CHECK_WARN:
76                 warn |= SPAMSETTINGS_FLOODSCAN;
77                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_FLOODEXCINDEX))
78                     needwho = 1;
79                 break;
80             case SPAMSERV_CHECK_PUNISH:
81                 punish |= SPAMSETTINGS_FLOODSCAN;
82                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_FLOODEXCINDEX))
83                     needwho = 1;
84                 break;
85         }
86     }
87     if((settings->flags & SPAMSETTINGS_BOTNETSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_BOTNETSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_BOTNETSCAN_VOICE)) {
88         result = neonspam_botnetscan(client, settings, chanuser, message);
89         switch(result) {
90             case SPAMSERV_CHECK_DEAD:
91                 return;
92             case SPAMSERV_CHECK_IGNORE:
93                 break;
94         }
95     }
96     if((settings->flags & SPAMSETTINGS_CAPSSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_CAPSSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_CAPSSCAN_VOICE) && NEONSPAM_CHANMSG_DO_EXCEPT(SPAMSETTINGS_CAPSEXCINDEX)) {
97         result = neonspam_capsscan(settings, chanuser, message);
98         switch(result) {
99             case SPAMSERV_CHECK_IGNORE:
100                 break;
101             case SPAMSERV_CHECK_WARN:
102                 warn |= SPAMSETTINGS_CAPSSCAN;
103                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_CAPSEXCINDEX))
104                     needwho = 1;
105                 break;
106             case SPAMSERV_CHECK_PUNISH:
107                 punish |= SPAMSETTINGS_CAPSSCAN;
108                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_CAPSEXCINDEX))
109                     needwho = 1;
110                 break;
111         }
112     }
113     if((settings->flags & SPAMSETTINGS_DIGITSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_DIGITSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_DIGITSCAN_VOICE) && NEONSPAM_CHANMSG_DO_EXCEPT(SPAMSETTINGS_DIGITEXCINDEX)) {
114         result = neonspam_digitscan(settings, chanuser, message);
115         switch(result) {
116             case SPAMSERV_CHECK_IGNORE:
117                 break;
118             case SPAMSERV_CHECK_WARN:
119                 warn |= SPAMSETTINGS_DIGITSCAN;
120                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_DIGITEXCINDEX))
121                     needwho = 1;
122                 break;
123             case SPAMSERV_CHECK_PUNISH:
124                 punish |= SPAMSETTINGS_DIGITSCAN;
125                 if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_DIGITEXCINDEX))
126                     needwho = 1;
127                 break;
128         }
129     }
130     //some other checks?
131     
132     if(warn || punish) {
133         //whois the user to check against exceptlevel
134         if(!needwho || (user->flags & USERFLAG_ISAUTHED)) {
135             neonspam_event_chanmsg_punish(client, chanuser, settings, warn, punish);
136         } else {
137             struct neonspam_event_chanmsg_cache *cache = malloc(sizeof(*cache));
138             if (!cache) {
139                 perror("malloc() failed");
140                 return;
141             }
142             cache->client = client;
143             cache->chanuser = chanuser;
144             cache->settings = settings;
145             cache->warn = warn;
146             cache->punish = punish;
147             get_userauth(user, neonspam_event_chanmsg_nick_lookup, cache);
148         }
149         
150     }
151     #undef NEONSPAM_CHANMSG_DO_SCANOPS
152     #undef NEONSPAM_CHANMSG_DO_SCANVOICE
153     #undef NEONSPAM_CHANMSG_DO_EXCEPT
154     #undef NEONSPAM_CHANMSG_NEED_WHO
155 }
156
157 static USERAUTH_CALLBACK(neonspam_event_chanmsg_nick_lookup) {
158     struct neonspam_event_chanmsg_cache *cache = data;
159     neonspam_event_chanmsg_punish(cache->client, cache->chanuser, cache->settings, cache->warn, cache->punish);
160     free(cache);
161 }
162
163 static void neonspam_event_chanmsg_punish(struct ClientSocket *client, struct ChanUser *chanuser, struct NeonSpamSettings *settings, unsigned int warn, unsigned int punish) {
164     MYSQL_RES *res;
165     MYSQL_ROW row;
166     loadChannelSettings(chanuser->chan);
167     int uaccess = 0;
168     if(chanuser->user->flags & USERFLAG_ISAUTHED)
169         uaccess = getChannelAccess(chanuser->user, chanuser->chan, 0);
170     char reason[MAXLEN];
171     reason[0] = '\0';
172     int punishment = 0;
173     int punish_time = 0;
174     if(!punishment && (punish & SPAMSETTINGS_SPAMSCAN) && settings->exceptlevel[SPAMSETTINGS_SPAMEXCINDEX] > uaccess) {
175         printf_mysql_query("SELECT `channel_spam_reaction`, `channel_spam_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
176         res = mysql_use();
177         row = mysql_fetch_row(res);
178         if(!row[0]) {
179             printf_mysql_query("SELECT `channel_spam_reaction`, `channel_spam_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
180             res = mysql_use();
181             row = mysql_fetch_row(res);
182         }
183         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_SPAM);
184         punishment = atoi(row[0]) + 1;
185         punish_time = atoi(row[1]);
186     }
187     if(!punishment && (punish & SPAMSETTINGS_FLOODSCAN) && settings->exceptlevel[SPAMSETTINGS_FLOODEXCINDEX] > uaccess) {
188         printf_mysql_query("SELECT `channel_flood_reaction`, `channel_flood_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
189         res = mysql_use();
190         row = mysql_fetch_row(res);
191         if(!row[0]) {
192             printf_mysql_query("SELECT `channel_flood_reaction`, `channel_flood_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
193             res = mysql_use();
194             row = mysql_fetch_row(res);
195         }
196         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_FLOOD);
197         punishment = atoi(row[0]) + 1;
198         punish_time = atoi(row[1]);
199     }
200     if(!punishment && (punish & SPAMSETTINGS_CAPSSCAN) && settings->exceptlevel[SPAMSETTINGS_CAPSEXCINDEX] > uaccess) {
201         printf_mysql_query("SELECT `channel_caps_reaction`, `channel_caps_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
202         res = mysql_use();
203         row = mysql_fetch_row(res);
204         if(!row[0]) {
205             printf_mysql_query("SELECT `channel_caps_reaction`, `channel_caps_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
206             res = mysql_use();
207             row = mysql_fetch_row(res);
208         }
209         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_CAPS);
210         punishment = atoi(row[0]) + 1;
211         punish_time = atoi(row[1]);
212     }
213     if(!punishment && (punish & SPAMSETTINGS_DIGITSCAN) && settings->exceptlevel[SPAMSETTINGS_DIGITEXCINDEX] > uaccess) {
214         printf_mysql_query("SELECT `channel_digit_reaction`, `channel_digit_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
215         res = mysql_use();
216         row = mysql_fetch_row(res);
217         if(!row[0]) {
218             printf_mysql_query("SELECT `channel_digit_reaction`, `channel_digit_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
219             res = mysql_use();
220             row = mysql_fetch_row(res);
221         }
222         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_DIGIT);
223         punishment = atoi(row[0]) + 1;
224         punish_time = atoi(row[1]);
225     }
226     if(!punishment && (warn & SPAMSETTINGS_SPAMSCAN) && settings->exceptlevel[SPAMSETTINGS_SPAMEXCINDEX] > uaccess) {
227         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_SPAM);
228     }
229     if(!punishment && (warn & SPAMSETTINGS_FLOODSCAN) && settings->exceptlevel[SPAMSETTINGS_FLOODEXCINDEX] > uaccess) {
230         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_FLOOD);
231     }
232     if(!punishment && (warn & SPAMSETTINGS_CAPSSCAN) && settings->exceptlevel[SPAMSETTINGS_CAPSEXCINDEX] > uaccess) {
233         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_CAPS);
234     }
235     if(!punishment && (warn & SPAMSETTINGS_DIGITSCAN) && settings->exceptlevel[SPAMSETTINGS_DIGITEXCINDEX] > uaccess) {
236         sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_DIGIT);
237     }
238     if(punishment) {
239         char banmaskBuf[NICKLEN+USERLEN+HOSTLEN+3];
240         char *banmask = NULL;
241         switch (punishment) {
242             case 3: //TIMEBAN: 1h
243                 banmask = generate_banmask(chanuser->user, banmaskBuf);
244                 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));
245                 if(punish_time) {
246                     int banid = (int) mysql_insert_id(mysql_conn);
247                     char nameBuf[MAXLEN];
248                     char banidBuf[20];
249                     sprintf(nameBuf, "ban_%d", banid);
250                     sprintf(banidBuf, "%d", banid);
251                     timeq_add_name(nameBuf, punish_time, channel_ban_timeout, strdup(banidBuf));
252                 }
253             case 2: //KICKBAN
254                 if(!banmask)
255                     banmask = generate_banmask(chanuser->user, banmaskBuf);
256                 putsock(client, "MODE %s +b %s", chanuser->chan->name, banmask);
257             case 1: //KICK
258                 putsock(client, "KICK %s %s :%s", chanuser->chan->name, chanuser->user->nick, reason);
259                 break;
260         }
261     } else if(*reason)
262         reply(client, chanuser->user, "%s", reason);
263 }
264
265 static int neonspam_spamscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) {
266     //crc32 hash of the message
267     unsigned long msghash = crc32(message);
268     if(chanuser->spamnode) {
269         if(chanuser->spamnode->lastmsg == msghash) {
270             chanuser->spamnode->spamcount++;
271             if(chanuser->spamnode->spamcount == settings->spam_amount)
272                 return SPAMSERV_CHECK_WARN;
273             else if(chanuser->spamnode->spamcount > settings->spam_amount)
274                 return SPAMSERV_CHECK_PUNISH;
275             else
276                 return SPAMSERV_CHECK_IGNORE;
277         }
278     } else
279         createSpamNode(chanuser);
280     chanuser->spamnode->lastmsg = msghash;
281     chanuser->spamnode->spamcount = 1;
282     return SPAMSERV_CHECK_IGNORE;
283 }
284
285 static int neonspam_update_penalty(struct NeonSpamSettings *settings, struct ChanUser *chanuser, int addmsg) {
286     int last_update = time(0) - chanuser->spamnode->last_penalty_update;
287     if(last_update) {
288         if(last_update < MAX_FLOOD_TIME && chanuser->spamnode->floodpenalty) {
289             chanuser->spamnode->floodpenalty -= last_update * (MAX_FLOOD_TIME / settings->sensibility_time[SPAMSETTINGS_FLOODSENINDEX]);
290             if(chanuser->spamnode->floodpenalty < 0)
291                 chanuser->spamnode->floodpenalty = 0;
292         } else
293             chanuser->spamnode->floodpenalty = 0;
294         chanuser->spamnode->last_penalty_update = time(0);
295     }
296     chanuser->spamnode->floodpenalty += MAX_FLOOD_TIME * addmsg;
297     return chanuser->spamnode->floodpenalty / MAX_FLOOD_TIME + ((chanuser->spamnode->floodpenalty % MAX_FLOOD_TIME) ? 1 : 0);
298 }
299
300 static int neonspam_floodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser) {
301     if(!chanuser->spamnode)
302         createSpamNode(chanuser);
303     int messages_pending = neonspam_update_penalty(settings, chanuser, 1);
304     if(messages_pending == settings->sensibility_amount[SPAMSETTINGS_FLOODSENINDEX])
305         return SPAMSERV_CHECK_WARN;
306     else if(messages_pending > settings->sensibility_amount[SPAMSETTINGS_FLOODSENINDEX])
307         return SPAMSERV_CHECK_PUNISH;
308     else
309         return SPAMSERV_CHECK_IGNORE;
310 }
311
312 static int neonspam_botnetscan(struct ClientSocket *client, struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) {
313     //crc32 hash of the message
314     unsigned long msghash = crc32(message);
315     if((time(0) - settings->lastmsg_time) > BOTNETSCAN_TIME || settings->lastmsg != msghash) {
316         int i;
317         for(i = 0; i < BOTNETSCAN_USERS; i++) {
318             if(settings->botnicks[i]) {
319                 free(settings->botnicks[i]);
320                 settings->botnicks[i] = NULL;
321             }
322         }
323         settings->flags &= ~SPAMSETTINGS_KICKEDBOTQUEUE;
324         settings->lastmsg = msghash;
325     } else if(settings->lastmsg == msghash) {
326         int i;
327         for(i = 0; i < BOTNETSCAN_USERS; i++) {
328             if(!settings->botnicks[i]) {
329                 settings->botnicks[i] = strdup(chanuser->user->nick);
330                 break;
331             } else if(!stricmp(chanuser->user->nick, settings->botnicks[i])) {
332                 return SPAMSERV_CHECK_IGNORE;
333             }
334         }
335         if(i == BOTNETSCAN_USERS) {
336             //BOTNETSCAN_USERS exceeded
337             if(!(settings->flags & SPAMSETTINGS_KICKEDBOTQUEUE)) {
338                 for(i = 0; i < BOTNETSCAN_USERS; i++) {
339                     putsock(client, "KICK %s %s :%s", chanuser->chan->name, settings->botnicks[i], SPAMSERV_MSG_BOTNET);
340                 }
341                 settings->flags |= SPAMSETTINGS_KICKEDBOTQUEUE;
342             }
343             putsock(client, "KICK %s %s :%s", chanuser->chan->name, chanuser->user->nick, SPAMSERV_MSG_BOTNET);
344             return SPAMSERV_CHECK_DEAD;
345         }
346     }
347     settings->lastmsg_time = time(0);
348     return SPAMSERV_CHECK_IGNORE;
349 }
350
351 static int neonspam_capsscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) {
352     int caps = 0, msglen = strlen(message);
353     int i;
354     if(msglen <= 4) return SPAMSERV_CHECK_IGNORE;
355     for(i = 0; i < msglen; i++) {
356         if(isupper(message[i])) caps++;
357     }
358     caps = 100*caps/msglen;
359     if(caps >= settings->percent[SPAMSETTINGS_CAPSPERCENTINDEX]) {
360         if(!chanuser->spamnode)
361             createSpamNode(chanuser);
362         if(chanuser->spamnode->flags & NEONSPAMNODE_FLAG_CAPSSCAN_WARNED)
363             return SPAMSERV_CHECK_PUNISH;
364         else {
365             chanuser->spamnode->flags |= NEONSPAMNODE_FLAG_CAPSSCAN_WARNED;
366             return SPAMSERV_CHECK_WARN;
367         }
368     }
369     return SPAMSERV_CHECK_IGNORE;
370 }
371
372 static int neonspam_digitscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) {
373     int digit = 0, msglen = strlen(message);
374     int i;
375     if(msglen <= 4) return SPAMSERV_CHECK_IGNORE;
376     for(i = 0; i < msglen; i++) {
377         if(isdigit(message[i])) digit++;
378     }
379     digit = 100*digit/msglen;
380     if(digit >= settings->percent[SPAMSETTINGS_DIGITPERCENTINDEX]) {
381         if(!chanuser->spamnode)
382             createSpamNode(chanuser);
383         if(chanuser->spamnode->flags & NEONSPAMNODE_FLAG_DIGITSCAN_WARNED)
384             return SPAMSERV_CHECK_PUNISH;
385         else {
386             chanuser->spamnode->flags |= NEONSPAMNODE_FLAG_DIGITSCAN_WARNED;
387             return SPAMSERV_CHECK_WARN;
388         }
389     }
390     return SPAMSERV_CHECK_IGNORE;
391 }
392