improved WHOHandler multi thread stability
[NeonServV5.git] / src / WHOHandler.c
1 /* WHOHandler.c - NeonServ v5.3
2  * Copyright (C) 2011-2012  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 #include "WHOHandler.h"
19 #include "ChanNode.h"
20 #include "UserNode.h"
21 #include "ChanUser.h"
22 #include "ModeNode.h"
23 #include "ClientSocket.h"
24 #include "IPNode.h"
25
26 #define WHOQUEUETYPE_ISONQUEUE 0x01
27 #define WHOQUEUETYPE_USERLIST  0x02
28 #define WHOQUEUETYPE_USERAUTH  0x04
29 #define WHOQUEUETYPE_CHECKTYPE 0x07
30 #define WHOQUEUETYPE_FOUND     0x08
31
32 #define MAXCALLBACKS 3
33
34 struct WHOQueueEntry {
35     char type;
36     int whoid;
37     struct ChanNode *chan;
38     struct UserNode *user;
39     struct WHOQueueEntry *next;
40     void *callback[MAXCALLBACKS];
41     void *data[MAXCALLBACKS];
42     #ifdef HAVE_THREADS
43     unsigned long lock_tid;
44     pthread_mutex_t lock_mutex;
45     int lock_count;
46     #endif
47 };
48
49 static int checkWHOID(struct ClientSocket *client, int whoid) {
50     struct WHOQueueEntry *entry;
51     for(entry = client->whoqueue_first; entry; entry = entry->next) {
52         if(entry->whoid == whoid)
53             return 1;
54     }
55     return 0;
56 }
57
58 static struct WHOQueueEntry* addWHOQueueEntry(struct ClientSocket *client) {
59     SYNCHRONIZE(whohandler_sync);
60     int whoid = 0;
61     do {
62         whoid++;
63     } while(checkWHOID(client, whoid) && whoid < 1000);
64     if(whoid == 1000) {
65         DESYNCHRONIZE(whohandler_sync);
66         return NULL;
67     }
68     struct WHOQueueEntry *entry = malloc(sizeof(*entry));
69     if (!entry)
70     {
71         perror("malloc() failed");
72         DESYNCHRONIZE(whohandler_sync);
73         return NULL;
74     }
75     entry->next = NULL;
76     entry->whoid = whoid;
77     #ifdef HAVE_THREADS
78     entry->lock_tid = 0;
79     THREAD_MUTEX_INIT(entry->lock_mutex);
80     entry->lock_count = 0;
81     #endif
82     if(client->whoqueue_last) {
83         client->whoqueue_last->next = entry;
84     } else {
85         client->whoqueue_first = entry;
86     }
87     client->whoqueue_last = entry;
88     DESYNCHRONIZE(whohandler_sync);
89     return entry;
90 }
91
92 static struct WHOQueueEntry* getNextWHOQueueEntry(struct ClientSocket *client, int whoid, int freeEntry) {
93     if(!client->whoqueue_first) return NULL;
94     SYNCHRONIZE(whohandler_sync);
95     struct WHOQueueEntry *entry;
96     for(entry = client->whoqueue_first; entry; entry = entry->next) {
97         if(entry->whoid == whoid)
98             break;
99     }
100     if(!entry) {
101         DESYNCHRONIZE(whohandler_sync);
102         return NULL;
103     }
104     if(freeEntry) {
105         client->whoqueue_first = entry->next;
106         if(entry == client->whoqueue_last) {
107             client->whoqueue_last = NULL;
108         }
109         #ifdef HAVE_THREADS
110         entry->lock_tid = -1;
111         if(entry->lock_count) {
112             int i;
113             for(i = 0; i < entry->lock_count; i++) {
114                 DESYNCHRONIZE(entry->lock_mutex); //unlock ALL
115             }
116         }
117         #endif
118     }
119     DESYNCHRONIZE(whohandler_sync);
120     return entry;
121 }
122
123 void clear_whoqueue(struct ClientSocket *client) {
124     if(!client->whoqueue_first) return;
125     SYNCHRONIZE(whohandler_sync);
126     struct WHOQueueEntry *entry, *next;
127     for(entry = client->whoqueue_first; entry; entry = next) {
128         next = entry->next;
129         #ifdef HAVE_THREADS
130         entry->lock_tid = -1;
131         if(entry->lock_count) {
132             int i;
133             for(i = 0; i < entry->lock_count; i++) {
134                 DESYNCHRONIZE(entry->lock_mutex); //unlock ALL
135             }
136         }
137         #endif
138         free(entry);
139     }
140     client->whoqueue_last = NULL;
141     client->whoqueue_first = NULL;
142     DESYNCHRONIZE(whohandler_sync);
143 }
144
145 #if HAVE_THREADS
146 void whohandler_end_of_recv(struct ClientSocket *client) {
147     SYNCHRONIZE(whohandler_sync);
148     unsigned long tid = syscall(SYS_gettid);
149     struct WHOQueueEntry *entry;
150     for(entry = client->whoqueue_first; entry; entry = entry->next) {
151         if(entry->lock_tid == tid) {
152             entry->lock_tid = 0;
153             DESYNCHRONIZE(entry->lock_mutex);
154         }
155     }
156     DESYNCHRONIZE(whohandler_sync);
157 }
158 #endif
159
160 void get_userlist(struct ChanNode *chan, userlist_callback_t callback, void *data) {
161     struct ClientSocket *bot;
162     for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
163         if(isUserOnChan(bot->user, chan))
164             break;
165     }
166     if(bot == NULL) return;
167     //check if we really need to who the channel
168     int do_who = (!(chan->flags & CHANFLAG_RECEIVED_USERLIST));
169     if(!do_who) {
170         struct ChanUser *chanuser;
171         for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
172             if(!(chanuser->user->flags & (USERFLAG_ISAUTHED | USERFLAG_ISIRCOP | USERFLAG_ISBOT)) && (time(0) - chanuser->user->last_who) > REWHO_TIMEOUT) {
173                 do_who = 1;
174                 break;
175             }
176         }
177     }
178     if(do_who) {
179         struct WHOQueueEntry* entry = addWHOQueueEntry(bot);
180         entry->type = WHOQUEUETYPE_ISONQUEUE | WHOQUEUETYPE_USERLIST;
181         entry->chan = chan;
182         entry->callback[0] = callback;
183         int i;
184         for(i = 1; i < MAXCALLBACKS; i++)
185             entry->callback[i] = NULL;
186         entry->data[0] = data;
187         for(i = 1; i < MAXCALLBACKS; i++)
188             entry->data[i] = NULL;
189         putsock(bot, "WHO %s,%d %%tuihnaf,%d", chan->name, entry->whoid, entry->whoid);
190     } else
191         callback(bot, chan, data);
192 }
193
194 void _get_userlist_with_invisible(struct ChanNode *chan, userlist_callback_t callback, void *data, int force) {
195     struct ClientSocket *bot;
196     for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
197         if(isUserOnChan(bot->user, chan))
198             break;
199     }
200     if(bot == NULL) return;
201     //check if we really need to who the channel
202     //invisible users can only be present if chanmode +D or +d is set!
203     int do_who = (!(chan->flags & CHANFLAG_RECEIVED_USERLIST))  || (isModeSet(chan->modes, 'd') || isModeSet(chan->modes, 'D'));
204     if(!do_who && force) {
205         struct ChanUser *chanuser;
206         for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
207             if(!(chanuser->user->flags & (USERFLAG_ISAUTHED | USERFLAG_ISIRCOP | USERFLAG_ISBOT)) && (time(0) - chanuser->user->last_who) > REWHO_TIMEOUT) {
208                 do_who = 1;
209                 break;
210             }
211         }
212     }
213     if(do_who) {
214         struct WHOQueueEntry* entry = addWHOQueueEntry(bot);
215         entry->type = WHOQUEUETYPE_ISONQUEUE | WHOQUEUETYPE_USERLIST;
216         entry->chan = chan;
217         entry->callback[0] = callback;
218         int i;
219         for(i = 1; i < MAXCALLBACKS; i++)
220             entry->callback[i] = NULL;
221         entry->data[0] = data;
222         for(i = 1; i < MAXCALLBACKS; i++)
223             entry->data[i] = NULL;
224         putsock(bot, "WHO %s,%d d%%tuihnaf,%d", chan->name, entry->whoid, entry->whoid);
225     } else
226         callback(bot, chan, data);
227 }
228
229 void get_userauth(struct UserNode *user, userauth_callback_t callback, void *data) {
230     //check if we have already an active WHO for this user
231     struct ClientSocket *bot, *whobot = NULL;
232     struct WHOQueueEntry *entry;
233     for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
234         for(entry = bot->whoqueue_first; entry; entry = entry->next) {
235             if((entry->type & WHOQUEUETYPE_USERAUTH) && entry->user == user) {
236                 int i = 0;
237                 for(i = 1; i < MAXCALLBACKS; i++) {
238                     if(!entry->callback[i]) {
239                         entry->callback[i] = callback;
240                         entry->data[i] = data;
241                         return;
242                     }
243                 }
244             }
245         }
246         if(bot->flags & SOCKET_FLAG_PREFERRED)
247             whobot = bot;
248     }
249     bot = whobot;
250     if(bot == NULL) bot = getBots(SOCKET_FLAG_READY, NULL);
251     //check if we really need to who the user
252     if(!is_valid_nick(user->nick)) {
253         callback(bot, user->nick, NULL, data);
254         return;
255     }
256     if((user->flags & (USERFLAG_ISAUTHED | USERFLAG_ISIRCOP | USERFLAG_ISBOT | USERFLAG_ISSERVER)) || (time(0) - user->last_who) <= REWHO_TIMEOUT) {
257         callback(bot, user->nick, user, data);
258         return;
259     }
260     entry = addWHOQueueEntry(bot);
261     entry->type = WHOQUEUETYPE_ISONQUEUE | WHOQUEUETYPE_USERAUTH;
262     entry->user = user;
263     user->flags |= USERFLAG_IS_ON_WHO_QUEUE;
264     entry->callback[0] = callback;
265     int i;
266     for(i = 1; i < MAXCALLBACKS; i++)
267         entry->callback[i] = NULL;
268     entry->data[0] = data;
269     for(i = 1; i < MAXCALLBACKS; i++)
270             entry->data[i] = NULL;
271     //WHO ".$user->getNick().",".$id." %tuhna,".$id
272     putsock(bot, "WHO %s,%d %%tuhna,%d", user->nick, entry->whoid, entry->whoid);
273 }
274
275 static void _recv_whohandler_354(struct ClientSocket *client, char **argv, unsigned int argc);
276 void recv_whohandler_354(struct ClientSocket *client, char **argv, unsigned int argc) {
277     _recv_whohandler_354(client, argv, argc);
278 }
279
280 static void _recv_whohandler_354(struct ClientSocket *client, char **argv, unsigned int argc) {
281     int i;
282     if(argc < 2) return;
283     int type = atoi(argv[1]);
284     if(!type) return;
285     struct WHOQueueEntry* entry = getNextWHOQueueEntry(client, type, 0);
286     if(entry == NULL) return;
287     #ifdef HAVE_THREADS
288     unsigned long tid = syscall(SYS_gettid);
289     if(entry->lock_tid != tid) {
290         entry->lock_count++;
291         SYNCHRONIZE(entry->lock_mutex);
292         if(entry->lock_tid == -1) {
293             return; //entry has been destroyed
294         }
295         entry->lock_tid = tid;
296     }
297     #endif
298     if(entry->type & WHOQUEUETYPE_USERLIST) {
299         if(argc < 7) return;
300         //:OGN2.OnlineGamesNet.net 354 skynet 1 pk910 2001:41d0:2:1d3b::babe skynet H@ pk910
301         struct ChanNode *chan = entry->chan;
302         //add the user toe the channel if he isn't added, yet and update its user data
303         //parse flags
304         int userflags = 0;
305         int chanuserflags = 0;
306         for(i = 0; i < strlen(argv[6]); i++) {
307             switch (argv[6][i]) {
308                 case '@':
309                     chanuserflags |= CHANUSERFLAG_OPPED;
310                     break;
311                 case '%':
312                     chanuserflags |= CHANUSERFLAG_HALFOPPED;
313                     break;
314                 case '+':
315                     chanuserflags |= CHANUSERFLAG_VOICED;
316                     break;
317                 case '*':
318                     userflags |= USERFLAG_ISIRCOP;
319                     break;
320                 case '<':
321                     chanuserflags |= CHANUSERFLAG_INVISIBLE;
322                     break;
323                 default:
324                     break;
325             }
326         }
327         
328         struct UserNode *user = getUserByNick(argv[5]);
329         struct ChanUser *chanuser;
330         if((chanuserflags & CHANUSERFLAG_INVISIBLE) && (!user || !isBot(user))) {
331             user = createTempUser(argv[5]);
332             user->flags |= USERFLAG_ISTMPUSER;
333             chan->flags |= CHANFLAG_HAVE_INVISIBLES;
334             chanuser = addInvisibleChanUser(chan, user);
335             chanuser->flags = (chanuser->flags & ~CHANUSERFLAG_OPPED_OR_VOICED) | chanuserflags;
336         } else {
337             if(user == NULL) {
338                 user = addUser(argv[5]);
339             }
340             if(!(chanuser = getChanUser(user, chan))) {
341                 chanuser = addChanUser(chan, user);
342             }
343             chanuser->flags = (chanuser->flags & ~(CHANUSERFLAG_OPPED_OR_VOICED | CHANUSERFLAG_INVISIBLE)) | chanuserflags;
344         }
345         user->flags = (user->flags & ~USERFLAG_ISIRCOP) | userflags;
346         user->last_who = time(0);
347         if(!*user->ident)
348             strcpy(user->ident, argv[2]);
349         if(!user->ip)
350             user->ip = createIPNode(argv[3]);
351         if(!*user->host)
352             strcpy(user->host, argv[4]);
353         if(!(user->flags & USERFLAG_ISAUTHED) && strcmp(argv[7], "0")) {
354             strcpy(user->auth, argv[7]);
355             user->flags |= USERFLAG_ISAUTHED;
356         }
357     } else if((entry->type & WHOQUEUETYPE_USERAUTH) && !(entry->type & WHOQUEUETYPE_FOUND)) {
358         //:OGN2.OnlineGamesNet.net 354 Skynet 1 pk910 2001:41d0:2:1d3b::babe Skynet pk910
359         entry->type |= WHOQUEUETYPE_FOUND;
360         entry->user->last_who = time(0);
361         if(strcmp(argv[5], "0") && !(entry->user->flags & USERFLAG_ISAUTHED)) {
362             strcpy(entry->user->auth, argv[5]);
363             entry->user->flags |= USERFLAG_ISAUTHED;
364         }
365         for(i = 0; i < MAXCALLBACKS; i++) {
366             userauth_callback_t *callback = entry->callback[i];
367             if(!callback) break;
368             callback(client, entry->user->nick, entry->user, entry->data[i]);
369         }
370     }
371 }
372
373 static void _recv_whohandler_315(struct ClientSocket *client, char **argv, unsigned int argc);
374 void recv_whohandler_315(struct ClientSocket *client, char **argv, unsigned int argc) {
375     _recv_whohandler_315(client, argv, argc);
376 }
377
378 static void _recv_whohandler_315(struct ClientSocket *client, char **argv, unsigned int argc) {
379     if(argc < 2) return;
380     char *typestr = strstr(argv[1], ",");
381     if(!typestr) return;
382     typestr++;
383     int type = atoi(typestr);
384     struct WHOQueueEntry* entry = getNextWHOQueueEntry(client, type, 0);
385     if(entry == NULL) return;
386     #ifdef HAVE_THREADS
387     unsigned long tid = syscall(SYS_gettid);
388     if(entry->lock_tid != tid) {
389         entry->lock_count++;
390         SYNCHRONIZE(entry->lock_mutex);
391         if(entry->lock_tid == -1) {
392             return; //entry has been destroyed
393         }
394         entry->lock_tid = tid;
395     }
396     #endif
397     getNextWHOQueueEntry(client, type, 1);
398     if(entry->type & WHOQUEUETYPE_USERLIST) {
399         //:OGN2.OnlineGamesNet.net 315 skynet #pk910,1 :End of /WHO list.
400         entry->chan->flags |= CHANFLAG_RECEIVED_USERLIST;
401         userlist_callback_t *callback;
402         int i;
403         for(i = 0; i < MAXCALLBACKS; i++) {
404             callback = entry->callback[i];
405             if(!callback) break;
406             callback(client, entry->chan, entry->data[i]);
407         }
408         if(entry->chan->flags & CHANFLAG_HAVE_INVISIBLES) {
409             //remove all invisible users again
410             struct ChanUser *chanuser, *next;
411             for(chanuser = getChannelUsers(entry->chan, NULL); chanuser; chanuser = next) {
412                 next = getChannelUsers(entry->chan, chanuser);
413                 if((chanuser->flags & CHANUSERFLAG_INVISIBLE) && !isBot(chanuser->user)) {
414                     delChanUser(chanuser, 1);
415                 }
416             }
417         }
418     } else if(entry->type & WHOQUEUETYPE_USERAUTH) {
419         if(!(entry->type & WHOQUEUETYPE_FOUND)) {
420             userauth_callback_t *callback;
421             int i;
422             for(i = 0; i < MAXCALLBACKS; i++) {
423                 callback = entry->callback[i];
424                 if(!callback) break;
425                 callback(client, entry->user->nick, NULL, entry->data[i]);
426             }
427         }
428         entry->user->flags &= ~USERFLAG_IS_ON_WHO_QUEUE;
429         if(entry->user->flags & USERFLAG_FREE_AFTER_WHO) {
430             delUser(entry->user, 1);
431         }
432     }
433     free(entry);
434 }
435
436 void free_whoqueue() {
437     
438 }