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