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