1 /*
2  channel-rejoin.c : rejoin to channel if it's "temporarily unavailable"
3                     this has nothing to do with autorejoin if kicked
4 
5     Copyright (C) 2000 Timo Sirainen
6 
7     This program is free software; you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation; either version 2 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License along
18     with this program; if not, write to the Free Software Foundation, Inc.,
19     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21 
22 #include "module.h"
23 #include "signals.h"
24 #include "settings.h"
25 #include "misc.h"
26 
27 #include "irc-servers.h"
28 #include "irc-channels.h"
29 #include "irc-commands.h"
30 #include "channel-rejoin.h"
31 
32 #define REJOIN_TIMEOUT (1000*60*5) /* try to rejoin every 5 minutes */
33 
34 static int rejoin_tag;
35 
rejoin_destroy(IRC_SERVER_REC * server,REJOIN_REC * rec)36 static void rejoin_destroy(IRC_SERVER_REC *server, REJOIN_REC *rec)
37 {
38 	g_return_if_fail(IS_IRC_SERVER(server));
39 	g_return_if_fail(rec != NULL);
40 
41 	server->rejoin_channels =
42 		g_slist_remove(server->rejoin_channels, rec);
43 
44 	signal_emit("channel rejoin remove", 2, server, rec);
45 
46 	g_free(rec->channel);
47 	g_free_not_null(rec->key);
48 	g_free(rec);
49 }
50 
rejoin_find(IRC_SERVER_REC * server,const char * channel)51 static REJOIN_REC *rejoin_find(IRC_SERVER_REC *server, const char *channel)
52 {
53 	GSList *tmp;
54 
55 	g_return_val_if_fail(IS_IRC_SERVER(server), NULL);
56 	g_return_val_if_fail(channel != NULL, NULL);
57 
58 	for (tmp = server->rejoin_channels; tmp != NULL; tmp = tmp->next) {
59                 REJOIN_REC *rec = tmp->data;
60 
61 		if (g_ascii_strcasecmp(rec->channel, channel) == 0)
62                         return rec;
63 	}
64 
65 	return NULL;
66 }
67 
68 #define channel_have_key(chan) \
69 	((chan) != NULL && (chan)->key != NULL && (chan)->key[0] != '\0')
70 
channel_rejoin(IRC_SERVER_REC * server,const char * channel)71 static int channel_rejoin(IRC_SERVER_REC *server, const char *channel)
72 {
73 	IRC_CHANNEL_REC *chanrec;
74 	REJOIN_REC *rec;
75 
76 	g_return_val_if_fail(IS_IRC_SERVER(server), 0);
77 	g_return_val_if_fail(channel != NULL, 0);
78 
79 	chanrec = irc_channel_find(server, channel);
80 	if (chanrec == NULL || chanrec->joined) return 0;
81 
82 	if (!settings_get_bool("channels_rejoin_unavailable")) {
83 		chanrec->left = TRUE;
84 		channel_destroy(CHANNEL(chanrec));
85 		return 0;
86 	}
87 
88 	rec = rejoin_find(server, channel);
89 	if (rec != NULL) {
90 		/* already exists */
91 		rec->joining = FALSE;
92 
93 		/* update channel key */
94 		g_free_and_null(rec->key);
95 		if (channel_have_key(chanrec))
96 			rec->key = g_strdup(chanrec->key);
97 	} else {
98 		/* new rejoin */
99 		rec = g_new0(REJOIN_REC, 1);
100 		rec->channel = g_strdup(channel);
101 		if (channel_have_key(chanrec))
102 			rec->key = g_strdup(chanrec->key);
103 
104 		server->rejoin_channels =
105 			g_slist_append(server->rejoin_channels, rec);
106 		signal_emit("channel rejoin new", 2, server, rec);
107 	}
108 
109 	chanrec->left = TRUE;
110 	channel_destroy(CHANNEL(chanrec));
111 	return 1;
112 }
113 
event_duplicate_channel(IRC_SERVER_REC * server,const char * data)114 static void event_duplicate_channel(IRC_SERVER_REC *server, const char *data)
115 {
116 	CHANNEL_REC *chanrec;
117 	char *params, *channel, *p;
118 
119 	g_return_if_fail(data != NULL);
120 
121 	params = event_get_params(data, 3, NULL, NULL, &channel);
122 	p = strchr(channel, ' ');
123 	if (p != NULL) *p = '\0';
124 
125 	if (channel[0] == '!' && channel[1] != '!') {
126 		chanrec = channel_find(SERVER(server), channel);
127 		if (chanrec != NULL && !chanrec->names_got) {
128 			/* duplicate channel - this should only happen when
129 			   there's some sync problem with servers, rejoining
130 			   after a while should help.
131 
132 			   note that this same 407 is sent when trying to
133 			   create !!channel that already exists so we don't
134 			   want to try rejoining then. */
135 			if (channel_rejoin(server, channel)) {
136 				signal_stop();
137 			}
138 		}
139 	}
140 
141 	g_free(params);
142 }
143 
event_target_unavailable(IRC_SERVER_REC * server,const char * data)144 static void event_target_unavailable(IRC_SERVER_REC *server, const char *data)
145 {
146 	char *params, *channel;
147 	IRC_CHANNEL_REC *chanrec;
148 
149 	g_return_if_fail(data != NULL);
150 
151 	params = event_get_params(data, 2, NULL, &channel);
152 	if (server_ischannel(SERVER(server), channel)) {
153 		chanrec = irc_channel_find(server, channel);
154 		if (chanrec != NULL && chanrec->joined) {
155 			/* dalnet event - can't change nick while
156 			   banned in channel */
157 		} else {
158 			/* channel is unavailable - try to join again
159 			   a bit later */
160 			if (channel_rejoin(server, channel)) {
161 				signal_stop();
162 			}
163 		}
164 	}
165 
166 	g_free(params);
167 }
168 
169 /* join ok/failed - remove from rejoins list. this happens always after join
170    except if the "target unavailable" error happens again */
sig_remove_rejoin(IRC_CHANNEL_REC * channel)171 static void sig_remove_rejoin(IRC_CHANNEL_REC *channel)
172 {
173 	REJOIN_REC *rec;
174 
175 	if (!IS_IRC_CHANNEL(channel))
176 		return;
177 
178 	rec = rejoin_find(channel->server, channel->name);
179 	if (rec != NULL && rec->joining) {
180 		/* join failed, remove the rejoin */
181 		rejoin_destroy(channel->server, rec);
182 	}
183 }
184 
sig_disconnected(IRC_SERVER_REC * server)185 static void sig_disconnected(IRC_SERVER_REC *server)
186 {
187 	if (!IS_IRC_SERVER(server))
188 		return;
189 
190 	while (server->rejoin_channels != NULL)
191 		rejoin_destroy(server, server->rejoin_channels->data);
192 }
193 
server_rejoin_channels(IRC_SERVER_REC * server)194 static void server_rejoin_channels(IRC_SERVER_REC *server)
195 {
196 	GSList *tmp, *next;
197 	GString *channels, *keys;
198 	int use_keys;
199 
200 	g_return_if_fail(IS_IRC_SERVER(server));
201 
202 	channels = g_string_new(NULL);
203 	keys = g_string_new(NULL);
204 
205         use_keys = FALSE;
206 	for (tmp = server->rejoin_channels; tmp != NULL; tmp = next) {
207 		REJOIN_REC *rec = tmp->data;
208 		next = tmp->next;
209 
210 		if (rec->joining) {
211 			/* we missed the join (failed) message,
212 			   remove from rejoins.. */
213 			rejoin_destroy(server, rec);
214 			continue;
215 		}
216 
217 		rec->joining = TRUE;
218 		g_string_append_printf(channels, "%s,", rec->channel);
219 		if (rec->key == NULL)
220 			g_string_append(keys, "x,");
221 		else {
222 			g_string_append_printf(keys, "%s,", rec->key);
223                         use_keys = TRUE;
224 		}
225 	}
226 
227 	if (channels->len > 0) {
228                 g_string_truncate(channels, channels->len-1);
229                 g_string_truncate(keys, keys->len-1);
230 
231 		if (use_keys) g_string_append_printf(channels, " %s", keys->str);
232 		server->channels_join(SERVER(server), channels->str, TRUE);
233 	}
234 
235 	g_string_free(channels, TRUE);
236 	g_string_free(keys, TRUE);
237 }
238 
sig_rejoin(void)239 static int sig_rejoin(void)
240 {
241 	GSList *tmp;
242 
243 	for (tmp = servers; tmp != NULL; tmp = tmp->next) {
244 		IRC_SERVER_REC *rec = tmp->data;
245 
246 		if (IS_IRC_SERVER(rec) && rec->rejoin_channels != NULL)
247 			server_rejoin_channels(rec);
248 	}
249 
250 	return TRUE;
251 }
252 
cmd_rmrejoins(const char * data,IRC_SERVER_REC * server)253 static void cmd_rmrejoins(const char *data, IRC_SERVER_REC *server)
254 {
255         CMD_IRC_SERVER(server);
256 
257 	while (server->rejoin_channels != NULL)
258 		rejoin_destroy(server, server->rejoin_channels->data);
259 }
260 
channel_rejoin_init(void)261 void channel_rejoin_init(void)
262 {
263 	settings_add_bool("servers", "channels_rejoin_unavailable", TRUE);
264 
265 	rejoin_tag = g_timeout_add(REJOIN_TIMEOUT,
266 				   (GSourceFunc) sig_rejoin, NULL);
267 
268 	command_bind_irc("rmrejoins", NULL, (SIGNAL_FUNC) cmd_rmrejoins);
269 	signal_add_first("event 407", (SIGNAL_FUNC) event_duplicate_channel);
270 	signal_add_first("event 437", (SIGNAL_FUNC) event_target_unavailable);
271 	signal_add_first("channel joined", (SIGNAL_FUNC) sig_remove_rejoin);
272 	signal_add_first("channel destroyed", (SIGNAL_FUNC) sig_remove_rejoin);
273 	signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
274 }
275 
channel_rejoin_deinit(void)276 void channel_rejoin_deinit(void)
277 {
278 	g_source_remove(rejoin_tag);
279 
280 	command_unbind("rmrejoins", (SIGNAL_FUNC) cmd_rmrejoins);
281 	signal_remove("event 407", (SIGNAL_FUNC) event_duplicate_channel);
282 	signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable);
283 	signal_remove("channel joined", (SIGNAL_FUNC) sig_remove_rejoin);
284 	signal_remove("channel destroyed", (SIGNAL_FUNC) sig_remove_rejoin);
285 	signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
286 }
287