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