summaryrefslogtreecommitdiff
path: root/src/irc/core
diff options
context:
space:
mode:
authorTimo Sirainen <cras@irssi.org>2000-04-26 08:03:38 +0000
committercras <cras@dbcabf3a-b0e7-0310-adc4-f8d773084564>2000-04-26 08:03:38 +0000
commitc95034c6de1bf72536595e1e3431d8ec64b9880e (patch)
treee51aa4528257ed8aa9d53640649519f299aaf0c7 /src/irc/core
parentd01b094151705d433bc43cae9eeb304e6f110a17 (diff)
downloadirssi-c95034c6de1bf72536595e1e3431d8ec64b9880e.zip
..adding new files..
git-svn-id: http://svn.irssi.org/repos/irssi/trunk@171 dbcabf3a-b0e7-0310-adc4-f8d773084564
Diffstat (limited to 'src/irc/core')
-rw-r--r--src/irc/core/Makefile.am61
-rw-r--r--src/irc/core/bans.c218
-rw-r--r--src/irc/core/bans.h15
-rw-r--r--src/irc/core/channel-events.c241
-rw-r--r--src/irc/core/channel-events.h7
-rw-r--r--src/irc/core/channels-query.c594
-rw-r--r--src/irc/core/channels-query.h7
-rw-r--r--src/irc/core/channels-setup.c215
-rw-r--r--src/irc/core/channels-setup.h27
-rw-r--r--src/irc/core/channels.c231
-rw-r--r--src/irc/core/channels.h73
-rw-r--r--src/irc/core/ctcp.c193
-rw-r--r--src/irc/core/ctcp.h10
-rw-r--r--src/irc/core/ignore.c296
-rw-r--r--src/irc/core/ignore.h30
-rw-r--r--src/irc/core/irc-commands.c878
-rw-r--r--src/irc/core/irc-commands.h7
-rw-r--r--src/irc/core/irc-core.c63
-rw-r--r--src/irc/core/irc-core.h7
-rw-r--r--src/irc/core/irc-log.c70
-rw-r--r--src/irc/core/irc-rawlog.c77
-rw-r--r--src/irc/core/irc-rawlog.h7
-rw-r--r--src/irc/core/irc-server.c457
-rw-r--r--src/irc/core/irc-server.h146
-rw-r--r--src/irc/core/irc-special-vars.c332
-rw-r--r--src/irc/core/irc-special-vars.h7
-rw-r--r--src/irc/core/irc.c440
-rw-r--r--src/irc/core/irc.h90
-rw-r--r--src/irc/core/ircnet-setup.c116
-rw-r--r--src/irc/core/ircnet-setup.h23
-rw-r--r--src/irc/core/lag.c178
-rw-r--r--src/irc/core/lag.h7
-rw-r--r--src/irc/core/masks.c170
-rw-r--r--src/irc/core/masks.h15
-rw-r--r--src/irc/core/massjoin.c254
-rw-r--r--src/irc/core/massjoin.h7
-rw-r--r--src/irc/core/mode-lists.c234
-rw-r--r--src/irc/core/mode-lists.h24
-rw-r--r--src/irc/core/modes.c451
-rw-r--r--src/irc/core/modes.h18
-rw-r--r--src/irc/core/module.h4
-rw-r--r--src/irc/core/netsplit.c270
-rw-r--r--src/irc/core/netsplit.h27
-rw-r--r--src/irc/core/nicklist.c566
-rw-r--r--src/irc/core/nicklist.h41
-rw-r--r--src/irc/core/query.c158
-rw-r--r--src/irc/core/query.h30
-rw-r--r--src/irc/core/server-idle.c252
-rw-r--r--src/irc/core/server-idle.h24
-rw-r--r--src/irc/core/server-reconnect.c398
-rw-r--r--src/irc/core/server-reconnect.h16
-rw-r--r--src/irc/core/server-setup.c317
-rw-r--r--src/irc/core/server-setup.h40
53 files changed, 8439 insertions, 0 deletions
diff --git a/src/irc/core/Makefile.am b/src/irc/core/Makefile.am
new file mode 100644
index 00000000..6e13c75c
--- /dev/null
+++ b/src/irc/core/Makefile.am
@@ -0,0 +1,61 @@
+noinst_LTLIBRARIES = libirc_core.la
+
+INCLUDES = \
+ $(GLIB_CFLAGS) \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -I$(top_srcdir)/src -I$(top_srcdir)/src/core
+
+libirc_core_la_SOURCES = \
+ bans.c \
+ ctcp.c \
+ channels.c \
+ channels-query.c \
+ channels-setup.c \
+ channel-events.c \
+ ignore.c \
+ irc.c \
+ irc-core.c \
+ irc-commands.c \
+ irc-rawlog.c \
+ irc-server.c \
+ irc-special-vars.c \
+ ircnet-setup.c \
+ lag.c \
+ masks.c \
+ massjoin.c \
+ modes.c \
+ mode-lists.c \
+ netsplit.c \
+ nicklist.c \
+ query.c \
+ server-idle.c \
+ server-reconnect.c \
+ server-setup.c
+
+noinst_HEADERS = \
+ bans.h \
+ ctcp.h \
+ channels.h \
+ channels-query.h \
+ channels-setup.h \
+ channel-events.h \
+ ignore.h \
+ irc.h \
+ irc-core.h \
+ irc-commands.h \
+ irc-rawlog.h \
+ irc-server.h \
+ irc-special-vars.h \
+ ircnet-setup.h \
+ lag.h \
+ masks.h \
+ massjoin.h \
+ modes.h \
+ mode-lists.h \
+ module.h \
+ netsplit.h \
+ nicklist.h \
+ query.h \
+ server-idle.h \
+ server-reconnect.h \
+ server-setup.h
diff --git a/src/irc/core/bans.c b/src/irc/core/bans.c
new file mode 100644
index 00000000..c492d3da
--- /dev/null
+++ b/src/irc/core/bans.c
@@ -0,0 +1,218 @@
+/*
+ bans.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 "commands.h"
+#include "misc.h"
+#include "signals.h"
+
+#include "masks.h"
+#include "modes.h"
+#include "mode-lists.h"
+#include "irc.h"
+#include "nicklist.h"
+
+static int bantype;
+
+/* Get ban mask */
+char *ban_get_mask(CHANNEL_REC *channel, const char *nick)
+{
+ NICK_REC *rec;
+ char *str, *user, *host;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = nicklist_find(channel, nick);
+ if (rec == NULL || rec->host == NULL) return NULL;
+
+ str = irc_get_mask(nick, rec->host, bantype);
+
+ /* there's a limit of 10 characters in user mask. so, banning
+ someone with user mask of 10 characters gives us "*1234567890",
+ which is one too much.. so, replace the 10th character with '*' */
+ user = strchr(str, '!');
+ if (user == NULL) return str;
+
+ host = strchr(++user, '@');
+ if (host == NULL) return str;
+
+ if ((int) (host-user) < 10) {
+ /* too long user mask */
+ user[9] = '*';
+ g_memmove(user+10, host, strlen(host)+1);
+ }
+ return str;
+}
+
+void ban_set_type(const char *type)
+{
+ char bantypestr[5], **list;
+ int n, max;
+
+ g_return_if_fail(type != NULL);
+
+ if (toupper(type[0]) == 'N') {
+ bantype = IRC_MASK_USER | IRC_MASK_DOMAIN;
+ strcpy(bantypestr, "UD");
+ }
+ else if (toupper(type[0]) == 'H') {
+ bantype = IRC_MASK_HOST | IRC_MASK_DOMAIN;
+ strcpy(bantypestr, "HD");
+ }
+ else if (toupper(type[0]) == 'D') {
+ bantype = IRC_MASK_DOMAIN;
+ strcpy(bantypestr, "D");
+ }
+ else if (toupper(type[0]) == 'C') {
+ list = g_strsplit(type, " ", -1);
+
+ max = strarray_length(list);
+ bantype = 0;
+ for (n = 1; n < max; n++) {
+ if (toupper(list[n][0]) == 'N')
+ bantype |= IRC_MASK_NICK;
+ else if (toupper(list[n][0]) == 'U')
+ bantype |= IRC_MASK_USER;
+ else if (toupper(list[n][0]) == 'H')
+ bantype |= IRC_MASK_HOST;
+ else if (toupper(list[n][0]) == 'D')
+ bantype |= IRC_MASK_DOMAIN;
+ }
+ g_strfreev(list);
+
+ bantypestr[0] = '\0';
+ if (bantype & IRC_MASK_NICK) strcat(bantypestr, "N");
+ if (bantype & IRC_MASK_USER) strcat(bantypestr, "U");
+ if (bantype & IRC_MASK_HOST) strcat(bantypestr, "H");
+ if (bantype & IRC_MASK_DOMAIN) strcat(bantypestr, "D");
+ }
+
+ signal_emit("ban type changed", 1, bantypestr);
+}
+
+void ban_set(CHANNEL_REC *channel, const char *bans)
+{
+ GString *str;
+ char **ban, **banlist;
+
+ g_return_if_fail(bans != NULL);
+
+ str = g_string_new(NULL);
+ banlist = g_strsplit(bans, " ", -1);
+ for (ban = banlist; *ban != NULL; ban++) {
+ if (strchr(*ban, '!') != NULL) {
+ /* explicit ban */
+ g_string_sprintfa(str, " %s", *ban);
+ continue;
+ }
+
+ /* ban nick */
+ *ban = ban_get_mask(channel, *ban);
+ if (*ban != NULL) {
+ g_string_sprintfa(str, " %s", *ban);
+ g_free(*ban);
+ }
+ }
+ g_strfreev(banlist);
+
+ channel_set_singlemode(channel->server, channel->name, str->str, "+b");
+ g_string_free(str, TRUE);
+}
+
+void ban_remove(CHANNEL_REC *channel, const char *bans)
+{
+ GString *str;
+ GSList *tmp;
+ char **ban, **banlist;
+
+ str = g_string_new(NULL);
+ banlist = g_strsplit(bans, " ", -1);
+ for (ban = banlist; *ban != NULL; ban++) {
+ for (tmp = channel->banlist; tmp != NULL; tmp = tmp->next) {
+ BAN_REC *rec = tmp->data;
+
+ if (match_wildcards(*ban, rec->ban))
+ g_string_sprintfa(str, "%s ", rec->ban);
+ }
+ }
+ g_strfreev(banlist);
+
+ channel_set_singlemode(channel->server, channel->name, str->str, "-b");
+ g_string_free(str, TRUE);
+}
+
+static void command_set_ban(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item, int set)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel, *nicks;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST,
+ item, &channel, &nicks);
+ if (!ischannel(*channel)) cmd_param_error(CMDERR_NOT_JOINED);
+ if (*nicks == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ chanrec = channel_find(server, channel);
+ if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND);
+ if (!chanrec->wholist) cmd_param_error(CMDERR_CHAN_NOT_SYNCED);
+
+ if (set)
+ ban_set(chanrec, nicks);
+ else
+ ban_remove(chanrec, nicks);
+
+ g_free(params);
+}
+
+static void cmd_bantype(const char *data)
+{
+ ban_set_type(data);
+}
+
+static void cmd_ban(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ command_set_ban(data, server, item, TRUE);
+}
+
+static void cmd_unban(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ command_set_ban(data, server, item, FALSE);
+}
+
+void bans_init(void)
+{
+ /* default bantype */
+ bantype = IRC_MASK_USER | IRC_MASK_DOMAIN;
+ command_bind("bantype", NULL, (SIGNAL_FUNC) cmd_bantype);
+ command_bind("ban", NULL, (SIGNAL_FUNC) cmd_ban);
+ command_bind("unban", NULL, (SIGNAL_FUNC) cmd_unban);
+}
+
+void bans_deinit(void)
+{
+ command_unbind("bantype", (SIGNAL_FUNC) cmd_bantype);
+ command_unbind("ban", (SIGNAL_FUNC) cmd_ban);
+ command_unbind("unban", (SIGNAL_FUNC) cmd_unban);
+}
diff --git a/src/irc/core/bans.h b/src/irc/core/bans.h
new file mode 100644
index 00000000..aa6ff346
--- /dev/null
+++ b/src/irc/core/bans.h
@@ -0,0 +1,15 @@
+#ifndef __BANS_H
+#define __BANS_H
+
+#include "channels.h"
+
+void bans_init(void);
+void bans_deinit(void);
+
+char *ban_get_mask(CHANNEL_REC *channel, const char *nick);
+
+void ban_set_type(const char *type);
+void ban_set(CHANNEL_REC *channel, const char *bans);
+void ban_remove(CHANNEL_REC *channel, const char *ban);
+
+#endif
diff --git a/src/irc/core/channel-events.c b/src/irc/core/channel-events.c
new file mode 100644
index 00000000..8f11ee47
--- /dev/null
+++ b/src/irc/core/channel-events.c
@@ -0,0 +1,241 @@
+/*
+ channel-events.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 "misc.h"
+#include "channels.h"
+#include "irc.h"
+
+static void event_cannot_join(const char *data, IRC_SERVER_REC *server)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+
+ if (channel[0] == '!' && channel[1] == '!')
+ channel++; /* server didn't understand !channels */
+
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL && !chanrec->names_got) {
+ chanrec->left = TRUE;
+ channel_destroy(chanrec);
+ }
+
+ g_free(params);
+}
+
+static void event_target_unavailable(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+ if (ischannel(*channel)) {
+ /* channel is unavailable. */
+ event_cannot_join(data, server);
+ }
+
+ g_free(params);
+}
+
+static void channel_change_topic(IRC_SERVER_REC *server, const char *channel, const char *topic)
+{
+ CHANNEL_REC *chanrec;
+
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL) {
+ g_free_not_null(chanrec->topic);
+ chanrec->topic = *topic == '\0' ? NULL : g_strdup(topic);
+
+ signal_emit("channel topic changed", 1, chanrec);
+ }
+}
+static void event_topic_get(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *channel, *topic;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &channel, &topic);
+ channel_change_topic(server, channel, topic);
+ g_free(params);
+}
+
+static void event_topic(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *channel, *topic;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, &channel, &topic);
+ channel_change_topic(server, channel, topic);
+ g_free(params);
+}
+
+static void event_join(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address)
+{
+ char *params, *channel, *tmp;
+ CHANNEL_REC *chanrec;
+
+ g_return_if_fail(data != NULL);
+
+ if (g_strcasecmp(nick, server->nick) != 0) {
+ /* someone else joined channel, no need to do anything */
+ return;
+ }
+
+ if (server->userhost == NULL)
+ server->userhost = g_strdup(address);
+
+ params = event_get_params(data, 1, &channel);
+ tmp = strchr(channel, 7); /* ^G does something weird.. */
+ if (tmp != NULL) *tmp = '\0';
+
+ if (*channel == '!') {
+ /* !channels have 5 chars long identification string before
+ it's name, it's not known when /join is called so rename
+ !channel here to !ABCDEchannel */
+ char *shortchan;
+
+ shortchan = g_strdup(channel);
+ sprintf(shortchan, "!%s", channel+6);
+
+ chanrec = channel_find(server, shortchan);
+ if (chanrec != NULL) {
+ g_free(chanrec->name);
+ chanrec->name = g_strdup(channel);
+ }
+
+ g_free(shortchan);
+ }
+
+ chanrec = channel_find(server, channel);
+ if (chanrec == NULL) {
+ /* didn't get here with /join command.. */
+ chanrec = channel_create(server, channel, TRUE);
+ }
+
+ g_free(params);
+}
+
+static void event_part(const char *data, IRC_SERVER_REC *server, const char *nick)
+{
+ char *params, *channel, *reason;
+ CHANNEL_REC *chanrec;
+
+ g_return_if_fail(data != NULL);
+
+ if (g_strcasecmp(nick, server->nick) != 0) {
+ /* someone else part, no need to do anything here */
+ return;
+ }
+
+ params = event_get_params(data, 2, &channel, &reason);
+
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL) {
+ chanrec->left = TRUE;
+ channel_destroy(chanrec);
+ }
+
+ g_free(params);
+}
+
+static void event_kick(const char *data, IRC_SERVER_REC *server)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel, *nick, *reason;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, &channel, &nick, &reason);
+
+ if (g_strcasecmp(nick, server->nick) != 0) {
+ /* someone else was kicked, no need to do anything */
+ g_free(params);
+ return;
+ }
+
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL) {
+ chanrec->kicked = TRUE;
+ channel_destroy(chanrec);
+ }
+
+ g_free(params);
+}
+
+static void event_invite(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+ g_free_not_null(server->last_invite);
+ server->last_invite = g_strdup(channel);
+ g_free(params);
+}
+
+void channel_events_init(void)
+{
+ signal_add_first("event 403", (SIGNAL_FUNC) event_cannot_join); /* no such channel */
+ signal_add_first("event 405", (SIGNAL_FUNC) event_cannot_join); /* too many channels */
+ signal_add_first("event 407", (SIGNAL_FUNC) event_cannot_join); /* duplicate channel */
+ signal_add_first("event 471", (SIGNAL_FUNC) event_cannot_join); /* channel is full */
+ signal_add_first("event 473", (SIGNAL_FUNC) event_cannot_join); /* invite only */
+ signal_add_first("event 474", (SIGNAL_FUNC) event_cannot_join); /* banned */
+ signal_add_first("event 475", (SIGNAL_FUNC) event_cannot_join); /* bad channel key */
+ signal_add_first("event 476", (SIGNAL_FUNC) event_cannot_join); /* bad channel mask */
+
+ signal_add("event topic", (SIGNAL_FUNC) event_topic);
+ signal_add("event join", (SIGNAL_FUNC) event_join);
+ signal_add("event part", (SIGNAL_FUNC) event_part);
+ signal_add("event kick", (SIGNAL_FUNC) event_kick);
+ signal_add("event invite", (SIGNAL_FUNC) event_invite);
+ signal_add("event 332", (SIGNAL_FUNC) event_topic_get);
+ signal_add_first("event 437", (SIGNAL_FUNC) event_target_unavailable); /* channel/nick unavailable */
+}
+
+void channel_events_deinit(void)
+{
+ signal_remove("event 403", (SIGNAL_FUNC) event_cannot_join); /* no such channel */
+ signal_remove("event 405", (SIGNAL_FUNC) event_cannot_join); /* too many channels */
+ signal_remove("event 407", (SIGNAL_FUNC) event_cannot_join); /* duplicate channel */
+ signal_remove("event 471", (SIGNAL_FUNC) event_cannot_join); /* channel is full */
+ signal_remove("event 473", (SIGNAL_FUNC) event_cannot_join); /* invite only */
+ signal_remove("event 474", (SIGNAL_FUNC) event_cannot_join); /* banned */
+ signal_remove("event 475", (SIGNAL_FUNC) event_cannot_join); /* bad channel key */
+ signal_remove("event 476", (SIGNAL_FUNC) event_cannot_join); /* bad channel mask */
+
+ signal_remove("event topic", (SIGNAL_FUNC) event_topic);
+ signal_remove("event join", (SIGNAL_FUNC) event_join);
+ signal_remove("event part", (SIGNAL_FUNC) event_part);
+ signal_remove("event kick", (SIGNAL_FUNC) event_kick);
+ signal_remove("event invite", (SIGNAL_FUNC) event_invite);
+ signal_remove("event 332", (SIGNAL_FUNC) event_topic_get);
+ signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable); /* channel/nick unavailable */
+}
diff --git a/src/irc/core/channel-events.h b/src/irc/core/channel-events.h
new file mode 100644
index 00000000..f1fe69ba
--- /dev/null
+++ b/src/irc/core/channel-events.h
@@ -0,0 +1,7 @@
+#ifndef __CHANNEL_EVENTS_H
+#define __CHANNEL_EVENTS_H
+
+void channel_events_init(void);
+void channel_events_deinit(void);
+
+#endif
diff --git a/src/irc/core/channels-query.c b/src/irc/core/channels-query.c
new file mode 100644
index 00000000..c6d4f51e
--- /dev/null
+++ b/src/irc/core/channels-query.c
@@ -0,0 +1,594 @@
+/*
+ channels-query.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
+*/
+
+/*
+
+ How the thing works:
+
+ - After channel is joined and NAMES list is got, send "channel query" signal
+ - "channel query" : add channel to server->quries lists
+
+loop:
+ - Wait for NAMES list from all channels before doing anything else..
+ - After got the last NAMES list, start sending the queries ..
+ - find the query to send, check where server->queries list isn't NULL
+ (mode, who, banlist, ban exceptions, invite list)
+ - if not found anything -> all channels are synced
+ - send "command #chan1,#chan2,#chan3,.." command to server
+ - wait for reply from server, then check if it was last query to be sent to
+ server. If it was, send "channel sync" signal
+ - check if the reply was for last channel in the command list. If so,
+ goto loop
+*/
+
+#include "module.h"
+#include "modules.h"
+#include "misc.h"
+#include "signals.h"
+
+#include "channels.h"
+#include "irc.h"
+#include "modes.h"
+#include "mode-lists.h"
+#include "nicklist.h"
+#include "irc-server.h"
+#include "server-redirect.h"
+
+enum {
+ CHANNEL_QUERY_MODE,
+ CHANNEL_QUERY_WHO,
+ CHANNEL_QUERY_BMODE,
+ CHANNEL_QUERY_EMODE,
+ CHANNEL_QUERY_IMODE,
+
+ CHANNEL_QUERIES
+};
+
+#define CHANNEL_IS_MODE_QUERY(a) ((a) != CHANNEL_QUERY_WHO)
+
+typedef struct {
+ char *last_query_chan;
+ GSList *queries[CHANNEL_QUERIES];
+} SERVER_QUERY_REC;
+
+static void sig_connected(IRC_SERVER_REC *server)
+{
+ SERVER_QUERY_REC *rec;
+
+ g_return_if_fail(server != NULL);
+ if (!irc_server_check(server)) return;
+
+ rec = g_new0(SERVER_QUERY_REC, 1);
+ server->chanqueries = rec;
+}
+
+static void sig_disconnected(IRC_SERVER_REC *server)
+{
+ SERVER_QUERY_REC *rec;
+ int n;
+
+ g_return_if_fail(server != NULL);
+ if (!irc_server_check(server)) return;
+
+ rec = server->chanqueries;
+ g_return_if_fail(rec != NULL);
+
+ for (n = 0; n < CHANNEL_QUERIES; n++)
+ g_slist_free(rec->queries[n]);
+ g_free_not_null(rec->last_query_chan);
+ g_free(rec);
+}
+
+/* Add channel to query list */
+static void channel_query_add(CHANNEL_REC *channel, int query)
+{
+ SERVER_QUERY_REC *rec;
+
+ g_return_if_fail(channel != NULL);
+
+ rec = channel->server->chanqueries;
+ g_return_if_fail(rec != NULL);
+
+ rec->queries[query] = g_slist_append(rec->queries[query], channel);
+}
+
+static void channel_query_remove_all(CHANNEL_REC *channel)
+{
+ SERVER_QUERY_REC *rec;
+ int n;
+
+ rec = channel->server->chanqueries;
+ g_return_if_fail(rec != NULL);
+
+ /* remove channel from query lists */
+ for (n = 0; n < CHANNEL_QUERIES; n++)
+ rec->queries[n] = g_slist_remove(rec->queries[n], channel);
+}
+
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ if (channel->server != NULL && !channel->synced)
+ channel_query_remove_all(channel);
+}
+
+static int channels_have_all_names(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec = tmp->data;
+
+ if (!rec->names_got)
+ return 0;
+ }
+
+ return 1;
+}
+
+static int find_next_query(SERVER_QUERY_REC *server)
+{
+ int n;
+
+ for (n = 0; n < CHANNEL_QUERIES; n++) {
+ if (server->queries[n] != NULL)
+ return n;
+ }
+
+ return -1;
+}
+
+static void channel_send_query(IRC_SERVER_REC *server, int query)
+{
+ SERVER_QUERY_REC *rec;
+ CHANNEL_REC *chanrec;
+ GSList *tmp, *chans;
+ char *cmd, *chanstr_commas, *chanstr;
+ int onlyone;
+
+ rec = server->chanqueries;
+ g_return_if_fail(rec != NULL);
+
+ onlyone = (server->no_multi_who && query == CHANNEL_QUERY_WHO) ||
+ (server->no_multi_mode && CHANNEL_IS_MODE_QUERY(query));
+
+ if (onlyone) {
+ chanrec = rec->queries[query]->data;
+ chans = g_slist_append(NULL, chanrec);
+ chanstr_commas = g_strdup(chanrec->name);
+ chanstr = g_strdup(chanrec->name);
+ } else {
+ char *chanstr_spaces;
+
+ chans = rec->queries[query];
+
+ chanstr_commas = gslist_to_string(rec->queries[query], G_STRUCT_OFFSET(CHANNEL_REC, name), ",");
+ chanstr_spaces = gslist_to_string(rec->queries[query], G_STRUCT_OFFSET(CHANNEL_REC, name), " ");
+
+ chanstr = g_strconcat(chanstr_commas, " ", chanstr_spaces, NULL);
+ g_free(chanstr_spaces);
+ }
+
+ switch (query) {
+ case CHANNEL_QUERY_MODE:
+ cmd = g_strdup_printf("MODE %s", chanstr_commas);
+ for (tmp = chans; tmp != NULL; tmp = tmp->next) {
+ chanrec = tmp->data;
+
+ server_redirect_event((SERVER_REC *) server, chanstr, 3,
+ "event 403", "chanquery mode abort", 1,
+ "event 442", "chanquery mode abort", 1, /* "you're not on that channel" */
+ "event 324", "chanquery mode", 1, NULL);
+ }
+ break;
+
+ case CHANNEL_QUERY_WHO:
+ cmd = g_strdup_printf("WHO %s", chanstr_commas);
+
+ for (tmp = chans; tmp != NULL; tmp = tmp->next) {
+ chanrec = tmp->data;
+
+ server_redirect_event((SERVER_REC *) server, chanstr, 2,
+ "event 401", "chanquery who abort", 1,
+ "event 315", "chanquery who end", 1,
+ "event 352", "silent event who", 1, NULL);
+ }
+ break;
+
+ case CHANNEL_QUERY_BMODE:
+ cmd = g_strdup_printf("MODE %s b", chanstr_commas);
+ for (tmp = chans; tmp != NULL; tmp = tmp->next) {
+ chanrec = tmp->data;
+
+ server_redirect_event((SERVER_REC *) server, chanrec->name, 2,
+ "event 403", "chanquery mode abort", 1,
+ "event 368", "chanquery ban end", 1,
+ "event 367", "chanquery ban", 1, NULL);
+ }
+ break;
+
+ case CHANNEL_QUERY_EMODE:
+ cmd = g_strdup_printf("MODE %s e", chanstr_commas);
+ for (tmp = chans; tmp != NULL; tmp = tmp->next) {
+ chanrec = tmp->data;
+
+ server_redirect_event((SERVER_REC *) server, chanrec->name, 4,
+ "event 403", "chanquery mode abort", 1,
+ "event 349", "chanquery eban end", 1,
+ "event 348", "chanquery eban", 1, NULL);
+ }
+ break;
+
+ case CHANNEL_QUERY_IMODE:
+ cmd = g_strdup_printf("MODE %s I", chanstr_commas);
+ for (tmp = chans; tmp != NULL; tmp = tmp->next) {
+ chanrec = tmp->data;
+
+ server_redirect_event((SERVER_REC *) server, chanrec->name, 4,
+ "event 403", "chanquery mode abort", 1,
+ "event 347", "chanquery ilist end", 1,
+ "event 346", "chanquery ilist", 1, NULL);
+ }
+ break;
+
+ default:
+ cmd = NULL;
+ }
+
+ g_free(chanstr);
+ g_free(chanstr_commas);
+
+ /* Get the channel of last query */
+ chanrec = g_slist_last(chans)->data;
+ rec->last_query_chan = g_strdup(chanrec->name);
+
+ if (!onlyone) {
+ /* all channels queried, set to NULL */
+ g_slist_free(rec->queries[query]);
+ rec->queries[query] = NULL;
+ } else {
+ /* remove the first channel from list */
+ rec->queries[query] = g_slist_remove(rec->queries[query], chans->data);
+ }
+
+ /* send the command */
+ irc_send_cmd(server, cmd);
+ g_free(cmd);
+}
+
+static void channels_query_check(IRC_SERVER_REC *server)
+{
+ SERVER_QUERY_REC *rec;
+ int query;
+
+ g_return_if_fail(server != NULL);
+
+ rec = server->chanqueries;
+ g_return_if_fail(rec != NULL);
+
+ g_free_and_null(rec->last_query_chan);
+ if (!channels_have_all_names(server)) {
+ /* all channels haven't sent /NAMES list yet */
+ return;
+ }
+
+ query = find_next_query(rec);
+ if (query == -1) {
+ /* no queries left */
+ return;
+ }
+
+ channel_send_query(server, query);
+}
+
+static void sig_channel_query(CHANNEL_REC *channel)
+{
+ SERVER_QUERY_REC *rec;
+
+ g_return_if_fail(channel != NULL);
+
+ /* Add channel to query lists */
+ if (!channel->no_modes)
+ channel_query_add(channel, CHANNEL_QUERY_MODE);
+ channel_query_add(channel, CHANNEL_QUERY_WHO);
+ if (!channel->no_modes) {
+ channel_query_add(channel, CHANNEL_QUERY_BMODE);
+ if (channel->server->emode_known) {
+ channel_query_add(channel, CHANNEL_QUERY_EMODE);
+ channel_query_add(channel, CHANNEL_QUERY_IMODE);
+ }
+ }
+
+ rec = channel->server->chanqueries;
+ if (rec->last_query_chan == NULL)
+ channels_query_check(channel->server);
+}
+
+/* if there's no more queries in queries in buffer, send the sync signal */
+static void channel_checksync(CHANNEL_REC *channel)
+{
+ SERVER_QUERY_REC *rec;
+ int n;
+
+ g_return_if_fail(channel != NULL);
+
+ if (channel->synced)
+ return; /* already synced */
+
+ rec = channel->server->chanqueries;
+ g_return_if_fail(rec != NULL);
+
+ for (n = 0; n < CHANNEL_QUERIES; n++) {
+ if (g_slist_find(rec->queries[n], channel))
+ return;
+ }
+
+ channel->synced = TRUE;
+ signal_emit("channel sync", 1, channel);
+}
+
+static void channel_got_query(IRC_SERVER_REC *server, CHANNEL_REC *chanrec, const char *channel)
+{
+ SERVER_QUERY_REC *rec;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(channel != NULL);
+
+ rec = server->chanqueries;
+ g_return_if_fail(rec != NULL);
+ g_return_if_fail(rec->last_query_chan != NULL);
+
+ /* check if we need to get another query.. */
+ if (g_strcasecmp(rec->last_query_chan, channel) == 0)
+ channels_query_check(server);
+
+ /* check if channel is synced */
+ if (chanrec != NULL) channel_checksync(chanrec);
+}
+
+static void event_channel_mode(char *data, IRC_SERVER_REC *server, const char *nick)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel, *mode;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3 | PARAM_FLAG_GETREST, NULL, &channel, &mode);
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL)
+ parse_channel_modes(chanrec, nick, mode);
+ channel_got_query(server, chanrec, channel);
+
+ g_free(params);
+}
+
+static void multi_query_remove(IRC_SERVER_REC *server, const char *event, const char *data)
+{
+ GSList *queue;
+
+ while ((queue = server_redirect_getqueue((SERVER_REC *) server, event, data)) != NULL)
+ server_redirect_remove_next((SERVER_REC *) server, event, queue);
+}
+
+static void event_end_of_who(const char *data, IRC_SERVER_REC *server)
+{
+ CHANNEL_REC *chanrec;
+ NICK_REC *nick;
+ char *params, *channel, **chans;
+ int n, onewho;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+
+ onewho = strchr(channel, ',') != NULL;
+ if (onewho) {
+ /* instead of multiple End of WHO replies we get
+ only this one... */
+ server->one_endofwho = TRUE;
+ multi_query_remove(server, "event 315", data);
+
+ /* check that the WHO actually did return something
+ (that it understood #chan1,#chan2,..) */
+ chanrec = channel_find(server, channel);
+ nick = nicklist_find(chanrec, server->nick);
+ if (nick->host == NULL)
+ server->no_multi_who = TRUE;
+ }
+
+ chans = g_strsplit(channel, ",", -1);
+ for (n = 0; chans[n] != NULL; n++) {
+ chanrec = channel_find(server, chans[n]);
+ if (chanrec == NULL)
+ continue;
+
+ if (onewho && server->no_multi_who) {
+ channel_query_add(chanrec, CHANNEL_QUERY_WHO);
+ continue;
+ }
+
+ chanrec->wholist = TRUE;
+ signal_emit("channel wholist", 1, chanrec);
+
+ /* check if we need can send another query */
+ channel_got_query(server, chanrec, chans[n]);
+ }
+
+ g_strfreev(chans);
+ g_free(params);
+
+ if (onewho && server->no_multi_who) {
+ /* server didn't understand multiple WHO replies,
+ send them again separately */
+ channels_query_check(server);
+ }
+}
+
+static void event_end_of_banlist(const char *data, IRC_SERVER_REC *server)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+ chanrec = channel_find(server, channel);
+
+ channel_got_query(server, chanrec, channel);
+
+ g_free(params);
+}
+
+static void event_end_of_ebanlist(const char *data, IRC_SERVER_REC *server)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+ chanrec = channel_find(server, channel);
+
+ channel_got_query(server, chanrec, channel);
+
+ g_free(params);
+}
+
+static void event_end_of_invitelist(const char *data, IRC_SERVER_REC *server)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+ chanrec = channel_find(server, channel);
+
+ channel_got_query(server, chanrec, channel);
+
+ g_free(params);
+}
+
+static void channel_lost(IRC_SERVER_REC *server, const char *channel)
+{
+ CHANNEL_REC *chanrec;
+
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL) {
+ /* channel not found - probably created a new channel
+ and left it immediately. */
+ channel_query_remove_all(chanrec);
+ }
+
+ channel_got_query(server, chanrec, channel);
+}
+
+static void multi_command_error(IRC_SERVER_REC *server, const char *data, int query, const char *event)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel, **chans;
+ int n;
+
+ multi_query_remove(server, event, data);
+
+ params = event_get_params(data, 2, NULL, &channel);
+
+ chans = g_strsplit(channel, ",", -1);
+ for (n = 0; chans[n] != NULL; n++)
+ {
+ chanrec = channel_find(server, chans[n]);
+ if (chanrec != NULL)
+ channel_query_add(chanrec, query);
+ }
+ g_strfreev(chans);
+ g_free(params);
+
+ channels_query_check(server);
+}
+
+static void event_mode_abort(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+ params = event_get_params(data, 2, NULL, &channel);
+
+ if (strchr(channel, ',') == NULL) {
+ channel_lost(server, channel);
+ } else {
+ server->no_multi_mode = TRUE;
+ multi_command_error(server, data, CHANNEL_QUERY_MODE, "event 324");
+ }
+
+ g_free(params);
+}
+
+static void event_who_abort(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+ params = event_get_params(data, 2, NULL, &channel);
+
+ if (strchr(channel, ',') == NULL) {
+ channel_lost(server, channel);
+ } else {
+ server->no_multi_who = TRUE;
+ multi_command_error(server, data, CHANNEL_QUERY_WHO, "event 315");
+ }
+
+ g_free(params);
+}
+
+void channels_query_init(void)
+{
+ signal_add("server connected", (SIGNAL_FUNC) sig_connected);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+ signal_add("channel query", (SIGNAL_FUNC) sig_channel_query);
+ signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+
+ signal_add("chanquery mode", (SIGNAL_FUNC) event_channel_mode);
+ signal_add("chanquery who end", (SIGNAL_FUNC) event_end_of_who);
+
+ signal_add("chanquery eban end", (SIGNAL_FUNC) event_end_of_ebanlist);
+ signal_add("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist);
+ signal_add("chanquery ilist end", (SIGNAL_FUNC) event_end_of_invitelist);
+ signal_add("chanquery mode abort", (SIGNAL_FUNC) event_mode_abort);
+ signal_add("chanquery who abort", (SIGNAL_FUNC) event_who_abort);
+}
+
+void channels_query_deinit(void)
+{
+ signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+ signal_remove("channel query", (SIGNAL_FUNC) sig_channel_query);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+
+ signal_remove("chanquery mode", (SIGNAL_FUNC) event_channel_mode);
+ signal_remove("chanquery who end", (SIGNAL_FUNC) event_end_of_who);
+
+ signal_remove("chanquery eban end", (SIGNAL_FUNC) event_end_of_ebanlist);
+ signal_remove("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist);
+ signal_remove("chanquery ilist end", (SIGNAL_FUNC) event_end_of_invitelist);
+ signal_remove("chanquery mode abort", (SIGNAL_FUNC) event_mode_abort);
+ signal_remove("chanquery who abort", (SIGNAL_FUNC) event_who_abort);
+}
diff --git a/src/irc/core/channels-query.h b/src/irc/core/channels-query.h
new file mode 100644
index 00000000..2498afb7
--- /dev/null
+++ b/src/irc/core/channels-query.h
@@ -0,0 +1,7 @@
+#ifndef __CHANNELS_QUERY
+#define __CHANNELS_QUERY
+
+void channels_query_init(void);
+void channels_query_deinit(void);
+
+#endif
diff --git a/src/irc/core/channels-setup.c b/src/irc/core/channels-setup.c
new file mode 100644
index 00000000..c8400d4e
--- /dev/null
+++ b/src/irc/core/channels-setup.c
@@ -0,0 +1,215 @@
+/*
+ channels-setup.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 "channels.h"
+#include "channels-setup.h"
+#include "nicklist.h"
+#include "irc-server.h"
+#include "server-setup.h"
+
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+GSList *setupchannels;
+
+#define ircnet_match(a, b) \
+ ((a[0]) == '\0' || (b != NULL && g_strcasecmp(a, b) == 0))
+
+SETUP_CHANNEL_REC *channels_setup_find(const char *channel, IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+ g_return_val_if_fail(server != NULL, NULL);
+
+ for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+ SETUP_CHANNEL_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, channel) == 0 &&
+ ircnet_match(rec->ircnet, server->connrec->ircnet))
+ return rec;
+ }
+
+ return NULL;
+}
+
+void channels_setup_destroy(SETUP_CHANNEL_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ g_free(channel->name);
+ g_free(channel->ircnet);
+ g_free_not_null(channel->password);
+ g_free_not_null(channel->botmasks);
+ g_free_not_null(channel->autosendcmd);
+ g_free_not_null(channel->background);
+ g_free_not_null(channel->font);
+ g_free(channel);
+
+ setupchannels = g_slist_remove(setupchannels, channel);
+}
+
+/* connected to server, autojoin to channels. */
+static void event_connected(IRC_SERVER_REC *server)
+{
+ GString *chans, *keys;
+ GSList *tmp;
+ int use_keys;
+
+ g_return_if_fail(server != NULL);
+
+ if (server->connrec->reconnection)
+ return;
+
+ /* join to the channels marked with autojoin in setup */
+ chans = g_string_new(NULL);
+ keys = g_string_new(NULL);
+
+ use_keys = FALSE;
+ for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+ SETUP_CHANNEL_REC *rec = tmp->data;
+
+ if (!rec->autojoin || !ircnet_match(rec->ircnet, server->connrec->ircnet))
+ continue;
+
+ g_string_sprintfa(chans, "%s,", rec->name);
+ g_string_sprintfa(keys, "%s,", rec->password == NULL ? "x" : rec->password);
+ if (rec->password != 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);
+
+ channels_join(server, chans->str, TRUE);
+ }
+
+ g_string_free(chans, TRUE);
+ g_string_free(keys, TRUE);
+}
+
+/* channel wholist received: send the auto send command */
+static void channel_wholist(CHANNEL_REC *channel)
+{
+ SETUP_CHANNEL_REC *rec;
+ NICK_REC *nick;
+ char **bots, **bot, *str;
+
+ g_return_if_fail(channel != NULL);
+
+ rec = channels_setup_find(channel->name, channel->server);
+ if (rec == NULL || rec->autosendcmd == NULL || !*rec->autosendcmd)
+ return;
+
+ if (rec->botmasks == NULL || !*rec->botmasks) {
+ /* just send the command. */
+ signal_emit("send command", 3, rec->autosendcmd, channel->server, channel);
+ }
+
+ /* find first available bot.. */
+ bots = g_strsplit(rec->botmasks, " ", -1);
+ for (bot = bots; *bot != NULL; bot++) {
+ nick = nicklist_find(channel, *bot);
+ if (nick == NULL)
+ continue;
+
+ /* got one! */
+ str = g_strdup_printf(rec->autosendcmd, nick->nick);
+ signal_emit("send command", 3, str, channel->server, channel);
+ g_free(str);
+ break;
+ }
+ g_strfreev(bots);
+}
+
+static SETUP_CHANNEL_REC *setupchannel_add(CONFIG_NODE *node)
+{
+ SETUP_CHANNEL_REC *rec;
+ char *channel, *ircnet, *password, *botmasks, *autosendcmd, *background, *font;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ channel = config_node_get_str(node, "name", NULL);
+ ircnet = config_node_get_str(node, "ircnet", NULL);
+ if (channel == NULL || ircnet == NULL) {
+ /* missing information.. */
+ return NULL;
+ }
+
+ password = config_node_get_str(node, "password", NULL);
+ botmasks = config_node_get_str(node, "botmasks", NULL);
+ autosendcmd = config_node_get_str(node, "autosendcmd", NULL);
+ background = config_node_get_str(node, "background", NULL);
+ font = config_node_get_str(node, "font", NULL);
+
+ rec = g_new(SETUP_CHANNEL_REC, 1);
+ rec->autojoin = config_node_get_bool(node, "autojoin", FALSE);
+ rec->name = g_strdup(channel);
+ rec->ircnet = g_strdup(ircnet);
+ rec->password = (password == NULL || *password == '\0') ? NULL : g_strdup(password);
+ rec->botmasks = (botmasks == NULL || *botmasks == '\0') ? NULL : g_strdup(botmasks);
+ rec->autosendcmd = (autosendcmd == NULL || *autosendcmd == '\0') ? NULL : g_strdup(autosendcmd);
+ rec->background = (background == NULL || *background == '\0') ? NULL : g_strdup(background);
+ rec->font = (font == NULL || *font == '\0') ? NULL : g_strdup(font);
+
+ setupchannels = g_slist_append(setupchannels, rec);
+ return rec;
+}
+
+static void channels_read_config(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (setupchannels != NULL)
+ channels_setup_destroy(setupchannels->data);
+
+ /* Read channels */
+ node = iconfig_node_traverse("channels", FALSE);
+ if (node != NULL) {
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next)
+ setupchannel_add(tmp->data);
+ }
+}
+
+void channels_setup_init(void)
+{
+ source_host_ok = FALSE;
+
+ channels_read_config();
+ signal_add("event connected", (SIGNAL_FUNC) event_connected);
+ signal_add("channel wholist", (SIGNAL_FUNC) channel_wholist);
+ signal_add("setup reread", (SIGNAL_FUNC) channels_read_config);
+}
+
+void channels_setup_deinit(void)
+{
+ while (setupchannels != NULL)
+ channels_setup_destroy(setupchannels->data);
+
+ signal_remove("event connected", (SIGNAL_FUNC) event_connected);
+ signal_remove("channel wholist", (SIGNAL_FUNC) channel_wholist);
+ signal_remove("setup reread", (SIGNAL_FUNC) channels_read_config);
+}
diff --git a/src/irc/core/channels-setup.h b/src/irc/core/channels-setup.h
new file mode 100644
index 00000000..0556019a
--- /dev/null
+++ b/src/irc/core/channels-setup.h
@@ -0,0 +1,27 @@
+#ifndef __CHANNELS_SETUP_H
+#define __CHANNELS_SETUP_H
+
+typedef struct {
+ int autojoin;
+
+ char *name;
+ char *ircnet;
+ char *password;
+
+ char *botmasks;
+ char *autosendcmd;
+
+ char *background;
+ char *font;
+} SETUP_CHANNEL_REC;
+
+extern GSList *setupchannels;
+
+void channels_setup_init(void);
+void channels_setup_deinit(void);
+
+void channels_setup_destroy(SETUP_CHANNEL_REC *channel);
+
+SETUP_CHANNEL_REC *channels_setup_find(const char *channel, IRC_SERVER_REC *server);
+
+#endif
diff --git a/src/irc/core/channels.c b/src/irc/core/channels.c
new file mode 100644
index 00000000..7f835af3
--- /dev/null
+++ b/src/irc/core/channels.c
@@ -0,0 +1,231 @@
+/*
+ channels.c : irssi
+
+ Copyright (C) 1999 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 "modules.h"
+#include "misc.h"
+
+#include "bans.h"
+#include "channels.h"
+#include "channel-events.h"
+#include "channels-query.h"
+#include "channels-setup.h"
+#include "irc.h"
+#include "modes.h"
+#include "levels.h"
+#include "mode-lists.h"
+#include "massjoin.h"
+#include "nicklist.h"
+
+GSList *channels; /* List of all channels */
+
+CHANNEL_REC *channel_create(IRC_SERVER_REC *server, const char *channel, int automatic)
+{
+ CHANNEL_REC *rec;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+
+ rec = g_new0(CHANNEL_REC, 1);
+ channels = g_slist_append(channels, rec);
+ if (server != NULL)
+ server->channels = g_slist_append(server->channels, rec);
+
+ MODULE_DATA_INIT(rec);
+ rec->type = module_get_uniq_id("IRC", WI_IRC_CHANNEL);
+ rec->name = g_strdup(channel);
+ rec->server = server;
+ rec->createtime = time(NULL);
+
+ if (*channel == '+')
+ rec->no_modes = TRUE;
+
+ signal_emit("channel created", 2, rec, GINT_TO_POINTER(automatic));
+
+ return rec;
+}
+
+void channel_destroy(CHANNEL_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ if (channel->destroying) return;
+ channel->destroying = TRUE;
+
+ channels = g_slist_remove(channels, channel);
+ if (channel->server != NULL)
+ channel->server->channels = g_slist_remove(channel->server->channels, channel);
+ signal_emit("channel destroyed", 1, channel);
+
+ if (channel->server != NULL && !channel->left && !channel->kicked) {
+ /* destroying channel record without actually left the channel yet */
+ irc_send_cmdv(channel->server, "PART %s", channel->name);
+ }
+
+ MODULE_DATA_DEINIT(channel);
+ g_free_not_null(channel->topic);
+ g_free_not_null(channel->key);
+ g_free(channel->name);
+ g_free(channel);
+}
+
+static CHANNEL_REC *channel_find_server(IRC_SERVER_REC *server, const char *channel)
+{
+ GSList *tmp;
+
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec = tmp->data;
+
+ if (g_strcasecmp(channel, rec->name) == 0)
+ return rec;
+
+ /* check after removing ABCDE from !ABCDEchannel */
+ if (*channel == '!' && *rec->name == '!' &&
+ g_strcasecmp(channel+1, rec->name+6) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+CHANNEL_REC *channel_find(IRC_SERVER_REC *server, const char *channel)
+{
+ g_return_val_if_fail(channel != NULL, NULL);
+
+ if (server != NULL)
+ return channel_find_server(server, channel);
+
+ /* find from any server */
+ return gslist_foreach_find(servers, (FOREACH_FIND_FUNC) channel_find_server, (void *) channel);
+}
+
+
+char *channel_get_mode(CHANNEL_REC *channel)
+{
+ GString *mode;
+ char *ret;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+
+ mode = g_string_new(NULL);
+
+ if (channel->mode_secret) g_string_append_c(mode, 's');
+ if (channel->mode_private) g_string_append_c(mode, 'p');
+ if (channel->mode_moderate) g_string_append_c(mode, 'm');
+ if (channel->mode_invite) g_string_append_c(mode, 'i');
+ if (channel->mode_nomsgs) g_string_append_c(mode, 'n');
+ if (channel->mode_optopic) g_string_append_c(mode, 't');
+ if (channel->mode_anonymous) g_string_append_c(mode, 'a');
+ if (channel->mode_reop) g_string_append_c(mode, 'r');
+ if (channel->mode_key) g_string_append_c(mode, 'k');
+ if (channel->limit > 0) g_string_append_c(mode, 'l');
+
+ if (channel->mode_key) g_string_sprintfa(mode, " %s", channel->key);
+ if (channel->limit > 0) g_string_sprintfa(mode, " %d", channel->limit);
+
+ ret = mode->str;
+ g_string_free(mode, FALSE);
+ return ret;
+}
+
+#define get_join_key(key) \
+ (((key) == NULL || *(key) == '\0') ? "x" : (key))
+
+void channels_join(IRC_SERVER_REC *server, const char *data, int automatic)
+{
+ CHANNEL_REC *chanrec;
+ GString *outchans, *outkeys;
+ char *params, *channels, *keys;
+ char **chanlist, **keylist, **tmp, **tmpkey, *channel;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &channels, &keys);
+ if (*channels == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ chanlist = g_strsplit(channels, ",", -1);
+ keylist = g_strsplit(keys, ",", -1);
+
+ outchans = g_string_new(NULL);
+ outkeys = g_string_new(NULL);
+
+ tmpkey = keylist;
+ for (tmp = chanlist; *tmp != NULL; tmp++) {
+ channel = ischannel(**tmp) ? g_strdup(*tmp) :
+ g_strdup_printf("#%s", *tmp);
+
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL) {
+ /* already joined this channel */
+ signal_emit("gui channel open", 1, chanrec);
+ } else {
+ g_string_sprintfa(outchans, "%s,", channel);
+ if (*keys != '\0')
+ g_string_sprintfa(outkeys, "%s,", get_join_key(*tmpkey));
+
+ channel_create(server, channel + (channel[0] == '!' && channel[1] == '!'), automatic);
+ }
+ g_free(channel);
+
+ if (*tmpkey != NULL)
+ tmpkey++;
+ }
+
+ if (outchans->len > 0) {
+ irc_send_cmdv(server, *keys == '\0' ? "JOIN %s" : "JOIN %s %s",
+ outchans->str, outkeys->str);
+ }
+
+ g_string_free(outchans, TRUE);
+ g_string_free(outkeys, TRUE);
+
+ g_strfreev(chanlist);
+ g_strfreev(keylist);
+
+ g_free(params);
+}
+
+void channels_init(void)
+{
+ channel_events_init();
+ channels_query_init();
+ channels_setup_init();
+
+ bans_init();
+ modes_init();
+ mode_lists_init();
+ massjoin_init();
+ nicklist_init();
+}
+
+void channels_deinit(void)
+{
+ channel_events_deinit();
+ channels_query_deinit();
+ channels_setup_deinit();
+
+ bans_deinit();
+ modes_deinit();
+ mode_lists_deinit();
+ massjoin_deinit();
+ nicklist_deinit();
+}
diff --git a/src/irc/core/channels.h b/src/irc/core/channels.h
new file mode 100644
index 00000000..5cf53aa5
--- /dev/null
+++ b/src/irc/core/channels.h
@@ -0,0 +1,73 @@
+#ifndef __CHANNELS_H
+#define __CHANNELS_H
+
+#include "irc-server.h"
+
+typedef struct {
+ int type;
+ GHashTable *module_data;
+
+ IRC_SERVER_REC *server;
+ char *name;
+
+ int new_data;
+
+ time_t createtime;
+
+ GHashTable *nicks; /* list of nicks */
+ GSList *banlist; /* list of bans */
+ GSList *ebanlist; /* list of ban exceptions */
+ GSList *invitelist; /* invite list */
+
+ char *topic;
+ int limit; /* user limit */
+ char *key; /* password key */
+
+ /* channel mode */
+ int no_modes:1; /* channel doesn't support modes */
+ int mode_invite:1;
+ int mode_secret:1;
+ int mode_private:1;
+ int mode_moderate:1;
+ int mode_nomsgs:1;
+ int mode_optopic:1;
+ int mode_key:1;
+ int mode_anonymous:1;
+ int mode_reop:1;
+
+ int chanop:1; /* You're a channel operator */
+
+ int names_got:1; /* Received /NAMES list */
+ int wholist:1; /* WHO list got */
+ int synced:1; /* Channel synced - all queries done */
+
+ int left:1; /* You just left the channel */
+ int kicked:1; /* You just got kicked */
+ int destroying:1;
+
+ time_t massjoin_start; /* Massjoin start time */
+ int massjoins; /* Number of nicks waiting for massjoin signal.. */
+ int last_massjoins; /* Massjoins when last checked in timeout function */
+
+ GSList *lastmsgs; /* List of nicks who last send message */
+ GSList *lastownmsgs; /* List of nicks who last send message to you */
+} CHANNEL_REC;
+
+extern GSList *channels;
+
+void channels_init(void);
+void channels_deinit(void);
+
+/* Create new channel record */
+CHANNEL_REC *channel_create(IRC_SERVER_REC *server, const char *channel, int automatic);
+void channel_destroy(CHANNEL_REC *channel);
+
+/* find channel by name, if `server' is NULL, search from all servers */
+CHANNEL_REC *channel_find(IRC_SERVER_REC *server, const char *channel);
+
+char *channel_get_mode(CHANNEL_REC *channel);
+
+/* Join to channels. `data' contains channels and channel keys */
+void channels_join(IRC_SERVER_REC *server, const char *data, int automatic);
+
+#endif
diff --git a/src/irc/core/ctcp.c b/src/irc/core/ctcp.c
new file mode 100644
index 00000000..967c22a3
--- /dev/null
+++ b/src/irc/core/ctcp.c
@@ -0,0 +1,193 @@
+/*
+ ctcp.c : irssi
+
+ Copyright (C) 1999 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 "levels.h"
+#include "special-vars.h"
+#include "settings.h"
+#include "irssi-version.h"
+
+#include "irc.h"
+#include "irc-server.h"
+#include "server-idle.h"
+#include "ignore.h"
+
+static void ctcp_queue_clean(IRC_SERVER_REC *server)
+{
+ GSList *tmp, *next;
+
+ for (tmp = server->ctcpqueue; tmp != NULL; tmp = tmp->next) {
+ next = tmp->next;
+ if (!server_idle_find(server, GPOINTER_TO_INT(tmp->data)))
+ server->ctcpqueue = g_slist_remove(server->ctcpqueue, tmp->data);
+ }
+}
+
+/* Send CTCP reply with flood protection */
+void ctcp_send_reply(IRC_SERVER_REC *server, const char *data)
+{
+ int tag;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(data != NULL);
+
+ ctcp_queue_clean(server);
+
+ if (g_slist_length(server->ctcpqueue) < settings_get_int("max_ctcp_queue")) {
+ /* Add to first in idle queue */
+ tag = server_idle_add_first(server, data, NULL, 0, NULL);
+ server->ctcpqueue = g_slist_append(server->ctcpqueue, GINT_TO_POINTER(tag));
+ }
+}
+
+/* CTCP ping */
+static void ctcp_ping(const char *data, IRC_SERVER_REC *server, const char *nick)
+{
+ char *str;
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(nick != NULL);
+
+ str = g_strdup_printf("NOTICE %s :\001PING %s\001", nick, data);
+ ctcp_send_reply(server, str);
+ g_free(str);
+}
+
+/* CTCP version */
+static void ctcp_version(const char *data, IRC_SERVER_REC *server, const char *nick)
+{
+ char *str, *reply;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(nick != NULL);
+
+ reply = parse_special_string(settings_get_str("ctcp_version_reply"), server, NULL, "", NULL);
+ str = g_strdup_printf("NOTICE %s :\001VERSION %s\001", nick, reply);
+ ctcp_send_reply(server, str);
+ g_free(str);
+ g_free(reply);
+}
+
+static void ctcp_msg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target)
+{
+ char *args, *str;
+
+ if (ignore_check(server, nick, addr, target, data, MSGLEVEL_CTCPS))
+ return;
+
+ str = g_strconcat("ctcp msg ", data, NULL);
+ args = strchr(str+9, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+
+ g_strdown(str+9);
+ if (!signal_emit(str, 5, args, server, nick, addr, target))
+ signal_emit("default ctcp msg", 5, data, server, nick, addr, target);
+ g_free(str);
+}
+
+static void ctcp_reply(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target)
+{
+ char *args, *str;
+
+ if (ignore_check(server, nick, addr, target, data, MSGLEVEL_CTCPS))
+ return;
+
+ str = g_strconcat("ctcp reply ", data, NULL);
+ args = strchr(str+11, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+
+ g_strdown(str+11);
+ if (!signal_emit(str, 5, args, server, nick, addr, target))
+ signal_emit("default ctcp reply", 5, data, server, nick, addr, target);
+ g_free(str);
+}
+
+static void event_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr)
+{
+ char *params, *target, *msg, *ptr;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, &target, &msg);
+
+ /* handle only ctcp messages.. */
+ if (*msg == 1) {
+ /* remove the later \001 */
+ ptr = strrchr(++msg, 1);
+ if (ptr != NULL) *ptr = '\0';
+
+ signal_emit("ctcp msg", 5, msg, server, nick, addr, target);
+ }
+
+ g_free(params);
+}
+
+static void event_notice(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr)
+{
+ char *params, *target, *ptr, *msg;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, &target, &msg); /* Channel or nick name */
+
+ /* handle only ctcp replies */
+ if (*msg == 1) {
+ ptr = strrchr(++msg, 1);
+ if (ptr != NULL) *ptr = '\0';
+
+ signal_emit("ctcp reply", 5, msg, server, nick, addr, target);
+ }
+
+ g_free(params);
+}
+
+static void ctcp_deinit_server(IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ g_slist_free(server->ctcpqueue);
+}
+
+void ctcp_init(void)
+{
+ settings_add_str("misc", "ctcp_version_reply", PACKAGE" v$J - running on $sysname $sysrelease");
+ settings_add_int("flood", "max_ctcp_queue", 5);
+
+ signal_add("server disconnected", (SIGNAL_FUNC) ctcp_deinit_server);
+ signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_add("event notice", (SIGNAL_FUNC) event_notice);
+ signal_add("ctcp msg", (SIGNAL_FUNC) ctcp_msg);
+ signal_add("ctcp reply", (SIGNAL_FUNC) ctcp_reply);
+ signal_add("ctcp msg ping", (SIGNAL_FUNC) ctcp_ping);
+ signal_add("ctcp msg version", (SIGNAL_FUNC) ctcp_version);
+}
+
+void ctcp_deinit(void)
+{
+ signal_remove("server disconnected", (SIGNAL_FUNC) ctcp_deinit_server);
+ signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_remove("event notice", (SIGNAL_FUNC) event_notice);
+ signal_remove("ctcp msg", (SIGNAL_FUNC) ctcp_msg);
+ signal_remove("ctcp reply", (SIGNAL_FUNC) ctcp_reply);
+ signal_remove("ctcp msg ping", (SIGNAL_FUNC) ctcp_ping);
+ signal_remove("ctcp msg version", (SIGNAL_FUNC) ctcp_version);
+}
diff --git a/src/irc/core/ctcp.h b/src/irc/core/ctcp.h
new file mode 100644
index 00000000..cb326228
--- /dev/null
+++ b/src/irc/core/ctcp.h
@@ -0,0 +1,10 @@
+#ifndef __CTCP_H
+#define __CTCP_H
+
+void ctcp_init(void);
+void ctcp_deinit(void);
+
+/* Send CTCP reply with flood protection */
+void ctcp_send_reply(SERVER_REC *server, gchar *data);
+
+#endif
diff --git a/src/irc/core/ignore.c b/src/irc/core/ignore.c
new file mode 100644
index 00000000..ab817ae1
--- /dev/null
+++ b/src/irc/core/ignore.c
@@ -0,0 +1,296 @@
+/*
+ ignore.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 "misc.h"
+#include "levels.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "irc.h"
+#include "masks.h"
+#include "irc-server.h"
+
+#include "ignore.h"
+
+GSList *ignores;
+
+int ignore_check(IRC_SERVER_REC *server, const char *nick, const char *host,
+ const char *channel, const char *text, int level)
+{
+ GSList *tmp;
+ int ok, mask_len, patt_len;
+ int best_mask, best_patt, best_ignore;
+
+ g_return_val_if_fail(server != NULL, 0);
+
+ best_mask = 0; best_patt = 0; best_ignore = FALSE;
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if ((level & (rec->level|rec->except_level)) == 0)
+ continue;
+
+ /* server */
+ if (rec->servertag != NULL && g_strcasecmp(server->tag, rec->servertag) != 0)
+ continue;
+
+ /* nick mask */
+ mask_len = 0;
+ if (rec->mask != NULL) {
+ if (nick == NULL)
+ continue;
+
+ mask_len = strlen(rec->mask);
+ if (mask_len <= best_mask) continue;
+
+ ok = ((host == NULL || *host == '\0')) ?
+ match_wildcards(rec->mask, nick) :
+ irc_mask_match_address(rec->mask, nick, host);
+ if (!ok) continue;
+ }
+
+ /* channel list */
+ if (rec->channels != NULL) {
+ if (channel == NULL || !ischannel(*channel))
+ continue;
+ if (strarray_find(rec->channels, channel) == -1)
+ continue;
+ }
+
+ /* pattern */
+ patt_len = 0;
+ if (rec->pattern != NULL) {
+ if (!mask_len && !best_mask) {
+ patt_len = strlen(rec->pattern);
+ if (patt_len <= best_patt) continue;
+ }
+
+ ok = rec->regexp ? regexp_match(text, rec->pattern) :
+ rec->fullword ? stristr_full(text, rec->pattern) != NULL :
+ stristr(text, rec->pattern) != NULL;
+ if (!ok) continue;
+ }
+
+ if (mask_len || best_mask)
+ best_mask = mask_len;
+ else if (patt_len)
+ best_patt = patt_len;
+
+ best_ignore = (rec->level & level) != 0;
+ }
+
+ return best_ignore;
+}
+
+IGNORE_REC *ignore_find(const char *servertag, const char *mask, char **channels)
+{
+ GSList *tmp;
+ char **chan;
+ int ignore_servertag;
+
+ if (mask != NULL && *mask == '\0') mask = NULL;
+
+ ignore_servertag = servertag != NULL && strcmp(servertag, "*") == 0;
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (!ignore_servertag) {
+ if ((servertag == NULL && rec->servertag != NULL) ||
+ (servertag != NULL && rec->servertag == NULL))
+ continue;
+
+ if (servertag != NULL && g_strcasecmp(servertag, rec->servertag) != 0)
+ continue;
+ }
+
+ if ((rec->mask == NULL && mask != NULL) ||
+ (rec->mask != NULL && mask == NULL)) continue;
+
+ if (rec->mask != NULL && g_strcasecmp(rec->mask, mask) != 0)
+ continue;
+
+ if ((channels == NULL && rec->channels == NULL))
+ return rec; /* no channels - ok */
+
+ if (channels != NULL && strcmp(*channels, "*") == 0)
+ return rec; /* ignore channels */
+
+ if (channels == NULL || rec->channels == NULL)
+ continue; /* other doesn't have channels */
+
+ if (strarray_length(channels) != strarray_length(rec->channels))
+ continue; /* different amount of channels */
+
+ /* check that channels match */
+ for (chan = channels; *chan != NULL; chan++) {
+ if (strarray_find(rec->channels, *chan) == -1)
+ break;
+ }
+
+ if (*chan == NULL)
+ return rec; /* channels ok */
+ }
+
+ return NULL;
+}
+
+static void ignore_set_config(IGNORE_REC *rec)
+{
+ CONFIG_NODE *node;
+ char *levelstr;
+
+ if (rec->level == 0 && rec->except_level == 0)
+ return;
+
+ node = iconfig_node_traverse("(ignores", TRUE);
+ node = config_node_section(node, NULL, NODE_TYPE_BLOCK);
+
+ if (rec->mask != NULL) config_node_set_str(node, "mask", rec->mask);
+ if (rec->level) {
+ levelstr = bits2level(rec->level);
+ config_node_set_str(node, "level", levelstr);
+ g_free(levelstr);
+ }
+ if (rec->except_level) {
+ levelstr = bits2level(rec->except_level);
+ config_node_set_str(node, "except_level", levelstr);
+ g_free(levelstr);
+ }
+ config_node_set_str(node, "pattern", rec->pattern);
+ if (rec->regexp) config_node_set_bool(node, "regexp", TRUE);
+ if (rec->fullword) config_node_set_bool(node, "fullword", TRUE);
+
+ if (rec->channels != NULL && *rec->channels != NULL) {
+ node = config_node_section(node, "channels", NODE_TYPE_LIST);
+ config_node_add_list(node, rec->channels);
+ }
+}
+
+static int ignore_index(IGNORE_REC *find)
+{
+ GSList *tmp;
+ int index;
+
+ index = 0;
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (rec->servertag != NULL)
+ continue;
+
+ if (rec == find)
+ return index;
+ index++;
+ }
+
+ return -1;
+}
+
+static void ignore_remove_config(IGNORE_REC *rec)
+{
+ CONFIG_NODE *node;
+
+ node = iconfig_node_traverse("ignores", FALSE);
+ if (node != NULL) config_node_list_remove(node, ignore_index(rec));
+}
+
+void ignore_add_rec(IGNORE_REC *rec)
+{
+ ignores = g_slist_append(ignores, rec);
+ ignore_set_config(rec);
+}
+
+static void ignore_destroy(IGNORE_REC *rec)
+{
+ ignores = g_slist_remove(ignores, rec);
+
+ if (rec->channels != NULL) g_strfreev(rec->channels);
+ g_free_not_null(rec->mask);
+ g_free_not_null(rec->servertag);
+ g_free_not_null(rec->pattern);
+ g_free(rec);
+}
+
+void ignore_update_rec(IGNORE_REC *rec)
+{
+ if (rec->level == 0 && rec->except_level == 0) {
+ /* unignored everything */
+ ignore_remove_config(rec);
+ ignore_destroy(rec);
+ } else {
+ /* unignore just some levels.. */
+ ignore_remove_config(rec);
+ ignores = g_slist_remove(ignores, rec);
+
+ ignores = g_slist_append(ignores, rec);
+ ignore_set_config(rec);
+ }
+}
+
+static void read_ignores(void)
+{
+ IGNORE_REC *rec;
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (ignores != NULL)
+ ignore_destroy(ignores->data);
+
+ node = iconfig_node_traverse("ignores", FALSE);
+ if (node == NULL) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_BLOCK)
+ continue;
+
+ rec = g_new0(IGNORE_REC, 1);
+ ignores = g_slist_append(ignores, rec);
+
+ rec->mask = g_strdup(config_node_get_str(node, "mask", NULL));
+ rec->pattern = g_strdup(config_node_get_str(node, "pattern", NULL));
+ rec->level = level2bits(config_node_get_str(node, "level", 0));
+ rec->except_level = level2bits(config_node_get_str(node, "except_level", 0));
+ rec->regexp = config_node_get_bool(node, "regexp", FALSE);
+ rec->fullword = config_node_get_bool(node, "fullword", FALSE);
+
+ node = config_node_section(node, "channels", -1);
+ if (node != NULL) rec->channels = config_node_get_list(node);
+ }
+}
+
+void ignore_init(void)
+{
+ ignores = NULL;
+
+ read_ignores();
+ signal_add("setup reread", (SIGNAL_FUNC) read_ignores);
+}
+
+void ignore_deinit(void)
+{
+ while (ignores != NULL)
+ ignore_destroy(ignores->data);
+
+ signal_remove("setup reread", (SIGNAL_FUNC) read_ignores);
+}
diff --git a/src/irc/core/ignore.h b/src/irc/core/ignore.h
new file mode 100644
index 00000000..17e591d4
--- /dev/null
+++ b/src/irc/core/ignore.h
@@ -0,0 +1,30 @@
+#ifndef __IGNORE_H
+#define __IGNORE_H
+
+typedef struct {
+ char *mask; /* nick mask */
+ char *servertag; /* this is for autoignoring */
+ char **channels; /* ignore only in these channels */
+ char *pattern; /* text body must match this pattern */
+
+ int level; /* ignore these levels */
+ int except_level; /* don't ignore these levels */
+
+ int regexp:1;
+ int fullword:1;
+} IGNORE_REC;
+
+extern GSList *ignores;
+
+int ignore_check(IRC_SERVER_REC *server, const char *nick, const char *host,
+ const char *channel, const char *text, int level);
+
+IGNORE_REC *ignore_find(const char *servertag, const char *mask, char **channels);
+
+void ignore_add_rec(IGNORE_REC *rec);
+void ignore_update_rec(IGNORE_REC *rec);
+
+void ignore_init(void);
+void ignore_deinit(void);
+
+#endif
diff --git a/src/irc/core/irc-commands.c b/src/irc/core/irc-commands.c
new file mode 100644
index 00000000..8fcd4259
--- /dev/null
+++ b/src/irc/core/irc-commands.c
@@ -0,0 +1,878 @@
+/*
+ irc-commands.c : irssi
+
+ Copyright (C) 1999 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 "commands.h"
+#include "misc.h"
+#include "special-vars.h"
+#include "settings.h"
+#include "common-setup.h"
+
+#include "bans.h"
+#include "channels.h"
+#include "irc-server.h"
+#include "irc.h"
+#include "nicklist.h"
+#include "server-redirect.h"
+#include "server-setup.h"
+
+typedef struct {
+ CHANNEL_REC *channel;
+ char *ban;
+ int timeleft;
+} KNOCKOUT_REC;
+
+static GString *tmpstr;
+static int knockout_tag;
+
+static IRC_SERVER_REC *connect_server(const char *data)
+{
+ IRC_SERVER_CONNECT_REC *conn;
+ IRC_SERVER_REC *server;
+ char *params, *addr, *portstr, *password, *nick;
+ int port;
+
+ g_return_val_if_fail(data != NULL, NULL);
+
+ params = cmd_get_params(data, 4, &addr, &portstr, &password, &nick);
+ if (*addr == '\0') return NULL;
+
+ if (strcmp(password, "-") == 0)
+ *password = '\0';
+
+ port = 6667;
+ if (*portstr != '\0')
+ sscanf(portstr, "%d", &port);
+
+ /* connect to server */
+ conn = irc_server_create_conn(addr, port, password, nick);
+ server = irc_server_connect(conn);
+
+ g_free(params);
+ return server;
+}
+
+static void cmd_connect(const char *data)
+{
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+ connect_server(data);
+}
+
+static void cmd_disconnect(const char *data, IRC_SERVER_REC *server)
+{
+ IRC_SERVER_REC *ircserver;
+ char *params, *tag, *msg;
+
+ g_return_if_fail(data != NULL);
+
+ if (g_strncasecmp(data, "RECON-", 6) == 0)
+ return; /* remove reconnection, handle in server-reconnect.c */
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &tag, &msg);
+
+ if (*tag != '\0' && strcmp(tag, "*") != 0)
+ server = (IRC_SERVER_REC *) server_find_tag(tag);
+ if (server == NULL || !irc_server_check(server))
+ cmd_param_error(CMDERR_NOT_CONNECTED);
+
+ ircserver = (IRC_SERVER_REC *) server;
+ if (ircserver->handle != -1 && ircserver->buffer != NULL) {
+ /* flush transmit queue */
+ g_slist_foreach(ircserver->cmdqueue, (GFunc) g_free, NULL);
+ g_slist_free(ircserver->cmdqueue);
+ ircserver->cmdqueue = NULL;
+ ircserver->cmdcount = 0;
+
+ /* then send quit message */
+ if (*msg == '\0') msg = (char *) settings_get_str("default_quit_message");
+ irc_send_cmdv(ircserver, "QUIT :%s", msg);
+ }
+ g_free(params);
+
+ server_disconnect((SERVER_REC *) server);
+}
+
+static void cmd_server(const char *data, IRC_SERVER_REC *server)
+{
+ char *channels, *away_reason, *usermode, *ircnet;
+
+ g_return_if_fail(data != NULL);
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (*data == '+' || server == NULL) {
+ channels = away_reason = usermode = ircnet = NULL;
+ } else {
+ ircnet = g_strdup(server->connrec->ircnet);
+ channels = irc_server_get_channels((IRC_SERVER_REC *) server);
+ if (*channels == '\0')
+ g_free_and_null(channels);
+ usermode = g_strdup(server->usermode);
+ away_reason = !server->usermode_away ? NULL :
+ g_strdup(server->away_reason);
+ cmd_disconnect("* Changing server", server);
+ }
+
+ server = connect_server(data + (*data == '+' ? 1 : 0));
+ if (*data == '+' || server == NULL ||
+ (ircnet != NULL && server->connrec->ircnet != NULL &&
+ g_strcasecmp(ircnet, server->connrec->ircnet) != 0)) {
+ g_free_not_null(channels);
+ g_free_not_null(usermode);
+ g_free_not_null(away_reason);
+ } else if (server != NULL) {
+ server->connrec->reconnection = TRUE;
+ server->connrec->channels = channels;
+ server->connrec->usermode = usermode;
+ server->connrec->away_reason = away_reason;
+ }
+ g_free_not_null(ircnet);
+}
+
+static void cmd_quit(const char *data)
+{
+ GSList *tmp, *next;
+ const char *quitmsg;
+ char *str;
+
+ g_return_if_fail(data != NULL);
+
+ quitmsg = *data != '\0' ? data :
+ settings_get_str("default_quit_message");
+
+ /* disconnect from every server */
+ for (tmp = servers; tmp != NULL; tmp = next) {
+ next = tmp->next;
+
+ str = g_strdup_printf("* %s", quitmsg);
+ cmd_disconnect(str, tmp->data);
+ g_free(str);
+ }
+
+ signal_emit("gui exit", 0);
+}
+
+static void cmd_msg(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *target, *msg;
+ int free_ret;
+
+ g_return_if_fail(data != NULL);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg);
+ if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (*target == '=') {
+ /* dcc msg - don't even try to handle here.. */
+ g_free(params);
+ return;
+ }
+
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_param_error(CMDERR_NOT_CONNECTED);
+
+ free_ret = FALSE;
+ if (strcmp(target, ",") == 0 || strcmp(target, ".") == 0)
+ target = parse_special(&target, server, item, NULL, &free_ret, NULL);
+ else if (strcmp(target, "*") == 0 &&
+ (irc_item_channel(item) || irc_item_query(item)))
+ target = item->name;
+ if (target != NULL) {
+ g_string_sprintf(tmpstr, "PRIVMSG %s :%s", target, msg);
+ irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd);
+ }
+
+ if (free_ret && target != NULL) g_free(target);
+
+ g_free(params);
+}
+
+static void cmd_notice(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *target, *msg;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg);
+ if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ g_string_sprintf(tmpstr, "NOTICE %s :%s", target, msg);
+ irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd);
+
+ g_free(params);
+}
+
+static void cmd_ctcp(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *target, *ctcpcmd, *ctcpdata;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &ctcpcmd, &ctcpdata);
+ if (*target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ g_strup(ctcpcmd);
+ if (*ctcpdata == '\0')
+ g_string_sprintf(tmpstr, "PRIVMSG %s :\001%s\001", target, ctcpcmd);
+ else
+ g_string_sprintf(tmpstr, "PRIVMSG %s :\001%s %s\001", target, ctcpcmd, ctcpdata);
+ irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd);
+
+ g_free(params);
+}
+
+static void cmd_nctcp(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *target, *ctcpcmd, *ctcpdata;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &ctcpcmd, &ctcpdata);
+ if (*target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ g_strup(ctcpcmd);
+ g_string_sprintf(tmpstr, "NOTICE %s :\001%s %s\001", target, ctcpcmd, ctcpdata);
+ irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd);
+
+ g_free(params);
+}
+
+static void cmd_join(const char *data, IRC_SERVER_REC *server)
+{
+ if (*data == '\0' || g_strncasecmp(data, "-invite", 7) == 0) {
+ if (server->last_invite != NULL)
+ channels_join(server, server->last_invite, FALSE);
+ } else
+ channels_join(server, data, FALSE);
+}
+
+static void cmd_part(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *channame, *msg;
+ CHANNEL_REC *chanrec;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, item, &channame, &msg);
+ if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ chanrec = channel_find(server, channame);
+ if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND);
+
+ irc_send_cmdv(server, *msg == '\0' ? "PART %s" : "PART %s %s",
+ channame, msg);
+
+ g_free(params);
+}
+
+static void cmd_kick(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *channame, *nicks, *reason;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 3 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST,
+ item, &channame, &nicks, &reason);
+
+ if (*channame == '\0' || *nicks == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ if (!ischannel(*channame)) cmd_param_error(CMDERR_NOT_JOINED);
+
+ g_string_sprintf(tmpstr, "KICK %s %s :%s", channame, nicks, reason);
+ irc_send_cmd_split(server, tmpstr->str, 3, server->max_kicks_in_cmd);
+
+ g_free(params);
+}
+
+static void cmd_topic(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *channame, *topic;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, item, &channame, &topic);
+
+ irc_send_cmdv(server, *topic == '\0' ? "TOPIC %s" : "TOPIC %s :%s",
+ channame, topic);
+
+ g_free(params);
+}
+
+static void cmd_invite(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *nick, *channame;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2, &nick, &channame);
+ if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ if (*channame == '\0' || strcmp(channame, "*") == 0) {
+ if (!irc_item_channel(item))
+ cmd_param_error(CMDERR_NOT_JOINED);
+
+ channame = item->name;
+ }
+
+ irc_send_cmdv(server, "INVITE %s %s", nick, channame);
+ g_free(params);
+}
+
+static void cmd_list(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *args, *str;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_OPTARGS | PARAM_FLAG_GETREST, &args, &str);
+
+ if (*str == '\0' && stristr(args, "-yes") == NULL)
+ cmd_param_error(CMDERR_NOT_GOOD_IDEA);
+
+ irc_send_cmdv(server, "LIST %s", str);
+ g_free(params);
+
+ /* add default redirection */
+ server_redirect_default((SERVER_REC *) server, "bogus command list");
+}
+
+static void cmd_who(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *channel, *args, *rest;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 3 | PARAM_FLAG_OPTARGS | PARAM_FLAG_GETREST, &args, &channel, &rest);
+
+ if (strcmp(channel, "*") == 0 || *channel == '\0') {
+ if (!irc_item_check(item))
+ cmd_return_error(CMDERR_NOT_JOINED);
+
+ data = item->name;
+ }
+ if (strcmp(channel, "**") == 0) {
+ /* ** displays all nicks.. */
+ *channel = '\0';
+ }
+
+ irc_send_cmdv(server, *rest == '\0' ? "WHO %s" : "WHO %s %s",
+ channel, rest);
+ g_free(params);
+
+ /* add default redirection */
+ server_redirect_default((SERVER_REC *) server, "bogus command who");
+}
+
+static void cmd_names(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ g_return_if_fail(data != NULL);
+
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_GOOD_IDEA);
+
+ if (strcmp(data, "*") == 0) {
+ if (!irc_item_channel(item))
+ cmd_return_error(CMDERR_NOT_JOINED);
+
+ data = item->name;
+ }
+
+ if (g_strcasecmp(data, "-YES") == 0)
+ irc_send_cmd(server, "NAMES");
+ else
+ irc_send_cmdv(server, "NAMES %s", data);
+}
+
+static void cmd_whois(const char *data, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ while (*data == ' ') data++;
+ if (*data == '\0') data = server->nick;
+
+ g_string_sprintf(tmpstr, "WHOIS %s", data);
+ irc_send_cmd_split(server, tmpstr->str, 2, server->max_whois_in_cmd);
+}
+
+static void cmd_whowas(const char *data, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ while (*data == ' ') data++;
+ if (*data == '\0') data = server->nick;
+
+ irc_send_cmdv(server, "WHOWAS %s", data);
+}
+
+static void cmd_ping(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ GTimeVal tv;
+ char *str;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (*data == '\0' || strcmp(data, "*") == 0) {
+ if (!irc_item_check(item))
+ cmd_return_error(CMDERR_NOT_JOINED);
+
+ data = item->name;
+ }
+
+ g_get_current_time(&tv);
+
+ str = g_strdup_printf("%s PING %ld %ld", data, tv.tv_sec, tv.tv_usec);
+ signal_emit("command ctcp", 3, str, server, item);
+ g_free(str);
+}
+
+static void server_send_away(IRC_SERVER_REC *server, const char *reason)
+{
+ g_free_not_null(server->away_reason);
+ server->away_reason = g_strdup(reason);
+
+ irc_send_cmdv(server, "AWAY :%s", reason);
+}
+
+static void cmd_away(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *args, *reason;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_OPTARGS | PARAM_FLAG_GETREST, &args, &reason);
+
+ if (stristr(args, "-all") != NULL)
+ g_slist_foreach(servers, (GFunc) server_send_away, reason);
+ else
+ server_send_away(server, reason);
+
+ g_free(params);
+}
+
+static void cmd_deop(const char *data, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (*data == '\0')
+ irc_send_cmdv(server, "MODE %s -o", server->nick);
+}
+
+static void cmd_sconnect(const char *data, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ irc_send_cmdv(server, "CONNECT %s", data);
+}
+
+static void cmd_quote(const char *data, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ irc_send_cmd(server, data);
+}
+
+static void cmd_wall_hash(gpointer key, NICK_REC *nick, GSList **nicks)
+{
+ if (nick->op) *nicks = g_slist_append(*nicks, nick);
+}
+
+static void cmd_wall(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *channame, *msg;
+ CHANNEL_REC *chanrec;
+ GSList *tmp, *nicks;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, item, &channame, &msg);
+ if (*msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ chanrec = channel_find(server, channame);
+ if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND);
+
+ /* send notice to all ops */
+ nicks = NULL;
+ g_hash_table_foreach(chanrec->nicks, (GHFunc) cmd_wall_hash, &nicks);
+
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *rec = tmp->data;
+
+ irc_send_cmdv(server, "NOTICE %s :%s", rec->nick, msg);
+ }
+ g_slist_free(nicks);
+
+ g_free(params);
+}
+
+static void cmd_cycle(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *channame, *msg;
+ CHANNEL_REC *chanrec;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_OPTCHAN, item, &channame, &msg);
+ if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ chanrec = channel_find(server, channame);
+ if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND);
+
+ irc_send_cmdv(server, *msg == '\0' ? "PART %s" : "PART %s %s",
+ channame, msg);
+ irc_send_cmdv(server, chanrec->key == NULL ? "JOIN %s" : "JOIN %s %s",
+ channame, chanrec->key);
+
+ g_free(params);
+}
+
+static void cmd_kickban(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *nick;
+
+ g_return_if_fail(data != NULL);
+
+ params = cmd_get_params(data, 1, &nick);
+ if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ signal_emit("command ban", 3, nick, server, item);
+ signal_emit("command kick", 3, data, server, item);
+ g_free(params);
+}
+
+static void knockout_destroy(IRC_SERVER_REC *server, KNOCKOUT_REC *rec)
+{
+ server->knockoutlist = g_slist_remove(server->knockoutlist, rec);
+ g_free(rec->ban);
+ g_free(rec);
+}
+
+/* timeout function: knockout */
+static void knockout_timeout_server(IRC_SERVER_REC *server)
+{
+ GSList *tmp, *next;
+ time_t t;
+
+ g_return_if_fail(server != NULL);
+
+ t = server->knockout_lastcheck == 0 ? 0 :
+ time(NULL)-server->knockout_lastcheck;
+ server->knockout_lastcheck = time(NULL);
+
+ for (tmp = server->knockoutlist; tmp != NULL; tmp = next) {
+ KNOCKOUT_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->timeleft > t)
+ rec->timeleft -= t;
+ else {
+ /* timeout, unban. */
+ ban_remove(rec->channel, rec->ban);
+ knockout_destroy(server, rec);
+ }
+ }
+}
+
+static int knockout_timeout(void)
+{
+ g_slist_foreach(servers, (GFunc) knockout_timeout_server, NULL);
+ return 1;
+}
+
+static void cmd_knockout(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ KNOCKOUT_REC *rec;
+ CHANNEL_REC *channel;
+ char *params, *nick, *reason, *timeoutstr, *str;
+ int timeleft;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL) cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ channel = irc_item_channel(item);
+ if (channel == NULL) cmd_return_error(CMDERR_NOT_JOINED);
+
+ if (is_numeric(data, ' ')) {
+ /* first argument is the timeout */
+ params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &timeoutstr, &nick, &reason);
+ timeleft = atol(timeoutstr);
+ } else {
+ timeleft = 0;
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &nick, &reason);
+ }
+
+ if (timeleft == 0) timeleft = settings_get_int("knockout_time");
+ if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ signal_emit("command ban", 3, nick, server, channel);
+
+ str = g_strdup_printf("%s %s", nick, reason);
+ signal_emit("command kick", 3, str, server, channel);
+ g_free(str);
+
+ /* create knockout record */
+ rec = g_new(KNOCKOUT_REC, 1);
+ rec->timeleft = timeleft;
+ rec->channel = channel;
+ rec->ban = ban_get_mask(channel, nick);
+
+ server->knockoutlist = g_slist_append(server->knockoutlist, rec);
+
+ g_free(params);
+}
+
+/* destroy all knockouts in server */
+static void sig_server_disconnected(IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ while (server->knockoutlist != NULL)
+ knockout_destroy(server, server->knockoutlist->data);
+}
+
+/* destroy all knockouts in channel */
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+ GSList *tmp, *next;
+
+ g_return_if_fail(channel != NULL);
+ if (channel->server == NULL) return;
+
+ for (tmp = channel->server->knockoutlist; tmp != NULL; tmp = next) {
+ KNOCKOUT_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->channel == channel)
+ knockout_destroy(channel->server, rec);
+ }
+}
+
+static void command_self(const char *data, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ irc_send_cmdv(server, *data == '\0' ? "%s" : "%s %s", current_command, data);
+}
+
+static void command_1self(const char *data, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ irc_send_cmdv(server, "%s :%s", current_command, data);
+}
+
+static void command_2self(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *target, *text;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &text);
+ if (*target == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ irc_send_cmdv(server, "%s %s :%s", current_command, target, text);
+ g_free(params);
+}
+
+static void sig_connected(IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ server_redirect_init((SERVER_REC *) server, "", 2, "event 318", "event 402", "event 401",
+ "event 301", "event 311", "event 312", "event 313",
+ "event 317", "event 319", NULL);
+
+ /* gui-gnome can use server_redirect_event() in who/list commands so
+ we can't use "command who" or list here.. */
+ server_redirect_init((SERVER_REC *) server, "bogus command who", 2, "event 401", "event 315", "event 352", NULL);
+ server_redirect_init((SERVER_REC *) server, "bogus command list", 1, "event 321", "event 322", "event 323", NULL);
+}
+
+void irc_commands_init(void)
+{
+ tmpstr = g_string_new(NULL);
+
+ settings_add_str("misc", "default_quit_message", "leaving");
+ settings_add_int("misc", "knockout_time", 300);
+
+ knockout_tag = g_timeout_add(KNOCKOUT_TIMECHECK, (GSourceFunc) knockout_timeout, NULL);
+
+ signal_add("server connected", (SIGNAL_FUNC) sig_connected);
+ command_bind("server", NULL, (SIGNAL_FUNC) cmd_server);
+ command_bind("connect", NULL, (SIGNAL_FUNC) cmd_connect);
+ command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
+ command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg);
+ command_bind("notice", NULL, (SIGNAL_FUNC) cmd_notice);
+ command_bind("ctcp", NULL, (SIGNAL_FUNC) cmd_ctcp);
+ command_bind("nctcp", NULL, (SIGNAL_FUNC) cmd_nctcp);
+ command_bind("quit", NULL, (SIGNAL_FUNC) cmd_quit);
+ command_bind("join", NULL, (SIGNAL_FUNC) cmd_join);
+ command_bind("part", NULL, (SIGNAL_FUNC) cmd_part);
+ command_bind("kick", NULL, (SIGNAL_FUNC) cmd_kick);
+ command_bind("topic", NULL, (SIGNAL_FUNC) cmd_topic);
+ command_bind("invite", NULL, (SIGNAL_FUNC) cmd_invite);
+ command_bind("list", NULL, (SIGNAL_FUNC) cmd_list);
+ command_bind("who", NULL, (SIGNAL_FUNC) cmd_who);
+ command_bind("names", NULL, (SIGNAL_FUNC) cmd_names);
+ command_bind("nick", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("note", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("whois", NULL, (SIGNAL_FUNC) cmd_whois);
+ command_bind("whowas", NULL, (SIGNAL_FUNC) cmd_whowas);
+ command_bind("ping", NULL, (SIGNAL_FUNC) cmd_ping);
+ command_bind("kill", NULL, (SIGNAL_FUNC) command_2self);
+ command_bind("away", NULL, (SIGNAL_FUNC) cmd_away);
+ command_bind("ison", NULL, (SIGNAL_FUNC) command_1self);
+ command_bind("admin", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("info", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("links", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("lusers", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("map", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("motd", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("stats", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("time", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("trace", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("version", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("servlist", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("silence", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("sconnect", NULL, (SIGNAL_FUNC) cmd_sconnect);
+ command_bind("squery", NULL, (SIGNAL_FUNC) command_2self);
+ command_bind("deop", NULL, (SIGNAL_FUNC) cmd_deop);
+ command_bind("die", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("hash", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("oper", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("restart", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("rping", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("squit", NULL, (SIGNAL_FUNC) command_2self);
+ command_bind("uping", NULL, (SIGNAL_FUNC) command_self);
+ command_bind("quote", NULL, (SIGNAL_FUNC) cmd_quote);
+ command_bind("wall", NULL, (SIGNAL_FUNC) cmd_wall);
+ command_bind("wallops", NULL, (SIGNAL_FUNC) command_1self);
+ command_bind("wallchops", NULL, (SIGNAL_FUNC) command_2self);
+ command_bind("cycle", NULL, (SIGNAL_FUNC) cmd_cycle);
+ command_bind("kickban", NULL, (SIGNAL_FUNC) cmd_kickban);
+ command_bind("knockout", NULL, (SIGNAL_FUNC) cmd_knockout);
+
+ signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+}
+
+void irc_commands_deinit(void)
+{
+ g_source_remove(knockout_tag);
+
+ signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
+ command_unbind("server", (SIGNAL_FUNC) cmd_server);
+ command_unbind("connect", (SIGNAL_FUNC) cmd_connect);
+ command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
+ command_unbind("msg", (SIGNAL_FUNC) cmd_msg);
+ command_unbind("notice", (SIGNAL_FUNC) cmd_notice);
+ command_unbind("ctcp", (SIGNAL_FUNC) cmd_ctcp);
+ command_unbind("nctcp", (SIGNAL_FUNC) cmd_nctcp);
+ command_unbind("quit", (SIGNAL_FUNC) cmd_quit);
+ command_unbind("join", (SIGNAL_FUNC) cmd_join);
+ command_unbind("part", (SIGNAL_FUNC) cmd_part);
+ command_unbind("kick", (SIGNAL_FUNC) cmd_kick);
+ command_unbind("topic", (SIGNAL_FUNC) cmd_topic);
+ command_unbind("invite", (SIGNAL_FUNC) cmd_invite);
+ command_unbind("list", (SIGNAL_FUNC) cmd_list);
+ command_unbind("who", (SIGNAL_FUNC) cmd_who);
+ command_unbind("names", (SIGNAL_FUNC) cmd_names);
+ command_unbind("nick", (SIGNAL_FUNC) command_self);
+ command_unbind("note", (SIGNAL_FUNC) command_self);
+ command_unbind("whois", (SIGNAL_FUNC) cmd_whois);
+ command_unbind("whowas", (SIGNAL_FUNC) cmd_whowas);
+ command_unbind("ping", (SIGNAL_FUNC) cmd_ping);
+ command_unbind("kill", (SIGNAL_FUNC) command_2self);
+ command_unbind("away", (SIGNAL_FUNC) cmd_away);
+ command_unbind("ison", (SIGNAL_FUNC) command_1self);
+ command_unbind("admin", (SIGNAL_FUNC) command_self);
+ command_unbind("info", (SIGNAL_FUNC) command_self);
+ command_unbind("links", (SIGNAL_FUNC) command_self);
+ command_unbind("lusers", (SIGNAL_FUNC) command_self);
+ command_unbind("map", (SIGNAL_FUNC) command_self);
+ command_unbind("motd", (SIGNAL_FUNC) command_self);
+ command_unbind("stats", (SIGNAL_FUNC) command_self);
+ command_unbind("time", (SIGNAL_FUNC) command_self);
+ command_unbind("trace", (SIGNAL_FUNC) command_self);
+ command_unbind("version", (SIGNAL_FUNC) command_self);
+ command_unbind("servlist", (SIGNAL_FUNC) command_self);
+ command_unbind("silence", (SIGNAL_FUNC) command_self);
+ command_unbind("sconnect", (SIGNAL_FUNC) cmd_sconnect);
+ command_unbind("squery", (SIGNAL_FUNC) command_2self);
+ command_unbind("deop", (SIGNAL_FUNC) cmd_deop);
+ command_unbind("die", (SIGNAL_FUNC) command_self);
+ command_unbind("hash", (SIGNAL_FUNC) command_self);
+ command_unbind("oper", (SIGNAL_FUNC) command_self);
+ command_unbind("restart", (SIGNAL_FUNC) command_self);
+ command_unbind("rping", (SIGNAL_FUNC) command_self);
+ command_unbind("squit", (SIGNAL_FUNC) command_2self);
+ command_unbind("uping", (SIGNAL_FUNC) command_self);
+ command_unbind("quote", (SIGNAL_FUNC) cmd_quote);
+ command_unbind("wall", (SIGNAL_FUNC) cmd_wall);
+ command_unbind("wallops", (SIGNAL_FUNC) command_1self);
+ command_unbind("wallchops", (SIGNAL_FUNC) command_2self);
+ command_unbind("cycle", (SIGNAL_FUNC) cmd_cycle);
+ command_unbind("kickban", (SIGNAL_FUNC) cmd_kickban);
+ command_unbind("knockout", (SIGNAL_FUNC) cmd_knockout);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+
+ g_string_free(tmpstr, TRUE);
+}
diff --git a/src/irc/core/irc-commands.h b/src/irc/core/irc-commands.h
new file mode 100644
index 00000000..f368be96
--- /dev/null
+++ b/src/irc/core/irc-commands.h
@@ -0,0 +1,7 @@
+#ifndef __IRC_COMMANDS_H
+#define __IRC_COMMANDS_H
+
+void irc_commands_init(void);
+void irc_commands_deinit(void);
+
+#endif
diff --git a/src/irc/core/irc-core.c b/src/irc/core/irc-core.c
new file mode 100644
index 00000000..5969e41d
--- /dev/null
+++ b/src/irc/core/irc-core.c
@@ -0,0 +1,63 @@
+/*
+ irc-core.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 "irc-server.h"
+#include "channels.h"
+
+#include "ctcp.h"
+#include "irc-commands.h"
+#include "irc-rawlog.h"
+#include "irc-special-vars.h"
+#include "ignore.h"
+#include "irc.h"
+#include "lag.h"
+#include "netsplit.h"
+
+void irc_core_init(void)
+{
+ irc_servers_init();
+ channels_init();
+
+ ctcp_init();
+ irc_commands_init();
+ irc_irc_init();
+ lag_init();
+ netsplit_init();
+ ignore_init();
+ irc_rawlog_init();
+ irc_special_vars_init();
+}
+
+void irc_core_deinit(void)
+{
+ irc_special_vars_deinit();
+ irc_rawlog_deinit();
+ ignore_deinit();
+ netsplit_deinit();
+ lag_deinit();
+ irc_irc_deinit();
+ irc_commands_deinit();
+ ctcp_deinit();
+
+ channels_deinit();
+ irc_servers_deinit();
+}
diff --git a/src/irc/core/irc-core.h b/src/irc/core/irc-core.h
new file mode 100644
index 00000000..31b1fc26
--- /dev/null
+++ b/src/irc/core/irc-core.h
@@ -0,0 +1,7 @@
+#ifndef __IRC_CORE_H
+#define __IRC_CORE_H
+
+void irc_core_init(void);
+void irc_core_deinit(void);
+
+#endif
diff --git a/src/irc/core/irc-log.c b/src/irc/core/irc-log.c
new file mode 100644
index 00000000..c6dabc13
--- /dev/null
+++ b/src/irc/core/irc-log.c
@@ -0,0 +1,70 @@
+static void sig_log(SERVER_REC *server, const char *channel, gpointer level, const char *str)
+{
+ gint loglevel;
+
+ g_return_if_fail(str != NULL);
+
+ loglevel = GPOINTER_TO_INT(level);
+ if (loglevel == MSGLEVEL_NEVER || logs == NULL) return;
+
+ /* Check if line should be saved in logs */
+ log_file_write(server, channel, loglevel, str);
+}
+
+
+static void event_away(const char *data, IRC_SERVER_REC *server)
+{
+ LOG_REC *log;
+ const char *fname, *level;
+
+ fname = settings_get_str("awaylog_file");
+ level = settings_get_str("awaylog_level");
+ if (*fname == '\0' || *level == '\0') return;
+
+ log = log_file_find(fname);
+ if (log != NULL) {
+ /* awaylog already created */
+ if (log->handle == -1) {
+ /* ..but not open, open it. */
+ log_file_open(log);
+ }
+ return;
+ }
+
+ log = log_create(fname, level);
+ if (log != NULL) log_file_open(log);
+}
+
+static void event_unaway(const char *data, IRC_SERVER_REC *server)
+{
+ LOG_REC *rec;
+ const char *fname;
+
+ fname = settings_get_str("awaylog_file");
+ if (*fname == '\0') return;
+
+ rec = log_file_find(fname);
+ if (rec == NULL || rec->handle == -1) {
+ /* awaylog not open */
+ return;
+ }
+
+ log_file_destroy(rec);
+}
+
+void log_init(void)
+{
+ settings_add_str("misc", "awaylog_file", "~/.irssi/away.log");
+ settings_add_str("misc", "awaylog_level", "-all +msgs +hilight");
+
+ signal_add("print text stripped", (SIGNAL_FUNC) sig_log);
+ signal_add("event 306", (SIGNAL_FUNC) event_away);
+ signal_add("event 305", (SIGNAL_FUNC) event_unaway);
+}
+
+void log_deinit(void)
+{
+ signal_remove("print text stripped", (SIGNAL_FUNC) sig_log);
+ signal_remove("event 306", (SIGNAL_FUNC) event_away);
+ signal_remove("event 305", (SIGNAL_FUNC) event_unaway);
+}
diff --git a/src/irc/core/irc-rawlog.c b/src/irc/core/irc-rawlog.c
new file mode 100644
index 00000000..b2fd26bc
--- /dev/null
+++ b/src/irc/core/irc-rawlog.c
@@ -0,0 +1,77 @@
+/*
+ irc-rawlog.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 "rawlog.h"
+#include "modules.h"
+#include "signals.h"
+#include "misc.h"
+
+#include "commands.h"
+#include "server.h"
+
+#include "settings.h"
+
+static void cmd_rawlog(const char *data, SERVER_REC *server, void *item)
+{
+ command_runsub("rawlog", data, server, item);
+}
+
+static void cmd_rawlog_save(const char *data, SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+ rawlog_save(server->rawlog, data);
+}
+
+static void cmd_rawlog_open(const char *data, SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+ rawlog_open(server->rawlog, data);
+}
+
+static void cmd_rawlog_close(const char *data, SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected) cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ rawlog_close(server->rawlog);
+}
+
+void irc_rawlog_init(void)
+{
+ command_bind("rawlog", NULL, (SIGNAL_FUNC) cmd_rawlog);
+ command_bind("rawlog save", NULL, (SIGNAL_FUNC) cmd_rawlog_save);
+ command_bind("rawlog open", NULL, (SIGNAL_FUNC) cmd_rawlog_open);
+ command_bind("rawlog close", NULL, (SIGNAL_FUNC) cmd_rawlog_close);
+}
+
+void irc_rawlog_deinit(void)
+{
+ command_unbind("rawlog", (SIGNAL_FUNC) cmd_rawlog);
+ command_unbind("rawlog save", (SIGNAL_FUNC) cmd_rawlog_save);
+ command_unbind("rawlog open", (SIGNAL_FUNC) cmd_rawlog_open);
+ command_unbind("rawlog close", (SIGNAL_FUNC) cmd_rawlog_close);
+}
diff --git a/src/irc/core/irc-rawlog.h b/src/irc/core/irc-rawlog.h
new file mode 100644
index 00000000..ebab7155
--- /dev/null
+++ b/src/irc/core/irc-rawlog.h
@@ -0,0 +1,7 @@
+#ifndef __IRC_RAWLOG_H
+#define __IRC_RAWLOG_H
+
+void irc_rawlog_init(void);
+void irc_rawlog_deinit(void);
+
+#endif
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();
+}
diff --git a/src/irc/core/irc-server.h b/src/irc/core/irc-server.h
new file mode 100644
index 00000000..21e3e73c
--- /dev/null
+++ b/src/irc/core/irc-server.h
@@ -0,0 +1,146 @@
+#ifndef __IRC_SERVER_H
+#define __IRC_SERVER_H
+
+#include "server.h"
+
+enum {
+ SERVER_TYPE_IRC
+};
+
+/* return if `server' doesn't point to IRC server record. */
+#define irc_server_check(server) \
+ ((server) != NULL && module_find_id("IRC SERVER", (server)->type) != -1)
+
+/* all strings should be either NULL or dynamically allocated */
+/* address and nick are mandatory, rest are optional */
+typedef struct {
+ /* -- GENERIC SERVER_CONNECT_REC - don't change! -- */
+ /* if we're connecting via proxy, or just NULLs */
+ char *proxy;
+ int proxy_port;
+ char *proxy_string;
+
+ /* server where we want to connect */
+ char *address;
+ int port;
+ char *ircnet;
+
+ IPADDR *own_ip;
+
+ /* -- IRC specific - change if you wish -- */
+ char *password;
+ char *nick, *alternate_nick;
+ char *username;
+ char *realname;
+
+ int max_cmds_at_once;
+ int cmd_queue_speed;
+ int max_kicks, max_msgs, max_modes, max_whois;
+
+ /* when reconnecting, the old server status */
+ int reconnection:1; /* we're trying to reconnect */
+ char *channels;
+ char *away_reason;
+ char *usermode;
+} IRC_SERVER_CONNECT_REC;
+
+typedef struct {
+ /* -- GENERIC SERVER_REC - don't change! -- */
+ int type; /* server type */
+
+ IRC_SERVER_CONNECT_REC *connrec;
+ time_t connect_time; /* connection time */
+
+ char *tag; /* tag name for addressing server */
+ char *nick; /* current nick */
+
+ int connected:1; /* connected to server */
+ int connection_lost:1; /* Connection lost unintentionally */
+
+ int handle; /* socket handle */
+ int readtag; /* input tag */
+
+ /* for net_connect_nonblock() */
+ int connect_pipe[2];
+ int connect_tag;
+ int connect_pid;
+
+ /* For deciding if event should be handled internally */
+ GHashTable *eventtable; /* "event xxx" : GSList* of REDIRECT_RECs */
+ GHashTable *eventgrouptable; /* event group : GSList* of REDIRECT_RECs */
+ GHashTable *cmdtable; /* "command xxx" : REDIRECT_CMD_REC* */
+
+ void *rawlog;
+ void *buffer; /* receive buffer */
+ GHashTable *module_data;
+
+ /* -- IRC specific - change if you wish -- */
+ char *real_address; /* address the irc server gives */
+ char *version; /* server version - taken from 004 event */
+ char *usermode; /* The whole mode string .. */
+ char *userhost; /* /USERHOST <nick> - set when joined to first channel */
+ char *last_invite; /* channel where you were last invited */
+ char *away_reason;
+ int usermode_away:1;
+ int server_operator:1;
+
+ int whois_coming:1; /* Mostly just to display away message right.. */
+
+ int emode_known:1; /* Server understands ban exceptions and invite lists */
+ int no_multi_mode:1; /* Server doesn't understand MODE #chan1,#chan2,... */
+ int no_multi_who:1; /* Server doesn't understand WHO #chan1,#chan2,... */
+ int one_endofwho:1; /* /WHO #a,#b,.. replies only with one End of WHO message */
+
+ int max_kicks_in_cmd; /* max. number of people to kick with one /KICK command */
+ int max_modes_in_cmd; /* max. number of mode changes in one /MODE command */
+ int max_whois_in_cmd; /* max. number of nicks in one /WHOIS command */
+ int max_msgs_in_cmd; /* max. number of targets in one /MSG */
+
+ /* Command sending queue */
+ int cmdcount; /* number of commands in `cmdqueue'. Can be more than
+ there actually is, to make flood control remember
+ how many messages can be sent before starting the
+ flood control */
+ int cmd_last_split; /* Last command wasn't sent entirely to server.
+ First item in `cmdqueue' should be re-sent. */
+ GSList *cmdqueue;
+ GTimeVal last_cmd; /* last time command was sent to server */
+
+ int max_cmds_at_once; /* How many messages can be sent immediately before timeouting starts */
+ int cmd_queue_speed; /* Timeout between sending commands */
+
+ GSList *idles; /* Idle queue - send these commands to server
+ if there's nothing else to do */
+
+ GSList *ctcpqueue; /* CTCP flood protection - list of tags in idle queue */
+
+ /* /knockout ban list */
+ GSList *knockoutlist;
+ time_t knockout_lastcheck;
+
+ GSList *lastmsgs; /* List of nicks who last send you msg */
+ GHashTable *splits; /* For keeping track of netsplits */
+
+ time_t lag_sent; /* 0 or time when last lag query was sent to server */
+ time_t lag_last_check; /* last time we checked lag */
+ int lag; /* server lag in milliseconds */
+
+ GSList *channels;
+ GSList *queries;
+
+ gpointer chanqueries;
+} IRC_SERVER_REC;
+
+IRC_SERVER_REC *irc_server_connect(IRC_SERVER_CONNECT_REC *conn);
+
+/* 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);
+
+/* INTERNAL: Free memory used by connection record */
+void irc_server_connect_free(IRC_SERVER_CONNECT_REC *rec);
+
+void irc_servers_init(void);
+void irc_servers_deinit(void);
+
+#endif
diff --git a/src/irc/core/irc-special-vars.c b/src/irc/core/irc-special-vars.c
new file mode 100644
index 00000000..9fcb5808
--- /dev/null
+++ b/src/irc/core/irc-special-vars.c
@@ -0,0 +1,332 @@
+/*
+ irc-special-vars.c : irssi
+
+ Copyright (C) 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 "misc.h"
+#include "special-vars.h"
+#include "settings.h"
+
+#include "irc.h"
+#include "irc-server.h"
+#include "channels.h"
+#include "query.h"
+
+static char *last_privmsg_from;
+static char *last_sent_msg, *last_sent_msg_body;
+static char *last_join, *last_public_from;
+
+/* last person who sent you a MSG */
+static char *expando_lastmsg(void *server, void *item, int *free_ret)
+{
+ return last_privmsg_from;
+}
+
+/* last person to whom you sent a MSG */
+static char *expando_lastmymsg(void *server, void *item, int *free_ret)
+{
+ return last_sent_msg;
+}
+
+/* last person to join a channel you are on */
+static char *expando_lastjoin(void *server, void *item, int *free_ret)
+{
+ return last_join;
+}
+
+/* last person to send a public message to a channel you are on */
+static char *expando_lastpublic(void *server, void *item, int *free_ret)
+{
+ return last_public_from;
+}
+
+/* text of your AWAY message, if any */
+static char *expando_awaymsg(void *server, void *item, int *free_ret)
+{
+ IRC_SERVER_REC *ircserver = server;
+
+ return ircserver == NULL ? "" : ircserver->away_reason;
+}
+
+/* body of last MSG you sent */
+static char *expando_lastmymsg_body(void *server, void *item, int *free_ret)
+{
+ return last_sent_msg_body;
+}
+
+/* current channel */
+static char *expando_channel(void *server, void *item, int *free_ret)
+{
+ CHANNEL_REC *channel;
+
+ channel = irc_item_channel(item);
+ return channel == NULL ? NULL : channel->name;
+}
+
+/* current server numeric being processed */
+static char *expando_server_numeric(void *server, void *item, int *free_ret)
+{
+ return current_server_event == NULL ||
+ !is_numeric(current_server_event, 0) ? NULL :
+ current_server_event;
+}
+
+/* channel you were last INVITEd to */
+static char *expando_last_invite(void *server, void *item, int *free_ret)
+{
+ IRC_SERVER_REC *ircserver = server;
+
+ return ircserver == NULL ? "" : ircserver->last_invite;
+}
+
+/* modes of current channel, if any */
+static char *expando_chanmode(void *server, void *item, int *free_ret)
+{
+ CHANNEL_REC *channel;
+
+ channel = irc_item_channel(item);
+ if (channel == NULL) return NULL;
+
+ *free_ret = TRUE;
+ return channel_get_mode(channel);
+}
+
+/* current nickname */
+static char *expando_nick(void *server, void *item, int *free_ret)
+{
+ IRC_SERVER_REC *ircserver = server;
+
+ return ircserver == NULL ? "" : ircserver->nick;
+}
+
+/* value of STATUS_OPER if you are an irc operator */
+static char *expando_statusoper(void *server, void *item, int *free_ret)
+{
+ IRC_SERVER_REC *ircserver = server;
+
+ return ircserver == NULL || !ircserver->server_operator ? "" :
+ (char *) settings_get_str("STATUS_OPER");
+}
+
+/* if you are a channel operator in $C, expands to a '@' */
+static char *expando_chanop(void *server, void *item, int *free_ret)
+{
+ CHANNEL_REC *channel;
+
+ channel = irc_item_channel(item);
+ if (channel == NULL) return NULL;
+
+ return channel->chanop ? "@" : "";
+}
+
+/* nickname of whomever you are QUERYing */
+static char *expando_query(void *server, void *item, int *free_ret)
+{
+ QUERY_REC *query;
+
+ query = irc_item_query(item);
+ return query == NULL ? NULL : query->nick;
+}
+
+/* version of current server */
+static char *expando_serverversion(void *server, void *item, int *free_ret)
+{
+ IRC_SERVER_REC *ircserver = server;
+
+ return ircserver == NULL ? "" : ircserver->version;
+}
+
+/* current server name */
+static char *expando_servername(void *server, void *item, int *free_ret)
+{
+ IRC_SERVER_REC *ircserver = server;
+
+ return ircserver == NULL ? "" : ircserver->real_address;
+}
+
+/* target of current input (channel or QUERY nickname) */
+static char *expando_target(void *server, void *item, int *free_ret)
+{
+ if (!irc_item_check(item))
+ return NULL;
+
+ return ((WI_IRC_REC *) item)->name;
+}
+/* your /userhost $N address (user@host) */
+static char *expando_userhost(void *server, void *item, int *free_ret)
+{
+ IRC_SERVER_REC *ircserver = server;
+ const char *username;
+ char hostname[100];
+
+ /* prefer the _real_ /userhost reply */
+ if (ircserver != NULL && ircserver->userhost != NULL)
+ return ircserver->userhost;
+
+ /* haven't received userhost reply yet. guess something */
+ *free_ret = TRUE;
+ if (server == NULL)
+ username = settings_get_str("user_name");
+ else
+ username = ircserver->connrec->username;
+
+ if (gethostname(hostname, sizeof(hostname)) != 0 || *hostname == '\0')
+ strcpy(hostname, "??");
+ return g_strconcat(username, "@", hostname, NULL);;
+}
+
+/* value of REALNAME */
+static char *expando_realname(void *server, void *item, int *free_ret)
+{
+ IRC_SERVER_REC *ircserver = server;
+
+ return ircserver == NULL ? "" : ircserver->connrec->realname;
+}
+
+/* Server tag */
+static char *expando_servertag(void *server, void *item, int *free_ret)
+{
+ IRC_SERVER_REC *ircserver = server;
+
+ return ircserver == NULL ? "" : ircserver->tag;
+}
+
+/* Server ircnet */
+static char *expando_ircnet(void *server, void *item, int *free_ret)
+{
+ IRC_SERVER_REC *ircserver = server;
+
+ return ircserver == NULL ? "" : ircserver->connrec->ircnet;
+}
+
+static void event_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr)
+{
+ char *params, *target, *msg;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg);
+
+ if (*msg != 1) {
+ if (!ischannel(*target)) {
+ g_free_not_null(last_privmsg_from);
+ last_privmsg_from = g_strdup(nick);
+ } else {
+ g_free_not_null(last_public_from);
+ last_public_from = g_strdup(nick);
+ }
+ }
+
+ g_free(params);
+}
+
+static void cmd_msg(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *target, *msg;
+
+ g_return_if_fail(data != NULL);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg);
+ if (*target != '\0' && *msg != '\0' && !ischannel(*target) && isalpha(*target)) {
+ g_free_not_null(last_sent_msg);
+ g_free_not_null(last_sent_msg_body);
+ last_sent_msg = g_strdup(target);
+ last_sent_msg_body = g_strdup(msg);
+ }
+
+ g_free(params);
+}
+
+static void event_join(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address)
+{
+ g_return_if_fail(nick != NULL);
+
+ if (g_strcasecmp(nick, server->nick) != 0) {
+ g_free_not_null(last_join);
+ last_join = g_strdup(nick);
+ }
+}
+
+void irc_special_vars_init(void)
+{
+ settings_add_str("misc", "STATUS_OPER", "*");
+
+ last_privmsg_from = NULL;
+ last_sent_msg = NULL; last_sent_msg_body = NULL;
+ last_join = NULL; last_public_from = NULL;
+
+ expando_create(",", expando_lastmsg);
+ expando_create(".", expando_lastmymsg);
+ expando_create(":", expando_lastjoin);
+ expando_create(";", expando_lastpublic);
+ expando_create("A", expando_awaymsg);
+ expando_create("B", expando_lastmymsg_body);
+ expando_create("C", expando_channel);
+ expando_create("H", expando_server_numeric);
+ expando_create("I", expando_last_invite);
+ expando_create("M", expando_chanmode);
+ expando_create("N", expando_nick);
+ expando_create("O", expando_statusoper);
+ expando_create("P", expando_chanop);
+ expando_create("Q", expando_query);
+ expando_create("R", expando_serverversion);
+ expando_create("S", expando_servername);
+ expando_create("T", expando_target);
+ expando_create("X", expando_userhost);
+ expando_create("Y", expando_realname);
+ expando_create("tag", expando_servertag);
+ expando_create("ircnet", expando_ircnet);
+
+ signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_add("event join", (SIGNAL_FUNC) event_join);
+ signal_add("command msg", (SIGNAL_FUNC) cmd_msg);
+}
+
+void irc_special_vars_deinit(void)
+{
+ g_free_not_null(last_privmsg_from);
+ g_free_not_null(last_sent_msg); g_free_not_null(last_sent_msg_body);
+ g_free_not_null(last_join); g_free_not_null(last_public_from);
+
+ expando_destroy(",", expando_lastmsg);
+ expando_destroy(".", expando_lastmymsg);
+ expando_destroy(":", expando_lastjoin);
+ expando_destroy(";", expando_lastpublic);
+ expando_destroy("A", expando_awaymsg);
+ expando_destroy("B", expando_lastmymsg_body);
+ expando_destroy("C", expando_channel);
+ expando_destroy("H", expando_server_numeric);
+ expando_destroy("I", expando_last_invite);
+ expando_destroy("M", expando_chanmode);
+ expando_destroy("N", expando_nick);
+ expando_destroy("O", expando_statusoper);
+ expando_destroy("P", expando_chanop);
+ expando_destroy("Q", expando_query);
+ expando_destroy("R", expando_serverversion);
+ expando_destroy("S", expando_servername);
+ expando_destroy("T", expando_target);
+ expando_destroy("X", expando_userhost);
+ expando_destroy("Y", expando_realname);
+ expando_destroy("tag", expando_servertag);
+ expando_destroy("ircnet", expando_ircnet);
+
+ signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_remove("event join", (SIGNAL_FUNC) event_join);
+ signal_remove("command msg", (SIGNAL_FUNC) cmd_msg);
+}
diff --git a/src/irc/core/irc-special-vars.h b/src/irc/core/irc-special-vars.h
new file mode 100644
index 00000000..49e0f1d6
--- /dev/null
+++ b/src/irc/core/irc-special-vars.h
@@ -0,0 +1,7 @@
+#ifndef __IRC_SPECIAL_VARS_H
+#define __IRC_SPECIAL_VARS_H
+
+void irc_special_vars_init(void);
+void irc_special_vars_deinit(void);
+
+#endif
diff --git a/src/irc/core/irc.c b/src/irc/core/irc.c
new file mode 100644
index 00000000..c25f32b6
--- /dev/null
+++ b/src/irc/core/irc.c
@@ -0,0 +1,440 @@
+/*
+ irc.c : irssi
+
+ Copyright (C) 1999 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 "modules.h"
+#include "network.h"
+#include "line-split.h"
+#include "rawlog.h"
+
+#include "irc.h"
+#include "irc-server.h"
+#include "channels.h"
+#include "server-redirect.h"
+
+char *current_server_event;
+static int signal_send_command;
+static int signal_default_event;
+static int signal_server_event;
+static int signal_server_incoming;
+
+static void cmd_send(IRC_SERVER_REC *server, const char *cmd, int send_now, int immediate)
+{
+ char str[513], *ptr;
+ int len, ret;
+
+ server->cmdcount++;
+
+ if (send_now)
+ rawlog_output(server->rawlog, cmd);
+
+ /* just check that we don't send any longer commands than 512 bytes.. */
+ strncpy(str, cmd, 510);
+ len = strlen(cmd);
+ str[len++] = 13; str[len++] = 10; str[len] = '\0';
+
+ ptr = str;
+ if (send_now) {
+ ret = net_transmit(server->handle, str, len);
+ if (ret == len) {
+ g_get_current_time(&server->last_cmd);
+ return;
+ }
+
+ /* we didn't transmit all data, try again a bit later.. */
+ ptr += ret;
+ server->cmd_last_split = TRUE;
+ }
+
+ /* add to queue */
+ ptr = g_strdup(ptr);
+ if (!immediate)
+ server->cmdqueue = g_slist_append(server->cmdqueue, ptr);
+ else if (send_now)
+ server->cmdqueue = g_slist_prepend(server->cmdqueue, ptr);
+ else
+ server->cmdqueue = g_slist_insert(server->cmdqueue, ptr, 1);
+}
+
+/* Send command to IRC server */
+void irc_send_cmd(IRC_SERVER_REC *server, const char *cmd)
+{
+ int send_now;
+
+ g_return_if_fail(cmd != NULL);
+ if (server == NULL) return;
+
+ send_now = !server->cmd_last_split &&
+ (server->cmdcount < server->max_cmds_at_once ||
+ server->cmd_queue_speed <= 0);
+
+ cmd_send(server, cmd, send_now, FALSE);
+}
+
+/* Send command to IRC server */
+void irc_send_cmdv(IRC_SERVER_REC *server, const char *cmd, ...)
+{
+ va_list args;
+ char *str;
+
+ va_start(args, cmd);
+
+ str = g_strdup_vprintf(cmd, args);
+ irc_send_cmd(server, str);
+ g_free(str);
+
+ va_end(args);
+}
+
+/* Send command to server immediately bypassing all flood protections
+ and queues. */
+void irc_send_cmd_now(IRC_SERVER_REC *server, const char *cmd)
+{
+ g_return_if_fail(cmd != NULL);
+ if (server == NULL) return;
+
+ cmd_send(server, cmd, !server->cmd_last_split, TRUE);
+}
+
+static char *split_nicks(const char *cmd, char **pre, char **nicks, char **post, int arg)
+{
+ char *p;
+
+ *pre = g_strdup(cmd);
+ *post = *nicks = NULL;
+ for (p = *pre; *p != '\0'; p++) {
+ if (!isspace(*p))
+ continue;
+
+ if (arg == 1) {
+ /* text after nicks */
+ *p++ = '\0';
+ while (isspace(*p)) p++;
+ *post = p;
+ break;
+ }
+
+ /* find nicks */
+ while (isspace(p[1])) p++;
+ if (--arg == 1) {
+ *p = '\0';
+ *nicks = p+1;
+ }
+ }
+
+ return *pre;
+}
+
+void irc_send_cmd_split(IRC_SERVER_REC *server, const char *cmd,
+ int nickarg, int max_nicks)
+{
+ char *str, *pre, *post, *nicks;
+ char **nicklist, **tmp;
+ GString *nickstr;
+ int count;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(cmd != NULL);
+
+ str = split_nicks(cmd, &pre, &nicks, &post, nickarg);
+
+ /* split the nicks */
+ nickstr = g_string_new(NULL);
+ nicklist = g_strsplit(nicks, ",", -1); count = 0;
+
+ tmp = nicklist;
+ for (;; tmp++) {
+ if (*tmp != NULL) {
+ g_string_sprintfa(nickstr, "%s,", *tmp);
+ if (++count < max_nicks)
+ continue;
+ }
+
+ count = 0;
+ g_string_truncate(nickstr, nickstr->len-1);
+ irc_send_cmdv(server, post == NULL ? "%s %s" : "%s %s %s",
+ pre, nickstr->str, post);
+ g_string_truncate(nickstr, 0);
+
+ if (*tmp == NULL || tmp[1] == NULL)
+ break;
+ }
+ g_strfreev(nicklist);
+ g_string_free(nickstr, TRUE);
+
+ g_free(str);
+}
+
+/* Nick can be in format "servertag/nick" - Update `nick' to
+ position "nick" and return "servertag" which you need to free */
+char *irc_nick_get_server(char **nick)
+{
+ char *ptr, *tag;
+
+ ptr = strchr(*nick, '/');
+ if (ptr == NULL) return NULL;
+ if (ptr == *nick) {
+ (*nick)++;
+ return NULL;
+ }
+
+ tag = g_strndup(*nick, (int) (ptr-*nick));
+ *nick = ptr+1;
+
+ return tag;
+}
+
+/* Get next parameter */
+char *event_get_param(char **data)
+{
+ char *pos;
+
+ g_return_val_if_fail(data != NULL, NULL);
+ g_return_val_if_fail(*data != NULL, NULL);
+
+ if (**data == ':') {
+ /* last parameter */
+ pos = *data;
+ *data += strlen(*data);
+ return pos+1;
+ }
+
+ pos = *data;
+ while (**data != '\0' && **data != ' ') (*data)++;
+ if (**data == ' ') *(*data)++ = '\0';
+
+ return pos;
+}
+
+/* Get count parameters from data */
+char *event_get_params(const char *data, int count, ...)
+{
+ char **str, *tmp, *duprec, *datad;
+ gboolean rest;
+ va_list args;
+
+ g_return_val_if_fail(data != NULL, NULL);
+
+ va_start(args, count);
+ duprec = datad = g_strdup(data);
+
+ rest = count & PARAM_FLAG_GETREST;
+ count = PARAM_WITHOUT_FLAGS(count);
+
+ while (count-- > 0) {
+ str = (char **) va_arg(args, char **);
+ if (count == 0 && rest) {
+ /* put the rest to last parameter */
+ tmp = *datad == ':' ? datad+1 : datad;
+ } else {
+ tmp = event_get_param(&datad);
+ }
+ if (str != NULL) *str = tmp;
+ }
+ va_end(args);
+
+ return duprec;
+}
+
+static void irc_server_event(const char *line, IRC_SERVER_REC *server, const char *nick, const char *address)
+{
+ char *event, *args, *callcmd;
+ GSList *list;
+
+ g_return_if_fail(line != NULL);
+
+ /* get command.. */
+ event = g_strconcat("event ", line, NULL);
+ args = strchr(event+6, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+ while (*args == ' ') args++;
+
+ list = server_redirect_getqueue((SERVER_REC *) server, event, args);
+ if (list == NULL)
+ callcmd = g_strdup(event);
+ else {
+ /* event is redirected somewhere else.. */
+ REDIRECT_REC *rec;
+
+ rec = list->data;
+ callcmd = g_strdup(rec->name);
+ rawlog_redirect(server->rawlog, callcmd);
+ server_redirect_remove_next((SERVER_REC *) server, event, list);
+ }
+
+ current_server_event = event+6;
+ g_strdown(callcmd);
+ if (!signal_emit(callcmd, 4, args, server, nick, address))
+ signal_emit_id(signal_default_event, 4, line, server, nick, address);
+ current_server_event = NULL;
+
+ g_free(callcmd);
+ g_free(event);
+}
+
+/* Read line from server */
+static int irc_receive_line(SERVER_REC *server, char **str)
+{
+ char tmpbuf[512];
+ int recvlen, ret;
+
+ g_return_val_if_fail(server != NULL, -1);
+ g_return_val_if_fail(str != NULL, -1);
+
+ recvlen = net_receive(server->handle, tmpbuf, sizeof(tmpbuf));
+
+ ret = line_split(tmpbuf, recvlen, str, (LINEBUF_REC **) &server->buffer);
+ if (ret == -1) {
+ /* connection lost */
+ server->connection_lost = TRUE;
+ server_disconnect(server);
+ }
+ return ret;
+}
+
+static char *irc_parse_prefix(char *line, char **nick, char **address)
+{
+ *nick = *address = NULL;
+
+ if (*line != ':')
+ return line;
+
+ *nick = ++line;
+ while (*line != '\0' && *line != ' ') {
+ if (*line == '!') {
+ *line = '\0';
+ *address = line+1;
+ }
+ line++;
+ }
+
+ if (*line == ' ') {
+ *line++ = '\0';
+ while (*line == ' ') line++;
+ }
+
+ return line;
+}
+
+/* Parse command line sent by server */
+static void irc_parse_incoming_line(IRC_SERVER_REC *server, char *line)
+{
+ char *nick, *address;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(line != NULL);
+
+ line = irc_parse_prefix(line, &nick, &address);
+ if (*line != '\0')
+ signal_emit_id(signal_server_event, 4, line, server, nick, address);
+}
+
+/* input function: handle incoming server messages */
+static void irc_parse_incoming(SERVER_REC *server)
+{
+ char *str;
+
+ g_return_if_fail(server != NULL);
+
+ while (irc_receive_line(server, &str) > 0) {
+ rawlog_input(server->rawlog, str);
+ signal_emit_id(signal_server_incoming, 2, server, str);
+ }
+}
+
+static void irc_init_server(IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ server->readtag =
+ g_input_add(server->handle, G_INPUT_READ,
+ (GInputFunction) irc_parse_incoming, server);
+}
+
+static void irc_deinit_server(IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ if (server->readtag > 0)
+ g_source_remove(server->readtag);
+}
+
+#define isoptchan(a) \
+ (ischannel((a)[0]) || ((a)[0] == '*' && ((a)[1] == '\0' || (a)[1] == ' ')))
+
+static char *irc_cmd_get_func(const char *data, int *count, va_list *vargs)
+{
+ WI_IRC_REC *item;
+ CHANNEL_REC *channel;
+ char *ret, *args, *chan, *p;
+
+ if ((*count & PARAM_FLAG_OPTCHAN) == 0)
+ return g_strdup(data);
+
+ *count &= ~PARAM_FLAG_OPTCHAN;
+ item = (WI_IRC_REC *) va_arg(*vargs, WI_IRC_REC *);
+ channel = irc_item_channel(item);
+
+ /* change first argument in data to full channel name. */
+ p = args = g_strdup(data);
+
+ chan = isoptchan(args) ? cmd_get_param(&args) : NULL;
+ if (chan != NULL && *chan == '!') {
+ /* whenever trying to send something to !channel,
+ change it to the real joined !XXXXXchannel */
+ channel = channel_find(channel->server, chan);
+ if (channel != NULL) chan = channel->name;
+ }
+
+ if (chan == NULL || strcmp(chan, "*") == 0) {
+ chan = channel == NULL ? "*" : channel->name;
+ }
+
+ ret = g_strconcat(chan, " ", args, NULL);
+ g_free(p);
+ return ret;
+}
+
+void irc_irc_init(void)
+{
+ cmd_get_add_func(irc_cmd_get_func);
+
+ signal_add("server event", (SIGNAL_FUNC) irc_server_event);
+ signal_add("server connected", (SIGNAL_FUNC) irc_init_server);
+ signal_add_first("server disconnected", (SIGNAL_FUNC) irc_deinit_server);
+ signal_add("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line);
+
+ current_server_event = NULL;
+ signal_send_command = module_get_uniq_id_str("signals", "send command");
+ signal_default_event = module_get_uniq_id_str("signals", "default event");
+ signal_server_event = module_get_uniq_id_str("signals", "server event");
+ signal_server_incoming = module_get_uniq_id_str("signals", "server incoming");
+}
+
+void irc_irc_deinit(void)
+{
+ signal_remove("server event", (SIGNAL_FUNC) irc_server_event);
+ signal_remove("server connected", (SIGNAL_FUNC) irc_init_server);
+ signal_remove("server disconnected", (SIGNAL_FUNC) irc_deinit_server);
+ signal_remove("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line);
+
+ module_uniq_destroy("IRC");
+ module_uniq_destroy("IRC SERVER");
+}
diff --git a/src/irc/core/irc.h b/src/irc/core/irc.h
new file mode 100644
index 00000000..aa5fbc9d
--- /dev/null
+++ b/src/irc/core/irc.h
@@ -0,0 +1,90 @@
+#ifndef __IRC_H
+#define __IRC_H
+
+#include "modules.h"
+#include "irc-server.h"
+
+/* From ircd 2.9.5:
+ none I line with ident
+ ^ I line with OTHER type ident
+ ~ I line, no ident
+ + i line with ident
+ = i line with OTHER type ident
+ - i line, no ident
+*/
+#define ishostflag(a) ((a) == '^' || (a) == '~' || (a) == '+' || (a) == '=' || (a) == '-')
+#define isnickflag(a) ((a) == '@' || (a) == '+' || (a) == '-' || (a) == '~')
+#define ischannel(a) ((a) == '#' || (a) == '&' || (a) == '!' || (a) == '+')
+
+/* values returned by module_category() */
+enum {
+ WI_IRC_CHANNEL,
+ WI_IRC_QUERY,
+ WI_IRC_DCC_CHAT
+};
+
+/* *MUST* have the same contents as WI_ITEM_REC in same order. */
+typedef struct {
+ int type;
+ GHashTable *module_data;
+
+ IRC_SERVER_REC *server;
+ char *name;
+
+ int new_data;
+} WI_IRC_REC;
+
+/* return TRUE if `item' is an IRC type. */
+#define irc_item_check(item) \
+ (item != NULL && module_find_id("IRC", ((WI_IRC_REC *) (item))->type) != -1)
+
+/* return `item' type, or -1 if it's not IRC type. */
+#define irc_item_get(item) \
+ (item == NULL ? -1 : module_find_id("IRC", ((WI_IRC_REC *) (item))->type))
+
+/* Return `item' if it's channel, NULL if it isn't. */
+#define irc_item_channel(item) \
+ (item != NULL && module_find_id("IRC", ((WI_IRC_REC *) (item))->type) == WI_IRC_CHANNEL ? \
+ (void *) (item) : NULL)
+
+/* Return `item' if it's query, NULL if it isn't. */
+#define irc_item_query(item) \
+ (item != NULL && module_find_id("IRC", ((WI_IRC_REC *) (item))->type) == WI_IRC_QUERY ? \
+ (void *) (item) : NULL)
+
+/* Return `item' if it's DCC chat, NULL if it isn't. */
+#define irc_item_dcc_chat(item) \
+ (item != NULL && module_find_id("IRC", ((WI_IRC_REC *) (item))->type) == WI_IRC_DCC_CHAT ? \
+ (void *) (item) : NULL)
+
+extern char *current_server_event; /* current server event being processed */
+
+/* Send command to IRC server */
+void irc_send_cmd(IRC_SERVER_REC *server, const char *cmd);
+void irc_send_cmdv(IRC_SERVER_REC *server, const char *cmd, ...) G_GNUC_PRINTF (2, 3);;
+/* Send command to IRC server, split to multiple commands if necessary so
+ that command will never have more target nicks than `max_nicks'. Nicks
+ are separated with commas. (works with /msg, /kick, ...) */
+void irc_send_cmd_split(IRC_SERVER_REC *server, const char *cmd,
+ int nickarg, int max_nicks);
+/* Send command to server immediately bypassing all flood protections
+ and queues. */
+void irc_send_cmd_now(IRC_SERVER_REC *server, const char *cmd);
+
+/* Nick can be in format "servertag/nick" - Update `nick' to
+ position "nick" and return "servertag" which you need to free */
+char *irc_nick_get_server(char **nick);
+
+#include "commands.h" /* contains the generic PARAM_FLAG_xxx defines */
+
+/* IRC specific: optional channel in first argument */
+#define PARAM_FLAG_OPTCHAN 0x10000000
+
+/* Get count parameters from data */
+char *event_get_param(char **data);
+char *event_get_params(const char *data, int count, ...);
+
+void irc_irc_init(void);
+void irc_irc_deinit(void);
+
+#endif
diff --git a/src/irc/core/ircnet-setup.c b/src/irc/core/ircnet-setup.c
new file mode 100644
index 00000000..0e19f3a5
--- /dev/null
+++ b/src/irc/core/ircnet-setup.c
@@ -0,0 +1,116 @@
+/*
+ ircnet-setup.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 "network.h"
+#include "signals.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "irc-server.h"
+#include "ircnet-setup.h"
+
+GSList *ircnets; /* list of available ircnets */
+
+/* Find the irc network by name */
+IRCNET_REC *ircnet_find(const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = ircnets; tmp != NULL; tmp = tmp->next) {
+ IRCNET_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void ircnet_destroy(IRCNET_REC *rec)
+{
+ ircnets = g_slist_remove(ircnets, rec);
+
+ g_free(rec->name);
+ if (rec->nick != NULL) g_free(rec->nick);
+ if (rec->username != NULL) g_free(rec->username);
+ if (rec->realname != NULL) g_free(rec->realname);
+ g_free(rec);
+}
+
+static IRCNET_REC *ircnet_add(CONFIG_NODE *node)
+{
+ IRCNET_REC *rec;
+ char *name, *nick, *username, *realname;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ name = config_node_get_str(node, "name", NULL);
+ if (name == NULL) return NULL;
+
+ nick = config_node_get_str(node, "nick", NULL);
+ username = config_node_get_str(node, "username", NULL);
+ realname = config_node_get_str(node, "realname", NULL);
+
+ rec = g_new0(IRCNET_REC, 1);
+ rec->max_kicks = config_node_get_int(node, "max_kicks", 0);
+ rec->max_msgs = config_node_get_int(node, "max_msgs", 0);
+ rec->max_modes = config_node_get_int(node, "max_modes", 0);
+ rec->max_whois = config_node_get_int(node, "max_whois", 0);
+
+ rec->name = g_strdup(name);
+ rec->nick = (nick == NULL || *nick == '\0') ? NULL : g_strdup(nick);
+ rec->username = (username == NULL || *username == '\0') ? NULL : g_strdup(username);
+ rec->realname = (realname == NULL || *realname == '\0') ? NULL : g_strdup(realname);
+
+ ircnets = g_slist_append(ircnets, rec);
+ return rec;
+}
+
+static void read_ircnets(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (ircnets != NULL)
+ ircnet_destroy(ircnets->data);
+
+ /* read ircnets */
+ node = iconfig_node_traverse("ircnets", FALSE);
+ if (node != NULL) {
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next)
+ ircnet_add(tmp->data);
+ }
+}
+
+void ircnets_setup_init(void)
+{
+ signal_add("setup reread", (SIGNAL_FUNC) read_ircnets);
+}
+
+void ircnets_setup_deinit(void)
+{
+ while (ircnets != NULL)
+ ircnet_destroy(ircnets->data);
+
+ signal_remove("setup reread", (SIGNAL_FUNC) read_ircnets);
+}
diff --git a/src/irc/core/ircnet-setup.h b/src/irc/core/ircnet-setup.h
new file mode 100644
index 00000000..dea530fb
--- /dev/null
+++ b/src/irc/core/ircnet-setup.h
@@ -0,0 +1,23 @@
+#ifndef __IRCNET_SETUP_H
+#define __IRCNET_SETUP_H
+
+typedef struct {
+ char *name;
+
+ char *nick;
+ char *username;
+ char *realname;
+
+ /* max. number of kicks/msgs/mode/whois per command */
+ int max_kicks, max_msgs, max_modes, max_whois;
+} IRCNET_REC;
+
+extern GSList *ircnets; /* list of available ircnets */
+
+/* Find the irc network by name */
+IRCNET_REC *ircnet_find(const char *name);
+
+void ircnets_setup_init(void);
+void ircnets_setup_deinit(void);
+
+#endif
diff --git a/src/irc/core/lag.c b/src/irc/core/lag.c
new file mode 100644
index 00000000..5b52fcce
--- /dev/null
+++ b/src/irc/core/lag.c
@@ -0,0 +1,178 @@
+/*
+ lag.c : irssi
+
+ Copyright (C) 1999 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 "misc.h"
+#include "settings.h"
+
+#include "irc.h"
+#include "irc-server.h"
+
+typedef struct {
+ IRC_SERVER_REC *server;
+ GTimeVal time;
+} LAG_REC;
+
+static gint timeout_tag;
+static GSList *lags;
+
+static LAG_REC *lag_find(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ for (tmp = lags; tmp != NULL; tmp = tmp->next) {
+ LAG_REC *lag = tmp->data;
+
+ if (lag->server == server)
+ return lag;
+ }
+
+ return NULL;
+}
+
+static void lag_free(LAG_REC *rec)
+{
+ lags = g_slist_remove(lags, rec);
+ g_free(rec);
+}
+
+static void lag_get(IRC_SERVER_REC *server)
+{
+ LAG_REC *lag;
+
+ g_return_if_fail(server != NULL);
+
+ lag = g_new0(LAG_REC, 1);
+ lags = g_slist_append(lags, lag);
+ lag->server = server;
+
+ g_get_current_time(&lag->time);
+
+ if (server->lag_sent == 0)
+ server->lag_sent = time(NULL);
+ server->lag_last_check = time(NULL);
+
+ /* NOTE: this will fail if there's any nick changes in buffer -
+ that's why this function should be called only when the buffer
+ is empty */
+ irc_send_cmdv(server, "NOTICE %s :\001IRSSILAG %ld %ld\001",
+ server->nick, lag->time.tv_sec, lag->time.tv_usec);
+}
+
+/* we use "ctcp reply" signal here, because "ctcp reply irssilag" can be
+ ignored with /IGNORE * CTCPS */
+static void sig_irssilag(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr, const char *target)
+{
+ GTimeVal now, sent;
+ LAG_REC *lag;
+
+ g_return_if_fail(data != NULL);
+
+ if (strncmp(data, "IRSSILAG ", 9) != 0)
+ return;
+ data += 9;
+
+ lag = lag_find(server);
+ if (lag == NULL) {
+ /* not expecting lag reply.. */
+ return;
+ }
+
+ if (g_strcasecmp(nick, server->nick) != 0) {
+ /* we didn't sent this - not a lag notice */
+ return;
+ }
+
+ /* OK, it is a lag notice. */
+ server->lag_sent = 0;
+
+ if (sscanf(data, "%ld %ld", &sent.tv_sec, &sent.tv_usec) == 2) {
+ g_get_current_time(&now);
+ server->lag = (int) get_timeval_diff(&now, &sent);
+ signal_emit("server lag", 1, server);
+ }
+
+ lag_free(lag);
+}
+
+static int sig_check_lag(void)
+{
+ GSList *tmp, *next;
+ time_t now;
+ int lag_check_time, max_lag;
+
+ lag_check_time = settings_get_int("lag_check_time");
+ max_lag = settings_get_int("lag_max_before_disconnect");
+
+ if (lag_check_time <= 0)
+ return 1;
+
+ now = time(NULL);
+ for (tmp = servers; tmp != NULL; tmp = next) {
+ IRC_SERVER_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (!irc_server_check(rec))
+ continue;
+
+ if (rec->lag_sent != 0) {
+ /* waiting for lag reply */
+ if (max_lag > 1 && now-rec->lag_sent > max_lag) {
+ /* too much lag, disconnect */
+ signal_emit("server lag disconnect", 1, rec);
+ rec->connection_lost = TRUE;
+ server_disconnect((SERVER_REC *) rec);
+ }
+ }
+ else if (rec->lag_last_check+lag_check_time < now &&
+ rec->cmdcount == 0 && rec->connected) {
+ /* no commands in buffer - get the lag */
+ lag_get(rec);
+ }
+ }
+
+ return 1;
+}
+
+static void sig_empty(void)
+{
+ /* don't print the "CTCP IRSSILAG reply .." text */
+}
+
+void lag_init(void)
+{
+ settings_add_int("misc", "lag_check_time", 30);
+ settings_add_int("misc", "lag_max_before_disconnect", 300);
+
+ lags = NULL;
+ timeout_tag = g_timeout_add(1000, (GSourceFunc) sig_check_lag, NULL);
+ signal_add("ctcp reply", (SIGNAL_FUNC) sig_irssilag);
+ signal_add("ctcp reply irssilag", (SIGNAL_FUNC) sig_empty);
+}
+
+void lag_deinit(void)
+{
+ g_source_remove(timeout_tag);
+ while (lags != NULL)
+ lag_free(lags->data);
+ signal_remove("ctcp reply", (SIGNAL_FUNC) sig_irssilag);
+ signal_remove("ctcp reply irssilag", (SIGNAL_FUNC) sig_empty);
+}
diff --git a/src/irc/core/lag.h b/src/irc/core/lag.h
new file mode 100644
index 00000000..219de265
--- /dev/null
+++ b/src/irc/core/lag.h
@@ -0,0 +1,7 @@
+#ifndef __LAG_H
+#define __LAG_H
+
+void lag_init(void);
+void lag_deinit(void);
+
+#endif
diff --git a/src/irc/core/masks.c b/src/irc/core/masks.c
new file mode 100644
index 00000000..17e3048a
--- /dev/null
+++ b/src/irc/core/masks.c
@@ -0,0 +1,170 @@
+/*
+ masks.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 "network.h"
+#include "misc.h"
+
+#include "irc.h"
+#include "masks.h"
+
+static int check_mask(const char *mask, int *wildcards)
+{
+ while (*mask != '\0') {
+ if (*mask == '!')
+ return TRUE;
+
+ if (*mask == '?' || *mask == '*')
+ *wildcards = TRUE;
+ mask++;
+ }
+
+ return FALSE;
+}
+
+int irc_mask_match(const char *mask, const char *nick, const char *user, const char *host)
+{
+ char *str;
+ int ret, wildcards;
+
+ if (!check_mask(mask, &wildcards)) {
+ return wildcards ?
+ match_wildcards(mask, nick) :
+ g_strcasecmp(mask, nick) == 0;
+ }
+
+ str = g_strdup_printf("%s!%s@%s", nick, user, host);
+ ret = match_wildcards(mask, str);
+ g_free(str);
+
+ return ret;
+}
+
+int irc_mask_match_address(const char *mask, const char *nick, const char *address)
+{
+ char *str;
+ int ret, wildcards;
+
+ g_return_val_if_fail(address != NULL, FALSE);
+
+ if (!check_mask(mask, &wildcards)) {
+ return wildcards ?
+ match_wildcards(mask, nick) :
+ g_strcasecmp(mask, nick) == 0;
+ }
+
+ str = g_strdup_printf("%s!%s", nick, address);
+ ret = match_wildcards(mask, str);
+ g_free(str);
+
+ return ret;
+}
+
+int irc_masks_match(const char *masks, const char *nick, const char *address)
+{
+ char **list, **tmp, *mask;
+
+ g_return_val_if_fail(masks != NULL, FALSE);
+
+ mask = g_strdup_printf("%s!%s", nick, address);
+ list = g_strsplit(masks, " ", -1);
+ for (tmp = list; *tmp != NULL; tmp++) {
+ if (strchr(*tmp, '!') == NULL && g_strcasecmp(*tmp, nick) == 0)
+ break;
+
+ if (match_wildcards(*tmp, mask))
+ break;
+ }
+ g_strfreev(list);
+ g_free(mask);
+
+ return *tmp != NULL;
+}
+
+static char *get_domain_mask(char *host)
+{
+ char *ptr;
+
+ if (strchr(host, '.') == NULL) {
+ /* no dots - toplevel domain or IPv6 address */
+ ptr = strrchr(host, ':');
+ if (ptr != NULL) {
+ /* IPv6 address, ban the last 64k addresses */
+ if (ptr[1] != '\0') strcpy(ptr+1, "*");
+ }
+
+ return host;
+ }
+
+ if (is_ipv4_address(host)) {
+ /* it's an IP address, change last digit to * */
+ ptr = strrchr(host, '.');
+ if (ptr != NULL && isdigit(ptr[1]))
+ strcpy(ptr+1, "*");
+ } else {
+ /* if more than one dot, skip the first
+ (dyn123.blah.net -> *.blah.net) */
+ ptr = strchr(host, '.');
+ if (ptr != NULL && strchr(ptr+1, '.') != NULL) {
+ host = ptr-1;
+ host[0] = '*';
+ }
+ }
+
+ return host;
+}
+
+char *irc_get_mask(const char *nick, const char *address, int flags)
+{
+ char *ret, *user, *host;
+
+ /* strip -, ^ or ~ from start.. */
+ user = g_strconcat("*", ishostflag(*address) ? address+1 : address, NULL);
+
+ /* split user and host */
+ host = strchr(user, '@');
+ if (host == NULL) {
+ g_free(user);
+ return NULL;
+ }
+ *host++ = '\0';
+
+ switch (flags & (IRC_MASK_HOST|IRC_MASK_DOMAIN)) {
+ case IRC_MASK_HOST:
+ /* we already have the host */
+ break;
+ case IRC_MASK_DOMAIN:
+ /* domain - *.blah.org */
+ host = get_domain_mask(host);
+ break;
+ default:
+ /* no domain/host */
+ host = "*";
+ break;
+ }
+
+ ret = g_strdup_printf("%s!%s@%s",
+ (flags & IRC_MASK_NICK) ? nick : "*",
+ (flags & IRC_MASK_USER) ? user : "*",
+ host);
+ g_free(user);
+
+ return ret;
+}
diff --git a/src/irc/core/masks.h b/src/irc/core/masks.h
new file mode 100644
index 00000000..a735dd9d
--- /dev/null
+++ b/src/irc/core/masks.h
@@ -0,0 +1,15 @@
+#ifndef __MASKS_H
+#define __MASKS_H
+
+#define IRC_MASK_NICK 0x01
+#define IRC_MASK_USER 0x02
+#define IRC_MASK_HOST 0x04
+#define IRC_MASK_DOMAIN 0x08
+
+int irc_mask_match(const char *mask, const char *nick, const char *user, const char *host);
+int irc_mask_match_address(const char *mask, const char *nick, const char *address);
+int irc_masks_match(const char *masks, const char *nick, const char *address);
+
+char *irc_get_mask(const char *nick, const char *address, int flags);
+
+#endif
diff --git a/src/irc/core/massjoin.c b/src/irc/core/massjoin.c
new file mode 100644
index 00000000..3cd0d31a
--- /dev/null
+++ b/src/irc/core/massjoin.c
@@ -0,0 +1,254 @@
+/*
+ massjoin.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 "common-setup.h"
+
+#include "channels.h"
+#include "irc.h"
+#include "nicklist.h"
+#include "irc-server.h"
+
+static int massjoin_tag;
+
+/* Massjoin support - really useful when trying to do things (like op/deop)
+ to people after netjoins. It sends
+ "massjoin #channel nick!user@host nick2!user@host ..." signals */
+static void event_join(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address)
+{
+ char *params, *channel, *ptr;
+ CHANNEL_REC *chanrec;
+ NICK_REC *nickrec;
+ GSList *nicks, *tmp;
+
+ g_return_if_fail(data != NULL);
+
+ if (g_strcasecmp(nick, server->nick) == 0) {
+ /* You joined, no need to do anything here */
+ return;
+ }
+
+ params = event_get_params(data, 1, &channel);
+ ptr = strchr(channel, 7); /* ^G does something weird.. */
+ if (ptr != NULL) *ptr = '\0';
+
+ /* find channel */
+ chanrec = channel_find(server, channel);
+ g_free(params);
+ if (chanrec == NULL) return;
+
+ /* add user to nicklist */
+ nickrec = nicklist_insert(chanrec, nick, FALSE, FALSE, TRUE);
+ nickrec->host = g_strdup(address);
+
+ if (chanrec->massjoins == 0) {
+ /* no nicks waiting in massjoin queue */
+ chanrec->massjoin_start = time(NULL);
+ chanrec->last_massjoins = 0;
+ }
+
+ /* Check if user is already in some other channel,
+ get the realname from there */
+ nicks = nicklist_get_same(server, nick);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+ NICK_REC *rec = tmp->next->data;
+
+ if (rec->realname != NULL) {
+ nickrec->last_check = rec->last_check;
+ nickrec->realname = g_strdup(rec->realname);
+ nickrec->gone = rec->gone;
+ }
+ }
+ g_slist_free(nicks);
+
+ chanrec->massjoins++;
+}
+
+static void event_part(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr)
+{
+ char *params, *channel, *reason;
+ CHANNEL_REC *chanrec;
+ NICK_REC *nickrec;
+
+ g_return_if_fail(data != NULL);
+
+ if (g_strcasecmp(nick, server->nick) == 0) {
+ /* you left channel, no need to do anything here */
+ return;
+ }
+
+ params = event_get_params(data, 2, &channel, &reason);
+
+ /* find channel */
+ chanrec = channel_find(server, channel);
+ if (chanrec == NULL) {
+ g_free(params);
+ return;
+ }
+
+ /* remove user from nicklist */
+ nickrec = nicklist_find(chanrec, nick);
+ if (nickrec != NULL) {
+ if (nickrec->send_massjoin) {
+ /* quick join/part after which it's useless to send
+ nick in massjoin */
+ chanrec->massjoins--;
+ }
+ nicklist_remove(chanrec, nickrec);
+ }
+ g_free(params);
+}
+
+static void event_quit(const char *data, IRC_SERVER_REC *server, const char *nick)
+{
+ CHANNEL_REC *channel;
+ NICK_REC *nickrec;
+ GSList *nicks, *tmp;
+
+ g_return_if_fail(data != NULL);
+
+ if (g_strcasecmp(nick, server->nick) == 0) {
+ /* you quit, don't do anything here */
+ return;
+ }
+
+ /* Remove nick from all channels */
+ nicks = nicklist_get_same(server, nick);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+ channel = tmp->data;
+ nickrec = tmp->next->data;
+
+ if (nickrec->send_massjoin) {
+ /* quick join/quit after which it's useless to
+ send nick in massjoin */
+ channel->massjoins--;
+ }
+ nicklist_remove(channel, nickrec);
+ }
+ g_slist_free(nicks);
+}
+
+static void event_kick(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *channel, *nick, *reason;
+ CHANNEL_REC *chanrec;
+ NICK_REC *nickrec;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, &channel, &nick, &reason);
+
+ if (g_strcasecmp(nick, server->nick) == 0) {
+ /* you were kicked, no need to do anything */
+ g_free(params);
+ return;
+ }
+
+ /* Remove user from nicklist */
+ chanrec = channel_find(server, channel);
+ nickrec = chanrec == NULL ? NULL : nicklist_find(chanrec, nick);
+ if (chanrec != NULL && nickrec != NULL) {
+ if (nickrec->send_massjoin) {
+ /* quick join/kick after which it's useless to
+ send nick in massjoin */
+ chanrec->massjoins--;
+ }
+ nicklist_remove(chanrec, nickrec);
+ }
+
+ g_free(params);
+}
+
+static void massjoin_send_hash(gpointer key, NICK_REC *nick, GSList **list)
+{
+ if (nick->send_massjoin) {
+ nick->send_massjoin = FALSE;
+ *list = g_slist_append(*list, nick);
+ }
+}
+
+/* Send channel's massjoin list signal */
+static void massjoin_send(CHANNEL_REC *channel)
+{
+ GSList *list;
+
+ list = NULL;
+ g_hash_table_foreach(channel->nicks, (GHFunc) massjoin_send_hash, &list);
+
+ channel->massjoins = 0;
+ signal_emit("massjoin", 2, channel, list);
+ g_slist_free(list);
+}
+
+static void server_check_massjoins(IRC_SERVER_REC *server, time_t max)
+{
+ GSList *tmp;
+
+ /* Scan all channels through for massjoins */
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec = tmp->data;
+
+ if (rec->massjoins <= 0)
+ continue;
+
+ if (rec->massjoin_start < max || /* We've waited long enough */
+ rec->massjoins-5 < rec->last_massjoins) { /* Less than 5 joins since last check */
+ /* send them */
+ massjoin_send(rec);
+ } else {
+ /* Wait for some more.. */
+ rec->last_massjoins = rec->massjoins;
+ }
+ }
+
+}
+
+static int sig_massjoin_timeout(void)
+{
+ GSList *tmp;
+ time_t max;
+
+ max = time(NULL)-MAX_MASSJOIN_WAIT;
+ for (tmp = servers; tmp != NULL; tmp = tmp->next)
+ server_check_massjoins(tmp->data, max);
+
+ return 1;
+}
+
+void massjoin_init(void)
+{
+ massjoin_tag = g_timeout_add(1000, (GSourceFunc) sig_massjoin_timeout, NULL);
+
+ signal_add("event join", (SIGNAL_FUNC) event_join);
+ signal_add("event part", (SIGNAL_FUNC) event_part);
+ signal_add("event kick", (SIGNAL_FUNC) event_kick);
+ signal_add("event quit", (SIGNAL_FUNC) event_quit);
+}
+
+void massjoin_deinit(void)
+{
+ g_source_remove(massjoin_tag);
+
+ signal_remove("event join", (SIGNAL_FUNC) event_join);
+ signal_remove("event part", (SIGNAL_FUNC) event_part);
+ signal_remove("event kick", (SIGNAL_FUNC) event_kick);
+ signal_remove("event quit", (SIGNAL_FUNC) event_quit);
+}
diff --git a/src/irc/core/massjoin.h b/src/irc/core/massjoin.h
new file mode 100644
index 00000000..021884af
--- /dev/null
+++ b/src/irc/core/massjoin.h
@@ -0,0 +1,7 @@
+#ifndef __MASSJOIN_H
+#define __MASSJOIN_H
+
+void massjoin_init(void);
+void massjoin_deinit(void);
+
+#endif
diff --git a/src/irc/core/mode-lists.c b/src/irc/core/mode-lists.c
new file mode 100644
index 00000000..21e7b563
--- /dev/null
+++ b/src/irc/core/mode-lists.c
@@ -0,0 +1,234 @@
+/*
+ mode-lists.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 "misc.h"
+#include "signals.h"
+
+#include "irc.h"
+#include "mode-lists.h"
+
+static void ban_free(GSList **list, BAN_REC *rec)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(rec != NULL);
+
+ g_free(rec->ban);
+ g_free_not_null(rec->setby);
+ g_free(rec);
+
+ *list = g_slist_remove(*list, rec);
+}
+
+void banlist_free(GSList *banlist)
+{
+ while (banlist != NULL)
+ ban_free(&banlist, banlist->data);
+}
+
+BAN_REC *banlist_add(CHANNEL_REC *channel, const char *ban, const char *nick, time_t time)
+{
+ BAN_REC *rec;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+ g_return_val_if_fail(ban != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = g_new(BAN_REC, 1);
+ rec->ban = g_strdup(ban);
+ rec->setby = g_strdup(nick);
+ rec->time = time;
+
+ channel->banlist = g_slist_append(channel->banlist, rec);
+
+ signal_emit("ban new", 1, rec);
+ return rec;
+}
+
+void banlist_remove(CHANNEL_REC *channel, const char *ban)
+{
+ GSList *tmp;
+
+ g_return_if_fail(ban != NULL);
+
+ for (tmp = channel->banlist; tmp != NULL; tmp = tmp->next)
+ {
+ BAN_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->ban, ban) == 0)
+ {
+ signal_emit("ban remove", 1, rec);
+ ban_free(&channel->banlist, rec);
+ break;
+ }
+ }
+}
+
+BAN_REC *banlist_exception_add(CHANNEL_REC *channel, const char *ban, const char *nick, time_t time)
+{
+ BAN_REC *rec;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+ g_return_val_if_fail(ban != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = g_new(BAN_REC, 1);
+ rec->ban = g_strdup(ban);
+ rec->setby = g_strdup(nick);
+ rec->time = time;
+
+ channel->ebanlist = g_slist_append(channel->ebanlist, rec);
+
+ signal_emit("ban exception new", 1, rec);
+ return rec;
+}
+
+void banlist_exception_remove(CHANNEL_REC *channel, const char *ban)
+{
+ GSList *tmp;
+
+ g_return_if_fail(ban != NULL);
+
+ for (tmp = channel->ebanlist; tmp != NULL; tmp = tmp->next)
+ {
+ BAN_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->ban, ban) == 0)
+ {
+ signal_emit("ban exception remove", 1, rec);
+ ban_free(&channel->ebanlist, rec);
+ break;
+ }
+ }
+}
+
+static void invitelist_free(CHANNEL_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ g_slist_foreach(channel->invitelist, (GFunc) g_free, NULL);
+ g_slist_free(channel->invitelist);
+}
+
+void invitelist_add(CHANNEL_REC *channel, const char *mask)
+{
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(mask != NULL);
+
+ channel->invitelist = g_slist_append(channel->invitelist, g_strdup(mask));
+
+ signal_emit("invitelist new", 2, channel, mask);
+}
+
+void invitelist_remove(CHANNEL_REC *channel, const char *mask)
+{
+ GSList *tmp;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(mask != NULL);
+
+ tmp = gslist_find_icase_string(channel->invitelist, mask);
+ if (tmp == NULL) return;
+
+ signal_emit("invitelist remove", 2, channel, tmp->data);
+ g_free(tmp->data);
+ channel->invitelist = g_slist_remove(channel->invitelist, tmp->data);
+}
+
+static void channel_destroyed(CHANNEL_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ banlist_free(channel->banlist);
+ banlist_free(channel->ebanlist);
+ invitelist_free(channel);
+}
+
+static void event_banlist(const char *data, IRC_SERVER_REC *server)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel, *ban, *setby, *tims;
+ time_t tim;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 5, NULL, &channel, &ban, &setby, &tims);
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL) {
+ if (sscanf(tims, "%ld", (long *) &tim) != 1)
+ tim = time(NULL);
+
+ banlist_add(chanrec, ban, setby, tim);
+ }
+ g_free(params);
+}
+
+static void event_ebanlist(const char *data, IRC_SERVER_REC *server)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel, *ban, *setby, *tims;
+ time_t tim;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 5, NULL, &channel, &ban, &setby, &tims);
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL) {
+ if (sscanf(tims, "%ld", (long *) &tim) != 1)
+ tim = time(NULL);
+
+ banlist_exception_add(chanrec, ban, setby, tim);
+ }
+ g_free(params);
+}
+
+static void event_invite_list(const char *data, IRC_SERVER_REC *server)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel, *invite;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &channel, &invite);
+ chanrec = channel_find(server, channel);
+
+ if (chanrec != NULL)
+ invitelist_add(chanrec, invite);
+
+ g_free(params);
+}
+
+void mode_lists_init(void)
+{
+ signal_add("channel destroyed", (SIGNAL_FUNC) channel_destroyed);
+
+ signal_add("chanquery ban", (SIGNAL_FUNC) event_banlist);
+ signal_add("chanquery eban", (SIGNAL_FUNC) event_ebanlist);
+ signal_add("chanquery ilist", (SIGNAL_FUNC) event_invite_list);
+}
+
+void mode_lists_deinit(void)
+{
+ signal_remove("channel destroyed", (SIGNAL_FUNC) channel_destroyed);
+
+ signal_remove("chanquery ban", (SIGNAL_FUNC) event_banlist);
+ signal_remove("chanquery eban", (SIGNAL_FUNC) event_ebanlist);
+ signal_remove("chanquery ilist", (SIGNAL_FUNC) event_invite_list);
+}
diff --git a/src/irc/core/mode-lists.h b/src/irc/core/mode-lists.h
new file mode 100644
index 00000000..473ba5be
--- /dev/null
+++ b/src/irc/core/mode-lists.h
@@ -0,0 +1,24 @@
+#ifndef __MODE_LISTS_H
+#define __MODE_LISTS_H
+
+#include "channels.h"
+
+typedef struct {
+ char *ban;
+ char *setby;
+ time_t time;
+} BAN_REC;
+
+BAN_REC *banlist_add(CHANNEL_REC *channel, const char *ban, const char *nick, time_t time);
+void banlist_remove(CHANNEL_REC *channel, const char *ban);
+
+BAN_REC *banlist_exception_add(CHANNEL_REC *channel, const char *ban, const char *nick, time_t time);
+void banlist_exception_remove(CHANNEL_REC *channel, const char *ban);
+
+void invitelist_add(CHANNEL_REC *channel, const char *mask);
+void invitelist_remove(CHANNEL_REC *channel, const char *mask);
+
+void mode_lists_init(void);
+void mode_lists_deinit(void);
+
+#endif
diff --git a/src/irc/core/modes.c b/src/irc/core/modes.c
new file mode 100644
index 00000000..b73d5eb8
--- /dev/null
+++ b/src/irc/core/modes.c
@@ -0,0 +1,451 @@
+/*
+ modes.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 "commands.h"
+#include "signals.h"
+
+#include "irc.h"
+#include "mode-lists.h"
+#include "nicklist.h"
+
+/* Change nick's mode in channel */
+static void nick_mode_change(CHANNEL_REC *channel, const char *nick, const char mode, gboolean set)
+{
+ NICK_REC *nickrec;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nick != NULL);
+
+ nickrec = nicklist_find(channel, nick);
+ if (nickrec == NULL) return; /* No /names list got yet */
+
+ if (mode == '@') nickrec->op = set;
+ if (mode == '+') nickrec->voice = set;
+
+ signal_emit("nick mode changed", 2, channel, nickrec);
+}
+
+/* Parse channel mode string */
+void parse_channel_modes(CHANNEL_REC *channel, const char *setby, const char *mode)
+{
+ char *dup, *modestr, *ptr, *curmode, type;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(setby != NULL);
+ g_return_if_fail(modestr != NULL);
+
+ type = '+';
+
+ dup = modestr = g_strdup(mode);
+ curmode = cmd_get_param(&modestr);
+ while (*curmode != '\0') {
+ switch (*curmode) {
+ case '+':
+ case '-':
+ type = *curmode;
+ break;
+
+ case 'b':
+ ptr = cmd_get_param(&modestr);
+ if (*ptr == '\0') break;
+
+ if (type == '+')
+ banlist_add(channel, ptr, setby, time(NULL));
+ else
+ banlist_remove(channel, ptr);
+ break;
+
+ case 'e':
+ ptr = cmd_get_param(&modestr);
+ if (*ptr == '\0') break;
+
+ if (type == '+')
+ banlist_exception_add(channel, ptr, setby, time(NULL));
+ else
+ banlist_exception_remove(channel, ptr);
+ break;
+
+ case 'I':
+ ptr = cmd_get_param(&modestr);
+ if (*ptr == '\0') break;
+
+ if (type == '+')
+ invitelist_add(channel, ptr);
+ else
+ invitelist_remove(channel, ptr);
+ break;
+
+ case 'v':
+ ptr = cmd_get_param(&modestr);
+ if (*ptr != '\0')
+ nick_mode_change(channel, ptr, '+', type == '+');
+ break;
+
+ case 'o':
+ ptr = cmd_get_param(&modestr);
+ if (*ptr == '\0') break;
+
+ if (strcmp(channel->server->nick, ptr) == 0)
+ channel->chanop = type == '+' ? TRUE : FALSE;
+ nick_mode_change(channel, ptr, '@', type == '+');
+ break;
+
+ case 'l':
+ if (type == '-')
+ channel->limit = 0;
+ else {
+ ptr = cmd_get_param(&modestr);
+ sscanf(ptr, "%d", &channel->limit);
+ }
+ signal_emit("channel mode changed", 1, channel);
+ break;
+ case 'k':
+ ptr = cmd_get_param(&modestr);
+ if (*ptr != '\0' || type == '-') {
+ g_free_and_null(channel->key);
+ if (type == '+') {
+ channel->key = g_strdup(ptr);
+ channel->mode_key = TRUE;
+ }
+ }
+ signal_emit("channel mode changed", 1, channel);
+ break;
+
+ default:
+ switch (*curmode) {
+ case 'i':
+ channel->mode_invite = type == '+';
+ break;
+ case 'm':
+ channel->mode_moderate = type == '+';
+ break;
+ case 's':
+ channel->mode_secret = type == '+';
+ break;
+ case 'p':
+ channel->mode_private = type == '+';
+ break;
+ case 'n':
+ channel->mode_nomsgs = type == '+';
+ break;
+ case 't':
+ channel->mode_optopic = type == '+';
+ break;
+ case 'a':
+ channel->mode_anonymous = type == '+';
+ break;
+ case 'r':
+ channel->mode_reop = type == '+';
+ break;
+ }
+ signal_emit("channel mode changed", 1, channel);
+ break;
+ }
+
+ curmode++;
+ }
+ g_free(dup);
+
+ if (!channel->mode_key && channel->key != NULL) {
+ /* join was used with key but there's no key set
+ in channel modes.. */
+ g_free(channel->key);
+ channel->key = NULL;
+ }
+}
+
+static int compare_char(const void *p1, const void *p2)
+{
+ const char *c1 = p1, *c2 = p2;
+
+ return *c1 < *c2 ? -1 :
+ (*c1 > *c2 ? 1 : 0);
+}
+
+/* add `mode' to `old' - return newly allocated mode. */
+char *modes_join(const char *old, const char *mode)
+{
+ GString *newmode;
+ char type, *p;
+
+ g_return_val_if_fail(mode != NULL, NULL);
+
+ type = '+';
+ newmode = g_string_new(old);
+ while (*mode != '\0' && *mode != ' ') {
+ if (*mode == '+' || *mode == '-') {
+ type = *mode;
+ } else {
+ p = strchr(newmode->str, *mode);
+
+ if (type == '+' && p == NULL)
+ g_string_append_c(newmode, *mode);
+ else if (type == '-' && p != NULL)
+ g_string_erase(newmode, (int) (p-newmode->str), 1);
+ }
+
+ mode++;
+ }
+
+ qsort(newmode->str, sizeof(char), newmode->len, compare_char);
+
+ p = newmode->str;
+ g_string_free(newmode, FALSE);
+ return p;
+}
+
+/* Parse user mode string */
+static void parse_user_mode(IRC_SERVER_REC *server, const char *modestr)
+{
+ char *newmode, *oldmode;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(modestr != NULL);
+
+ newmode = modes_join(server->usermode, modestr);
+ oldmode = server->usermode;
+ server->usermode = newmode;
+ server->server_operator = (strchr(newmode, 'o') != NULL);
+
+ signal_emit("user mode changed", 2, server, oldmode);
+ g_free_not_null(oldmode);
+}
+
+static void event_user_mode(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *nick, *mode;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &mode);
+ parse_user_mode(server, mode);
+
+ g_free(params);
+}
+
+static void event_mode(const char *data, IRC_SERVER_REC *server, const char *nick)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *channel, *mode;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &channel, &mode);
+
+ if (!ischannel(*channel)) {
+ /* user mode change */
+ parse_user_mode(server, mode);
+ } else {
+ /* channel mode change */
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL)
+ parse_channel_modes(chanrec, nick, mode);
+ }
+
+ g_free(params);
+}
+
+static void event_away(const char *data, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ server->usermode_away = TRUE;
+ signal_emit("away mode changed", 1, server);
+}
+
+static void event_unaway(const char *data, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ server->usermode_away = FALSE;
+ g_free_and_null(server->away_reason);
+ signal_emit("away mode changed", 1, server);
+}
+
+void channel_set_singlemode(IRC_SERVER_REC *server, const char *channel, const char *nicks, const char *mode)
+{
+ GString *str;
+ int num, modepos;
+ char **nick, **nicklist;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nicks != NULL);
+ g_return_if_fail(mode != NULL);
+ if (*nicks == '\0') return;
+
+ num = modepos = 0;
+ str = g_string_new(NULL);
+
+ nicklist = g_strsplit(nicks, " ", -1);
+ for (nick = nicklist; *nick != NULL; nick++) {
+ if (num == 0)
+ {
+ g_string_sprintf(str, "MODE %s %s", channel, mode);
+ modepos = str->len;
+ } else {
+ /* insert the mode string */
+ g_string_insert(str, modepos, mode);
+ }
+
+ g_string_sprintfa(str, " %s", *nick);
+
+ if (++num == server->connrec->max_modes) {
+ /* max. modes / command reached, send to server */
+ irc_send_cmd(server, str->str);
+ num = 0;
+ }
+ }
+ if (num > 0) irc_send_cmd(server, str->str);
+
+ g_strfreev(nicklist);
+ g_string_free(str, TRUE);
+}
+
+#define MODE_HAS_ARG(c) ((c) == 'b' || (c) == 'e' || (c) == 'I' || \
+ (c) == 'v' || (c) == 'o' || (c) == 'l' || (c) == 'k')
+
+void channel_set_mode(IRC_SERVER_REC *server, const char *channel, const char *mode)
+{
+ char *modestr, *curmode, type, *orig;
+ GString *tmode, *targs;
+ int count;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(modestr != NULL);
+
+ tmode = g_string_new(NULL);
+ targs = g_string_new(NULL);
+ type = '+'; count = 0;
+
+ orig = modestr = g_strdup(mode);
+
+ curmode = cmd_get_param(&modestr);
+ for (; *curmode != '\0'; curmode++) {
+ if (*curmode == '+' || *curmode == '-') {
+ type = *curmode;
+ continue;
+ }
+
+ if (count == server->connrec->max_modes && MODE_HAS_ARG(*curmode)) {
+ irc_send_cmdv(server, "MODE %s %s%s", channel, tmode->str, targs->str);
+
+ count = 0;
+ g_string_truncate(tmode, 0);
+ g_string_truncate(targs, 0);
+ }
+
+ g_string_append_c(tmode, *curmode);
+
+ if (MODE_HAS_ARG(*curmode)) {
+ char *arg;
+
+ count++;
+ arg = cmd_get_param(&modestr);
+ if (*arg != '\0') g_string_sprintfa(targs, " %s", arg);
+ }
+ }
+
+ if (tmode->len > 0)
+ irc_send_cmdv(server, "MODE %s %s%s", channel, tmode->str, targs->str);
+
+ g_string_free(tmode, TRUE);
+ g_string_free(targs, TRUE);
+ g_free(orig);
+}
+
+static void cmd_op(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ if (!irc_item_channel(item)) return;
+ channel_set_singlemode(server, item->name, data, "+o");
+}
+
+static void cmd_deop(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ if (!irc_item_channel(item)) return;
+ channel_set_singlemode(server, item->name, data, "-o");
+}
+
+static void cmd_voice(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ if (!irc_item_channel(item)) return;
+ channel_set_singlemode(server, item->name, data, "+v");
+}
+
+static void cmd_devoice(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ if (!irc_item_channel(item)) return;
+ channel_set_singlemode(server, item->name, data, "-v");
+}
+
+static void cmd_mode(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ char *params, *target, *mode;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected || !irc_server_check(server))
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &mode);
+ if (strcmp(target, "*") == 0) {
+ if (!irc_item_channel(item))
+ cmd_return_error(CMDERR_NOT_JOINED);
+
+ target = item->name;
+ }
+ if (*target == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (ischannel(*target))
+ channel_set_mode(server, target, mode);
+ else
+ irc_send_cmdv(server, "MODE %s %s", target, mode);
+
+ g_free(params);
+}
+
+void modes_init(void)
+{
+ signal_add("event 221", (SIGNAL_FUNC) event_user_mode);
+ signal_add("event 305", (SIGNAL_FUNC) event_unaway);
+ signal_add("event 306", (SIGNAL_FUNC) event_away);
+ signal_add("event mode", (SIGNAL_FUNC) event_mode);
+
+ command_bind("op", NULL, (SIGNAL_FUNC) cmd_op);
+ command_bind("deop", NULL, (SIGNAL_FUNC) cmd_deop);
+ command_bind("voice", NULL, (SIGNAL_FUNC) cmd_voice);
+ command_bind("devoice", NULL, (SIGNAL_FUNC) cmd_devoice);
+ command_bind("mode", NULL, (SIGNAL_FUNC) cmd_mode);
+}
+
+void modes_deinit(void)
+{
+ signal_remove("event 221", (SIGNAL_FUNC) event_user_mode);
+ signal_remove("event 305", (SIGNAL_FUNC) event_unaway);
+ signal_remove("event 306", (SIGNAL_FUNC) event_away);
+ signal_remove("event mode", (SIGNAL_FUNC) event_mode);
+
+ command_unbind("op", (SIGNAL_FUNC) cmd_op);
+ command_unbind("deop", (SIGNAL_FUNC) cmd_deop);
+ command_unbind("voice", (SIGNAL_FUNC) cmd_voice);
+ command_unbind("devoice", (SIGNAL_FUNC) cmd_devoice);
+ command_unbind("mode", (SIGNAL_FUNC) cmd_mode);
+}
diff --git a/src/irc/core/modes.h b/src/irc/core/modes.h
new file mode 100644
index 00000000..10ea28d5
--- /dev/null
+++ b/src/irc/core/modes.h
@@ -0,0 +1,18 @@
+#ifndef __MODES_H
+#define __MODES_H
+
+#include "server.h"
+#include "channels.h"
+
+void modes_init(void);
+void modes_deinit(void);
+
+/* add `mode' to `old' - return newly allocated mode. */
+char *modes_join(const char *old, const char *mode);
+
+void parse_channel_modes(CHANNEL_REC *channel, const char *setby, const char *modestr);
+
+void channel_set_singlemode(IRC_SERVER_REC *server, const char *channel, const char *nicks, const char *mode);
+void channel_set_mode(IRC_SERVER_REC *server, const char *channel, const char *mode);
+
+#endif
diff --git a/src/irc/core/module.h b/src/irc/core/module.h
new file mode 100644
index 00000000..00599d91
--- /dev/null
+++ b/src/irc/core/module.h
@@ -0,0 +1,4 @@
+#include "common.h"
+
+#define MODULE_NAME "irc/core"
+
diff --git a/src/irc/core/netsplit.c b/src/irc/core/netsplit.c
new file mode 100644
index 00000000..b455f740
--- /dev/null
+++ b/src/irc/core/netsplit.c
@@ -0,0 +1,270 @@
+/*
+ netsplit.c : irssi
+
+ Copyright (C) 1999 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 "misc.h"
+#include "common-setup.h"
+
+#include "irc-server.h"
+#include "netsplit.h"
+
+static int split_tag;
+
+static NETSPLIT_REC *netsplit_add(IRC_SERVER_REC *server, const char *nick, const char *address, const char *servers)
+{
+ NETSPLIT_REC *rec;
+ NETSPLIT_CHAN_REC *splitchan;
+ NICK_REC *nickrec;
+ GSList *tmp;
+ char *p, *dupservers;
+
+ g_return_val_if_fail(server != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+ g_return_val_if_fail(address != NULL, NULL);
+
+ dupservers = g_strdup(servers);
+ p = strchr(dupservers, ' ');
+ if (p == NULL) {
+ g_free(dupservers);
+ g_return_val_if_fail(p != NULL, NULL);
+ }
+ *p++ = '\0';
+
+ rec = g_new0(NETSPLIT_REC, 1);
+ rec->nick = g_strdup(nick);
+ rec->address = g_strdup(address);
+ rec->destroy = time(NULL)+NETSPLIT_MAX_REMEMBER;
+
+ /* get splitted servers */
+ rec->server = g_strdup(dupservers);
+ rec->destserver = g_strdup(p);
+ g_free(dupservers);
+
+ /* copy the channel nick records.. */
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *channel = tmp->data;
+
+ nickrec = nicklist_find(channel, nick);
+ if (nickrec == NULL)
+ continue;
+
+ splitchan = g_new0(NETSPLIT_CHAN_REC, 1);
+ splitchan->name = g_strdup(channel->name);
+ memcpy(&splitchan->nick, nickrec, sizeof(NICK_REC));
+
+ rec->channels = g_slist_append(rec->channels, splitchan);
+ }
+
+ g_hash_table_insert(server->splits, rec->nick, rec);
+ signal_emit("netsplit add", 1, rec);
+ return rec;
+}
+
+static void netsplit_destroy(NETSPLIT_REC *rec)
+{
+ GSList *tmp;
+
+ g_return_if_fail(rec != NULL);
+
+ signal_emit("netsplit remove", 1, rec);
+ for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) {
+ NETSPLIT_CHAN_REC *rec = tmp->data;
+
+ g_free(rec->name);
+ g_free(rec);
+ }
+
+ g_free(rec->server);
+ g_free(rec->destserver);
+ g_free(rec->nick);
+ g_free(rec->address);
+ g_free(rec);
+}
+
+static void netsplit_destroy_hash(gpointer key, NETSPLIT_REC *rec)
+{
+ netsplit_destroy(rec);
+}
+
+NETSPLIT_REC *netsplit_find(IRC_SERVER_REC *server, const char *nick, const char *address)
+{
+ NETSPLIT_REC *rec;
+
+ g_return_val_if_fail(server != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = g_hash_table_lookup(server->splits, nick);
+ if (rec == NULL) return NULL;
+
+ return (address == NULL || g_strcasecmp(rec->address, address) == 0) ? rec : NULL;
+}
+
+NICK_REC *netsplit_find_channel(IRC_SERVER_REC *server, const char *nick, const char *address, const char *channel)
+{
+ NETSPLIT_REC *rec;
+ GSList *tmp;
+
+ rec = netsplit_find(server, nick, address);
+ if (rec == NULL) return NULL;
+
+ for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) {
+ NETSPLIT_CHAN_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, channel) == 0)
+ return &rec->nick;
+ }
+
+ return NULL;
+}
+
+static int is_split(const char *msg)
+{
+ char *params, *host1, *host2, *p;
+ int ok;
+
+ g_return_val_if_fail(msg != NULL, FALSE);
+
+ /* must have only two words */
+ p = strchr(msg, ' ');
+ if (p == NULL || strchr(p+1, ' ') != NULL) return FALSE;
+
+ /* check that it looks ok.. */
+ if (!match_wildcards("*.* *.*", msg) ||
+ match_wildcards("*..*", msg) || strstr(msg, "))") != NULL)
+ return FALSE;
+
+ /* get the two hosts */
+ ok = FALSE;
+ params = cmd_get_params(msg, 2, &host1, &host2);
+ if (g_strcasecmp(host1, host2) != 0) { /* hosts can't be same.. */
+ /* check that domain length is 2 or 3 */
+ p = strrchr(host1, '.');
+ if (p != NULL && (strlen(p+1) == 2 || strlen(p+1) == 3)) {
+ p = strrchr(host2, '.');
+ if (p != NULL && (strlen(p+1) == 2 || strlen(p+1) == 3)) {
+ /* it looks just like a netsplit to me. */
+ ok = TRUE;
+ }
+ }
+ }
+ g_free(params);
+
+ return ok;
+}
+
+static void split_set_timeout(gpointer key, NETSPLIT_REC *rec, NETSPLIT_REC *orig)
+{
+ if (rec == orig) {
+ /* original nick, destroy it in a few seconds.. */
+ rec->destroy = time(NULL)+4;
+ } else if (g_strcasecmp(rec->server, orig->server) == 0 &&
+ g_strcasecmp(rec->destserver, orig->destserver) == 0) {
+ /* same servers -> split over -> destroy old records sooner.. */
+ rec->destroy = time(NULL)+60;
+ }
+}
+
+static void event_join(gchar *data, IRC_SERVER_REC *server, gchar *nick, gchar *address)
+{
+ NETSPLIT_REC *rec;
+
+ /* check if split is over */
+ rec = g_hash_table_lookup(server->splits, nick);
+ if (rec == NULL) return;
+
+ if (g_strcasecmp(rec->address, address) == 0) {
+ /* yep, looks like it is. for same people that had the same
+ splitted servers set the timeout to one minute.
+
+ .. if the user just changed server, he/she can't use the
+ same nick (unless the server is broken) so don't bother
+ checking that the nick's server matches the split. */
+ g_hash_table_foreach(server->splits, (GHFunc) split_set_timeout, rec);
+ } else {
+ /* back from different address.. just destroy it. */
+ g_hash_table_remove(server->splits, rec->nick);
+ netsplit_destroy(rec);
+ }
+}
+
+static void event_quit(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address)
+{
+ g_return_if_fail(data != NULL);
+
+ if (*data == ':') data++;
+ if (g_strcasecmp(nick, server->nick) != 0 && is_split(data)) {
+ /* netsplit! */
+ netsplit_add(server, nick, address, data);
+ }
+}
+
+static void sig_disconnected(IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ g_hash_table_foreach(server->splits, (GHFunc) netsplit_destroy_hash, NULL);
+ g_hash_table_destroy(server->splits);
+}
+
+static int split_server_check(gpointer key, NETSPLIT_REC *rec, IRC_SERVER_REC *server)
+{
+ /* Check if this split record is too old.. */
+ if (rec->destroy > time(NULL))
+ return FALSE;
+
+ netsplit_destroy(rec);
+ return TRUE;
+}
+
+static int split_check_old(void)
+{
+ GSList *tmp;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ IRC_SERVER_REC *server = tmp->data;
+
+ g_hash_table_foreach_remove(server->splits, (GHRFunc) split_server_check, server);
+ }
+
+ return 1;
+}
+
+void netsplit_init(void)
+{
+ split_tag = g_timeout_add(1000, (GSourceFunc) split_check_old, NULL);
+ signal_add_first("event join", (SIGNAL_FUNC) event_join);
+ signal_add_first("event quit", (SIGNAL_FUNC) event_quit);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+}
+
+void netsplit_deinit(void)
+{
+ GSList *tmp;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next)
+ sig_disconnected(tmp->data);
+
+ g_source_remove(split_tag);
+ signal_remove("event join", (SIGNAL_FUNC) event_join);
+ signal_remove("event quit", (SIGNAL_FUNC) event_quit);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+}
diff --git a/src/irc/core/netsplit.h b/src/irc/core/netsplit.h
new file mode 100644
index 00000000..c2d221da
--- /dev/null
+++ b/src/irc/core/netsplit.h
@@ -0,0 +1,27 @@
+#ifndef __NETSPLIT_H
+#define __NETSPLIT_H
+
+#include "nicklist.h"
+
+typedef struct {
+ char *nick;
+ char *address;
+ char *server;
+ char *destserver;
+ GSList *channels;
+
+ time_t destroy;
+} NETSPLIT_REC;
+
+typedef struct {
+ char *name;
+ NICK_REC nick;
+} NETSPLIT_CHAN_REC;
+
+void netsplit_init(void);
+void netsplit_deinit(void);
+
+NETSPLIT_REC *netsplit_find(IRC_SERVER_REC *server, const char *nick, const char *address);
+NICK_REC *netsplit_find_channel(IRC_SERVER_REC *server, const char *nick, const char *address, const char *channel);
+
+#endif
diff --git a/src/irc/core/nicklist.c b/src/irc/core/nicklist.c
new file mode 100644
index 00000000..16305548
--- /dev/null
+++ b/src/irc/core/nicklist.c
@@ -0,0 +1,566 @@
+/*
+ nicklist.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 "misc.h"
+
+#include "channels.h"
+#include "irc.h"
+#include "masks.h"
+#include "nicklist.h"
+#include "irc-server.h"
+
+/* Add new nick to list */
+NICK_REC *nicklist_insert(CHANNEL_REC *channel, const char *nick, int op, int voice, int send_massjoin)
+{
+ NICK_REC *rec;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = g_new0(NICK_REC, 1);
+
+ if (op) rec->op = TRUE;
+ if (voice) rec->voice = TRUE;
+
+ rec->send_massjoin = send_massjoin;
+ rec->nick = g_strdup(nick);
+ rec->host = NULL;
+
+ g_hash_table_insert(channel->nicks, rec->nick, rec);
+ signal_emit("nicklist new", 2, channel, rec);
+ return rec;
+}
+
+static void nicklist_destroy(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ signal_emit("nicklist remove", 2, channel, nick);
+
+ g_free(nick->nick);
+ g_free_not_null(nick->realname);
+ g_free_not_null(nick->host);
+ g_free(nick);
+}
+
+/* remove nick from list */
+void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nick != NULL);
+
+ g_hash_table_remove(channel->nicks, nick->nick);
+ nicklist_destroy(channel, nick);
+}
+
+static NICK_REC *nicklist_find_wildcards(CHANNEL_REC *channel, const char *mask)
+{
+ GSList *nicks, *tmp;
+ NICK_REC *nick;
+
+ nicks = nicklist_getnicks(channel);
+ nick = NULL;
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ nick = tmp->data;
+
+ if (irc_mask_match_address(mask, nick->nick, nick->host == NULL ? "" : nick->host))
+ break;
+ }
+ g_slist_free(nicks);
+ return tmp == NULL ? NULL : nick;
+}
+
+/* Find nick record from list */
+NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *mask)
+{
+ NICK_REC *nickrec;
+ char *nick, *host;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+ g_return_val_if_fail(mask != NULL, NULL);
+
+ nick = g_strdup(mask);
+ host = strchr(nick, '!');
+ if (host != NULL) *host++ = '\0';
+
+ if (strchr(nick, '*') || strchr(nick, '?')) {
+ g_free(nick);
+ return nicklist_find_wildcards(channel, mask);
+ }
+
+ nickrec = g_hash_table_lookup(channel->nicks, nick);
+
+ if (nickrec != NULL && host != NULL &&
+ (nickrec->host == NULL || !match_wildcards(host, nickrec->host))) {
+ /* hosts didn't match */
+ nickrec = NULL;
+ }
+ g_free(nick);
+ return nickrec;
+}
+
+static void get_nicks_hash(gpointer key, NICK_REC *rec, GSList **list)
+{
+ *list = g_slist_append(*list, rec);
+}
+
+/* Get list of nicks */
+GSList *nicklist_getnicks(CHANNEL_REC *channel)
+{
+ GSList *list;
+
+ list = NULL;
+ g_hash_table_foreach(channel->nicks, (GHFunc) get_nicks_hash, &list);
+ return list;
+}
+
+typedef struct {
+ CHANNEL_REC *channel;
+ const char *nick;
+ GSList *list;
+} NICKLIST_GET_SAME_REC;
+
+static void get_nicks_same_hash(gpointer key, NICK_REC *nick, NICKLIST_GET_SAME_REC *rec)
+{
+ if (g_strcasecmp(nick->nick, rec->nick) == 0) {
+ rec->list = g_slist_append(rec->list, rec->channel);
+ rec->list = g_slist_append(rec->list, nick);
+ }
+}
+
+GSList *nicklist_get_same(IRC_SERVER_REC *server, const char *nick)
+{
+ NICKLIST_GET_SAME_REC rec;
+ GSList *tmp;
+
+ rec.nick = nick;
+ rec.list = NULL;
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ rec.channel = tmp->data;
+ g_hash_table_foreach(rec.channel->nicks, (GHFunc) get_nicks_same_hash, &rec);
+ }
+ return rec.list;
+}
+
+/* nick record comparision for sort functions */
+int nicklist_compare(NICK_REC *p1, NICK_REC *p2)
+{
+ if (p1 == NULL) return -1;
+ if (p2 == NULL) return 1;
+
+ if (p1->op && !p2->op) return -1;
+ if (!p1->op && p2->op) return 1;
+
+ if (!p1->op) {
+ if (p1->voice && !p2->voice) return -1;
+ if (!p1->voice && p2->voice) return 1;
+ }
+
+ return g_strcasecmp(p1->nick, p2->nick);
+}
+
+#define isnickchar(a) \
+ (isalnum(a) || (a) == '`' || (a) == '-' || (a) == '_' || \
+ (a) == '[' || (a) == ']' || (a) == '{' || (a) == '}' || \
+ (a) == '|' || (a) == '\\' || (a) == '^')
+
+char *nick_strip(const char *nick)
+{
+ char *stripped, *spos;
+
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ spos = stripped = g_strdup(nick);
+ while (isnickchar(*nick)) {
+ if (isalnum((gint) *nick)) *spos++ = *nick;
+ nick++;
+ }
+ if ((unsigned char) *nick >= 128)
+ *spos++ = *nick; /* just add it so that nicks won't match.. */
+ *spos = '\0';
+ return stripped;
+}
+
+static void event_names_list(const char *data, IRC_SERVER_REC *server)
+{
+ CHANNEL_REC *chanrec;
+ char *params, *type, *channel, *names, *ptr;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 4, NULL, &type, &channel, &names);
+
+ chanrec = channel_find(server, channel);
+ if (chanrec == NULL || chanrec->names_got) {
+ /* unknown channel / names list already read */
+ g_free(params);
+ return;
+ }
+
+ /* type = '=' = public, '*' = private, '@' = secret.
+
+ This is actually pretty useless to check here, but at least we
+ get to know if the channel is +p or +s a few seconds before
+ we receive the MODE reply... */
+ if (*type == '*')
+ chanrec->mode_private = TRUE;
+ else if (*type == '@')
+ chanrec->mode_secret = TRUE;
+
+ while (*names != '\0') {
+ while (*names == ' ') names++;
+ ptr = names;
+ while (*names != '\0' && *names != ' ') names++;
+ if (*names != '\0') *names++ = '\0';
+
+ if (*ptr == '@' && strcmp(server->nick, ptr+1) == 0)
+ chanrec->chanop = TRUE;
+
+ nicklist_insert(chanrec, ptr+isnickflag(*ptr), *ptr == '@', *ptr == '+', FALSE);
+ }
+
+ g_free(params);
+}
+
+static void event_end_of_names(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *channel;
+ CHANNEL_REC *chanrec;
+
+ g_return_if_fail(server != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL && !chanrec->names_got) {
+ chanrec->names_got = TRUE;
+ signal_emit("channel query", 1, chanrec);
+ }
+
+ g_free(params);
+}
+
+static void nicklist_update_flags(IRC_SERVER_REC *server, const char *nick, int gone, int ircop)
+{
+ GSList *nicks, *tmp;
+ CHANNEL_REC *channel;
+ NICK_REC *rec;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(nick != NULL);
+
+ nicks = nicklist_get_same(server, nick);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+ channel = tmp->data;
+ rec = tmp->next->data;
+
+ rec->last_check = time(NULL);
+
+ if (gone != -1 && rec->gone != gone) {
+ rec->gone = gone;
+ signal_emit("nick gone changed", 2, channel, rec);
+ }
+
+ if (ircop != -1 && rec->ircop != ircop) {
+ rec->ircop = ircop;
+ signal_emit("nick ircop changed", 2, channel, rec);
+ }
+ }
+ g_slist_free(nicks);
+}
+
+static void event_who(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *nick, *channel, *user, *host, *stat, *realname, *hops;
+ CHANNEL_REC *chanrec;
+ NICK_REC *nickrec;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 8, NULL, &channel, &user, &host, NULL, &nick, &stat, &realname);
+
+ /* get hop count */
+ hops = realname;
+ while (*realname != '\0' && *realname != ' ') realname++;
+ *realname++ = '\0';
+ while (*realname == ' ') realname++;
+
+ /* update host, realname, hopcount */
+ chanrec = channel_find(server, channel);
+ nickrec = chanrec == NULL ? NULL : nicklist_find(chanrec, nick);
+ if (nickrec != NULL) {
+ if (nickrec->host == NULL)
+ nickrec->host = g_strdup_printf("%s@%s", user, host);
+ if (nickrec->realname == NULL)
+ nickrec->realname = g_strdup(realname);
+ sscanf(hops, "%d", &nickrec->hops);
+ }
+
+ nicklist_update_flags(server, nick,
+ strchr(stat, 'G') != NULL, /* gone */
+ strchr(stat, '*') != NULL); /* ircop */
+
+ g_free(params);
+}
+
+static void event_whois(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *nick, *realname;
+ GSList *nicks, *tmp;
+ NICK_REC *rec;
+
+ g_return_if_fail(data != NULL);
+
+ server->whois_coming = TRUE;
+
+ /* first remove the gone-flag, if user is gone it will be set later.. */
+ params = event_get_params(data, 6, NULL, &nick, NULL, NULL, NULL, &realname);
+
+ nicks = nicklist_get_same(server, nick);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+ rec = tmp->next->data;
+
+ if (rec->realname == NULL)
+ rec->realname = g_strdup(realname);
+ }
+ g_slist_free(nicks);
+
+ /* reset gone and ircop status, we'll handle them in the following
+ WHOIS replies */
+ nicklist_update_flags(server, nick, FALSE, FALSE);
+ g_free(params);
+}
+
+static void event_whois_away(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *nick, *awaymsg;
+
+ g_return_if_fail(data != NULL);
+
+ /* set user's gone flag.. */
+ params = event_get_params(data, 3, NULL, &nick, &awaymsg);
+ nicklist_update_flags(server, nick, TRUE, -1);
+ g_free(params);
+}
+
+static void event_whois_ircop(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *nick, *awaymsg;
+
+ g_return_if_fail(data != NULL);
+
+ /* set user's gone flag.. */
+ params = event_get_params(data, 3, NULL, &nick, &awaymsg);
+ nicklist_update_flags(server, nick, -1, TRUE);
+ g_free(params);
+}
+
+static void event_end_of_whois(const char *data, IRC_SERVER_REC *server)
+{
+ server->whois_coming = FALSE;
+}
+
+static void event_nick_in_use(const char *data, IRC_SERVER_REC *server)
+{
+ char *str;
+ int n;
+
+ g_return_if_fail(data != NULL);
+
+ if (server->connected) {
+ /* Already connected, no need to handle this anymore. */
+ return;
+ }
+
+ /* nick already in use - need to change it .. */
+ if (strcmp(server->nick, server->connrec->nick) == 0) {
+ /* first try, so try the alternative nick.. */
+ g_free(server->nick);
+ server->nick = g_strdup(server->connrec->alternate_nick);
+ }
+ else if (strlen(server->nick) < 9) {
+ /* keep adding '_' to end of nick.. */
+ str = g_strdup_printf("%s_", server->nick);
+ g_free(server->nick);
+ server->nick = str;
+ } else {
+ /* nick full, keep adding number at the end */
+ for (n = 8; n > 0; n--) {
+ if (server->nick[n] < '0' || server->nick[n] > '9') {
+ server->nick[n] = '1';
+ break;
+ }
+
+ if (server->nick[n] < '9') {
+ server->nick[n]++;
+ break;
+ }
+ server->nick[n] = '0';
+ }
+ }
+
+ irc_send_cmdv(server, "NICK %s", server->nick);
+}
+
+static void event_target_unavailable(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+ if (!ischannel(*channel)) {
+ /* nick is unavailable. */
+ event_nick_in_use(data, server);
+ }
+
+ g_free(params);
+}
+
+static void event_nick(const char *data, IRC_SERVER_REC *server, const char *orignick)
+{
+ CHANNEL_REC *channel;
+ NICK_REC *nickrec;
+ GSList *nicks, *tmp;
+ char *params, *nick;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 1, &nick);
+
+ if (g_strcasecmp(orignick, server->nick) == 0) {
+ /* You changed your nick */
+ g_free(server->connrec->nick);
+ g_free(server->nick);
+ server->connrec->nick = g_strdup(nick);
+ server->nick = g_strdup(nick);
+ signal_emit("server nick changed", 1, server);
+ }
+
+ nicks = nicklist_get_same(server, nick);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+ channel = tmp->data;
+ nickrec = tmp->next->data;
+
+ /* remove old nick from hash table */
+ g_hash_table_remove(channel->nicks, nickrec->nick);
+
+ g_free(nickrec->nick);
+ nickrec->nick = g_strdup(nick);
+
+ /* add new nick to hash table */
+ g_hash_table_insert(channel->nicks, nickrec->nick, nickrec);
+
+ signal_emit("nicklist changed", 3, channel, nickrec, orignick);
+ }
+ g_slist_free(nicks);
+
+ g_free(params);
+}
+
+static void event_userhost(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *hosts, **phosts, **pos, *ptr;
+
+ g_return_if_fail(data != NULL);
+
+ /* set user's gone flag.. */
+ params = event_get_params(data, 2, NULL, &hosts);
+
+ phosts = g_strsplit(hosts, " ", -1);
+ for (pos = phosts; pos != NULL; pos++) {
+ ptr = strchr(*pos, '=');
+ if (ptr == NULL) continue;
+ *ptr++ = '\0';
+
+ nicklist_update_flags(server, *pos, *ptr == '-', -1);
+ }
+ g_strfreev(phosts);
+ g_free(params);
+}
+
+static void sig_usermode(IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ nicklist_update_flags(server, server->nick, server->usermode_away, -1);
+}
+
+static void sig_channel_created(CHANNEL_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ channel->nicks = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
+}
+
+static void nicklist_remove_hash(gpointer key, NICK_REC *nick, CHANNEL_REC *channel)
+{
+ nicklist_destroy(channel, nick);
+}
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ g_hash_table_foreach(channel->nicks, (GHFunc) nicklist_remove_hash, channel);
+ g_hash_table_destroy(channel->nicks);
+}
+
+void nicklist_init(void)
+{
+ signal_add("event nick", (SIGNAL_FUNC) event_nick);
+ signal_add_first("event 352", (SIGNAL_FUNC) event_who);
+ signal_add("silent event who", (SIGNAL_FUNC) event_who);
+ signal_add("silent event whois", (SIGNAL_FUNC) event_whois);
+ signal_add_first("event 311", (SIGNAL_FUNC) event_whois);
+ signal_add_first("event 301", (SIGNAL_FUNC) event_whois_away);
+ signal_add_first("event 313", (SIGNAL_FUNC) event_whois_ircop);
+ signal_add("event 318", (SIGNAL_FUNC) event_end_of_whois);
+ signal_add("event 353", (SIGNAL_FUNC) event_names_list);
+ signal_add("event 366", (SIGNAL_FUNC) event_end_of_names);
+ signal_add("event 433", (SIGNAL_FUNC) event_nick_in_use);
+ signal_add("event 437", (SIGNAL_FUNC) event_target_unavailable);
+ signal_add("event 302", (SIGNAL_FUNC) event_userhost);
+ signal_add("userhost event", (SIGNAL_FUNC) event_userhost);
+ signal_add("user mode changed", (SIGNAL_FUNC) sig_usermode);
+ signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created);
+ signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+}
+
+void nicklist_deinit(void)
+{
+ signal_remove("event nick", (SIGNAL_FUNC) event_nick);
+ signal_remove("event 352", (SIGNAL_FUNC) event_who);
+ signal_remove("silent event who", (SIGNAL_FUNC) event_who);
+ signal_remove("silent event whois", (SIGNAL_FUNC) event_whois);
+ signal_remove("event 311", (SIGNAL_FUNC) event_whois);
+ signal_remove("event 301", (SIGNAL_FUNC) event_whois_away);
+ signal_remove("event 313", (SIGNAL_FUNC) event_whois_ircop);
+ signal_remove("event 318", (SIGNAL_FUNC) event_end_of_whois);
+ signal_remove("event 353", (SIGNAL_FUNC) event_names_list);
+ signal_remove("event 366", (SIGNAL_FUNC) event_end_of_names);
+ signal_remove("event 433", (SIGNAL_FUNC) event_nick_in_use);
+ signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable);
+ signal_remove("event 302", (SIGNAL_FUNC) event_userhost);
+ signal_remove("userhost event", (SIGNAL_FUNC) event_userhost);
+ signal_remove("user mode changed", (SIGNAL_FUNC) sig_usermode);
+ signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+}
diff --git a/src/irc/core/nicklist.h b/src/irc/core/nicklist.h
new file mode 100644
index 00000000..8e83e97f
--- /dev/null
+++ b/src/irc/core/nicklist.h
@@ -0,0 +1,41 @@
+#ifndef __NICKLIST_H
+#define __NICKLIST_H
+
+#include "channels.h"
+
+typedef struct {
+ time_t last_check; /* last time gone was checked */
+ int send_massjoin; /* Waiting to be sent in massjoin signal */
+
+ char *nick;
+ char *host;
+ char *realname;
+
+ int hops;
+
+ int op:1;
+ int voice:1;
+ int gone:1;
+ int ircop:1;
+} NICK_REC;
+
+/* Add new nick to list */
+NICK_REC *nicklist_insert(CHANNEL_REC *channel, const char *nick, int op, int voice, int send_massjoin);
+/* remove nick from list */
+void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick);
+/* Find nick record from list */
+NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *mask);
+/* Get list of nicks */
+GSList *nicklist_getnicks(CHANNEL_REC *channel);
+/* Get all the nick records of `nick'. Returns channel, nick, channel, ... */
+GSList *nicklist_get_same(IRC_SERVER_REC *server, const char *nick);
+
+/* nick record comparision for sort functions */
+int nicklist_compare(NICK_REC *p1, NICK_REC *p2);
+
+char *nick_strip(const char *nick);
+
+void nicklist_init(void);
+void nicklist_deinit(void);
+
+#endif
diff --git a/src/irc/core/query.c b/src/irc/core/query.c
new file mode 100644
index 00000000..cce333af
--- /dev/null
+++ b/src/irc/core/query.c
@@ -0,0 +1,158 @@
+/*
+ query.c : irssi
+
+ Copyright (C) 1999 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 "misc.h"
+#include "signals.h"
+#include "modules.h"
+
+#include "irc.h"
+#include "query.h"
+
+GSList *queries;
+
+QUERY_REC *query_create(IRC_SERVER_REC *server, const char *nick, int automatic)
+{
+ QUERY_REC *rec;
+
+ g_return_val_if_fail(server != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = g_new0(QUERY_REC, 1);
+ queries = g_slist_append(queries, rec);
+ server->queries = g_slist_append(server->queries, rec);
+
+ MODULE_DATA_INIT(rec);
+ rec->type = module_get_uniq_id("IRC", WI_IRC_QUERY);
+ rec->nick = g_strdup(nick);
+ rec->server_tag = g_strdup(server->tag);
+ rec->server = server;
+
+ signal_emit("query created", 2, rec, GINT_TO_POINTER(automatic));
+ return rec;
+}
+
+void query_destroy(QUERY_REC *query)
+{
+ g_return_if_fail(query != NULL);
+
+ if (query->destroying) return;
+ query->destroying = TRUE;
+
+ queries = g_slist_remove(queries, query);
+ if (query->server != NULL)
+ query->server->queries = g_slist_remove(query->server->queries, query);
+ signal_emit("query destroyed", 1, query);
+
+ MODULE_DATA_DEINIT(query);
+ g_free(query->nick);
+ g_free(query->server_tag);
+ g_free(query);
+}
+
+
+static QUERY_REC *query_find_server(IRC_SERVER_REC *server, const char *nick)
+{
+ GSList *tmp;
+
+ for (tmp = server->queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *rec = tmp->data;
+
+ if (g_strcasecmp(nick, rec->nick) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+QUERY_REC *query_find(IRC_SERVER_REC *server, const char *nick)
+{
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ if (server != NULL)
+ return query_find_server(server, nick);
+
+ /* find from any server */
+ return gslist_foreach_find(servers, (FOREACH_FIND_FUNC) query_find_server, (void *) nick);
+}
+
+void query_change_server(QUERY_REC *query, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(query != NULL);
+
+ query->server = server;
+ signal_emit("query server changed", 2, query, server);
+}
+
+static void event_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr)
+{
+ char *params, *target, *msg;
+ QUERY_REC *query;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg);
+
+ if (addr != NULL && *msg != 1 && !ischannel(*target)) {
+ /* save nick's address to query */
+ query = query_find(server, nick);
+ if (query != NULL && (query->address == NULL || strcmp(query->address, addr) != 0)) {
+ g_free_not_null(query->address);
+ query->address = g_strdup(addr);
+
+ signal_emit("query address changed", 1, query);
+ }
+ }
+
+ g_free(params);
+}
+
+static void event_nick(const char *data, IRC_SERVER_REC *server, const char *orignick)
+{
+ char *params, *nick;
+ GSList *tmp;
+
+ params = event_get_params(data, 1, &nick);
+
+ for (tmp = server->queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->nick, orignick) == 0) {
+ g_free(rec->nick);
+ rec->nick = g_strdup(nick);
+ signal_emit("query nick changed", 1, rec);
+ }
+ }
+
+ g_free(params);
+}
+
+void query_init(void)
+{
+ signal_add_last("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_add("event nick", (SIGNAL_FUNC) event_nick);
+}
+
+void query_deinit(void)
+{
+ signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_remove("event nick", (SIGNAL_FUNC) event_nick);
+}
diff --git a/src/irc/core/query.h b/src/irc/core/query.h
new file mode 100644
index 00000000..048c890a
--- /dev/null
+++ b/src/irc/core/query.h
@@ -0,0 +1,30 @@
+#ifndef __QUERY_H
+#define __QUERY_H
+
+#include "server.h"
+
+typedef struct {
+ int type;
+ GHashTable *module_data;
+
+ IRC_SERVER_REC *server;
+ char *nick;
+
+ int new_data;
+
+ char *address;
+ char *server_tag;
+ int destroying:1;
+} QUERY_REC;
+
+extern GSList *queries;
+
+QUERY_REC *query_create(IRC_SERVER_REC *server, const char *nick, int automatic);
+void query_destroy(QUERY_REC *query);
+
+/* Find query by name, if `server' is NULL, search from all servers */
+QUERY_REC *query_find(IRC_SERVER_REC *server, const char *nick);
+
+void query_change_server(QUERY_REC *query, IRC_SERVER_REC *server);
+
+#endif
diff --git a/src/irc/core/server-idle.c b/src/irc/core/server-idle.c
new file mode 100644
index 00000000..273ee231
--- /dev/null
+++ b/src/irc/core/server-idle.c
@@ -0,0 +1,252 @@
+/*
+ server-idle.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 "irc-server.h"
+#include "server-idle.h"
+#include "server-redirect.h"
+#include "irc.h"
+
+typedef struct {
+ char *event;
+ char *signal;
+ int argpos;
+} REDIRECT_IDLE_REC;
+
+typedef struct {
+ char *cmd;
+ char *arg;
+ int tag;
+
+ int last;
+ GSList *redirects;
+} SERVER_IDLE_REC;
+
+static int idle_tag, idlepos;
+
+/* Add new idle command to queue */
+static SERVER_IDLE_REC *server_idle_create(const char *cmd, const char *arg, int last, va_list args)
+{
+ REDIRECT_IDLE_REC *rrec;
+ SERVER_IDLE_REC *rec;
+ char *event;
+
+ g_return_val_if_fail(cmd != NULL, FALSE);
+
+ rec = g_new0(SERVER_IDLE_REC, 1);
+
+ rec->tag = ++idlepos;
+ rec->arg = arg == NULL ? NULL : g_strdup(arg);
+ rec->cmd = g_strdup(cmd);
+ rec->last = last;
+
+ while ((event = va_arg(args, char *)) != NULL) {
+ rrec = g_new(REDIRECT_IDLE_REC, 1);
+ rec->redirects = g_slist_append(rec->redirects, rrec);
+
+ rrec->event = g_strdup(event);
+ rrec->signal = g_strdup(va_arg(args, char *));
+ rrec->argpos = va_arg(args, int);
+ }
+
+ return rec;
+}
+
+static SERVER_IDLE_REC *server_idle_find_rec(IRC_SERVER_REC *server, int tag)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(server != NULL, FALSE);
+
+ for (tmp = server->idles; tmp != NULL; tmp = tmp->next) {
+ SERVER_IDLE_REC *rec = tmp->data;
+
+ if (rec->tag == tag)
+ return rec;
+ }
+
+ return NULL;
+}
+
+/* Add new idle command to queue */
+int server_idle_add(IRC_SERVER_REC *server, const char *cmd, const char *arg, int last, ...)
+{
+ va_list args;
+ SERVER_IDLE_REC *rec;
+
+ g_return_val_if_fail(server != NULL, -1);
+
+ va_start(args, last);
+ rec = server_idle_create(cmd, arg, last, args);
+ server->idles = g_slist_append(server->idles, rec);
+ va_end(args);
+
+ return rec->tag;
+}
+
+/* Add new idle command to first of queue */
+int server_idle_add_first(IRC_SERVER_REC *server, const char *cmd, const char *arg, int last, ...)
+{
+ va_list args;
+ SERVER_IDLE_REC *rec;
+
+ g_return_val_if_fail(server != NULL, -1);
+
+ va_start(args, last);
+ rec = server_idle_create(cmd, arg, last, args);
+ server->idles = g_slist_prepend(server->idles, rec);
+ va_end(args);
+
+ return rec->tag;
+}
+
+/* Add new idle command to specified position of queue */
+int server_idle_insert(IRC_SERVER_REC *server, const char *cmd, const char *arg, int tag, int last, ...)
+{
+ va_list args;
+ SERVER_IDLE_REC *rec;
+ int pos;
+
+ g_return_val_if_fail(server != NULL, -1);
+
+ va_start(args, last);
+
+ /* find the position of tag in idle list */
+ rec = server_idle_find_rec(server, tag);
+ pos = g_slist_index(server->idles, rec);
+
+ rec = server_idle_create(cmd, arg, last, args);
+ server->idles = pos < 0 ?
+ g_slist_append(server->idles, rec) :
+ g_slist_insert(server->idles, rec, pos);
+ va_end(args);
+ return rec->tag;
+}
+
+static void server_idle_destroy(IRC_SERVER_REC *server, SERVER_IDLE_REC *rec)
+{
+ GSList *tmp;
+
+ g_return_if_fail(server != NULL);
+
+ server->idles = g_slist_remove(server->idles, rec);
+
+ for (tmp = rec->redirects; tmp != NULL; tmp = tmp->next) {
+ REDIRECT_IDLE_REC *rec = tmp->data;
+
+ g_free(rec->event);
+ g_free(rec->signal);
+ g_free(rec);
+ }
+ g_slist_free(rec->redirects);
+
+ g_free_not_null(rec->arg);
+ g_free(rec->cmd);
+ g_free(rec);
+}
+
+/* Check if record is still in queue */
+int server_idle_find(IRC_SERVER_REC *server, int tag)
+{
+ return server_idle_find_rec(server, tag) != NULL;
+}
+
+/* Remove record from idle queue */
+int server_idle_remove(IRC_SERVER_REC *server, int tag)
+{
+ SERVER_IDLE_REC *rec;
+
+ g_return_val_if_fail(server != NULL, FALSE);
+
+ rec = server_idle_find_rec(server, tag);
+ if (rec == NULL)
+ return FALSE;
+
+ server_idle_destroy(server, rec);
+ return TRUE;
+}
+
+/* Execute next idle command */
+static void server_idle_next(IRC_SERVER_REC *server)
+{
+ SERVER_IDLE_REC *rec;
+ GSList *tmp;
+ int group;
+
+ g_return_if_fail(server != NULL);
+
+ if (server->idles == NULL) return;
+ rec = server->idles->data;
+
+ /* Send command */
+ irc_send_cmd(server, rec->cmd);
+
+ /* Add server redirections */
+ group = 0;
+ for (tmp = rec->redirects; tmp != NULL; tmp = tmp->next) {
+ REDIRECT_IDLE_REC *rrec = tmp->data;
+
+ group = server_redirect_single_event((SERVER_REC *) server, rec->arg, rec->last > 0,
+ group, rrec->event, rrec->signal, rrec->argpos);
+ if (rec->last > 0) rec->last--;
+ }
+
+ server_idle_destroy(server, rec);
+}
+
+static void sig_disconnected(IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ while (server->idles != NULL)
+ server_idle_destroy(server, server->idles->data);
+}
+
+static int sig_idle_timeout(void)
+{
+ GSList *tmp;
+
+ /* Scan through every server */
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ IRC_SERVER_REC *rec = tmp->data;
+
+ if (rec->idles != NULL && rec->cmdcount == 0) {
+ /* We're idling and we have idle commands to run! */
+ server_idle_next(rec);
+ }
+ }
+ return 1;
+}
+
+void servers_idle_init(void)
+{
+ idlepos = 0;
+ idle_tag = g_timeout_add(1000, (GSourceFunc) sig_idle_timeout, NULL);
+
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+}
+
+void servers_idle_deinit(void)
+{
+ g_source_remove(idle_tag);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+}
diff --git a/src/irc/core/server-idle.h b/src/irc/core/server-idle.h
new file mode 100644
index 00000000..a8ef8ec6
--- /dev/null
+++ b/src/irc/core/server-idle.h
@@ -0,0 +1,24 @@
+#ifndef __SERVER_IDLE_H
+#define __SERVER_IDLE_H
+
+#include "irc-server.h"
+
+/* Add new idle command to queue */
+int server_idle_add(IRC_SERVER_REC *server, const char *cmd, const char *arg, int last, ...);
+
+/* Add new idle command to first of queue */
+int server_idle_add_first(IRC_SERVER_REC *server, const char *cmd, const char *arg, int last, ...);
+
+/* Add new idle command to specified position of queue */
+int server_idle_insert(IRC_SERVER_REC *server, const char *cmd, const char *arg, int tag, int last, ...);
+
+/* Check if record is still in queue */
+int server_idle_find(IRC_SERVER_REC *server, int tag);
+
+/* Remove record from idle queue */
+int server_idle_remove(IRC_SERVER_REC *server, int tag);
+
+void servers_idle_init(void);
+void servers_idle_deinit(void);
+
+#endif
diff --git a/src/irc/core/server-reconnect.c b/src/irc/core/server-reconnect.c
new file mode 100644
index 00000000..1edfe187
--- /dev/null
+++ b/src/irc/core/server-reconnect.c
@@ -0,0 +1,398 @@
+/*
+ server-reconnect.c : irssi
+
+ Copyright (C) 1999 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 "modules.h"
+#include "commands.h"
+#include "network.h"
+#include "signals.h"
+
+#include "irc.h"
+#include "modes.h"
+#include "irc-server.h"
+#include "server-setup.h"
+#include "server-reconnect.h"
+
+#include "settings.h"
+#include "common-setup.h"
+
+GSList *reconnects;
+static int last_reconnect_tag;
+static int reconnect_timeout_tag;
+static int reconnect_time;
+
+static void server_reconnect_add(IRC_SERVER_CONNECT_REC *conn, time_t next_connect)
+{
+ RECONNECT_REC *rec;
+
+ rec = g_new(RECONNECT_REC, 1);
+ rec->tag = ++last_reconnect_tag;
+ rec->conn = conn;
+ rec->next_connect = next_connect;
+
+ reconnects = g_slist_append(reconnects, rec);
+}
+
+static void server_reconnect_destroy(RECONNECT_REC *rec, int free_conn)
+{
+ reconnects = g_slist_remove(reconnects, rec);
+
+ signal_emit("server reconnect remove", 1, rec);
+ if (free_conn) irc_server_connect_free(rec->conn);
+ g_free(rec);
+
+ if (reconnects == NULL)
+ last_reconnect_tag = 0;
+}
+
+static int server_reconnect_timeout(void)
+{
+ IRC_SERVER_CONNECT_REC *conn;
+ GSList *tmp, *next;
+ time_t now;
+
+ now = time(NULL);
+ for (tmp = reconnects; tmp != NULL; tmp = next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->next_connect <= now) {
+ conn = rec->conn;
+ server_reconnect_destroy(rec, FALSE);
+ irc_server_connect(conn);
+ }
+ }
+
+ return 1;
+}
+
+static void sserver_connect(SETUP_SERVER_REC *rec, IRC_SERVER_CONNECT_REC *conn)
+{
+ conn->address = g_strdup(rec->server);
+ conn->port = rec->port;
+ conn->password = rec->password == NULL ? NULL :
+ g_strdup(rec->password);
+ if (rec->cmd_queue_speed > 0)
+ conn->cmd_queue_speed = rec->cmd_queue_speed;
+
+ if (rec->last_connect > time(NULL)-reconnect_time) {
+ /* can't reconnect this fast, wait.. */
+ server_reconnect_add(conn, rec->last_connect+reconnect_time);
+ } else {
+ /* connect to server.. */
+ irc_server_connect(conn);
+ }
+}
+
+static void server_connect_copy_skeleton(IRC_SERVER_CONNECT_REC *dest, IRC_SERVER_CONNECT_REC *src)
+{
+ dest->proxy = src->proxy == NULL ? NULL :
+ g_strdup(src->proxy);
+ dest->proxy_port = src->proxy_port;
+ dest->proxy_string = src->proxy_string == NULL ? NULL :
+ g_strdup(src->proxy_string);
+
+ dest->ircnet = src->ircnet == NULL ? NULL :
+ g_strdup(src->ircnet);
+ dest->nick = src->nick == NULL ? NULL :
+ g_strdup(src->nick);
+ dest->username = src->username == NULL ? NULL :
+ g_strdup(src->username);
+ dest->realname = src->realname == NULL ? NULL :
+ g_strdup(src->realname);
+
+ if (src->own_ip != NULL) {
+ dest->own_ip = g_new(IPADDR, 1);
+ memcpy(dest->own_ip, src->own_ip, sizeof(IPADDR));
+ }
+
+ dest->cmd_queue_speed = src->cmd_queue_speed;
+ dest->max_kicks = src->max_kicks;
+ dest->max_modes = src->max_modes;
+ dest->max_msgs = src->max_msgs;
+}
+
+static void sig_reconnect(IRC_SERVER_REC *server)
+{
+ IRC_SERVER_CONNECT_REC *conn;
+ SETUP_SERVER_REC *sserver;
+ GSList *tmp;
+ int found, through;
+ time_t now;
+
+ g_return_if_fail(server != NULL);
+
+ if (reconnect_time == -1 || !server->connection_lost || !irc_server_check(server))
+ return;
+
+ conn = g_new0(IRC_SERVER_CONNECT_REC, 1);
+ conn->reconnection = TRUE;
+ server_connect_copy_skeleton(conn, server->connrec);
+
+ /* save the server status */
+ if (!server->connected) {
+ conn->channels = g_strdup(server->connrec->channels);
+ conn->away_reason = g_strdup(server->connrec->away_reason);
+ conn->usermode = g_strdup(server->connrec->usermode);
+ } else {
+ conn->channels = irc_server_get_channels(server);
+ conn->away_reason = !server->usermode_away ? NULL :
+ g_strdup(server->away_reason);
+ conn->usermode = g_strdup(server->usermode);
+ }
+
+ sserver = server_setup_find(server->connrec->address, server->connrec->port);
+ if (sserver == NULL) {
+ /* port specific record not found, try without port.. */
+ sserver = server_setup_find(server->connrec->address, -1);
+ }
+
+ if (sserver != NULL) {
+ /* save the last connection time/status */
+ sserver->last_connect = server->connect_time == 0 ?
+ time(NULL) : server->connect_time;
+ sserver->last_failed = !server->connected;
+ }
+
+ if (sserver == NULL || conn->ircnet == NULL) {
+ /* not in any ircnet, just reconnect back to same server */
+ conn->address = g_strdup(server->connrec->address);
+ conn->port = server->connrec->port;
+ conn->password = server->connrec->password == NULL ? NULL :
+ g_strdup(server->connrec->password);
+
+ if (server->connect_time != 0 &&
+ time(NULL)-server->connect_time > reconnect_time) {
+ /* there's been enough time since last connection,
+ reconnect back immediately */
+ irc_server_connect(conn);
+ } else {
+ /* reconnect later.. */
+ server_reconnect_add(conn, (server->connect_time == 0 ? time(NULL) :
+ server->connect_time) + reconnect_time);
+ }
+ return;
+ }
+
+ /* always try to first connect to the first on the list where we
+ haven't got unsuccessful connection attempts for the last half
+ an hour. */
+
+ now = time(NULL);
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SETUP_SERVER_REC *rec = tmp->data;
+
+ if (rec->ircnet == NULL || g_strcasecmp(conn->ircnet, rec->ircnet) != 0)
+ continue;
+
+ if (!rec->last_connect || !rec->last_failed || rec->last_connect < now-FAILED_RECONNECT_WAIT) {
+ sserver_connect(rec, conn);
+ return;
+ }
+ }
+
+ /* just try the next server in list */
+ found = through = FALSE;
+ for (tmp = setupservers; tmp != NULL; ) {
+ SETUP_SERVER_REC *rec = tmp->data;
+
+ if (!found && g_strcasecmp(rec->server, server->connrec->address) == 0 &&
+ server->connrec->port == rec->port)
+ found = TRUE;
+ else if (found && rec->ircnet != NULL && g_strcasecmp(conn->ircnet, rec->ircnet) == 0) {
+ sserver_connect(rec, conn);
+ break;
+ }
+
+ if (tmp->next != NULL) {
+ tmp = tmp->next;
+ continue;
+ }
+
+ if (through) {
+ /* shouldn't happen unless there's no servers in
+ this ircnet in setup.. */
+ break;
+ }
+
+ tmp = setupservers;
+ found = through = TRUE;
+ }
+}
+
+static void sig_server_looking(IRC_SERVER_REC *server)
+{
+ IRC_SERVER_CONNECT_REC *conn;
+ GSList *tmp, *next;
+
+ g_return_if_fail(server != NULL);
+ if (!irc_server_check(server))
+ return;
+
+ /* trying to connect somewhere, check if there's anything in reconnect
+ queue waiting to connect to same ircnet or same server+port.. */
+ conn = server->connrec;
+ for (tmp = reconnects; tmp != NULL; tmp = next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (g_strcasecmp(conn->address, rec->conn->address) == 0 &&
+ conn->port == rec->conn->port) {
+ server_reconnect_destroy(rec, TRUE);
+ }
+ else if (conn->ircnet != NULL && rec->conn->ircnet != NULL &&
+ g_strcasecmp(conn->ircnet, rec->conn->ircnet) == 0) {
+ server_reconnect_destroy(rec, TRUE);
+ }
+ }
+}
+
+/* Remove all servers from reconnect list */
+static void cmd_rmreconns(void)
+{
+ while (reconnects != NULL)
+ server_reconnect_destroy(reconnects->data, TRUE);
+}
+
+static RECONNECT_REC *reconnect_find_tag(int tag)
+{
+ GSList *tmp;
+
+ for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ if (rec->tag == tag)
+ return rec;
+ }
+
+ return NULL;
+}
+
+/* Try to reconnect immediately */
+static void cmd_reconnect(const char *data)
+{
+ IRC_SERVER_CONNECT_REC *conn;
+ RECONNECT_REC *rec;
+ int tag;
+
+ if (g_strncasecmp(data, "RECON-", 6) == 0)
+ data += 6;
+
+ rec = sscanf(data, "%d", &tag) == 1 && tag > 0 ?
+ reconnect_find_tag(tag) : NULL;
+
+ if (rec == NULL)
+ signal_emit("server reconnect not found", 1, data);
+ else {
+ conn = rec->conn;
+ server_reconnect_destroy(rec, FALSE);
+ irc_server_connect(rec->conn);
+ }
+}
+
+static void cmd_disconnect(const char *data, SERVER_REC *server)
+{
+ RECONNECT_REC *rec;
+ int tag;
+
+ if (g_strncasecmp(data, "RECON-", 6) != 0)
+ return; /* handle only reconnection removing */
+
+ rec = sscanf(data+6, "%d", &tag) == 1 && tag > 0 ?
+ reconnect_find_tag(tag) : NULL;
+
+ if (rec == NULL)
+ signal_emit("server reconnect not found", 1, data);
+ else
+ server_reconnect_destroy(rec, TRUE);
+}
+
+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 = server->connrec->usermode;
+ if (mode == NULL) return 0;
+
+ newmode = modes_join(server->usermode, mode);
+ if (strcmp(newmode, server->usermode) != 0)
+ irc_send_cmdv(server, "MODE %s -%s+%s", server->nick, server->usermode, mode);
+ g_free(newmode);
+ return 0;
+}
+
+static void sig_connected(IRC_SERVER_REC *server)
+{
+ if (!server->connrec->reconnection)
+ return;
+
+ if (server->connrec->channels != NULL)
+ channels_join(server, server->connrec->channels, TRUE);
+ if (server->connrec->away_reason != NULL)
+ signal_emit("command away", 2, server->connrec->away_reason, server, NULL);
+ if (server->connrec->usermode != NULL) {
+ /* wait a second and then send the user mode */
+ g_timeout_add(1000, (GSourceFunc) sig_set_user_mode, server);
+ }
+}
+
+static void read_settings(void)
+{
+ reconnect_time = settings_get_int("server_reconnect_time");
+}
+
+void servers_reconnect_init(void)
+{
+ reconnects = NULL;
+ last_reconnect_tag = 0;
+
+ reconnect_timeout_tag = g_timeout_add(1000, (GSourceFunc) server_reconnect_timeout, NULL);
+ read_settings();
+
+ signal_add("server looking", (SIGNAL_FUNC) sig_server_looking);
+ signal_add("server connect failed", (SIGNAL_FUNC) sig_reconnect);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_reconnect);
+ signal_add("event connected", (SIGNAL_FUNC) sig_connected);
+ command_bind("rmreconns", NULL, (SIGNAL_FUNC) cmd_rmreconns);
+ command_bind("reconnect", NULL, (SIGNAL_FUNC) cmd_reconnect);
+ command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void servers_reconnect_deinit(void)
+{
+ g_source_remove(reconnect_timeout_tag);
+
+ while (reconnects != NULL)
+ server_reconnect_destroy(reconnects->data, TRUE);
+
+ signal_remove("server looking", (SIGNAL_FUNC) sig_server_looking);
+ signal_remove("server connect failed", (SIGNAL_FUNC) sig_reconnect);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_reconnect);
+ signal_remove("event connected", (SIGNAL_FUNC) sig_connected);
+ command_unbind("rmreconns", (SIGNAL_FUNC) cmd_rmreconns);
+ command_unbind("reconnect", (SIGNAL_FUNC) cmd_reconnect);
+ command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/irc/core/server-reconnect.h b/src/irc/core/server-reconnect.h
new file mode 100644
index 00000000..6f4b5336
--- /dev/null
+++ b/src/irc/core/server-reconnect.h
@@ -0,0 +1,16 @@
+#ifndef __SERVER_RECONNECT_H
+#define __SERVER_RECONNECT_H
+
+typedef struct {
+ int tag;
+ time_t next_connect;
+
+ IRC_SERVER_CONNECT_REC *conn;
+} RECONNECT_REC;
+
+extern GSList *reconnects;
+
+void servers_reconnect_init(void);
+void servers_reconnect_deinit(void);
+
+#endif
diff --git a/src/irc/core/server-setup.c b/src/irc/core/server-setup.c
new file mode 100644
index 00000000..f56d839f
--- /dev/null
+++ b/src/irc/core/server-setup.c
@@ -0,0 +1,317 @@
+/*
+ server-setup.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 "network.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+#include "common-setup.h"
+
+#include "irc-server.h"
+#include "server-setup.h"
+#include "ircnet-setup.h"
+
+GSList *setupservers; /* list of irc servers */
+
+int source_host_ok; /* Use source_host_ip .. */
+IPADDR *source_host_ip; /* Resolved address */
+
+static void get_source_host_ip(void)
+{
+ IPADDR ip;
+
+ /* FIXME: This will block! */
+ if (!source_host_ok) {
+ source_host_ok = *settings_get_str("hostname") != '\0' &&
+ net_gethostname(settings_get_str("hostname"), &ip) == 0;
+ if (source_host_ok) {
+ source_host_ip = g_new(IPADDR, 1);
+ memcpy(source_host_ip, &ip, sizeof(IPADDR));
+ }
+ }
+}
+
+/* Create server connection record. `address' is required, rest can be NULL */
+static IRC_SERVER_CONNECT_REC *
+create_addr_conn(const char *address, int port, const char *password,
+ const char *nick)
+{
+ IRC_SERVER_CONNECT_REC *conn;
+ SETUP_SERVER_REC *sserver;
+ IRCNET_REC *ircnet;
+
+ g_return_val_if_fail(address != NULL, NULL);
+
+ conn = g_new0(IRC_SERVER_CONNECT_REC, 1);
+
+ conn->address = g_strdup(address);
+ conn->port = port > 0 ? port : 6667;
+
+ if (password && *password) conn->password = g_strdup(password);
+ if (nick && *nick) conn->nick = g_strdup(nick);
+
+ if (!conn->nick) conn->nick = g_strdup(settings_get_str("default_nick"));
+ conn->alternate_nick = g_strdup(settings_get_str("alternate_nick"));
+ conn->username = g_strdup(settings_get_str("user_name"));
+ conn->realname = g_strdup(settings_get_str("real_name"));
+
+ /* proxy settings */
+ if (settings_get_bool("toggle_use_ircproxy")) {
+ conn->proxy = g_strdup(settings_get_str("proxy_address"));
+ conn->proxy_port = settings_get_int("proxy_port");
+ conn->proxy_string = g_strdup(settings_get_str("proxy_string"));
+ }
+
+ /* source IP */
+ get_source_host_ip();
+ if (source_host_ok) {
+ conn->own_ip = g_new(IPADDR, 1);
+ memcpy(conn->own_ip, source_host_ip, sizeof(IPADDR));
+ }
+
+ /* fill the information from setup */
+ sserver = server_setup_find(address, -1);
+ if (sserver == NULL) return conn;
+
+ sserver->last_connect = time(NULL);
+
+ if (sserver->ircnet) conn->ircnet = g_strdup(sserver->ircnet);
+ if (sserver->password && !conn->password)
+ conn->password = g_strdup(sserver->password);
+ if (sserver->cmd_queue_speed > 0)
+ conn->cmd_queue_speed = sserver->cmd_queue_speed;
+ if (sserver->max_cmds_at_once > 0)
+ conn->max_cmds_at_once = sserver->max_cmds_at_once;
+
+ /* fill the rest from IRC network settings */
+ ircnet = sserver->ircnet == NULL ? NULL : ircnet_find(sserver->ircnet);
+ if (ircnet == NULL) return conn;
+
+ if (ircnet->nick && !nick) {
+ g_free(conn->nick);
+ conn->nick = g_strdup(ircnet->nick);;
+ }
+ if (ircnet->username) {
+ g_free(conn->username);
+ conn->username = g_strdup(ircnet->username);;
+ }
+ if (ircnet->realname) {
+ g_free(conn->realname);
+ conn->realname = g_strdup(ircnet->realname);;
+ }
+ if (ircnet->max_kicks > 0) conn->max_kicks = ircnet->max_kicks;
+ if (ircnet->max_msgs > 0) conn->max_msgs = ircnet->max_msgs;
+ if (ircnet->max_modes > 0) conn->max_modes = ircnet->max_modes;
+ if (ircnet->max_whois > 0) conn->max_whois = ircnet->max_whois;
+
+ return conn;
+}
+
+/* Create server connection record. `dest' is required, rest can be NULL.
+ `dest' is either a server address or irc network */
+IRC_SERVER_CONNECT_REC *
+irc_server_create_conn(const char *dest, int port, const char *password, const char *nick)
+{
+ GSList *tmp;
+ time_t now;
+ int n;
+
+ g_return_val_if_fail(dest != NULL, NULL);
+
+ /* check if `dest' is IRC network */
+ if (ircnet_find(dest) == NULL)
+ return create_addr_conn(dest, port, password, nick);
+
+ /* first try to find a server that hasn't had any connection failures
+ for the past half an hour. If that isn't found, try any server. */
+ now = time(NULL);
+ for (n = 0; n < 2; n++) {
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SETUP_SERVER_REC *rec = tmp->data;
+
+ if (rec->ircnet == NULL || g_strcasecmp(rec->ircnet, dest) != 0)
+ continue;
+
+ if (n == 1 || !rec->last_failed || rec->last_connect < now-FAILED_RECONNECT_WAIT)
+ return create_addr_conn(rec->server, port, password, nick);
+ }
+ }
+
+ return NULL;
+}
+
+/* Find matching server from setup. Set port to -1 if you don't care about it */
+SETUP_SERVER_REC *server_setup_find(const char *address, int port)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(address != NULL, NULL);
+
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SETUP_SERVER_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->server, address) == 0 &&
+ (port == -1 || rec->port == port)) return rec;
+ }
+
+ return NULL;
+}
+
+static void init_userinfo(void)
+{
+ const char *set, *default_nick, *user_name;
+ char *str;
+
+ /* check if nick/username/realname wasn't read from setup.. */
+ set = settings_get_str("real_name");
+ if (set == NULL || *set == '\0') {
+ str = g_getenv("IRCNAME");
+ iconfig_set_str("settings", "real_name",
+ str != NULL ? str : g_get_real_name());
+ g_free_not_null(str);
+ }
+
+ /* username */
+ user_name = settings_get_str("user_name");
+ if (user_name == NULL || *user_name == '\0') {
+ str = g_getenv("IRCUSER");
+ iconfig_set_str("settings", "user_name",
+ str != NULL ? str : g_get_user_name());
+ g_free_not_null(str);
+
+ user_name = settings_get_str("user_name");
+ }
+
+ /* nick */
+ default_nick = settings_get_str("default_nick");
+ if (default_nick == NULL || *default_nick == '\0') {
+ str = g_getenv("IRCNICK");
+ iconfig_set_str("settings", "default_nick",
+ str != NULL ? str : user_name);
+ g_free_not_null(str);
+
+ default_nick = settings_get_str("default_nick");
+ }
+
+ /* alternate nick */
+ set = settings_get_str("alternate_nick");
+ if (set == NULL || *set == '\0') {
+ if (strlen(default_nick) < 9)
+ str = g_strconcat(default_nick, "_", NULL);
+ else {
+ str = g_strdup(default_nick);
+ str[strlen(str)-1] = '_';
+ }
+ iconfig_set_str("settings", "alternate_nick", str);
+ g_free(str);
+ }
+}
+
+static SETUP_SERVER_REC *setupserver_add(CONFIG_NODE *node)
+{
+ SETUP_SERVER_REC *rec;
+ char *ircnet, *server;
+ int port;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ ircnet = config_node_get_str(node, "ircnet", NULL);
+ server = config_node_get_str(node, "server", NULL);
+ if (ircnet == NULL || server == NULL) return NULL;
+
+ port = config_node_get_int(node, "port", 6667);
+ if (server_setup_find(server, port) != NULL) {
+ /* already exists - don't let it get there twice or
+ server reconnects will screw up! */
+ return NULL;
+ }
+
+ rec = g_new0(SETUP_SERVER_REC, 1);
+ rec->ircnet = g_strdup(ircnet);
+ rec->server = g_strdup(server);
+ rec->password = g_strdup(config_node_get_str(node, "password", ""));
+ rec->port = port;
+ rec->autoconnect = config_node_get_bool(node, "autoconnect", FALSE);
+ rec->max_cmds_at_once = config_node_get_int(node, "cmds_max_at_once", 0);
+ rec->cmd_queue_speed = config_node_get_int(node, "cmd_queue_speed", 0);
+
+ setupservers = g_slist_append(setupservers, rec);
+ return rec;
+}
+
+static void setupserver_destroy(SETUP_SERVER_REC *rec)
+{
+ setupservers = g_slist_remove(setupservers, rec);
+
+ g_free(rec->ircnet);
+ g_free(rec->server);
+ g_free(rec->password);
+ g_free(rec);
+}
+
+static void read_servers(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (setupservers != NULL)
+ setupserver_destroy(setupservers->data);
+
+ /* Read servers */
+ node = iconfig_node_traverse("(setupservers", FALSE);
+ if (node != NULL) {
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next)
+ setupserver_add(tmp->data);
+ }
+}
+
+void servers_setup_init(void)
+{
+ source_host_ok = FALSE;
+ source_host_ip = NULL;
+
+ settings_add_int("server", "server_reconnect_time", 300);
+ settings_add_str("server", "hostname", "");
+ settings_add_bool("server", "toggle_skip_motd", FALSE);
+
+ settings_add_str("server", "default_nick", NULL);
+ settings_add_str("server", "alternate_nick", NULL);
+ settings_add_str("server", "user_name", NULL);
+ settings_add_str("server", "real_name", NULL);
+
+ settings_add_bool("ircproxy", "toggle_use_ircproxy", FALSE);
+ settings_add_str("ircproxy", "proxy_address", "");
+ settings_add_int("ircproxy", "proxy_port", 6667);
+ settings_add_str("ircproxy", "proxy_string", "CONNECT %s %d");
+
+ init_userinfo();
+
+ read_servers();
+ signal_add("setup reread", (SIGNAL_FUNC) read_servers);
+}
+
+void servers_setup_deinit(void)
+{
+ while (setupservers != NULL)
+ setupserver_destroy(setupservers->data);
+
+ signal_remove("setup reread", (SIGNAL_FUNC) read_servers);
+}
diff --git a/src/irc/core/server-setup.h b/src/irc/core/server-setup.h
new file mode 100644
index 00000000..a3a3d4ff
--- /dev/null
+++ b/src/irc/core/server-setup.h
@@ -0,0 +1,40 @@
+#ifndef __SERVER_SETUP_H
+#define __SERVER_SETUP_H
+
+#include "irc-server.h"
+
+/* servers */
+typedef struct {
+ char *server;
+ int port;
+
+ char *ircnet;
+ char *password;
+ int autoconnect;
+ int max_cmds_at_once; /* override the default if > 0 */
+ int cmd_queue_speed; /* override the default if > 0 */
+
+ char *own_address; /* address to use when connecting this server */
+ IPADDR *own_ip; /* resolved own_address or full of zeros */
+
+ time_t last_connect; /* to avoid reconnecting too fast.. */
+ int last_failed; /* if last connection attempt failed */
+} SETUP_SERVER_REC;
+
+extern GSList *setupservers; /* list of irc servers */
+
+extern IPADDR *source_host_ip; /* Resolved address */
+extern gboolean source_host_ok; /* Use source_host_ip .. */
+
+/* Create server connection record. `dest' is required, rest can be NULL.
+ `dest' is either a server address or irc network */
+IRC_SERVER_CONNECT_REC *
+irc_server_create_conn(const char *dest, int port, const char *password, const char *nick);
+
+/* Find matching server from setup. Set port to -1 if you don't care about it */
+SETUP_SERVER_REC *server_setup_find(const char *address, int port);
+
+void servers_setup_init(void);
+void servers_setup_deinit(void);
+
+#endif