1 /*
2  channels-query.c : irssi
3 
4     Copyright (C) 1999-2000 Timo Sirainen
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License along
17     with this program; if not, write to the Free Software Foundation, Inc.,
18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20 
21 /*
22 
23  How the thing works:
24 
25  - After channel is joined and NAMES list is got, send "channel joined" signal
26  - "channel joined" : add channel to server->queries lists
27 
28 loop:
29  - Wait for NAMES list from all channels before doing anything else..
30  - After got the last NAMES list, start sending the queries ..
31  - find the query to send, check where server->queries list isn't NULL
32    (mode, who, banlist, ban exceptions, invite list)
33  - if not found anything -> all channels are synced
34  - send "command #chan1,#chan2,#chan3,.." command to server
35  - wait for reply from server, then check if it was last query to be sent to
36    channel. If it was, send "channel sync" signal
37  - check if the reply was for last channel in the command list. If so,
38    goto loop
39 */
40 
41 #include "module.h"
42 #include "misc.h"
43 #include "signals.h"
44 #include "settings.h"
45 
46 #include "modes.h"
47 #include "mode-lists.h"
48 #include "nicklist.h"
49 #include "irc-servers.h"
50 #include "irc-channels.h"
51 #include "servers-redirect.h"
52 
53 enum {
54 	CHANNEL_QUERY_MODE,
55 	CHANNEL_QUERY_WHO,
56 	CHANNEL_QUERY_BMODE,
57 
58 	CHANNEL_QUERIES
59 };
60 
61 #define CHANNEL_IS_MODE_QUERY(a) ((a) != CHANNEL_QUERY_WHO)
62 
63 typedef struct {
64 	int current_query_type; /* query type that is currently being asked */
65         GSList *current_queries; /* All channels that are currently being queried */
66 
67 	GSList *queries[CHANNEL_QUERIES]; /* All queries that need to be asked from server */
68 } SERVER_QUERY_REC;
69 
sig_connected(IRC_SERVER_REC * server)70 static void sig_connected(IRC_SERVER_REC *server)
71 {
72 	SERVER_QUERY_REC *rec;
73 
74 	g_return_if_fail(server != NULL);
75 	if (!IS_IRC_SERVER(server))
76 		return;
77 
78 	rec = g_new0(SERVER_QUERY_REC, 1);
79         server->chanqueries = rec;
80 }
81 
sig_disconnected(IRC_SERVER_REC * server)82 static void sig_disconnected(IRC_SERVER_REC *server)
83 {
84 	SERVER_QUERY_REC *rec;
85 	int n;
86 
87 	g_return_if_fail(server != NULL);
88 	if (!IS_IRC_SERVER(server))
89 		return;
90 
91 	rec = server->chanqueries;
92 	g_return_if_fail(rec != NULL);
93 
94 	for (n = 0; n < CHANNEL_QUERIES; n++)
95 		g_slist_free(rec->queries[n]);
96         g_slist_free(rec->current_queries);
97 	g_free(rec);
98 
99         server->chanqueries = NULL;
100 }
101 
102 /* Add channel to query list */
query_add_channel(IRC_CHANNEL_REC * channel,int query_type)103 static void query_add_channel(IRC_CHANNEL_REC *channel, int query_type)
104 {
105 	SERVER_QUERY_REC *rec;
106 
107 	g_return_if_fail(channel != NULL);
108 
109 	rec = channel->server->chanqueries;
110 	rec->queries[query_type] =
111 		g_slist_append(rec->queries[query_type], channel);
112 }
113 
114 static void query_check(IRC_SERVER_REC *server);
115 
query_remove_all(IRC_CHANNEL_REC * channel)116 static void query_remove_all(IRC_CHANNEL_REC *channel)
117 {
118 	SERVER_QUERY_REC *rec;
119 	int n;
120 
121 	rec = channel->server->chanqueries;
122 	if (rec == NULL) return;
123 
124 	/* remove channel from query lists */
125 	for (n = 0; n < CHANNEL_QUERIES; n++)
126 		rec->queries[n] = g_slist_remove(rec->queries[n], channel);
127 	rec->current_queries = g_slist_remove(rec->current_queries, channel);
128 
129 	if (!channel->server->disconnected)
130 		query_check(channel->server);
131 }
132 
sig_channel_destroyed(IRC_CHANNEL_REC * channel)133 static void sig_channel_destroyed(IRC_CHANNEL_REC *channel)
134 {
135 	g_return_if_fail(channel != NULL);
136 
137 	if (IS_IRC_CHANNEL(channel))
138 		query_remove_all(channel);
139 }
140 
channels_have_all_names(IRC_SERVER_REC * server)141 static int channels_have_all_names(IRC_SERVER_REC *server)
142 {
143 	GSList *tmp;
144 
145 	for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
146 		IRC_CHANNEL_REC *rec = tmp->data;
147 
148 		if (IS_IRC_CHANNEL(rec) && !rec->names_got)
149 			return 0;
150 	}
151 
152 	return 1;
153 }
154 
query_find_next(SERVER_QUERY_REC * server)155 static int query_find_next(SERVER_QUERY_REC *server)
156 {
157 	int n;
158 
159 	for (n = 0; n < CHANNEL_QUERIES; n++) {
160 		if (server->queries[n] != NULL)
161 			return n;
162 	}
163 
164 	return -1;
165 }
166 
query_send(IRC_SERVER_REC * server,int query)167 static void query_send(IRC_SERVER_REC *server, int query)
168 {
169 	SERVER_QUERY_REC *rec;
170 	IRC_CHANNEL_REC *chanrec;
171 	GSList *chans;
172 	char *cmd, *chanstr_commas, *chanstr;
173 	int onlyone, count;
174 
175 	rec = server->chanqueries;
176 
177         /* get the list of channels to query */
178 	onlyone = (server->no_multi_who && query == CHANNEL_QUERY_WHO) ||
179 		(server->no_multi_mode && CHANNEL_IS_MODE_QUERY(query));
180 
181 	if (onlyone) {
182                 chans = rec->queries[query];
183 		rec->queries[query] =
184 			g_slist_remove_link(rec->queries[query], chans);
185 
186 		chanrec = chans->data;
187 		chanstr_commas = g_strdup(chanrec->name);
188 		chanstr = g_strdup(chanrec->name);
189                 count = 1;
190 	} else {
191 		char *chanstr_spaces;
192 
193 		chans = rec->queries[query];
194                 count = g_slist_length(chans);
195 
196 		if (count > server->max_query_chans) {
197 			GSList *lastchan;
198 
199 			lastchan = g_slist_nth(rec->queries[query],
200 					       server->max_query_chans-1);
201                         count = server->max_query_chans;
202 			rec->queries[query] = lastchan->next;
203 			lastchan->next = NULL;
204 		} else {
205                         rec->queries[query] = NULL;
206 		}
207 
208 		chanstr_commas = gslistptr_to_string(chans, G_STRUCT_OFFSET(IRC_CHANNEL_REC, name), ",");
209 		chanstr_spaces = gslistptr_to_string(chans, G_STRUCT_OFFSET(IRC_CHANNEL_REC, name), " ");
210 
211 		chanstr = g_strconcat(chanstr_commas, " ", chanstr_spaces, NULL);
212 		g_free(chanstr_spaces);
213 	}
214 
215 	rec->current_query_type = query;
216         rec->current_queries = chans;
217 
218 	switch (query) {
219 	case CHANNEL_QUERY_MODE:
220 		cmd = g_strdup_printf("MODE %s", chanstr_commas);
221 
222 		/* the stop-event is received once for each channel,
223 		   and we want to print 329 event (channel created). */
224 		server_redirect_event(server, "mode channel", count,
225 				      chanstr, -1, "chanquery abort",
226 				      "event 324", "chanquery mode",
227                                       "event 329", "event 329",
228 				      "", "chanquery abort", NULL);
229 		break;
230 
231 	case CHANNEL_QUERY_WHO:
232 		cmd = g_strdup_printf("WHO %s", chanstr_commas);
233 
234 		server_redirect_event(server, "who",
235 				      server->one_endofwho ? 1 : count,
236 				      chanstr, -1,
237 				      "chanquery abort",
238 				      "event 315", "chanquery who end",
239 				      "event 352", "silent event who",
240 				      "", "chanquery abort", NULL);
241 		break;
242 
243 	case CHANNEL_QUERY_BMODE:
244 		cmd = g_strdup_printf("MODE %s b", chanstr_commas);
245 		/* check all the multichannel problems with all
246 		   mode requests - if channels are joined manually
247 		   irssi could ask modes separately but afterwards
248 		   join the two b/e/I modes together */
249 		server_redirect_event(server, "mode b", count, chanstr, -1,
250 				      "chanquery abort",
251 				      "event 367", "chanquery ban",
252 				      "event 368", "chanquery ban end",
253 				      "", "chanquery abort", NULL);
254 		break;
255 
256 	default:
257                 cmd = NULL;
258 	}
259 
260 	irc_send_cmd(server, cmd);
261 
262 	g_free(chanstr);
263 	g_free(chanstr_commas);
264 	g_free(cmd);
265 }
266 
query_check(IRC_SERVER_REC * server)267 static void query_check(IRC_SERVER_REC *server)
268 {
269 	SERVER_QUERY_REC *rec;
270         int query;
271 
272 	g_return_if_fail(server != NULL);
273 
274 	rec = server->chanqueries;
275 	if (rec->current_queries != NULL)
276                 return; /* old queries haven't been answered yet */
277 
278 	if (server->max_query_chans > 1 && !server->no_multi_who && !server->no_multi_mode && !channels_have_all_names(server)) {
279 		/* all channels haven't sent /NAMES list yet */
280 		/* only do this if there would be a benefit in combining
281 		 * queries -- jilles */
282 		return;
283 	}
284 
285 	query = query_find_next(rec);
286 	if (query == -1) {
287 		/* no queries left */
288 		return;
289 	}
290 
291         query_send(server, query);
292 }
293 
294 /* if there's no more queries in queries in buffer, send the sync signal */
channel_checksync(IRC_CHANNEL_REC * channel)295 static void channel_checksync(IRC_CHANNEL_REC *channel)
296 {
297 	SERVER_QUERY_REC *rec;
298 	int n;
299 
300 	g_return_if_fail(channel != NULL);
301 
302 	if (channel->synced)
303 		return; /* already synced */
304 
305 	rec = channel->server->chanqueries;
306 	for (n = 0; n < CHANNEL_QUERIES; n++) {
307 		if (g_slist_find(rec->queries[n], channel))
308 			return;
309 	}
310 
311 	channel->synced = TRUE;
312 	signal_emit("channel sync", 1, channel);
313 }
314 
315 /* Error occurred when trying to execute query - abort and try again. */
query_current_error(IRC_SERVER_REC * server)316 static void query_current_error(IRC_SERVER_REC *server)
317 {
318 	SERVER_QUERY_REC *rec;
319 	GSList *tmp;
320         int query, abort_query;
321 
322 	rec = server->chanqueries;
323 
324 	/* fix the thing that went wrong - or if it was already fixed,
325 	   then all we can do is abort. */
326         abort_query = FALSE;
327 
328 	query = rec->current_query_type;
329 	if (query == CHANNEL_QUERY_WHO) {
330 		if (server->no_multi_who)
331 			abort_query = TRUE;
332 		else
333 			server->no_multi_who = TRUE;
334 	} else {
335 		if (server->no_multi_mode)
336                         abort_query = TRUE;
337                 else
338 			server->no_multi_mode = TRUE;
339 	}
340 
341 	if (!abort_query) {
342 		/* move all currently queried channels to main query lists */
343 		for (tmp = rec->current_queries; tmp != NULL; tmp = tmp->next) {
344 			rec->queries[query] =
345 				g_slist_append(rec->queries[query], tmp->data);
346 		}
347 	} else {
348 		/* check if failed channels are synced after this error */
349 		g_slist_foreach(rec->current_queries,
350 				(GFunc) channel_checksync, NULL);
351 	}
352 
353 	g_slist_free(rec->current_queries);
354 	rec->current_queries = NULL;
355 
356         query_check(server);
357 }
358 
sig_channel_joined(IRC_CHANNEL_REC * channel)359 static void sig_channel_joined(IRC_CHANNEL_REC *channel)
360 {
361 	if (!IS_IRC_CHANNEL(channel))
362 		return;
363 
364 	if (!settings_get_bool("channel_sync"))
365 		return;
366 
367 	/* Add channel to query lists */
368 	if (!channel->no_modes)
369 		query_add_channel(channel, CHANNEL_QUERY_MODE);
370 	if (g_hash_table_size(channel->nicks) <
371 	    settings_get_int("channel_max_who_sync"))
372 		query_add_channel(channel, CHANNEL_QUERY_WHO);
373 	if (!channel->no_modes)
374 		query_add_channel(channel, CHANNEL_QUERY_BMODE);
375 
376 	query_check(channel->server);
377 }
378 
channel_got_query(IRC_CHANNEL_REC * chanrec,int query_type)379 static void channel_got_query(IRC_CHANNEL_REC *chanrec, int query_type)
380 {
381 	SERVER_QUERY_REC *rec;
382 
383 	g_return_if_fail(chanrec != NULL);
384 
385 	rec = chanrec->server->chanqueries;
386 	if (query_type != rec->current_query_type)
387                 return; /* shouldn't happen */
388 
389         /* got the query for channel.. */
390 	rec->current_queries =
391 		g_slist_remove(rec->current_queries, chanrec);
392 	channel_checksync(chanrec);
393 
394 	/* check if we need to send another query.. */
395 	query_check(chanrec->server);
396 }
397 
event_channel_mode(IRC_SERVER_REC * server,const char * data,const char * nick)398 static void event_channel_mode(IRC_SERVER_REC *server, const char *data,
399 			       const char *nick)
400 {
401 	IRC_CHANNEL_REC *chanrec;
402 	char *params, *channel, *mode;
403 
404 	g_return_if_fail(data != NULL);
405 
406 	params = event_get_params(data, 3 | PARAM_FLAG_GETREST,
407 				  NULL, &channel, &mode);
408 	chanrec = irc_channel_find(server, channel);
409 	if (chanrec != NULL) {
410 		if (chanrec->key != NULL && strchr(mode, 'k') == NULL) {
411 			/* we joined the channel with a key,
412 			   but it didn't have +k mode.. */
413                         parse_channel_modes(chanrec, NULL, "-k", TRUE);
414 		}
415 		parse_channel_modes(chanrec, nick, mode, FALSE);
416 		channel_got_query(chanrec, CHANNEL_QUERY_MODE);
417 	}
418 
419 	g_free(params);
420 }
421 
event_end_of_who(IRC_SERVER_REC * server,const char * data)422 static void event_end_of_who(IRC_SERVER_REC *server, const char *data)
423 {
424         SERVER_QUERY_REC *rec;
425         GSList *tmp, *next;
426 	char *params, *channel, **channels;
427         int failed, multiple;
428 
429 	g_return_if_fail(data != NULL);
430 
431 	params = event_get_params(data, 2, NULL, &channel);
432 	multiple = strchr(channel, ',') != NULL;
433 	channels = g_strsplit(channel, ",", -1);
434 
435         failed = FALSE;
436 	rec = server->chanqueries;
437 	for (tmp = rec->current_queries; tmp != NULL; tmp = next) {
438 		IRC_CHANNEL_REC *chanrec = tmp->data;
439 
440                 next = tmp->next;
441 		if (strarray_find(channels, chanrec->name) == -1)
442 			continue;
443 
444 		if (chanrec->ownnick->host == NULL && multiple &&
445 		    !server->one_endofwho) {
446 			/* we should receive our own host for each channel.
447 			   However, some servers really are stupid enough
448 			   not to reply anything to /WHO requests.. */
449 			failed = TRUE;
450 		} else {
451 			chanrec->wholist = TRUE;
452 			signal_emit("channel wholist", 1, chanrec);
453 			channel_got_query(chanrec, CHANNEL_QUERY_WHO);
454 		}
455 	}
456 
457 	g_strfreev(channels);
458 	if (multiple)
459 		server->one_endofwho = TRUE;
460 
461 	if (failed) {
462 		/* server didn't understand multiple WHO replies,
463 		   send them again separately */
464                 query_current_error(server);
465 	}
466 
467         g_free(params);
468 }
469 
event_end_of_banlist(IRC_SERVER_REC * server,const char * data)470 static void event_end_of_banlist(IRC_SERVER_REC *server, const char *data)
471 {
472 	IRC_CHANNEL_REC *chanrec;
473 	char *params, *channel;
474 
475 	g_return_if_fail(data != NULL);
476 
477 	params = event_get_params(data, 2, NULL, &channel);
478 	chanrec = irc_channel_find(server, channel);
479 
480 	if (chanrec != NULL)
481 		channel_got_query(chanrec, CHANNEL_QUERY_BMODE);
482 
483 	g_free(params);
484 }
485 
channels_query_init(void)486 void channels_query_init(void)
487 {
488 	settings_add_bool("misc", "channel_sync", TRUE);
489 	settings_add_int("misc", "channel_max_who_sync", 1000);
490 
491 	signal_add("server connected", (SIGNAL_FUNC) sig_connected);
492 	signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
493 	signal_add("channel joined", (SIGNAL_FUNC) sig_channel_joined);
494 	signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
495 
496 	signal_add("chanquery mode", (SIGNAL_FUNC) event_channel_mode);
497 	signal_add("chanquery who end", (SIGNAL_FUNC) event_end_of_who);
498 
499 	signal_add("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist);
500 	signal_add("chanquery abort", (SIGNAL_FUNC) query_current_error);
501 }
502 
channels_query_deinit(void)503 void channels_query_deinit(void)
504 {
505 	signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
506 	signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
507 	signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined);
508 	signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
509 
510 	signal_remove("chanquery mode", (SIGNAL_FUNC) event_channel_mode);
511 	signal_remove("chanquery who end", (SIGNAL_FUNC) event_end_of_who);
512 
513 	signal_remove("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist);
514 	signal_remove("chanquery abort", (SIGNAL_FUNC) query_current_error);
515 }
516