Added SILC Thread Queue API
[crypto.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             server_find_lookup_tag(conn->tag) == NULL &&
114             strncmp(conn->tag, tag, strlen(tag)) == 0) {
115                 /* use the existing tag if it begins with the same ID -
116                    this is useful when you have several connections to
117                    same server and you want to keep the same tags with
118                    the servers (or it would cause problems when rejoining
119                    /LAYOUT SAVEd channels). */
120                 g_free(tag);
121                 return g_strdup(conn->tag);
122         }
123
124
125         /* then just append numbers after tag until unused is found.. */
126         str = g_string_new(tag);
127
128         num = 2;
129         while (server_find_tag(str->str) != NULL ||
130                server_find_lookup_tag(str->str) != NULL) {
131                 g_string_sprintf(str, "%s%d", tag, num);
132                 num++;
133         }
134         g_free(tag);
135
136         tag = str->str;
137         g_string_free(str, FALSE);
138         return tag;
139 }
140
141 /* Connection to server finished, fill the rest of the fields */
142 void server_connect_finished(SERVER_REC *server)
143 {
144         server->connect_time = time(NULL);
145
146         servers = g_slist_append(servers, server);
147         signal_emit("server connected", 1, server);
148 }
149
150 static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle)
151 {
152         int error;
153
154         g_return_if_fail(IS_SERVER(server));
155
156         error = net_geterror(handle);
157         if (error != 0) {
158                 server->connection_lost = TRUE;
159                 server_connect_failed(server, g_strerror(error));
160                 return;
161         }
162
163         lookup_servers = g_slist_remove(lookup_servers, server);
164         g_source_remove(server->connect_tag);
165         server->connect_tag = -1;
166
167         server_connect_finished(server);
168 }
169
170 static void server_real_connect(SERVER_REC *server, IPADDR *ip,
171                                 const char *unix_socket)
172 {
173         GIOChannel *handle;
174         IPADDR *own_ip = NULL;
175         const char *errmsg;
176         char *errmsg2;
177         char ipaddr[MAX_IP_LEN];
178         int port;
179
180         g_return_if_fail(ip != NULL || unix_socket != NULL);
181
182         signal_emit("server connecting", 2, server, ip);
183
184         if (server->connrec->no_connect)
185                 return;
186
187         if (ip != NULL) {
188                 own_ip = ip == NULL ? NULL :
189                         (IPADDR_IS_V6(ip) ? server->connrec->own_ip6 :
190                          server->connrec->own_ip4);
191                 port = server->connrec->proxy != NULL ?
192                         server->connrec->proxy_port : server->connrec->port;
193                 handle = server->connrec->use_ssl ?
194                         net_connect_ip_ssl(ip, port, own_ip, server->connrec->ssl_cert, server->connrec->ssl_pkey,
195 server->connrec->ssl_cafile, server->connrec->ssl_capath, server->connrec->ssl_verify) :
196                         net_connect_ip(ip, port, own_ip);
197         } else {
198                 handle = net_connect_unix(unix_socket);
199         }
200
201         if (handle == NULL) {
202                 /* failed */
203                 errmsg = g_strerror(errno);
204                 errmsg2 = NULL;
205                 if (errno == EADDRNOTAVAIL) {
206                         if (own_ip != NULL) {
207                                 /* show the IP which is causing the error */
208                                 net_ip2host(own_ip, ipaddr);
209                                 errmsg2 = g_strconcat(errmsg, ": ", ipaddr, NULL);
210                         }
211                         server->no_reconnect = TRUE;
212                 }
213                 if (server->connrec->use_ssl && errno == ENOSYS)
214                         server->no_reconnect = TRUE;
215
216                 server->connection_lost = TRUE;
217                 server_connect_failed(server, errmsg2 ? errmsg2 : errmsg);
218                 g_free(errmsg2);
219         } else {
220                 server->handle = net_sendbuffer_create(handle, 0);
221                 server->connect_tag =
222                         g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
223                                     (GInputFunction)
224                                     server_connect_callback_init,
225                                     server);
226         }
227 }
228
229 static void server_connect_callback_readpipe(SERVER_REC *server)
230 {
231         RESOLVED_IP_REC iprec;
232         IPADDR *ip;
233         const char *errormsg;
234         char *servername = NULL;
235
236         g_source_remove(server->connect_tag);
237         server->connect_tag = -1;
238
239         net_gethostbyname_return(server->connect_pipe[0], &iprec);
240
241         g_io_channel_close(server->connect_pipe[0]);
242         g_io_channel_unref(server->connect_pipe[0]);
243         g_io_channel_close(server->connect_pipe[1]);
244         g_io_channel_unref(server->connect_pipe[1]);
245
246         server->connect_pipe[0] = NULL;
247         server->connect_pipe[1] = NULL;
248
249         /* figure out if we should use IPv4 or v6 address */
250         if (iprec.error != 0) {
251                 /* error */
252                 ip = NULL;
253         } else if (server->connrec->family == AF_INET) {
254                 /* force IPv4 connection */
255                 ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
256                 servername = iprec.host4;
257         } else if (server->connrec->family == AF_INET6) {
258                 /* force IPv6 connection */
259                 ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
260                 servername = iprec.host6;
261         } else {
262                 /* pick the one that was found, or if both do it like
263                    /SET resolve_prefer_ipv6 says. */
264                 if (iprec.ip4.family == 0 ||
265                     (iprec.ip6.family != 0 &&
266                      settings_get_bool("resolve_prefer_ipv6"))) {
267                         ip = &iprec.ip6;
268                         servername = iprec.host6;
269                 } else {
270                         ip = &iprec.ip4;
271                         servername = iprec.host4;
272                 }
273         }
274
275         if (ip != NULL) {
276                 /* host lookup ok */
277                 if (servername) {
278                         g_free(server->connrec->address);
279                         server->connrec->address = g_strdup(servername);
280                 }
281                 server_real_connect(server, ip, NULL);
282                 errormsg = NULL;
283         } else {
284                 if (iprec.error == 0 || net_hosterror_notfound(iprec.error)) {
285                         /* IP wasn't found for the host, don't try to
286                            reconnect back to this server */
287                         server->dns_error = TRUE;
288                 }
289
290                 if (iprec.error == 0) {
291                         /* forced IPv4 or IPv6 address but it wasn't found */
292                         errormsg = server->connrec->family == AF_INET ?
293                                 "IPv4 address not found for host" :
294                                 "IPv6 address not found for host";
295                 } else {
296                         /* gethostbyname() failed */
297                         errormsg = iprec.errorstr != NULL ? iprec.errorstr :
298                                 "Host lookup failed";
299                 }
300
301                 server->connection_lost = TRUE;
302                 server_connect_failed(server, errormsg);
303         }
304
305         g_free(iprec.errorstr);
306         g_free(iprec.host4);
307         g_free(iprec.host6);
308 }
309
310 SERVER_REC *server_connect(SERVER_CONNECT_REC *conn)
311 {
312         CHAT_PROTOCOL_REC *proto;
313         SERVER_REC *server;
314
315         proto = CHAT_PROTOCOL(conn);
316         server = proto->server_init_connect(conn);
317         proto->server_connect(server);
318
319         return server;
320 }
321
322 /* initializes server record but doesn't start connecting */
323 void server_connect_init(SERVER_REC *server)
324 {
325         const char *str;
326
327         g_return_if_fail(server != NULL);
328
329         MODULE_DATA_INIT(server);
330         server->type = module_get_uniq_id("SERVER", 0);
331         server_ref(server);
332
333         server->nick = g_strdup(server->connrec->nick);
334         if (server->connrec->username == NULL || *server->connrec->username == '\0') {
335                 g_free_not_null(server->connrec->username);
336
337                 str = g_get_user_name();
338                 if (*str == '\0') str = "unknown";
339                 server->connrec->username = g_strdup(str);
340         }
341         if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
342                 g_free_not_null(server->connrec->realname);
343
344                 str = g_get_real_name();
345                 if (*str == '\0') str = server->connrec->username;
346                 server->connrec->realname = g_strdup(str);
347         }
348
349         server->tag = server_create_tag(server->connrec);
350         server->connect_tag = -1;
351 }
352
353 /* starts connecting to server */
354 int server_start_connect(SERVER_REC *server)
355 {
356         const char *connect_address;
357         int fd[2];
358
359         g_return_val_if_fail(server != NULL, FALSE);
360         if (!server->connrec->unix_socket && server->connrec->port <= 0)
361                 return FALSE;
362
363         server->rawlog = rawlog_create();
364
365         if (server->connrec->connect_handle != NULL) {
366                 /* already connected */
367                 GIOChannel *handle = server->connrec->connect_handle;
368
369                 server->connrec->connect_handle = NULL;
370                 server->handle = net_sendbuffer_create(handle, 0);
371                 server_connect_finished(server);
372         } else if (server->connrec->unix_socket) {
373                 /* connect with unix socket */
374                 server_real_connect(server, NULL, server->connrec->address);
375         } else {
376                 /* resolve host name */
377                 if (pipe(fd) != 0) {
378                         g_warning("server_connect(): pipe() failed.");
379                         g_free(server->tag);
380                         g_free(server->nick);
381                         return FALSE;
382                 }
383
384                 server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
385                 server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
386
387                 connect_address = server->connrec->proxy != NULL ?
388                         server->connrec->proxy : server->connrec->address;
389                 server->connect_pid =
390                         net_gethostbyname_nonblock(connect_address,
391                                                    server->connect_pipe[1],
392                                                    settings_get_bool("resolve_reverse_lookup"));
393                 server->connect_tag =
394                         g_input_add(server->connect_pipe[0], G_INPUT_READ,
395                                     (GInputFunction)
396                                     server_connect_callback_readpipe,
397                                     server);
398
399                 lookup_servers = g_slist_append(lookup_servers, server);
400
401                 signal_emit("server looking", 1, server);
402         }
403         return TRUE;
404 }
405
406 static int server_remove_channels(SERVER_REC *server)
407 {
408         GSList *tmp, *next;
409         int found;
410
411         g_return_val_if_fail(server != NULL, FALSE);
412
413         found = FALSE;
414         for (tmp = server->channels; tmp != NULL; tmp = next) {
415                 CHANNEL_REC *channel = tmp->data;
416
417                 next = tmp->next;
418                 channel_destroy(channel);
419                 found = TRUE;
420         }
421
422         while (server->queries != NULL)
423                 query_change_server(server->queries->data, NULL);
424
425         g_slist_free(server->channels);
426         g_slist_free(server->queries);
427
428         return found;
429 }
430
431 void server_disconnect(SERVER_REC *server)
432 {
433         int chans;
434
435         g_return_if_fail(IS_SERVER(server));
436
437         if (server->disconnected)
438                 return;
439
440         if (server->connect_tag != -1) {
441                 /* still connecting to server.. */
442                 if (server->connect_pid != -1)
443                         net_disconnect_nonblock(server->connect_pid);
444                 server_connect_failed(server, NULL);
445                 return;
446         }
447
448         servers = g_slist_remove(servers, server);
449
450         server->disconnected = TRUE;
451         signal_emit("server disconnected", 1, server);
452
453         /* close all channels */
454         chans = server_remove_channels(server);
455
456         if (server->handle != NULL) {
457                 if (!chans || server->connection_lost)
458                         net_sendbuffer_destroy(server->handle, TRUE);
459                 else {
460                         /* we were on some channels, try to let the server
461                            disconnect so that our quit message is guaranteed
462                            to get displayed */
463                         net_disconnect_later(net_sendbuffer_handle(server->handle));
464                         net_sendbuffer_destroy(server->handle, FALSE);
465                 }
466                 server->handle = NULL;
467         }
468
469         if (server->readtag > 0) {
470                 g_source_remove(server->readtag);
471                 server->readtag = -1;
472         }
473
474         server_unref(server);
475 }
476
477 void server_ref(SERVER_REC *server)
478 {
479         g_return_if_fail(IS_SERVER(server));
480
481         server->refcount++;
482 }
483
484 int server_unref(SERVER_REC *server)
485 {
486         g_return_val_if_fail(IS_SERVER(server), FALSE);
487
488         if (--server->refcount > 0)
489                 return TRUE;
490
491         if (g_slist_find(servers, server) != NULL) {
492                 g_warning("Non-referenced server wasn't disconnected");
493                 server_disconnect(server);
494                 return TRUE;
495         }
496
497         MODULE_DATA_DEINIT(server);
498         server_connect_unref(server->connrec);
499         if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
500         if (server->buffer != NULL) line_split_free(server->buffer);
501         g_free(server->version);
502         g_free(server->away_reason);
503         g_free(server->nick);
504         g_free(server->tag);
505
506         server->type = 0;
507         g_free(server);
508         return FALSE;
509 }
510
511 SERVER_REC *server_find_tag(const char *tag)
512 {
513         GSList *tmp;
514
515         g_return_val_if_fail(tag != NULL, NULL);
516         if (*tag == '\0') return NULL;
517
518         for (tmp = servers; tmp != NULL; tmp = tmp->next) {
519                 SERVER_REC *server = tmp->data;
520
521                 if (g_strcasecmp(server->tag, tag) == 0)
522                         return server;
523         }
524
525         return NULL;
526 }
527
528 SERVER_REC *server_find_lookup_tag(const char *tag)
529 {
530         GSList *tmp;
531
532         g_return_val_if_fail(tag != NULL, NULL);
533         if (*tag == '\0') return NULL;
534
535         for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
536                 SERVER_REC *server = tmp->data;
537
538                 if (g_strcasecmp(server->tag, tag) == 0)
539                         return server;
540         }
541
542         return NULL;
543 }
544
545 SERVER_REC *server_find_chatnet(const char *chatnet)
546 {
547         GSList *tmp;
548
549         g_return_val_if_fail(chatnet != NULL, NULL);
550         if (*chatnet == '\0') return NULL;
551
552         for (tmp = servers; tmp != NULL; tmp = tmp->next) {
553                 SERVER_REC *server = tmp->data;
554
555                 if (server->connrec->chatnet != NULL &&
556                     g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
557                         return server;
558         }
559
560         return NULL;
561 }
562
563 void server_connect_ref(SERVER_CONNECT_REC *conn)
564 {
565         conn->refcount++;
566 }
567
568 void server_connect_unref(SERVER_CONNECT_REC *conn)
569 {
570         g_return_if_fail(IS_SERVER_CONNECT(conn));
571
572         if (--conn->refcount > 0)
573                 return;
574         if (conn->refcount < 0) {
575                 g_warning("Connection '%s' refcount = %d",
576                           conn->tag, conn->refcount);
577         }
578
579         CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
580
581         if (conn->connect_handle != NULL)
582                 net_disconnect(conn->connect_handle);
583
584         g_free_not_null(conn->proxy);
585         g_free_not_null(conn->proxy_string);
586         g_free_not_null(conn->proxy_string_after);
587         g_free_not_null(conn->proxy_password);
588
589         g_free_not_null(conn->tag);
590         g_free_not_null(conn->address);
591         g_free_not_null(conn->chatnet);
592
593         g_free_not_null(conn->own_ip4);
594         g_free_not_null(conn->own_ip6);
595
596         g_free_not_null(conn->password);
597         g_free_not_null(conn->nick);
598         g_free_not_null(conn->username);
599         g_free_not_null(conn->realname);
600
601         g_free_not_null(conn->ssl_cert);
602         g_free_not_null(conn->ssl_pkey);
603         g_free_not_null(conn->ssl_cafile);
604         g_free_not_null(conn->ssl_capath);
605
606         g_free_not_null(conn->channels);
607         g_free_not_null(conn->away_reason);
608
609         conn->type = 0;
610         g_free(conn);
611 }
612
613 void server_change_nick(SERVER_REC *server, const char *nick)
614 {
615         g_free(server->nick);
616         server->nick = g_strdup(nick);
617
618         signal_emit("server nick changed", 1, server);
619 }
620
621 /* Update own IPv4 and IPv6 records */
622 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
623                                 IPADDR *ip4, IPADDR *ip6)
624 {
625         if (ip4 == NULL || ip4->family == 0)
626                 g_free_and_null(conn->own_ip4);
627         if (ip6 == NULL || ip6->family == 0)
628                 g_free_and_null(conn->own_ip6);
629
630         if (ip4 != NULL && ip4->family != 0) {
631                 /* IPv4 address was found */
632                 if (conn->own_ip4 == NULL)
633                         conn->own_ip4 = g_new0(IPADDR, 1);
634                 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
635         }
636
637         if (ip6 != NULL && ip6->family != 0) {
638                 /* IPv6 address was found */
639                 if (conn->own_ip6 == NULL)
640                         conn->own_ip6 = g_new0(IPADDR, 1);
641                 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
642         }
643 }
644
645 /* `optlist' should contain only one unknown key - the server tag.
646    returns NULL if there was unknown -option */
647 SERVER_REC *cmd_options_get_server(const char *cmd,
648                                    GHashTable *optlist,
649                                    SERVER_REC *defserver)
650 {
651         SERVER_REC *server;
652         GSList *list, *tmp, *next;
653
654         /* get all the options, then remove the known ones. there should
655            be only one left - the server tag. */
656         list = hashtable_get_keys(optlist);
657         if (cmd != NULL) {
658                 for (tmp = list; tmp != NULL; tmp = next) {
659                         char *option = tmp->data;
660                         next = tmp->next;
661
662                         if (command_have_option(cmd, option))
663                                 list = g_slist_remove(list, option);
664                 }
665         }
666
667         if (list == NULL)
668                 return defserver;
669
670         server = server_find_tag(list->data);
671         if (server == NULL || list->next != NULL) {
672                 /* unknown option (not server tag) */
673                 signal_emit("error command", 2,
674                             GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
675                             server == NULL ? list->data : list->next->data);
676                 signal_stop();
677
678                 server = NULL;
679         }
680
681         g_slist_free(list);
682         return server;
683 }
684
685 static void disconnect_servers(GSList *servers, int chat_type)
686 {
687         GSList *tmp, *next;
688
689         for (tmp = servers; tmp != NULL; tmp = next) {
690                 SERVER_REC *rec = tmp->data;
691
692                 next = tmp->next;
693                 if (rec->chat_type == chat_type)
694                         server_disconnect(rec);
695         }
696 }
697
698 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
699 {
700         disconnect_servers(servers, proto->id);
701         disconnect_servers(lookup_servers, proto->id);
702 }
703
704 void servers_init(void)
705 {
706         settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
707         settings_add_bool("server", "resolve_reverse_lookup", FALSE);
708         lookup_servers = servers = NULL;
709
710         signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
711
712         servers_reconnect_init();
713         servers_setup_init();
714 }
715
716 void servers_deinit(void)
717 {
718         signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
719
720         servers_setup_deinit();
721         servers_reconnect_deinit();
722
723         module_uniq_destroy("SERVER");
724         module_uniq_destroy("SERVER CONNECT");
725 }