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