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