diff options
Diffstat (limited to 'src/irc/core/irc-server.c')
-rw-r--r-- | src/irc/core/irc-server.c | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/src/irc/core/irc-server.c b/src/irc/core/irc-server.c new file mode 100644 index 00000000..a02181e6 --- /dev/null +++ b/src/irc/core/irc-server.c @@ -0,0 +1,457 @@ +/* + irc-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 "net-nonblock.h" +#include "line-split.h" +#include "signals.h" +#include "modules.h" +#include "rawlog.h" +#include "misc.h" + +#include "irc-server.h" +#include "server-idle.h" +#include "server-reconnect.h" +#include "server-setup.h" +#include "ircnet-setup.h" +#include "channels.h" +#include "modes.h" +#include "irc.h" +#include "query.h" + +#include "settings.h" + +#define DEFAULT_MAX_KICKS 1 +#define DEFAULT_MAX_MODES 3 +#define DEFAULT_MAX_WHOIS 4 +#define DEFAULT_MAX_MSGS 1 + +#define DEFAULT_USER_MODE "+i" +#define DEFAULT_CMD_QUEUE_SPEED 2200 +#define DEFAULT_CMDS_MAX_AT_ONCE 5 + +static int cmd_tag; + +void irc_server_connect_free(IRC_SERVER_CONNECT_REC *rec) +{ + g_return_if_fail(rec != NULL); + + g_free_not_null(rec->proxy); + g_free_not_null(rec->proxy_string); + g_free_not_null(rec->ircnet); + g_free_not_null(rec->password); + g_free_not_null(rec->nick); + g_free_not_null(rec->alternate_nick); + g_free_not_null(rec->username); + g_free_not_null(rec->realname); + g_free_not_null(rec->own_ip); + g_free_not_null(rec->channels); + g_free_not_null(rec->away_reason); + g_free_not_null(rec->usermode); + g_free(rec->address); + g_free(rec); +} + +static void server_init(IRC_SERVER_REC *server) +{ + IRC_SERVER_CONNECT_REC *conn; + + g_return_if_fail(server != NULL); + + conn = server->connrec; + + if (conn->proxy_string != NULL) + irc_send_cmdv(server, conn->proxy_string, conn->address, conn->port); + + if (conn->password != NULL && *conn->password != '\0') { + /* send password */ + server->cmdcount = 0; + irc_send_cmdv(server, "PASS %s", conn->password); + } + + /* send nick */ + server->cmdcount = 0; + irc_send_cmdv(server, "NICK %s", conn->nick); + + /* send user/realname */ + server->cmdcount = 0; + irc_send_cmdv(server, "USER %s - - :%s", conn->username, conn->realname); + + server->cmdcount = 0; +} + +IRC_SERVER_REC *irc_server_connect(IRC_SERVER_CONNECT_REC *conn) +{ + IRC_SERVER_REC *server; + + g_return_val_if_fail(conn != NULL, NULL); + if (conn->address == NULL || *conn->address == '\0') return NULL; + if (conn->nick == NULL || *conn->nick == '\0') return NULL; + + server = g_new0(IRC_SERVER_REC, 1); + server->type = module_get_uniq_id("IRC SERVER", SERVER_TYPE_IRC); + + server->connrec = conn; + if (conn->port <= 0) conn->port = 6667; + if (conn->username == NULL || *conn->username == '\0') { + g_free_not_null(conn->username); + + conn->username = g_get_user_name(); + if (*conn->username == '\0') conn->username = "-"; + conn->username = g_strdup(conn->username); + } + if (conn->realname == NULL || *conn->realname == '\0') { + g_free_not_null(conn->realname); + + conn->realname = g_get_real_name(); + if (*conn->realname == '\0') conn->realname = "-"; + conn->realname = g_strdup(conn->realname); + } + + server->nick = g_strdup(conn->nick); + + server->cmd_queue_speed = conn->cmd_queue_speed > 0 ? + conn->cmd_queue_speed : settings_get_int("cmd_queue_speed"); + server->max_cmds_at_once = conn->max_cmds_at_once > 0 ? + conn->max_cmds_at_once : settings_get_int("cmds_max_at_once"); + + server->max_kicks_in_cmd = conn->max_kicks > 0 ? + conn->max_kicks : DEFAULT_MAX_KICKS; + server->max_modes_in_cmd = conn->max_modes > 0 ? + conn->max_modes : DEFAULT_MAX_MODES; + server->max_whois_in_cmd = conn->max_whois > 0 ? + conn->max_whois : DEFAULT_MAX_WHOIS; + server->max_msgs_in_cmd = conn->max_msgs > 0 ? + conn->max_msgs : DEFAULT_MAX_MSGS; + + if (!server_connect((SERVER_REC *) server)) { + irc_server_connect_free(conn); + g_free(server->nick); + g_free(server); + return NULL; + } + return server; +} + +static void sig_connected(IRC_SERVER_REC *server) +{ + server->eventtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + server->eventgrouptable = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + server->cmdtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + server->splits = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + + server_init(server); +} + +static int server_remove_channels(IRC_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; + } + + for (tmp = server->queries; tmp != NULL; tmp = tmp->next) + query_change_server(tmp->data, NULL); + + g_slist_free(server->channels); + g_slist_free(server->queries); + + return found; +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + int chans; + + /* close all channels */ + chans = server_remove_channels(server); + + g_slist_foreach(server->cmdqueue, (GFunc) g_free, NULL); + g_slist_free(server->cmdqueue); + + if (server->handle != -1) { + if (!chans || server->connection_lost) + net_disconnect(server->handle); + 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(server->handle); + } + server->handle = -1; + } + + irc_server_connect_free(server->connrec); + g_free_not_null(server->real_address); + g_free_not_null(server->version); + g_free_not_null(server->usermode); + g_free_not_null(server->userhost); + g_free_not_null(server->last_invite); + g_free_not_null(server->away_reason); +} + +static void sig_connect_failed(IRC_SERVER_REC *server) +{ + server_remove_channels(server); + irc_server_connect_free(server->connrec); +} + +static void server_cmd_timeout(IRC_SERVER_REC *server, GTimeVal *now) +{ + long usecs; + char *cmd; + int len, ret, add_rawlog; + + if (server->cmdcount == 0 && server->cmdqueue == NULL) + return; + + if (!server->cmd_last_split) { + usecs = get_timeval_diff(now, &server->last_cmd); + if (usecs < server->cmd_queue_speed) + return; + } + + server->cmdcount--; + if (server->cmdqueue == NULL) return; + + /* send command */ + cmd = server->cmdqueue->data; + len = strlen(cmd); + + add_rawlog = !server->cmd_last_split; + + ret = net_transmit(server->handle, cmd, len); + if (ret != len) { + /* we didn't transmit all data, try again a bit later.. */ + if (ret > 0) { + cmd = g_strdup((char *) (server->cmdqueue->data) + ret); + g_free(server->cmdqueue->data); + server->cmdqueue->data = cmd; + } + server->cmd_last_split = TRUE; + server->cmdcount++; + } else { + memcpy(&server->last_cmd, now, sizeof(GTimeVal)); + if (server->cmd_last_split) + server->cmd_last_split = FALSE; + } + + if (add_rawlog) { + /* add to rawlog without CR+LF */ + int slen; + + slen = strlen(cmd); + cmd[slen-2] = '\0'; + rawlog_output(server->rawlog, cmd); + cmd[slen-2] = '\r'; + } + + if (ret == len) { + /* remove from queue */ + g_free(cmd); + server->cmdqueue = g_slist_remove(server->cmdqueue, cmd); + } +} + +/* check every now and then if there's data to be sent in command buffer */ +static int servers_cmd_timeout(void) +{ + GTimeVal now; + + g_get_current_time(&now); + g_slist_foreach(servers, (GFunc) server_cmd_timeout, &now); + return 1; +} + +/* Return a string of all channels (and keys, if any have them) in server, + like "#a,#b,#c,#d x,b_chan_key,x,x" or just "#e,#f,#g" */ +char *irc_server_get_channels(IRC_SERVER_REC *server) +{ + GSList *tmp; + GString *chans, *keys; + char *ret; + int use_keys; + + g_return_val_if_fail(server != NULL, FALSE); + + chans = g_string_new(NULL); + keys = g_string_new(NULL); + + use_keys = FALSE; + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + + g_string_sprintfa(chans, "%s,", channel->name); + g_string_sprintfa(keys, "%s,", channel->key == NULL ? "x" : channel->key); + if (channel->key != NULL) + use_keys = TRUE; + } + + if (chans->len > 0) { + g_string_truncate(chans, chans->len-1); + g_string_truncate(keys, keys->len-1); + if (use_keys) g_string_sprintfa(chans, " %s", keys->str); + } + + ret = chans->str; + g_string_free(chans, FALSE); + g_string_free(keys, TRUE); + + return ret; +} + +static int sig_set_user_mode(IRC_SERVER_REC *server) +{ + const char *mode; + char *newmode; + + if (g_slist_find(servers, server) == NULL) + return 0; /* got disconnected */ + + mode = settings_get_str("default_user_mode"); + newmode = modes_join(server->usermode, mode); + if (strcmp(newmode, server->usermode) != 0) + irc_send_cmdv(server, "MODE %s %s", server->nick, mode); + g_free(newmode); + return 0; +} + +static void event_connected(const char *data, IRC_SERVER_REC *server, const char *from) +{ + char *params, *nick; + const char *mode; + + g_return_if_fail(server != NULL); + + params = event_get_params(data, 1, &nick); + + if (strcmp(server->nick, nick) != 0) { + /* nick changed unexpectedly .. connected via proxy, etc. */ + g_free(server->nick); + server->nick = g_strdup(nick); + } + + if (server->real_address == NULL) { + /* set the server address */ + server->real_address = g_strdup(from); + } + + /* last welcome message found - commands can be sent to server now. */ + server->connected = 1; + + if (!server->connrec->reconnection) { + /* wait a second and then send the user mode */ + mode = settings_get_str("default_user_mode"); + if (*mode != '\0') + g_timeout_add(1000, (GSourceFunc) sig_set_user_mode, server); + } + + signal_emit("event connected", 1, server); + g_free(params); +} + +static void event_server_info(const char *data, IRC_SERVER_REC *server) +{ + char *params, *ircd_version, *usermodes, *chanmodes; + + g_return_if_fail(server != NULL); + + params = event_get_params(data, 5, NULL, NULL, &ircd_version, &usermodes, &chanmodes); + + /* check if server understands I and e channel modes */ + if (strchr(chanmodes, 'I') && strchr(chanmodes, 'e')) + server->emode_known = TRUE; + + /* save server version */ + g_free_not_null(server->version); + server->version = g_strdup(ircd_version); + + g_free(params); +} + +static void event_ping(const char *data, IRC_SERVER_REC *server) +{ + char *str; + + g_return_if_fail(data != NULL); + + str = g_strdup_printf("PONG %s", data); + irc_send_cmd_now(server, str); + g_free(str); +} + +static void event_empty(void) +{ +} + +void irc_servers_init(void) +{ + settings_add_str("misc", "default_user_mode", DEFAULT_USER_MODE); + settings_add_int("flood", "cmd_queue_speed", DEFAULT_CMD_QUEUE_SPEED); + settings_add_int("flood", "cmds_max_at_once", DEFAULT_CMDS_MAX_AT_ONCE); + + cmd_tag = g_timeout_add(500, (GSourceFunc) servers_cmd_timeout, NULL); + + signal_add_first("server connected", (SIGNAL_FUNC) sig_connected); + signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_add_last("server connect failed", (SIGNAL_FUNC) sig_connect_failed); + signal_add("event 001", (SIGNAL_FUNC) event_connected); + signal_add("event 004", (SIGNAL_FUNC) event_server_info); + signal_add("event ping", (SIGNAL_FUNC) event_ping); + signal_add("event empty", (SIGNAL_FUNC) event_empty); + + servers_setup_init(); + ircnets_setup_init(); + servers_idle_init(); + servers_reconnect_init(); +} + +void irc_servers_deinit(void) +{ + while (servers != NULL) + server_disconnect(servers->data); + while (lookup_servers != NULL) + server_disconnect(lookup_servers->data); + + g_source_remove(cmd_tag); + + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_remove("server connect failed", (SIGNAL_FUNC) sig_connect_failed); + signal_remove("event 001", (SIGNAL_FUNC) event_connected); + signal_remove("event 004", (SIGNAL_FUNC) event_server_info); + signal_remove("event ping", (SIGNAL_FUNC) event_ping); + signal_remove("event empty", (SIGNAL_FUNC) event_empty); + + servers_setup_deinit(); + ircnets_setup_deinit(); + servers_idle_deinit(); + servers_reconnect_deinit(); +} |