imported.
[crypto.git] / apps / irssi / src / core / servers-reconnect.c
1 /*
2  servers-reconnect.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
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "commands.h"
23 #include "network.h"
24 #include "signals.h"
25
26 #include "chat-protocols.h"
27 #include "servers.h"
28 #include "servers-setup.h"
29 #include "servers-reconnect.h"
30
31 #include "settings.h"
32
33 GSList *reconnects;
34 static int last_reconnect_tag;
35 static int reconnect_timeout_tag;
36 static int reconnect_time;
37
38 void reconnect_save_status(SERVER_CONNECT_REC *conn, SERVER_REC *server)
39 {
40         g_free_not_null(conn->away_reason);
41         conn->away_reason = !server->usermode_away ? NULL :
42                 g_strdup(server->away_reason);
43
44         signal_emit("server reconnect save status", 2, conn, server);
45 }
46
47 static void server_reconnect_add(SERVER_CONNECT_REC *conn,
48                                  time_t next_connect)
49 {
50         RECONNECT_REC *rec;
51
52         g_return_if_fail(IS_SERVER_CONNECT(conn));
53
54         rec = g_new(RECONNECT_REC, 1);
55         rec->tag = ++last_reconnect_tag;
56         rec->conn = conn;
57         rec->next_connect = next_connect;
58
59         reconnects = g_slist_append(reconnects, rec);
60 }
61
62 void server_reconnect_destroy(RECONNECT_REC *rec, int free_conn)
63 {
64         g_return_if_fail(rec != NULL);
65
66         reconnects = g_slist_remove(reconnects, rec);
67
68         signal_emit("server reconnect remove", 1, rec);
69         if (free_conn) server_connect_free(rec->conn);
70         g_free(rec);
71
72         if (reconnects == NULL)
73             last_reconnect_tag = 0;
74 }
75
76 static int server_reconnect_timeout(void)
77 {
78         SERVER_CONNECT_REC *conn;
79         GSList *list, *tmp;
80         time_t now;
81
82         /* If server_connect() removes the next reconnection in queue,
83            we're screwed. I don't think this should happen anymore, but just
84            to be sure we don't crash, do this safely. */
85         list = g_slist_copy(reconnects);
86         now = time(NULL);
87         for (tmp = list; tmp != NULL; tmp = tmp->next) {
88                 RECONNECT_REC *rec = tmp->data;
89
90                 if (g_slist_find(reconnects, rec) == NULL)
91                         continue;
92
93                 if (rec->next_connect <= now) {
94                         conn = rec->conn;
95                         server_reconnect_destroy(rec, FALSE);
96                         CHAT_PROTOCOL(conn)->server_connect(conn);
97                 }
98         }
99
100         g_slist_free(list);
101         return 1;
102 }
103
104 static void sserver_connect(SERVER_SETUP_REC *rec, SERVER_CONNECT_REC *conn)
105 {
106         conn->family = rec->family;
107         conn->address = g_strdup(rec->address);
108         if (conn->port == 0) conn->port = rec->port;
109
110         server_setup_fill_reconn(conn, rec);
111         if (rec->last_connect > time(NULL)-reconnect_time) {
112                 /* can't reconnect this fast, wait.. */
113                 server_reconnect_add(conn, rec->last_connect+reconnect_time);
114         } else {
115                 /* connect to server.. */
116                 CHAT_PROTOCOL(conn)->server_connect(conn);
117         }
118 }
119
120 static SERVER_CONNECT_REC *
121 server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info)
122 {
123         SERVER_CONNECT_REC *dest;
124
125         dest = NULL;
126         signal_emit("server connect copy", 2, &dest, src);
127         g_return_val_if_fail(dest != NULL, NULL);
128
129         dest->type = module_get_uniq_id("SERVER CONNECT", 0);
130         dest->reconnection = src->reconnection;
131         dest->proxy = g_strdup(src->proxy);
132         dest->proxy_port = src->proxy_port;
133         dest->proxy_string = g_strdup(src->proxy_string);
134         dest->proxy_password = g_strdup(src->proxy_password);
135
136         if (connect_info) {
137                 dest->family = src->family;
138                 dest->address = g_strdup(src->address);
139                 dest->port = src->port;
140                 dest->password = g_strdup(src->password);
141         }
142
143         dest->chatnet = g_strdup(src->chatnet);
144         dest->nick = g_strdup(src->nick);
145         dest->username = g_strdup(src->username);
146         dest->realname = g_strdup(src->realname);
147
148         if (src->own_ip4 != NULL) {
149                 dest->own_ip4 = g_new(IPADDR, 1);
150                 memcpy(dest->own_ip4, src->own_ip4, sizeof(IPADDR));
151         }
152         if (src->own_ip6 != NULL) {
153                 dest->own_ip6 = g_new(IPADDR, 1);
154                 memcpy(dest->own_ip6, src->own_ip6, sizeof(IPADDR));
155         }
156
157         dest->channels = g_strdup(src->channels);
158         dest->away_reason = g_strdup(src->away_reason);
159
160         return dest;
161 }
162
163 #define server_should_reconnect(server) \
164         ((server)->connection_lost && ((server)->connrec->chatnet != NULL || \
165                                 (!(server)->banned && !(server)->dns_error)))
166
167 #define sserver_connect_ok(rec, net) \
168         (!(rec)->banned && !(rec)->dns_error && (rec)->chatnet != NULL && \
169         g_strcasecmp((rec)->chatnet, (net)) == 0)
170
171 static void sig_reconnect(SERVER_REC *server)
172 {
173         SERVER_CONNECT_REC *conn;
174         SERVER_SETUP_REC *sserver;
175         GSList *tmp;
176         int use_next, through;
177         time_t now;
178
179         g_return_if_fail(IS_SERVER(server));
180
181         if (reconnect_time == -1 || !server_should_reconnect(server))
182                 return;
183
184         conn = server_connect_copy_skeleton(server->connrec, FALSE);
185         g_return_if_fail(conn != NULL);
186
187         /* save the server status */
188         if (server->connected) {
189                 conn->reconnection = TRUE;
190
191                 reconnect_save_status(conn, server);
192         }
193
194         sserver = server_setup_find(server->connrec->address,
195                                     server->connrec->port);
196
197         if (sserver != NULL) {
198                 /* save the last connection time/status */
199                 sserver->last_connect = server->connect_time == 0 ?
200                         time(NULL) : server->connect_time;
201                 sserver->last_failed = !server->connected;
202                 if (server->banned) sserver->banned = TRUE;
203                 if (server->dns_error) sserver->dns_error = TRUE;
204         }
205
206         if (sserver == NULL || conn->chatnet == NULL) {
207                 /* not in any chatnet, just reconnect back to same server */
208                 conn->family = server->connrec->family;
209                 conn->address = g_strdup(server->connrec->address);
210                 conn->port = server->connrec->port;
211                 conn->password = g_strdup(server->connrec->password);
212
213                 if (server->connect_time != 0 &&
214                     time(NULL)-server->connect_time > reconnect_time) {
215                         /* there's been enough time since last connection,
216                            reconnect back immediately */
217                         CHAT_PROTOCOL(conn)->server_connect(conn);
218                 } else {
219                         /* reconnect later.. */
220                         server_reconnect_add(conn, (server->connect_time == 0 ? time(NULL) :
221                                                     server->connect_time) + reconnect_time);
222                 }
223                 return;
224         }
225
226         /* always try to first connect to the first on the list where we
227            haven't got unsuccessful connection attempts for the last half
228            an hour. */
229
230         now = time(NULL);
231         for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
232                 SERVER_SETUP_REC *rec = tmp->data;
233
234                 if (sserver_connect_ok(rec, conn->chatnet) &&
235                     (!rec->last_connect || !rec->last_failed ||
236                      rec->last_connect < now-FAILED_RECONNECT_WAIT)) {
237                         if (rec == sserver)
238                                 conn->port = server->connrec->port;
239                         sserver_connect(rec, conn);
240                         return;
241                 }
242         }
243
244         /* just try the next server in list */
245         use_next = through = FALSE;
246         for (tmp = setupservers; tmp != NULL; ) {
247                 SERVER_SETUP_REC *rec = tmp->data;
248
249                 if (!use_next && server->connrec->port == rec->port &&
250                     g_strcasecmp(rec->address, server->connrec->address) == 0)
251                         use_next = TRUE;
252                 else if (use_next && sserver_connect_ok(rec, conn->chatnet)) {
253                         if (rec == sserver)
254                                 conn->port = server->connrec->port;
255                         sserver_connect(rec, conn);
256                         break;
257                 }
258
259                 if (tmp->next != NULL) {
260                         tmp = tmp->next;
261                         continue;
262                 }
263
264                 if (through) {
265                         /* shouldn't happen unless there's no servers in
266                            this chatnet in setup.. */
267                         server_connect_free(conn);
268                         break;
269                 }
270
271                 tmp = setupservers;
272                 use_next = through = TRUE;
273         }
274 }
275
276 static void sig_connected(SERVER_REC *server)
277 {
278         g_return_if_fail(IS_SERVER(server));
279         if (!server->connrec->reconnection)
280                 return;
281
282         if (server->connrec->channels != NULL)
283                 server->channels_join(server, server->connrec->channels, TRUE);
284 }
285
286 /* Remove all servers from reconnect list */
287 /* SYNTAX: RMRECONNS */
288 static void cmd_rmreconns(void)
289 {
290         while (reconnects != NULL)
291                 server_reconnect_destroy(reconnects->data, TRUE);
292 }
293
294 static RECONNECT_REC *reconnect_find_tag(int tag)
295 {
296         GSList *tmp;
297
298         for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
299                 RECONNECT_REC *rec = tmp->data;
300
301                 if (rec->tag == tag)
302                         return rec;
303         }
304
305         return NULL;
306 }
307
308 static void reconnect_all(void)
309 {
310         GSList *list;
311         SERVER_CONNECT_REC *conn;
312         RECONNECT_REC *rec;
313
314         /* first move reconnects to another list so if server_connect()
315            fails and goes to reconnection list again, we won't get stuck
316            here forever */
317         list = NULL;
318         while (reconnects != NULL) {
319                 rec = reconnects->data;
320
321                 list = g_slist_append(list, rec->conn);
322                 server_reconnect_destroy(rec, FALSE);
323         }
324
325
326         while (list != NULL) {
327                 conn = list->data;
328
329                 CHAT_PROTOCOL(conn)->server_connect(conn);
330                 list = g_slist_remove(list, conn);
331         }
332 }
333
334 /* SYNTAX: RECONNECT <tag> */
335 static void cmd_reconnect(const char *data, SERVER_REC *server)
336 {
337         SERVER_CONNECT_REC *conn;
338         RECONNECT_REC *rec;
339         int tag;
340
341         if (*data == '\0' && server != NULL) {
342                 /* reconnect back to same server */
343                 conn = server_connect_copy_skeleton(server->connrec, TRUE);
344
345                 if (server->connected) {
346                         reconnect_save_status(conn, server);
347                         signal_emit("command disconnect", 2,
348                                     "* Reconnecting", server);
349                 }
350
351                 conn->reconnection = TRUE;
352                 CHAT_PROTOCOL(conn)->server_connect(conn);
353                 return;
354         }
355
356         if (g_strcasecmp(data, "all") == 0) {
357                 /* reconnect all servers in reconnect queue */
358                 reconnect_all();
359                 return;
360         }
361
362         if (*data == '\0') {
363                 /* reconnect to first server in reconnection list */
364                 if (reconnects == NULL)
365                         cmd_return_error(CMDERR_NOT_CONNECTED);
366                 rec = reconnects->data;
367         } else {
368                 if (g_strncasecmp(data, "RECON-", 6) == 0)
369                         data += 6;
370
371                 tag = atoi(data);
372                 rec = tag <= 0 ? NULL : reconnect_find_tag(tag);
373
374                 if (rec == NULL) {
375                         signal_emit("server reconnect not found", 1, data);
376                         return;
377                 }
378         }
379
380         conn = rec->conn;
381         server_reconnect_destroy(rec, FALSE);
382         CHAT_PROTOCOL(conn)->server_connect(conn);
383 }
384
385 static void cmd_disconnect(const char *data, SERVER_REC *server)
386 {
387         RECONNECT_REC *rec;
388         int tag;
389
390         if (g_strncasecmp(data, "RECON-", 6) != 0)
391                 return; /* handle only reconnection removing */
392
393         rec = sscanf(data+6, "%d", &tag) == 1 && tag > 0 ?
394                 reconnect_find_tag(tag) : NULL;
395
396         if (rec == NULL)
397                 signal_emit("server reconnect not found", 1, data);
398         else
399                 server_reconnect_destroy(rec, TRUE);
400         signal_stop();
401 }
402
403 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
404 {
405         GSList *tmp, *next;
406
407         for (tmp = reconnects; tmp != NULL; tmp = next) {
408                 RECONNECT_REC *rec = tmp->data;
409
410                 next = tmp->next;
411                 if (rec->conn->chat_type == proto->id)
412                         server_reconnect_destroy(rec, TRUE);
413         }
414 }
415
416 static void read_settings(void)
417 {
418         reconnect_time = settings_get_int("server_reconnect_time");
419 }
420
421 void servers_reconnect_init(void)
422 {
423         settings_add_int("server", "server_reconnect_time", 300);
424
425         reconnects = NULL;
426         last_reconnect_tag = 0;
427
428         reconnect_timeout_tag = g_timeout_add(1000, (GSourceFunc) server_reconnect_timeout, NULL);
429         read_settings();
430
431         signal_add("server connect failed", (SIGNAL_FUNC) sig_reconnect);
432         signal_add("server disconnected", (SIGNAL_FUNC) sig_reconnect);
433         signal_add("event connected", (SIGNAL_FUNC) sig_connected);
434         signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
435         signal_add("setup changed", (SIGNAL_FUNC) read_settings);
436
437         command_bind("rmreconns", NULL, (SIGNAL_FUNC) cmd_rmreconns);
438         command_bind("reconnect", NULL, (SIGNAL_FUNC) cmd_reconnect);
439         command_bind_first("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
440 }
441
442 void servers_reconnect_deinit(void)
443 {
444         g_source_remove(reconnect_timeout_tag);
445
446         signal_remove("server connect failed", (SIGNAL_FUNC) sig_reconnect);
447         signal_remove("server disconnected", (SIGNAL_FUNC) sig_reconnect);
448         signal_remove("event connected", (SIGNAL_FUNC) sig_connected);
449         signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
450         signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
451
452         command_unbind("rmreconns", (SIGNAL_FUNC) cmd_rmreconns);
453         command_unbind("reconnect", (SIGNAL_FUNC) cmd_reconnect);
454         command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
455 }