mirror of
https://github.com/irssi/irssi.git
synced 2024-09-15 04:28:09 -04:00
08c23ce6d4
we did the /NICK change. This is useful with the new irc servers changing your nick to your UID instead of killing you, at reconnect time you'd get "invalid nick" when irssi would try setting the UID as your nick.. git-svn-id: http://svn.irssi.org/repos/irssi/trunk@2351 dbcabf3a-b0e7-0310-adc4-f8d773084564
603 lines
15 KiB
C
603 lines
15 KiB
C
/*
|
|
server.c : irssi
|
|
|
|
Copyright (C) 1999-2000 Timo Sirainen
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "module.h"
|
|
#include "signals.h"
|
|
#include "commands.h"
|
|
#include "line-split.h"
|
|
#include "net-nonblock.h"
|
|
#include "net-sendbuffer.h"
|
|
#include "misc.h"
|
|
#include "rawlog.h"
|
|
#include "settings.h"
|
|
|
|
#include "chat-protocols.h"
|
|
#include "servers.h"
|
|
#include "servers-reconnect.h"
|
|
#include "servers-setup.h"
|
|
#include "channels.h"
|
|
#include "queries.h"
|
|
|
|
GSList *servers, *lookup_servers;
|
|
|
|
/* connection to server failed */
|
|
void server_connect_failed(SERVER_REC *server, const char *msg)
|
|
{
|
|
g_return_if_fail(IS_SERVER(server));
|
|
|
|
lookup_servers = g_slist_remove(lookup_servers, server);
|
|
|
|
signal_emit("server connect failed", 2, server, msg);
|
|
|
|
if (server->connect_tag != -1) {
|
|
g_source_remove(server->connect_tag);
|
|
server->connect_tag = -1;
|
|
}
|
|
if (server->handle != NULL) {
|
|
net_sendbuffer_destroy(server->handle, TRUE);
|
|
server->handle = NULL;
|
|
}
|
|
|
|
if (server->connect_pipe[0] != NULL) {
|
|
g_io_channel_close(server->connect_pipe[0]);
|
|
g_io_channel_unref(server->connect_pipe[0]);
|
|
g_io_channel_close(server->connect_pipe[1]);
|
|
g_io_channel_unref(server->connect_pipe[1]);
|
|
server->connect_pipe[0] = NULL;
|
|
server->connect_pipe[1] = NULL;
|
|
}
|
|
|
|
server_unref(server);
|
|
}
|
|
|
|
/* generate tag from server's address */
|
|
static char *server_create_address_tag(const char *address)
|
|
{
|
|
const char *start, *end;
|
|
|
|
g_return_val_if_fail(address != NULL, NULL);
|
|
|
|
/* try to generate a reasonable server tag */
|
|
if (strchr(address, '.') == NULL) {
|
|
start = end = NULL;
|
|
} else if (g_strncasecmp(address, "irc", 3) == 0 ||
|
|
g_strncasecmp(address, "chat", 4) == 0) {
|
|
/* irc-2.cs.hut.fi -> hut, chat.bt.net -> bt */
|
|
end = strrchr(address, '.');
|
|
start = end-1;
|
|
while (start > address && *start != '.') start--;
|
|
} else {
|
|
/* efnet.cs.hut.fi -> efnet */
|
|
end = strchr(address, '.');
|
|
start = end;
|
|
}
|
|
|
|
if (start == end) start = address; else start++;
|
|
if (end == NULL) end = address + strlen(address);
|
|
|
|
return g_strndup(start, (int) (end-start));
|
|
}
|
|
|
|
/* create unique tag for server. prefer ircnet's name or
|
|
generate it from server's address */
|
|
static char *server_create_tag(SERVER_CONNECT_REC *conn)
|
|
{
|
|
GString *str;
|
|
char *tag;
|
|
int num;
|
|
|
|
g_return_val_if_fail(IS_SERVER_CONNECT(conn), NULL);
|
|
|
|
tag = conn->chatnet != NULL && *conn->chatnet != '\0' ?
|
|
g_strdup(conn->chatnet) :
|
|
server_create_address_tag(conn->address);
|
|
|
|
if (conn->tag != NULL && server_find_tag(conn->tag) == NULL &&
|
|
strncmp(conn->tag, tag, strlen(tag)) == 0) {
|
|
/* use the existing tag if it begins with the same ID -
|
|
this is useful when you have several connections to
|
|
same server and you want to keep the same tags with
|
|
the servers (or it would cause problems when rejoining
|
|
/LAYOUT SAVEd channels). */
|
|
g_free(tag);
|
|
return g_strdup(conn->tag);
|
|
}
|
|
|
|
|
|
/* then just append numbers after tag until unused is found.. */
|
|
str = g_string_new(tag);
|
|
for (num = 2; server_find_tag(str->str) != NULL; num++)
|
|
g_string_sprintf(str, "%s%d", tag, num);
|
|
g_free(tag);
|
|
|
|
tag = str->str;
|
|
g_string_free(str, FALSE);
|
|
return tag;
|
|
}
|
|
|
|
/* Connection to server finished, fill the rest of the fields */
|
|
void server_connect_finished(SERVER_REC *server)
|
|
{
|
|
server->connect_time = time(NULL);
|
|
|
|
servers = g_slist_append(servers, server);
|
|
signal_emit("server connected", 1, server);
|
|
}
|
|
|
|
static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle)
|
|
{
|
|
int error;
|
|
|
|
g_return_if_fail(IS_SERVER(server));
|
|
|
|
error = net_geterror(handle);
|
|
if (error != 0) {
|
|
server->connection_lost = TRUE;
|
|
server_connect_failed(server, g_strerror(error));
|
|
return;
|
|
}
|
|
|
|
lookup_servers = g_slist_remove(lookup_servers, server);
|
|
g_source_remove(server->connect_tag);
|
|
server->connect_tag = -1;
|
|
|
|
server_connect_finished(server);
|
|
}
|
|
|
|
static void server_connect_callback_readpipe(SERVER_REC *server)
|
|
{
|
|
SERVER_CONNECT_REC *conn;
|
|
RESOLVED_IP_REC iprec;
|
|
GIOChannel *handle;
|
|
IPADDR *ip, *own_ip;
|
|
const char *errormsg;
|
|
int port;
|
|
|
|
g_return_if_fail(IS_SERVER(server));
|
|
|
|
g_source_remove(server->connect_tag);
|
|
server->connect_tag = -1;
|
|
|
|
net_gethostbyname_return(server->connect_pipe[0], &iprec);
|
|
|
|
g_io_channel_close(server->connect_pipe[0]);
|
|
g_io_channel_unref(server->connect_pipe[0]);
|
|
g_io_channel_close(server->connect_pipe[1]);
|
|
g_io_channel_unref(server->connect_pipe[1]);
|
|
|
|
server->connect_pipe[0] = NULL;
|
|
server->connect_pipe[1] = NULL;
|
|
|
|
/* figure out if we should use IPv4 or v6 address */
|
|
ip = iprec.error != 0 ? NULL : iprec.ip6.family == 0 ||
|
|
(server->connrec->family == AF_INET && iprec.ip4.family != 0) ?
|
|
&iprec.ip4 : &iprec.ip6;
|
|
if (iprec.ip4.family != 0 && server->connrec->family == 0 &&
|
|
!settings_get_bool("resolve_prefer_ipv6"))
|
|
ip = &iprec.ip4;
|
|
|
|
conn = server->connrec;
|
|
port = conn->proxy != NULL ? conn->proxy_port : conn->port;
|
|
own_ip = ip == NULL ? NULL :
|
|
(IPADDR_IS_V6(ip) ? conn->own_ip6 : conn->own_ip4);
|
|
|
|
handle = NULL;
|
|
if (ip != NULL) {
|
|
signal_emit("server connecting", 2, server, ip);
|
|
if (server->handle == NULL)
|
|
handle = net_connect_ip(ip, port, own_ip);
|
|
else
|
|
handle = net_sendbuffer_handle(server->handle);
|
|
}
|
|
|
|
if (handle == NULL) {
|
|
/* failed */
|
|
if (iprec.error != 0 && net_hosterror_notfound(iprec.error)) {
|
|
/* IP wasn't found for the host, don't try to reconnect
|
|
back to this server */
|
|
server->dns_error = TRUE;
|
|
}
|
|
|
|
if (iprec.error == 0) {
|
|
/* connect() failed */
|
|
errormsg = g_strerror(errno);
|
|
} else {
|
|
/* gethostbyname() failed */
|
|
errormsg = iprec.errorstr != NULL ? iprec.errorstr :
|
|
"Host lookup failed";
|
|
}
|
|
server->connection_lost = TRUE;
|
|
server_connect_failed(server, errormsg);
|
|
g_free_not_null(iprec.errorstr);
|
|
return;
|
|
}
|
|
|
|
if (server->handle == NULL)
|
|
server->handle = net_sendbuffer_create(handle, 0);
|
|
server->connect_tag =
|
|
g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
|
|
(GInputFunction) server_connect_callback_init,
|
|
server);
|
|
}
|
|
|
|
/* initializes server record but doesn't start connecting */
|
|
void server_connect_init(SERVER_REC *server)
|
|
{
|
|
g_return_if_fail(server != NULL);
|
|
|
|
MODULE_DATA_INIT(server);
|
|
server->type = module_get_uniq_id("SERVER", 0);
|
|
server_ref(server);
|
|
|
|
server->nick = g_strdup(server->connrec->nick);
|
|
if (server->connrec->username == NULL || *server->connrec->username == '\0') {
|
|
g_free_not_null(server->connrec->username);
|
|
|
|
server->connrec->username = g_get_user_name();
|
|
if (*server->connrec->username == '\0') server->connrec->username = "-";
|
|
server->connrec->username = g_strdup(server->connrec->username);
|
|
}
|
|
if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
|
|
g_free_not_null(server->connrec->realname);
|
|
|
|
server->connrec->realname = g_get_real_name();
|
|
if (*server->connrec->realname == '\0') server->connrec->realname = "-";
|
|
server->connrec->realname = g_strdup(server->connrec->realname);
|
|
}
|
|
|
|
server->tag = server_create_tag(server->connrec);
|
|
}
|
|
|
|
/* starts connecting to server */
|
|
int server_start_connect(SERVER_REC *server)
|
|
{
|
|
const char *connect_address;
|
|
int fd[2];
|
|
|
|
g_return_val_if_fail(server != NULL, FALSE);
|
|
if (server->connrec->port <= 0) return FALSE;
|
|
|
|
server_connect_init(server);
|
|
|
|
if (pipe(fd) != 0) {
|
|
g_warning("server_connect(): pipe() failed.");
|
|
g_free(server->tag);
|
|
g_free(server->nick);
|
|
return FALSE;
|
|
}
|
|
|
|
server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
|
|
server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
|
|
|
|
connect_address = server->connrec->proxy != NULL ?
|
|
server->connrec->proxy : server->connrec->address;
|
|
server->connect_pid =
|
|
net_gethostbyname_nonblock(connect_address,
|
|
server->connect_pipe[1]);
|
|
server->connect_tag =
|
|
g_input_add(server->connect_pipe[0], G_INPUT_READ,
|
|
(GInputFunction) server_connect_callback_readpipe,
|
|
server);
|
|
server->rawlog = rawlog_create();
|
|
|
|
lookup_servers = g_slist_append(lookup_servers, server);
|
|
|
|
signal_emit("server looking", 1, server);
|
|
return TRUE;
|
|
}
|
|
|
|
static int server_remove_channels(SERVER_REC *server)
|
|
{
|
|
GSList *tmp;
|
|
int found;
|
|
|
|
g_return_val_if_fail(server != NULL, FALSE);
|
|
|
|
found = FALSE;
|
|
for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
|
|
CHANNEL_REC *channel = tmp->data;
|
|
|
|
channel->server = NULL;
|
|
channel_destroy(channel);
|
|
found = TRUE;
|
|
}
|
|
|
|
while (server->queries != NULL)
|
|
query_change_server(server->queries->data, NULL);
|
|
|
|
g_slist_free(server->channels);
|
|
g_slist_free(server->queries);
|
|
|
|
return found;
|
|
}
|
|
|
|
void server_disconnect(SERVER_REC *server)
|
|
{
|
|
int chans;
|
|
|
|
g_return_if_fail(IS_SERVER(server));
|
|
|
|
if (server->disconnected)
|
|
return;
|
|
|
|
if (server->connect_tag != -1) {
|
|
/* still connecting to server.. */
|
|
if (server->connect_pid != -1)
|
|
net_disconnect_nonblock(server->connect_pid);
|
|
server_connect_failed(server, NULL);
|
|
return;
|
|
}
|
|
|
|
servers = g_slist_remove(servers, server);
|
|
|
|
server->disconnected = TRUE;
|
|
signal_emit("server disconnected", 1, server);
|
|
|
|
/* close all channels */
|
|
chans = server_remove_channels(server);
|
|
|
|
if (server->handle != NULL) {
|
|
if (!chans || server->connection_lost)
|
|
net_sendbuffer_destroy(server->handle, TRUE);
|
|
else {
|
|
/* we were on some channels, try to let the server
|
|
disconnect so that our quit message is guaranteed
|
|
to get displayed */
|
|
net_disconnect_later(net_sendbuffer_handle(server->handle));
|
|
net_sendbuffer_destroy(server->handle, FALSE);
|
|
}
|
|
server->handle = NULL;
|
|
}
|
|
|
|
if (server->readtag > 0) {
|
|
g_source_remove(server->readtag);
|
|
server->readtag = -1;
|
|
}
|
|
|
|
server_unref(server);
|
|
}
|
|
|
|
void server_ref(SERVER_REC *server)
|
|
{
|
|
g_return_if_fail(IS_SERVER(server));
|
|
|
|
server->refcount++;
|
|
}
|
|
|
|
int server_unref(SERVER_REC *server)
|
|
{
|
|
g_return_val_if_fail(IS_SERVER(server), FALSE);
|
|
|
|
if (--server->refcount > 0)
|
|
return TRUE;
|
|
|
|
if (g_slist_find(servers, server) != NULL) {
|
|
g_warning("Non-referenced server wasn't disconnected");
|
|
server_disconnect(server);
|
|
return TRUE;
|
|
}
|
|
|
|
MODULE_DATA_DEINIT(server);
|
|
server_connect_unref(server->connrec);
|
|
if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
|
|
if (server->buffer != NULL) line_split_free(server->buffer);
|
|
g_free(server->version);
|
|
g_free(server->away_reason);
|
|
g_free(server->nick);
|
|
g_free(server->tag);
|
|
g_free(server);
|
|
return FALSE;
|
|
}
|
|
|
|
SERVER_REC *server_find_tag(const char *tag)
|
|
{
|
|
GSList *tmp;
|
|
|
|
g_return_val_if_fail(tag != NULL, NULL);
|
|
if (*tag == '\0') return NULL;
|
|
|
|
for (tmp = servers; tmp != NULL; tmp = tmp->next) {
|
|
SERVER_REC *server = tmp->data;
|
|
|
|
if (g_strcasecmp(server->tag, tag) == 0)
|
|
return server;
|
|
}
|
|
|
|
for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
|
|
SERVER_REC *server = tmp->data;
|
|
|
|
if (g_strcasecmp(server->tag, tag) == 0)
|
|
return server;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
SERVER_REC *server_find_chatnet(const char *chatnet)
|
|
{
|
|
GSList *tmp;
|
|
|
|
g_return_val_if_fail(chatnet != NULL, NULL);
|
|
if (*chatnet == '\0') return NULL;
|
|
|
|
for (tmp = servers; tmp != NULL; tmp = tmp->next) {
|
|
SERVER_REC *server = tmp->data;
|
|
|
|
if (server->connrec->chatnet != NULL &&
|
|
g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
|
|
return server;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void server_connect_ref(SERVER_CONNECT_REC *conn)
|
|
{
|
|
conn->refcount++;
|
|
}
|
|
|
|
void server_connect_unref(SERVER_CONNECT_REC *conn)
|
|
{
|
|
g_return_if_fail(IS_SERVER_CONNECT(conn));
|
|
|
|
if (--conn->refcount > 0)
|
|
return;
|
|
if (conn->refcount < 0) {
|
|
g_warning("Connection '%s' refcount = %d",
|
|
conn->tag, conn->refcount);
|
|
}
|
|
|
|
CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
|
|
|
|
g_free_not_null(conn->proxy);
|
|
g_free_not_null(conn->proxy_string);
|
|
g_free_not_null(conn->proxy_string_after);
|
|
g_free_not_null(conn->proxy_password);
|
|
|
|
g_free_not_null(conn->tag);
|
|
g_free_not_null(conn->address);
|
|
g_free_not_null(conn->chatnet);
|
|
|
|
g_free_not_null(conn->own_ip4);
|
|
g_free_not_null(conn->own_ip6);
|
|
|
|
g_free_not_null(conn->password);
|
|
g_free_not_null(conn->nick);
|
|
g_free_not_null(conn->username);
|
|
g_free_not_null(conn->realname);
|
|
|
|
g_free_not_null(conn->channels);
|
|
g_free_not_null(conn->away_reason);
|
|
g_free(conn);
|
|
}
|
|
|
|
void server_change_nick(SERVER_REC *server, const char *nick)
|
|
{
|
|
g_free(server->nick);
|
|
server->nick = g_strdup(nick);
|
|
|
|
signal_emit("server nick changed", 1, server);
|
|
}
|
|
|
|
/* Update own IPv4 and IPv6 records */
|
|
void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
|
|
IPADDR *ip4, IPADDR *ip6)
|
|
{
|
|
if (ip4 == NULL || ip4->family == 0)
|
|
g_free_and_null(conn->own_ip4);
|
|
if (ip6 == NULL || ip6->family == 0)
|
|
g_free_and_null(conn->own_ip6);
|
|
|
|
if (ip4 != NULL && ip4->family != 0) {
|
|
/* IPv4 address was found */
|
|
if (conn->own_ip4 == NULL)
|
|
conn->own_ip4 = g_new0(IPADDR, 1);
|
|
memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
|
|
}
|
|
|
|
if (ip6 != NULL && ip6->family != 0) {
|
|
/* IPv6 address was found */
|
|
if (conn->own_ip6 == NULL)
|
|
conn->own_ip6 = g_new0(IPADDR, 1);
|
|
memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
|
|
}
|
|
}
|
|
|
|
/* `optlist' should contain only one unknown key - the server tag.
|
|
returns NULL if there was unknown -option */
|
|
SERVER_REC *cmd_options_get_server(const char *cmd,
|
|
GHashTable *optlist,
|
|
SERVER_REC *defserver)
|
|
{
|
|
SERVER_REC *server;
|
|
GSList *list, *tmp, *next;
|
|
|
|
/* get all the options, then remove the known ones. there should
|
|
be only one left - the server tag. */
|
|
list = hashtable_get_keys(optlist);
|
|
if (cmd != NULL) {
|
|
for (tmp = list; tmp != NULL; tmp = next) {
|
|
char *option = tmp->data;
|
|
next = tmp->next;
|
|
|
|
if (command_have_option(cmd, option))
|
|
list = g_slist_remove(list, option);
|
|
}
|
|
}
|
|
|
|
if (list == NULL)
|
|
return defserver;
|
|
|
|
server = server_find_tag(list->data);
|
|
if (server == NULL || list->next != NULL) {
|
|
/* unknown option (not server tag) */
|
|
signal_emit("error command", 2,
|
|
GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
|
|
server == NULL ? list->data : list->next->data);
|
|
signal_stop();
|
|
|
|
server = NULL;
|
|
}
|
|
|
|
g_slist_free(list);
|
|
return server;
|
|
}
|
|
|
|
static void disconnect_servers(GSList *servers, int chat_type)
|
|
{
|
|
GSList *tmp, *next;
|
|
|
|
for (tmp = servers; tmp != NULL; tmp = next) {
|
|
SERVER_REC *rec = tmp->data;
|
|
|
|
next = tmp->next;
|
|
if (rec->chat_type == chat_type)
|
|
server_disconnect(rec);
|
|
}
|
|
}
|
|
|
|
static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
|
|
{
|
|
disconnect_servers(servers, proto->id);
|
|
disconnect_servers(lookup_servers, proto->id);
|
|
}
|
|
|
|
void servers_init(void)
|
|
{
|
|
settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
|
|
lookup_servers = servers = NULL;
|
|
|
|
signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
|
|
|
|
servers_reconnect_init();
|
|
servers_setup_init();
|
|
}
|
|
|
|
void servers_deinit(void)
|
|
{
|
|
signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
|
|
|
|
servers_setup_deinit();
|
|
servers_reconnect_deinit();
|
|
|
|
module_uniq_destroy("SERVER");
|
|
module_uniq_destroy("SERVER CONNECT");
|
|
}
|