1 /*
2  netsplit.c : irssi
3 
4     Copyright (C) 1999 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 #include "module.h"
22 #include "signals.h"
23 #include "commands.h"
24 #include "misc.h"
25 
26 #include "irc-servers.h"
27 #include "irc-channels.h"
28 #include "netsplit.h"
29 
30 /* How long to keep netsplits in memory (seconds) */
31 #define NETSPLIT_MAX_REMEMBER (60*60)
32 
33 static int split_tag;
34 
netsplit_server_find(IRC_SERVER_REC * server,const char * servername,const char * destserver)35 static NETSPLIT_SERVER_REC *netsplit_server_find(IRC_SERVER_REC *server,
36 						 const char *servername,
37 						 const char *destserver)
38 {
39 	GSList *tmp;
40 
41 	g_return_val_if_fail(IS_IRC_SERVER(server), NULL);
42 
43 	for (tmp = server->split_servers; tmp != NULL; tmp = tmp->next) {
44 		NETSPLIT_SERVER_REC *rec = tmp->data;
45 
46 		if (g_ascii_strcasecmp(rec->server, servername) == 0 &&
47 		    g_ascii_strcasecmp(rec->destserver, destserver) == 0)
48 			return rec;
49 	}
50 
51 	return NULL;
52 }
53 
netsplit_server_create(IRC_SERVER_REC * server,const char * servername,const char * destserver)54 static NETSPLIT_SERVER_REC *netsplit_server_create(IRC_SERVER_REC *server,
55 						   const char *servername,
56 						   const char *destserver)
57 {
58 	NETSPLIT_SERVER_REC *rec;
59 
60 	g_return_val_if_fail(IS_IRC_SERVER(server), NULL);
61 
62 	rec = netsplit_server_find(server, servername, destserver);
63 	if (rec != NULL) {
64 		rec->last = time(NULL);
65 		return rec;
66 	}
67 
68 	rec = g_new0(NETSPLIT_SERVER_REC, 1);
69 	rec->last = time(NULL);
70 	rec->server = g_strdup(servername);
71 	rec->destserver = g_strdup(destserver);
72 
73 	server->split_servers = g_slist_append(server->split_servers, rec);
74 	signal_emit("netsplit server new", 2, server, rec);
75 
76 	return rec;
77 }
78 
netsplit_server_destroy(IRC_SERVER_REC * server,NETSPLIT_SERVER_REC * rec)79 static void netsplit_server_destroy(IRC_SERVER_REC *server,
80 				    NETSPLIT_SERVER_REC *rec)
81 {
82 	g_return_if_fail(IS_IRC_SERVER(server));
83 
84 	server->split_servers = g_slist_remove(server->split_servers, rec);
85 
86 	signal_emit("netsplit server remove", 2, server, rec);
87 
88         g_free(rec->server);
89 	g_free(rec->destserver);
90 	g_free(rec);
91 }
92 
netsplit_add(IRC_SERVER_REC * server,const char * nick,const char * address,const char * servers)93 static NETSPLIT_REC *netsplit_add(IRC_SERVER_REC *server, const char *nick,
94 				  const char *address, const char *servers)
95 {
96 	NETSPLIT_REC *rec;
97 	NETSPLIT_CHAN_REC *splitchan;
98 	NICK_REC *nickrec;
99 	GSList *tmp;
100 	char *p, *dupservers;
101 
102 	g_return_val_if_fail(IS_IRC_SERVER(server), NULL);
103 	g_return_val_if_fail(nick != NULL, NULL);
104 	g_return_val_if_fail(address != NULL, NULL);
105 
106 	/* get splitted servers */
107 	dupservers = g_strdup(servers);
108 	p = strchr(dupservers, ' ');
109 	if (p == NULL) {
110 		g_free(dupservers);
111 		g_warning("netsplit_add() : only one server found");
112 		return NULL;
113 	}
114 	*p++ = '\0';
115 
116 	rec = g_new0(NETSPLIT_REC, 1);
117 	rec->nick = g_strdup(nick);
118 	rec->address = g_strdup(address);
119 	rec->destroy = time(NULL)+NETSPLIT_MAX_REMEMBER;
120 
121 	rec->server = netsplit_server_create(server, dupservers, p);
122 	rec->server->count++;
123 	g_free(dupservers);
124 
125 	/* copy the channel nick records.. */
126 	for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
127 		CHANNEL_REC *channel = tmp->data;
128 
129 		nickrec = nicklist_find(channel, nick);
130 		if (nickrec == NULL)
131 			continue;
132 
133 		splitchan = g_new0(NETSPLIT_CHAN_REC, 1);
134 		splitchan->name = g_strdup(channel->visible_name);
135 		splitchan->op = nickrec->op;
136 		splitchan->halfop = nickrec->halfop;
137 		splitchan->voice = nickrec->voice;
138 		memcpy(splitchan->prefixes, nickrec->prefixes, sizeof(splitchan->prefixes));
139 
140 		rec->channels = g_slist_append(rec->channels, splitchan);
141 	}
142 
143 	if (rec->channels == NULL)
144 		g_warning("netsplit_add(): nick '%s' not in any channels", nick);
145 
146 	g_hash_table_insert(server->splits, rec->nick, rec);
147 
148 	signal_emit("netsplit new", 1, rec);
149 	return rec;
150 }
151 
netsplit_destroy(IRC_SERVER_REC * server,NETSPLIT_REC * rec)152 static void netsplit_destroy(IRC_SERVER_REC *server, NETSPLIT_REC *rec)
153 {
154 	GSList *tmp;
155 
156 	g_return_if_fail(IS_IRC_SERVER(server));
157 	g_return_if_fail(rec != NULL);
158 
159 	signal_emit("netsplit remove", 1, rec);
160 	for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) {
161 		NETSPLIT_CHAN_REC *rec = tmp->data;
162 
163 		g_free(rec->name);
164 		g_free(rec);
165 	}
166 	g_slist_free(rec->channels);
167 
168 	if (--rec->server->count == 0)
169 		netsplit_server_destroy(server, rec->server);
170 
171 	g_free(rec->nick);
172 	g_free(rec->address);
173 	g_free(rec);
174 }
175 
netsplit_destroy_hash(void * key,NETSPLIT_REC * rec,IRC_SERVER_REC * server)176 static void netsplit_destroy_hash(void *key, NETSPLIT_REC *rec,
177 				  IRC_SERVER_REC *server)
178 {
179 	netsplit_destroy(server, rec);
180 }
181 
netsplit_find(IRC_SERVER_REC * server,const char * nick,const char * address)182 NETSPLIT_REC *netsplit_find(IRC_SERVER_REC *server, const char *nick,
183 			    const char *address)
184 {
185 	NETSPLIT_REC *rec;
186 
187 	g_return_val_if_fail(IS_IRC_SERVER(server), NULL);
188 	g_return_val_if_fail(nick != NULL, NULL);
189 
190 	rec = g_hash_table_lookup(server->splits, nick);
191 	if (rec == NULL) return NULL;
192 
193 	return (address == NULL ||
194 		g_ascii_strcasecmp(rec->address, address) == 0) ? rec : NULL;
195 }
196 
netsplit_find_channel(IRC_SERVER_REC * server,const char * nick,const char * address,const char * channel)197 NETSPLIT_CHAN_REC *netsplit_find_channel(IRC_SERVER_REC *server,
198 					 const char *nick, const char *address,
199 					 const char *channel)
200 {
201 	NETSPLIT_REC *rec;
202 	GSList *tmp;
203 
204 	g_return_val_if_fail(IS_IRC_SERVER(server), NULL);
205 	g_return_val_if_fail(nick != NULL, NULL);
206 	g_return_val_if_fail(channel != NULL, NULL);
207 
208 	rec = netsplit_find(server, nick, address);
209 	if (rec == NULL) return NULL;
210 
211 	for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) {
212 		NETSPLIT_CHAN_REC *rec = tmp->data;
213 
214 		if (g_ascii_strcasecmp(rec->name, channel) == 0)
215 			return rec;
216 	}
217 
218 	return NULL;
219 }
220 
221 /* check if quit message is a netsplit message */
quitmsg_is_split(const char * msg)222 int quitmsg_is_split(const char *msg)
223 {
224 	const char *host2, *p;
225         int prev, len, host1_dot, host2_dot;
226 
227 	g_return_val_if_fail(msg != NULL, FALSE);
228 
229 	/* NOTE: there used to be some paranoia checks (some older IRC
230 	   clients have even more), but they're pretty useless nowadays,
231 	   since IRC server prefixes the quit message with a space if it
232 	   looks like a netsplit message.
233 
234 	   So, the check is currently just:
235              - host1.domain1 host2.domain2
236              - top-level domains have to be 2+ characters long,
237 	       containing only alphabets
238 	     - only 1 space
239 	     - no double-dots (".." - probably useless check)
240 	     - hosts/domains can't start or end with a dot
241              - the two hosts can't be identical (probably useless check)
242 	     - can't contain ':' or '/' chars (some servers allow URLs)
243 	   */
244 	host2 = NULL;
245 	prev = '\0'; len = 0; host1_dot = host2_dot = 0;
246 	while (*msg != '\0') {
247 		if (*msg == ' ') {
248 			if (prev == '.' || prev == '\0') {
249 				/* domains can't end with '.', space can't
250 				   be the first character in msg. */
251 				return FALSE;
252 			}
253 			if (host2 != NULL)
254 				return FALSE; /* only one space allowed */
255 			if (!host1_dot)
256                                 return FALSE; /* host1 didn't have domain */
257                         host2 = msg+1; len = -1;
258 		} else if (*msg == '.') {
259 			if (prev == '\0' || prev == ' ' || prev == '.') {
260 				/* domains can't start with '.'
261 				   and can't have ".." */
262 				return FALSE;
263 			}
264 
265 			if (host2 != NULL)
266 				host2_dot = TRUE;
267 			else
268                                 host1_dot = TRUE;
269 		} else if (*msg == ':' || *msg == '/')
270 			return FALSE;
271 
272 		prev = *msg;
273                 msg++; len++;
274 	}
275 
276 	if (!host2_dot || prev == '.')
277                 return FALSE;
278 
279         /* top-domain1 must be 2+ chars long and contain only alphabets */
280 	p = host2-1;
281 	while (p[-1] != '.') {
282 		if (!i_isalpha(p[-1]))
283                         return FALSE;
284 		p--;
285 	}
286 	if (host2-p-1 < 2) return FALSE;
287 
288         /* top-domain2 must be 2+ chars long and contain only alphabets */
289 	p = host2+strlen(host2);
290 	while (p[-1] != '.') {
291 		if (!i_isalpha(p[-1]))
292                         return FALSE;
293 		p--;
294 	}
295 	if (strlen(p) < 2) return FALSE;
296 
297         return TRUE;
298 }
299 
split_set_timeout(void * key,NETSPLIT_REC * rec,NETSPLIT_REC * orig)300 static void split_set_timeout(void *key, NETSPLIT_REC *rec, NETSPLIT_REC *orig)
301 {
302 	/* same servers -> split over -> destroy old records sooner.. */
303 	if (rec->server == orig->server)
304 		rec->destroy = time(NULL)+60;
305 }
306 
event_join(IRC_SERVER_REC * server,const char * data,const char * nick,const char * address)307 static void event_join(IRC_SERVER_REC *server, const char *data,
308 		       const char *nick, const char *address)
309 {
310 	NETSPLIT_REC *rec;
311 
312 	if (nick == NULL)
313 		return;
314 
315 	/* check if split is over */
316 	rec = g_hash_table_lookup(server->splits, nick);
317 
318 	if (rec != NULL && g_ascii_strcasecmp(rec->address, address) == 0) {
319 		/* yep, looks like it is. for same people that had the same
320 		   splitted servers set the timeout to one minute.
321 
322 		   .. if the user just changed server, she can't use the
323 		   same nick (unless the server is broken) so don't bother
324 		   checking that the nick's server matches the split. */
325 		g_hash_table_foreach(server->splits,
326 				     (GHFunc) split_set_timeout, rec);
327 	}
328 }
329 
330 /* remove the nick from netsplit, but do it last so that other "event join"
331    signal handlers can check if the join was a netjoin */
event_join_last(IRC_SERVER_REC * server,const char * data,const char * nick,const char * address)332 static void event_join_last(IRC_SERVER_REC *server, const char *data,
333 			    const char *nick, const char *address)
334 {
335 	NETSPLIT_REC *rec;
336 
337 	if (nick == NULL)
338 		return;
339 
340 	rec = g_hash_table_lookup(server->splits, nick);
341 	if (rec != NULL) {
342 		g_hash_table_remove(server->splits, rec->nick);
343 		netsplit_destroy(server, rec);
344 	}
345 }
346 
event_quit(IRC_SERVER_REC * server,const char * data,const char * nick,const char * address)347 static void event_quit(IRC_SERVER_REC *server, const char *data,
348 		       const char *nick, const char *address)
349 {
350 	g_return_if_fail(data != NULL);
351 
352 	if (*data == ':') data++;
353 	if (g_ascii_strcasecmp(nick, server->nick) != 0 && quitmsg_is_split(data)) {
354 		/* netsplit! */
355 		netsplit_add(server, nick, address, data);
356 	}
357 }
358 
event_nick(IRC_SERVER_REC * server,const char * data)359 static void event_nick(IRC_SERVER_REC *server, const char *data)
360 {
361 	NETSPLIT_REC *rec;
362 	char *params, *nick;
363 
364 	params = event_get_params(data, 1, &nick);
365 
366 	/* remove nick from split list when somebody changed
367 	   nick to this one during split */
368         rec = g_hash_table_lookup(server->splits, nick);
369 	if (rec != NULL) {
370 	        g_hash_table_remove(server->splits, rec->nick);
371 		netsplit_destroy(server, rec);
372 	}
373 
374         g_free(params);
375 }
376 
sig_disconnected(IRC_SERVER_REC * server)377 static void sig_disconnected(IRC_SERVER_REC *server)
378 {
379 	g_return_if_fail(server != NULL);
380 
381 	if (!IS_IRC_SERVER(server))
382 		return;
383 
384 	g_hash_table_foreach(server->splits,
385 			     (GHFunc) netsplit_destroy_hash, server);
386 	g_hash_table_destroy(server->splits);
387         server->splits = NULL;
388 }
389 
split_server_check(void * key,NETSPLIT_REC * rec,IRC_SERVER_REC * server)390 static int split_server_check(void *key, NETSPLIT_REC *rec,
391 			      IRC_SERVER_REC *server)
392 {
393 	/* Check if this split record is too old.. */
394 	if (rec->destroy > time(NULL))
395 		return FALSE;
396 
397 	netsplit_destroy(server, rec);
398 	return TRUE;
399 }
400 
split_check_old(void)401 static int split_check_old(void)
402 {
403 	GSList *tmp;
404 
405 	for (tmp = servers; tmp != NULL; tmp = tmp->next) {
406 		IRC_SERVER_REC *server = tmp->data;
407 
408 		if (!IS_IRC_SERVER(server))
409 			continue;
410 
411 		g_hash_table_foreach_remove(server->splits,
412 					    (GHRFunc) split_server_check,
413 					    server);
414 	}
415 
416 	return 1;
417 }
418 
netsplit_init(void)419 void netsplit_init(void)
420 {
421 	split_tag = g_timeout_add(1000, (GSourceFunc) split_check_old, NULL);
422 	signal_add_first("event join", (SIGNAL_FUNC) event_join);
423 	signal_add_last("event join", (SIGNAL_FUNC) event_join_last);
424 	signal_add_first("event quit", (SIGNAL_FUNC) event_quit);
425 	signal_add("event nick", (SIGNAL_FUNC) event_nick);
426 	signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
427 }
428 
netsplit_deinit(void)429 void netsplit_deinit(void)
430 {
431 	g_source_remove(split_tag);
432 	signal_remove("event join", (SIGNAL_FUNC) event_join);
433 	signal_remove("event join", (SIGNAL_FUNC) event_join_last);
434 	signal_remove("event quit", (SIGNAL_FUNC) event_quit);
435 	signal_remove("event nick", (SIGNAL_FUNC) event_nick);
436 	signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
437 }
438