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