Merged Irssi 0.8.2 from irssi.org cvs.
[silc.git] / apps / irssi / src / core / servers.c
1 /*
2  server.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 "signals.h"
23 #include "commands.h"
24 #include "line-split.h"
25 #include "net-nonblock.h"
26 #include "net-sendbuffer.h"
27 #include "misc.h"
28 #include "rawlog.h"
29 #include "settings.h"
30
31 #include "chat-protocols.h"
32 #include "servers.h"
33 #include "servers-reconnect.h"
34 #include "servers-setup.h"
35 #include "channels.h"
36 #include "queries.h"
37
38 GSList *servers, *lookup_servers;
39
40 /* connection to server failed */
41 void server_connect_failed(SERVER_REC *server, const char *msg)
42 {
43         g_return_if_fail(IS_SERVER(server));
44
45         lookup_servers = g_slist_remove(lookup_servers, server);
46
47         signal_emit("server connect failed", 2, server, msg);
48
49         if (server->connect_tag != -1) {
50                 g_source_remove(server->connect_tag);
51                 server->connect_tag = -1;
52         }
53         if (server->handle != NULL) {
54                 net_sendbuffer_destroy(server->handle, TRUE);
55                 server->handle = NULL;
56         }
57
58         if (server->connect_pipe[0] != NULL) {
59                 g_io_channel_close(server->connect_pipe[0]);
60                 g_io_channel_unref(server->connect_pipe[0]);
61                 g_io_channel_close(server->connect_pipe[1]);
62                 g_io_channel_unref(server->connect_pipe[1]);
63                 server->connect_pipe[0] = NULL;
64                 server->connect_pipe[1] = NULL;
65         }
66
67         server_unref(server);
68 }
69
70 /* generate tag from server's address */
71 static char *server_create_address_tag(const char *address)
72 {
73         const char *start, *end;
74
75         g_return_val_if_fail(address != NULL, NULL);
76
77         /* try to generate a reasonable server tag */
78         if (strchr(address, '.') == NULL) {
79                 start = end = NULL;
80         } else if (g_strncasecmp(address, "irc", 3) == 0 ||
81             g_strncasecmp(address, "chat", 4) == 0) {
82                 /* irc-2.cs.hut.fi -> hut, chat.bt.net -> bt */
83                 end = strrchr(address, '.');
84                 start = end-1;
85                 while (start > address && *start != '.') start--;
86         } else {
87                 /* efnet.cs.hut.fi -> efnet */
88                 end = strchr(address, '.');
89                 start = end;
90         }
91
92         if (start == end) start = address; else start++;
93         if (end == NULL) end = address + strlen(address);
94
95         return g_strndup(start, (int) (end-start));
96 }
97
98 /* create unique tag for server. prefer ircnet's name or
99    generate it from server's address */
100 static char *server_create_tag(SERVER_CONNECT_REC *conn)
101 {
102         GString *str;
103         char *tag;
104         int num;
105
106         g_return_val_if_fail(IS_SERVER_CONNECT(conn), NULL);
107
108         tag = conn->chatnet != NULL && *conn->chatnet != '\0' ?
109                 g_strdup(conn->chatnet) :
110                 server_create_address_tag(conn->address);
111
112         if (conn->tag != NULL && server_find_tag(conn->tag) == NULL &&
113             strncmp(conn->tag, tag, strlen(tag)) == 0) {
114                 /* use the existing tag if it begins with the same ID -
115                    this is useful when you have several connections to
116                    same server and you want to keep the same tags with
117                    the servers (or it would cause problems when rejoining
118                    /LAYOUT SAVEd channels). */
119                 g_free(tag);
120                 return g_strdup(conn->tag);
121         }
122
123
124         /* then just append numbers after tag until unused is found.. */
125         str = g_string_new(tag);
126         for (num = 2; server_find_tag(str->str) != NULL; num++)
127                 g_string_sprintf(str, "%s%d", tag, num);
128         g_free(tag);
129
130         tag = str->str;
131         g_string_free(str, FALSE);
132         return tag;
133 }
134
135 /* Connection to server finished, fill the rest of the fields */
136 void server_connect_finished(SERVER_REC *server)
137 {
138         server->connect_time = time(NULL);
139
140         servers = g_slist_append(servers, server);
141         signal_emit("server connected", 1, server);
142 }
143
144 static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle)
145 {
146         int error;
147
148         g_return_if_fail(IS_SERVER(server));
149
150         error = net_geterror(handle);
151         if (error != 0) {
152                 server->connection_lost = TRUE;
153                 server_connect_failed(server, g_strerror(error));
154                 return;
155         }
156
157         lookup_servers = g_slist_remove(lookup_servers, server);
158         g_source_remove(server->connect_tag);
159         server->connect_tag = -1;
160
161         server_connect_finished(server);
162 }
163
164 static void server_connect_callback_readpipe(SERVER_REC *server)
165 {
166         SERVER_CONNECT_REC *conn;
167         RESOLVED_IP_REC iprec;
168         GIOChannel *handle;
169         IPADDR *ip, *own_ip;
170         const char *errormsg;
171         int port;
172
173         g_return_if_fail(IS_SERVER(server));
174
175         g_source_remove(server->connect_tag);
176         server->connect_tag = -1;
177
178         net_gethostbyname_return(server->connect_pipe[0], &iprec);
179
180         g_io_channel_close(server->connect_pipe[0]);
181         g_io_channel_unref(server->connect_pipe[0]);
182         g_io_channel_close(server->connect_pipe[1]);
183         g_io_channel_unref(server->connect_pipe[1]);
184
185         server->connect_pipe[0] = NULL;
186         server->connect_pipe[1] = NULL;
187
188         /* figure out if we should use IPv4 or v6 address */
189         if (iprec.error != 0) {
190                 /* error */
191                 ip = NULL;
192         } else if (server->connrec->family == AF_INET) {
193                 /* force IPv4 connection */
194                 ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
195         } else if (server->connrec->family == AF_INET6) {
196                 /* force IPv6 connection */
197                 ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
198         } else {
199                 /* pick the one that was found, or if both do it like
200                    /SET resolve_prefer_ipv6 says. */
201                 ip = iprec.ip6.family != 0 &&
202                         settings_get_bool("resolve_prefer_ipv6") ?
203                         &iprec.ip6 : &iprec.ip4;
204         }
205
206         conn = server->connrec;
207         port = conn->proxy != NULL ? conn->proxy_port : conn->port;
208         own_ip = ip == NULL ? NULL :
209                 (IPADDR_IS_V6(ip) ? conn->own_ip6 : conn->own_ip4);
210
211         handle = NULL;
212         if (ip != NULL) {
213                 signal_emit("server connecting", 2, server, ip);
214                 if (server->handle == NULL)
215                         handle = net_connect_ip(ip, port, own_ip);
216                 else
217                         handle = net_sendbuffer_handle(server->handle);
218         }
219
220         if (handle == NULL) {
221                 /* failed */
222                 if (ip == NULL && (iprec.error == 0 ||
223                                    net_hosterror_notfound(iprec.error))) {
224                         /* IP wasn't found for the host, don't try to reconnect
225                            back to this server */
226                         server->dns_error = TRUE;
227                 }
228
229                 if (ip != NULL) {
230                         /* connect() failed */
231                         errormsg = g_strerror(errno);
232                 } else if (iprec.error == 0) {
233                         /* forced IPv4 or IPv6 address but it wasn't found */
234                         errormsg = server->connrec->family == AF_INET ?
235                                 "IPv4 address not found for host" :
236                                 "IPv6 address not found for host";
237                 } else {
238                         /* gethostbyname() failed */
239                         errormsg = iprec.errorstr != NULL ? iprec.errorstr :
240                                 "Host lookup failed";
241                 }
242                 server->connection_lost = TRUE;
243                 server_connect_failed(server, errormsg);
244                 g_free_not_null(iprec.errorstr);
245                 return;
246         }
247
248         if (server->handle == NULL)
249                 server->handle = net_sendbuffer_create(handle, 0);
250         server->connect_tag =
251                 g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
252                             (GInputFunction) server_connect_callback_init,
253                             server);
254 }
255
256 /* initializes server record but doesn't start connecting */
257 void server_connect_init(SERVER_REC *server)
258 {
259         g_return_if_fail(server != NULL);
260
261         MODULE_DATA_INIT(server);
262         server->type = module_get_uniq_id("SERVER", 0);
263         server_ref(server);
264
265         server->nick = g_strdup(server->connrec->nick);
266         if (server->connrec->username == NULL || *server->connrec->username == '\0') {
267                 g_free_not_null(server->connrec->username);
268
269                 server->connrec->username = g_get_user_name();
270                 if (*server->connrec->username == '\0') server->connrec->username = "-";
271                 server->connrec->username = g_strdup(server->connrec->username);
272         }
273         if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
274                 g_free_not_null(server->connrec->realname);
275
276                 server->connrec->realname = g_get_real_name();
277                 if (*server->connrec->realname == '\0') server->connrec->realname = "-";
278                 server->connrec->realname = g_strdup(server->connrec->realname);
279         }
280
281         server->tag = server_create_tag(server->connrec);
282 }
283
284 /* starts connecting to server */
285 int server_start_connect(SERVER_REC *server)
286 {
287         const char *connect_address;
288         int fd[2];
289
290         g_return_val_if_fail(server != NULL, FALSE);
291         if (server->connrec->port <= 0) return FALSE;
292
293         server_connect_init(server);
294
295         if (pipe(fd) != 0) {
296                 g_warning("server_connect(): pipe() failed.");
297                 g_free(server->tag);
298                 g_free(server->nick);
299                 return FALSE;
300         }
301
302         server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
303         server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
304
305         connect_address = server->connrec->proxy != NULL ?
306                 server->connrec->proxy : server->connrec->address;
307         server->connect_pid =
308                 net_gethostbyname_nonblock(connect_address,
309                                            server->connect_pipe[1]);
310         server->connect_tag =
311                 g_input_add(server->connect_pipe[0], G_INPUT_READ,
312                             (GInputFunction) server_connect_callback_readpipe,
313                             server);
314         server->rawlog = rawlog_create();
315
316         lookup_servers = g_slist_append(lookup_servers, server);
317
318         signal_emit("server looking", 1, server);
319         return TRUE;
320 }
321
322 static int server_remove_channels(SERVER_REC *server)
323 {
324         GSList *tmp;
325         int found;
326
327         g_return_val_if_fail(server != NULL, FALSE);
328
329         found = FALSE;
330         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
331                 CHANNEL_REC *channel = tmp->data;
332
333                 channel->server = NULL;
334                 channel_destroy(channel);
335                 found = TRUE;
336         }
337
338         while (server->queries != NULL)
339                 query_change_server(server->queries->data, NULL);
340
341         g_slist_free(server->channels);
342         g_slist_free(server->queries);
343
344         return found;
345 }
346
347 void server_disconnect(SERVER_REC *server)
348 {
349         int chans;
350
351         g_return_if_fail(IS_SERVER(server));
352
353         if (server->disconnected)
354                 return;
355
356         if (server->connect_tag != -1) {
357                 /* still connecting to server.. */
358                 if (server->connect_pid != -1)
359                         net_disconnect_nonblock(server->connect_pid);
360                 server_connect_failed(server, NULL);
361                 return;
362         }
363
364         servers = g_slist_remove(servers, server);
365
366         server->disconnected = TRUE;
367         signal_emit("server disconnected", 1, server);
368
369         /* close all channels */
370         chans = server_remove_channels(server);
371
372         if (server->handle != NULL) {
373                 if (!chans || server->connection_lost)
374                         net_sendbuffer_destroy(server->handle, TRUE);
375                 else {
376                         /* we were on some channels, try to let the server
377                            disconnect so that our quit message is guaranteed
378                            to get displayed */
379                         net_disconnect_later(net_sendbuffer_handle(server->handle));
380                         net_sendbuffer_destroy(server->handle, FALSE);
381                 }
382                 server->handle = NULL;
383         }
384
385         if (server->readtag > 0) {
386                 g_source_remove(server->readtag);
387                 server->readtag = -1;
388         }
389
390         server_unref(server);
391 }
392
393 void server_ref(SERVER_REC *server)
394 {
395         g_return_if_fail(IS_SERVER(server));
396
397         server->refcount++;
398 }
399
400 int server_unref(SERVER_REC *server)
401 {
402         g_return_val_if_fail(IS_SERVER(server), FALSE);
403
404         if (--server->refcount > 0)
405                 return TRUE;
406
407         if (g_slist_find(servers, server) != NULL) {
408                 g_warning("Non-referenced server wasn't disconnected");
409                 server_disconnect(server);
410                 return TRUE;
411         }
412
413         MODULE_DATA_DEINIT(server);
414         server_connect_unref(server->connrec);
415         if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
416         if (server->buffer != NULL) line_split_free(server->buffer);
417         g_free(server->version);
418         g_free(server->away_reason);
419         g_free(server->nick);
420         g_free(server->tag);
421
422         server->type = 0;
423         g_free(server);
424         return FALSE;
425 }
426
427 SERVER_REC *server_find_tag(const char *tag)
428 {
429         GSList *tmp;
430
431         g_return_val_if_fail(tag != NULL, NULL);
432         if (*tag == '\0') return NULL;
433
434         for (tmp = servers; tmp != NULL; tmp = tmp->next) {
435                 SERVER_REC *server = tmp->data;
436
437                 if (g_strcasecmp(server->tag, tag) == 0)
438                         return server;
439         }
440
441         for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
442                 SERVER_REC *server = tmp->data;
443
444                 if (g_strcasecmp(server->tag, tag) == 0)
445                         return server;
446         }
447
448         return NULL;
449 }
450
451 SERVER_REC *server_find_chatnet(const char *chatnet)
452 {
453         GSList *tmp;
454
455         g_return_val_if_fail(chatnet != NULL, NULL);
456         if (*chatnet == '\0') return NULL;
457
458         for (tmp = servers; tmp != NULL; tmp = tmp->next) {
459                 SERVER_REC *server = tmp->data;
460
461                 if (server->connrec->chatnet != NULL &&
462                     g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
463                         return server;
464         }
465
466         return NULL;
467 }
468
469 void server_connect_ref(SERVER_CONNECT_REC *conn)
470 {
471         conn->refcount++;
472 }
473
474 void server_connect_unref(SERVER_CONNECT_REC *conn)
475 {
476         g_return_if_fail(IS_SERVER_CONNECT(conn));
477
478         if (--conn->refcount > 0)
479                 return;
480         if (conn->refcount < 0) {
481                 g_warning("Connection '%s' refcount = %d",
482                           conn->tag, conn->refcount);
483         }
484
485         CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
486
487         g_free_not_null(conn->proxy);
488         g_free_not_null(conn->proxy_string);
489         g_free_not_null(conn->proxy_string_after);
490         g_free_not_null(conn->proxy_password);
491
492         g_free_not_null(conn->tag);
493         g_free_not_null(conn->address);
494         g_free_not_null(conn->chatnet);
495
496         g_free_not_null(conn->own_ip4);
497         g_free_not_null(conn->own_ip6);
498
499         g_free_not_null(conn->password);
500         g_free_not_null(conn->nick);
501         g_free_not_null(conn->username);
502         g_free_not_null(conn->realname);
503
504         g_free_not_null(conn->channels);
505         g_free_not_null(conn->away_reason);
506
507         conn->type = 0;
508         g_free(conn);
509 }
510
511 void server_change_nick(SERVER_REC *server, const char *nick)
512 {
513         g_free(server->nick);
514         server->nick = g_strdup(nick);
515
516         signal_emit("server nick changed", 1, server);
517 }
518
519 /* Update own IPv4 and IPv6 records */
520 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
521                                 IPADDR *ip4, IPADDR *ip6)
522 {
523         if (ip4 == NULL || ip4->family == 0)
524                 g_free_and_null(conn->own_ip4);
525         if (ip6 == NULL || ip6->family == 0)
526                 g_free_and_null(conn->own_ip6);
527
528         if (ip4 != NULL && ip4->family != 0) {
529                 /* IPv4 address was found */
530                 if (conn->own_ip4 == NULL)
531                         conn->own_ip4 = g_new0(IPADDR, 1);
532                 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
533         }
534
535         if (ip6 != NULL && ip6->family != 0) {
536                 /* IPv6 address was found */
537                 if (conn->own_ip6 == NULL)
538                         conn->own_ip6 = g_new0(IPADDR, 1);
539                 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
540         }
541 }
542
543 /* `optlist' should contain only one unknown key - the server tag.
544    returns NULL if there was unknown -option */
545 SERVER_REC *cmd_options_get_server(const char *cmd,
546                                    GHashTable *optlist,
547                                    SERVER_REC *defserver)
548 {
549         SERVER_REC *server;
550         GSList *list, *tmp, *next;
551
552         /* get all the options, then remove the known ones. there should
553            be only one left - the server tag. */
554         list = hashtable_get_keys(optlist);
555         if (cmd != NULL) {
556                 for (tmp = list; tmp != NULL; tmp = next) {
557                         char *option = tmp->data;
558                         next = tmp->next;
559
560                         if (command_have_option(cmd, option))
561                                 list = g_slist_remove(list, option);
562                 }
563         }
564
565         if (list == NULL)
566                 return defserver;
567
568         server = server_find_tag(list->data);
569         if (server == NULL || list->next != NULL) {
570                 /* unknown option (not server tag) */
571                 signal_emit("error command", 2,
572                             GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
573                             server == NULL ? list->data : list->next->data);
574                 signal_stop();
575
576                 server = NULL;
577         }
578
579         g_slist_free(list);
580         return server;
581 }
582
583 static void disconnect_servers(GSList *servers, int chat_type)
584 {
585         GSList *tmp, *next;
586
587         for (tmp = servers; tmp != NULL; tmp = next) {
588                 SERVER_REC *rec = tmp->data;
589
590                 next = tmp->next;
591                 if (rec->chat_type == chat_type)
592                         server_disconnect(rec);
593         }
594 }
595
596 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
597 {
598         disconnect_servers(servers, proto->id);
599         disconnect_servers(lookup_servers, proto->id);
600 }
601
602 void servers_init(void)
603 {
604         settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
605         lookup_servers = servers = NULL;
606
607         signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
608
609         servers_reconnect_init();
610         servers_setup_init();
611 }
612
613 void servers_deinit(void)
614 {
615         signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
616
617         servers_setup_deinit();
618         servers_reconnect_deinit();
619
620         module_uniq_destroy("SERVER");
621         module_uniq_destroy("SERVER CONNECT");
622 }