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