addition of silc.css
[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-redirect.h"
35 #include "servers-setup.h"
36 #include "channels.h"
37 #include "queries.h"
38
39 GSList *servers, *lookup_servers;
40
41 /* connection to server failed */
42 void server_connect_failed(SERVER_REC *server, const char *msg)
43 {
44         g_return_if_fail(IS_SERVER(server));
45
46         lookup_servers = g_slist_remove(lookup_servers, server);
47
48         signal_emit("server connect failed", 2, server, msg);
49         if (server->connect_tag != -1)
50                 g_source_remove(server->connect_tag);
51         if (server->handle != NULL)
52                 net_sendbuffer_destroy(server->handle, TRUE);
53
54         if (server->connect_pipe[0] != NULL) {
55                 g_io_channel_close(server->connect_pipe[0]);
56                 g_io_channel_unref(server->connect_pipe[0]);
57                 g_io_channel_close(server->connect_pipe[1]);
58                 g_io_channel_unref(server->connect_pipe[1]);
59         }
60
61         MODULE_DATA_DEINIT(server);
62         server_connect_free(server->connrec);
63         g_free_not_null(server->nick);
64         g_free(server->tag);
65         g_free(server);
66 }
67
68 /* generate tag from server's address */
69 static char *server_create_address_tag(const char *address)
70 {
71         const char *start, *end;
72
73         g_return_val_if_fail(address != NULL, NULL);
74
75         /* try to generate a reasonable server tag */
76         if (strchr(address, '.') == NULL) {
77                 start = end = NULL;
78         } else if (g_strncasecmp(address, "irc", 3) == 0 ||
79             g_strncasecmp(address, "chat", 4) == 0) {
80                 /* irc-2.cs.hut.fi -> hut, chat.bt.net -> bt */
81                 end = strrchr(address, '.');
82                 start = end-1;
83                 while (start > address && *start != '.') start--;
84         } else {
85                 /* efnet.cs.hut.fi -> efnet */
86                 end = strchr(address, '.');
87                 start = end;
88         }
89
90         if (start == end) start = address; else start++;
91         if (end == NULL) end = address + strlen(address);
92
93         return g_strndup(start, (int) (end-start));
94 }
95
96 /* create unique tag for server. prefer ircnet's name or
97    generate it from server's address */
98 static char *server_create_tag(SERVER_CONNECT_REC *conn)
99 {
100         GString *str;
101         char *tag;
102         int num;
103
104         g_return_val_if_fail(IS_SERVER_CONNECT(conn), NULL);
105
106         tag = conn->chatnet != NULL && *conn->chatnet != '\0' ?
107                 g_strdup(conn->chatnet) :
108                 server_create_address_tag(conn->address);
109
110         /* then just append numbers after tag until unused is found.. */
111         str = g_string_new(tag);
112         for (num = 2; server_find_tag(str->str) != NULL; num++)
113                 g_string_sprintf(str, "%s%d", tag, num);
114         g_free(tag);
115
116         tag = str->str;
117         g_string_free(str, FALSE);
118         return tag;
119 }
120
121 /* Connection to server finished, fill the rest of the fields */
122 void server_connect_finished(SERVER_REC *server)
123 {
124         server->connect_time = time(NULL);
125         server->rawlog = rawlog_create();
126
127         server->eventtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
128         server->eventgrouptable = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
129         server->cmdtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
130
131         servers = g_slist_append(servers, server);
132         signal_emit("server connected", 1, server);
133 }
134
135 static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle)
136 {
137         int error;
138
139         g_return_if_fail(IS_SERVER(server));
140
141         error = net_geterror(handle);
142         if (error != 0) {
143                 server->connection_lost = TRUE;
144                 server_connect_failed(server, g_strerror(error));
145                 return;
146         }
147
148         lookup_servers = g_slist_remove(lookup_servers, server);
149         g_source_remove(server->connect_tag);
150         server->connect_tag = -1;
151
152         server_connect_finished(server);
153 }
154
155 static void server_connect_callback_readpipe(SERVER_REC *server)
156 {
157         SERVER_CONNECT_REC *conn;
158         RESOLVED_IP_REC iprec;
159         GIOChannel *handle;
160         IPADDR *ip, *own_ip;
161         const char *errormsg;
162         int port;
163
164         g_return_if_fail(IS_SERVER(server));
165
166         g_source_remove(server->connect_tag);
167         server->connect_tag = -1;
168
169         net_gethostbyname_return(server->connect_pipe[0], &iprec);
170
171         g_io_channel_close(server->connect_pipe[0]);
172         g_io_channel_unref(server->connect_pipe[0]);
173         g_io_channel_close(server->connect_pipe[1]);
174         g_io_channel_unref(server->connect_pipe[1]);
175
176         server->connect_pipe[0] = NULL;
177         server->connect_pipe[1] = NULL;
178
179         /* figure out if we should use IPv4 or v6 address */
180         ip = iprec.error != 0 ? NULL : iprec.ip6.family == 0 ||
181                 (server->connrec->family == AF_INET && iprec.ip4.family != 0) ?
182                 &iprec.ip4 : &iprec.ip6;
183         if (iprec.ip4.family != 0 && server->connrec->family == 0 &&
184             !settings_get_bool("resolve_prefer_ipv6"))
185                 ip = &iprec.ip4;
186
187         conn = server->connrec;
188         port = conn->proxy != NULL ? conn->proxy_port : conn->port;
189         own_ip = ip == NULL ? NULL :
190                 (IPADDR_IS_V6(ip) ? conn->own_ip6 : conn->own_ip4);
191
192         if (ip != NULL)
193                 signal_emit("server connecting", 2, server, ip);
194
195         handle = ip == NULL ? NULL : net_connect_ip(ip, port, own_ip);
196         if (handle == NULL) {
197                 /* failed */
198                 if (iprec.error != 0 && net_hosterror_notfound(iprec.error)) {
199                         /* IP wasn't found for the host, don't try to reconnect
200                            back to this server */
201                         server->dns_error = TRUE;
202                 }
203
204                 if (iprec.error == 0) {
205                         /* connect() failed */
206                         errormsg = g_strerror(errno);
207                 } else {
208                         /* gethostbyname() failed */
209                         errormsg = iprec.errorstr != NULL ? iprec.errorstr :
210                                 "Host lookup failed";
211                 }
212                 server->connection_lost = TRUE;
213                 server_connect_failed(server, errormsg);
214                 g_free_not_null(iprec.errorstr);
215                 return;
216         }
217
218         server->handle = net_sendbuffer_create(handle, 0);
219         server->connect_tag =
220                 g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
221                             (GInputFunction) server_connect_callback_init,
222                             server);
223 }
224
225 /* initializes server record but doesn't start connecting */
226 void server_connect_init(SERVER_REC *server)
227 {
228         g_return_if_fail(server != NULL);
229
230         MODULE_DATA_INIT(server);
231         server->type = module_get_uniq_id("SERVER", 0);
232
233         server->nick = g_strdup(server->connrec->nick);
234         if (server->connrec->username == NULL || *server->connrec->username == '\0') {
235                 g_free_not_null(server->connrec->username);
236
237                 server->connrec->username = g_get_user_name();
238                 if (*server->connrec->username == '\0') server->connrec->username = "-";
239                 server->connrec->username = g_strdup(server->connrec->username);
240         }
241         if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
242                 g_free_not_null(server->connrec->realname);
243
244                 server->connrec->realname = g_get_real_name();
245                 if (*server->connrec->realname == '\0') server->connrec->realname = "-";
246                 server->connrec->realname = g_strdup(server->connrec->realname);
247         }
248
249         server->tag = server_create_tag(server->connrec);
250 }
251
252 /* starts connecting to server */
253 int server_start_connect(SERVER_REC *server)
254 {
255         const char *connect_address;
256         int fd[2];
257
258         g_return_val_if_fail(server != NULL, FALSE);
259         if (server->connrec->port <= 0) return FALSE;
260
261         server_connect_init(server);
262
263         if (pipe(fd) != 0) {
264                 g_warning("server_connect(): pipe() failed.");
265                 g_free(server->tag);
266                 g_free(server->nick);
267                 return FALSE;
268         }
269
270         server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
271         server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
272
273         connect_address = server->connrec->proxy != NULL ?
274                 server->connrec->proxy : server->connrec->address;
275         server->connect_pid =
276                 net_gethostbyname_nonblock(connect_address,
277                                            server->connect_pipe[1]);
278         server->connect_tag =
279                 g_input_add(server->connect_pipe[0], G_INPUT_READ,
280                             (GInputFunction) server_connect_callback_readpipe,
281                             server);
282
283         lookup_servers = g_slist_append(lookup_servers, server);
284
285         signal_emit("server looking", 1, server);
286         return TRUE;
287 }
288
289 static int server_remove_channels(SERVER_REC *server)
290 {
291         GSList *tmp;
292         int found;
293
294         g_return_val_if_fail(server != NULL, FALSE);
295
296         found = FALSE;
297         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
298                 CHANNEL_REC *channel = tmp->data;
299
300                 channel->server = NULL;
301                 channel_destroy(channel);
302                 found = TRUE;
303         }
304
305         while (server->queries != NULL)
306                 query_change_server(server->queries->data, NULL);
307
308         g_slist_free(server->channels);
309         g_slist_free(server->queries);
310
311         return found;
312 }
313
314 void server_disconnect(SERVER_REC *server)
315 {
316         int chans;
317
318         g_return_if_fail(IS_SERVER(server));
319
320         if (server->connect_tag != -1) {
321                 /* still connecting to server.. */
322                 if (server->connect_pid != -1)
323                         net_disconnect_nonblock(server->connect_pid);
324                 server_connect_failed(server, NULL);
325                 return;
326         }
327
328         servers = g_slist_remove(servers, server);
329
330         signal_emit("server disconnected", 1, server);
331
332         /* close all channels */
333         chans = server_remove_channels(server);
334
335         if (server->handle != NULL) {
336                 if (!chans || server->connection_lost)
337                         net_sendbuffer_destroy(server->handle, TRUE);
338                 else {
339                         /* we were on some channels, try to let the server
340                            disconnect so that our quit message is guaranteed
341                            to get displayed */
342                         net_disconnect_later(net_sendbuffer_handle(server->handle));
343                         net_sendbuffer_destroy(server->handle, FALSE);
344                 }
345                 server->handle = NULL;
346         }
347
348         if (server->readtag > 0)
349                 g_source_remove(server->readtag);
350
351         MODULE_DATA_DEINIT(server);
352         server_connect_free(server->connrec);
353         rawlog_destroy(server->rawlog);
354         line_split_free(server->buffer);
355         g_free_not_null(server->version);
356         g_free_not_null(server->away_reason);
357         g_free(server->nick);
358         g_free(server->tag);
359         g_free(server);
360 }
361
362 SERVER_REC *server_find_tag(const char *tag)
363 {
364         GSList *tmp;
365
366         g_return_val_if_fail(tag != NULL, NULL);
367         if (*tag == '\0') return NULL;
368
369         for (tmp = servers; tmp != NULL; tmp = tmp->next) {
370                 SERVER_REC *server = tmp->data;
371
372                 if (g_strcasecmp(server->tag, tag) == 0)
373                         return server;
374         }
375
376         for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
377                 SERVER_REC *server = tmp->data;
378
379                 if (g_strcasecmp(server->tag, tag) == 0)
380                         return server;
381         }
382
383         return NULL;
384 }
385
386 SERVER_REC *server_find_chatnet(const char *chatnet)
387 {
388         GSList *tmp;
389
390         g_return_val_if_fail(chatnet != NULL, NULL);
391         if (*chatnet == '\0') return NULL;
392
393         for (tmp = servers; tmp != NULL; tmp = tmp->next) {
394                 SERVER_REC *server = tmp->data;
395
396                 if (server->connrec->chatnet != NULL &&
397                     g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
398                         return server;
399         }
400
401         return NULL;
402 }
403
404 void server_connect_free(SERVER_CONNECT_REC *conn)
405 {
406         g_return_if_fail(IS_SERVER_CONNECT(conn));
407
408         signal_emit("server connect free", 1, conn);
409         g_free_not_null(conn->proxy);
410         g_free_not_null(conn->proxy_string);
411         g_free_not_null(conn->proxy_password);
412
413         g_free_not_null(conn->address);
414         g_free_not_null(conn->chatnet);
415
416         g_free_not_null(conn->own_ip4);
417         g_free_not_null(conn->own_ip6);
418
419         g_free_not_null(conn->password);
420         g_free_not_null(conn->nick);
421         g_free_not_null(conn->username);
422         g_free_not_null(conn->realname);
423
424         g_free_not_null(conn->channels);
425         g_free_not_null(conn->away_reason);
426         g_free(conn);
427 }
428
429 void server_change_nick(SERVER_REC *server, const char *nick)
430 {
431         g_free(server->connrec->nick);
432         g_free(server->nick);
433         server->connrec->nick = g_strdup(nick);
434         server->nick = g_strdup(nick);
435
436         signal_emit("server nick changed", 1, server);
437 }
438
439 /* Update own IPv4 and IPv6 records */
440 void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
441                                 IPADDR *ip4, IPADDR *ip6)
442 {
443         if (ip4 == NULL || ip4->family == 0)
444                 g_free_and_null(conn->own_ip4);
445         if (ip6 == NULL || ip6->family == 0)
446                 g_free_and_null(conn->own_ip6);
447
448         if (ip4 != NULL && ip4->family != 0) {
449                 /* IPv4 address was found */
450                 if (conn->own_ip4 == NULL)
451                         conn->own_ip4 = g_new0(IPADDR, 1);
452                 memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
453         }
454
455         if (ip6 != NULL && ip6->family != 0) {
456                 /* IPv6 address was found */
457                 if (conn->own_ip6 == NULL)
458                         conn->own_ip6 = g_new0(IPADDR, 1);
459                 memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
460         }
461 }
462
463 /* `optlist' should contain only one unknown key - the server tag.
464    returns NULL if there was unknown -option */
465 SERVER_REC *cmd_options_get_server(const char *cmd,
466                                    GHashTable *optlist,
467                                    SERVER_REC *defserver)
468 {
469         SERVER_REC *server;
470         GSList *list, *tmp, *next;
471
472         /* get all the options, then remove the known ones. there should
473            be only one left - the server tag. */
474         list = hashtable_get_keys(optlist);
475         if (cmd != NULL) {
476                 for (tmp = list; tmp != NULL; tmp = next) {
477                         char *option = tmp->data;
478                         next = tmp->next;
479
480                         if (command_have_option(cmd, option))
481                                 list = g_slist_remove(list, option);
482                 }
483         }
484
485         if (list == NULL)
486                 return defserver;
487
488         server = server_find_tag(list->data);
489         if (server == NULL || list->next != NULL) {
490                 /* unknown option (not server tag) */
491                 signal_emit("error command", 2,
492                             GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
493                             server == NULL ? list->data : list->next->data);
494                 signal_stop();
495
496                 server = NULL;
497         }
498
499         g_slist_free(list);
500         return server;
501 }
502
503 static void disconnect_servers(GSList *servers, int chat_type)
504 {
505         GSList *tmp, *next;
506
507         for (tmp = servers; tmp != NULL; tmp = next) {
508                 SERVER_REC *rec = tmp->data;
509
510                 next = tmp->next;
511                 if (rec->chat_type == chat_type)
512                         server_disconnect(rec);
513         }
514 }
515
516 static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
517 {
518         disconnect_servers(servers, proto->id);
519         disconnect_servers(lookup_servers, proto->id);
520 }
521
522 void servers_init(void)
523 {
524         settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
525         lookup_servers = servers = NULL;
526
527         signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
528
529         servers_reconnect_init();
530         servers_redirect_init();
531         servers_setup_init();
532 }
533
534 void servers_deinit(void)
535 {
536         signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
537
538         servers_setup_deinit();
539         servers_redirect_deinit();
540         servers_reconnect_deinit();
541
542         module_uniq_destroy("SERVER");
543         module_uniq_destroy("SERVER CONNECT");
544 }