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