summaryrefslogtreecommitdiff
path: root/src/irc
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
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')
-rw-r--r--src/irc/Makefile.am5
-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
-rw-r--r--src/irc/dcc/Makefile.am14
-rw-r--r--src/irc/dcc/dcc-chat.c371
-rw-r--r--src/irc/dcc/dcc-chat.h7
-rw-r--r--src/irc/dcc/dcc-files.c577
-rw-r--r--src/irc/dcc/dcc-files.h7
-rw-r--r--src/irc/dcc/dcc.c550
-rw-r--r--src/irc/dcc/dcc.h91
-rw-r--r--src/irc/dcc/module.h3
-rw-r--r--src/irc/flood/Makefile.am12
-rw-r--r--src/irc/flood/autoignore.c250
-rw-r--r--src/irc/flood/autoignore.h18
-rw-r--r--src/irc/flood/flood.c212
-rw-r--r--src/irc/flood/flood.h7
-rw-r--r--src/irc/flood/module.h12
-rw-r--r--src/irc/irc.c27
-rw-r--r--src/irc/notifylist/Makefile.am15
-rw-r--r--src/irc/notifylist/module.h44
-rw-r--r--src/irc/notifylist/notify-commands.c81
-rw-r--r--src/irc/notifylist/notify-ison.c354
-rw-r--r--src/irc/notifylist/notify-setup.c84
-rw-r--r--src/irc/notifylist/notify-setup.h9
-rw-r--r--src/irc/notifylist/notify-whois.c186
-rw-r--r--src/irc/notifylist/notifylist.c356
-rw-r--r--src/irc/notifylist/notifylist.h32
78 files changed, 11763 insertions, 0 deletions
diff --git a/src/irc/Makefile.am b/src/irc/Makefile.am
new file mode 100644
index 00000000..b2972564
--- /dev/null
+++ b/src/irc/Makefile.am
@@ -0,0 +1,5 @@
+SUBDIRS = core dcc flood notifylist
+
+noinst_LTLIBRARIES = libirc.la
+
+libirc_la_SOURCES = irc.c
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
diff --git a/src/irc/dcc/Makefile.am b/src/irc/dcc/Makefile.am
new file mode 100644
index 00000000..b7cd30e9
--- /dev/null
+++ b/src/irc/dcc/Makefile.am
@@ -0,0 +1,14 @@
+noinst_LTLIBRARIES = libirc_dcc.la
+
+INCLUDES = $(GLIB_CFLAGS) \
+ -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ -I$(top_srcdir)/src/irc/core/
+
+libirc_dcc_la_SOURCES = \
+ dcc.c \
+ dcc-chat.c \
+ dcc-files.c
+
+noinst_HEADERS = \
+ dcc.h \
+ dcc-chat.h \
+ dcc-files.h
diff --git a/src/irc/dcc/dcc-chat.c b/src/irc/dcc/dcc-chat.c
new file mode 100644
index 00000000..5cddddfa
--- /dev/null
+++ b/src/irc/dcc/dcc-chat.c
@@ -0,0 +1,371 @@
+/*
+ dcc-chat.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "signals.h"
+#include "commands.h"
+#include "network.h"
+#include "net-nonblock.h"
+#include "line-split.h"
+#include "settings.h"
+
+#include "masks.h"
+#include "irc.h"
+#include "server-setup.h"
+
+#include "dcc.h"
+
+/* Send text to DCC chat */
+static void dcc_chat_write(gchar *data)
+{
+ DCC_REC *dcc;
+ gchar *params, *text, *target;
+ gint len;
+
+ g_return_if_fail(text != NULL);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &text);
+
+ if (*target == '=')
+ {
+ /* dcc msg */
+ dcc = dcc_find_item(DCC_TYPE_CHAT, ++target, NULL);
+ if (dcc != NULL)
+ {
+ len = strlen(text);
+ /* FIXME: we need output queue! */
+ if (net_transmit(dcc->handle, text, len) != len)
+ g_warning("dcc_chat_write() : could not send all data!");
+ net_transmit(dcc->handle, "\n", 1);
+ }
+ }
+
+ g_free(params);
+}
+
+static void dcc_chat_me(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ DCC_REC *dcc;
+ char *str;
+
+ g_return_if_fail(data != NULL);
+
+ dcc = irc_item_dcc_chat(item);
+ if (dcc == NULL) return;
+
+ str = g_strdup_printf("ACTION %s", data);
+ dcc_ctcp_message(dcc->nick, NULL, dcc, FALSE, str);
+ g_free(str);
+}
+
+static void dcc_chat_action(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *target, *text;
+ DCC_REC *dcc;
+ char *str;
+
+ g_return_if_fail(data != NULL);
+
+ if (*data != '=') {
+ /* handle only DCC actions */
+ return;
+ }
+
+ params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &target, &text);
+ if (*target == '\0' || *text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ dcc = dcc_find_item(DCC_TYPE_CHAT, target+1, NULL);
+ if (dcc != NULL) {
+ str = g_strdup_printf("ACTION %s", data);
+ dcc_ctcp_message(dcc->nick, NULL, dcc, FALSE, str);
+ g_free(str);
+ }
+ g_free(params);
+}
+
+static void dcc_chat_ctcp(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *target, *ctcpcmd, *ctcpdata;
+ DCC_REC *dcc;
+ char *str;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected) 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);
+
+ if (*target != '=') {
+ /* handle only DCC CTCPs */
+ g_free(params);
+ return;
+ }
+
+ dcc = dcc_find_item(DCC_TYPE_CHAT, target+1, NULL);
+ if (dcc != NULL) {
+ g_strup(ctcpcmd);
+
+ str = g_strdup_printf("%s %s", ctcpcmd, ctcpdata);
+ dcc_ctcp_message(dcc->nick, NULL, dcc, FALSE, str);
+ g_free(str);
+ }
+
+ g_free(params);
+}
+
+/* DCC CHAT: text received */
+static void dcc_chat_msg(DCC_REC *dcc, gchar *msg)
+{
+ gchar *cmd, *ptr;
+ gboolean reply;
+
+ g_return_if_fail(dcc != NULL);
+ g_return_if_fail(msg != NULL);
+
+ reply = FALSE;
+ if (g_strncasecmp(msg, "CTCP_MESSAGE ", 13) != 0)
+ {
+ if (g_strncasecmp(msg, "CTCP_REPLY ", 11) != 0)
+ {
+ /* Use the mirc style CTCPing from now on.. */
+ dcc->mirc_ctcp = TRUE;
+ }
+ else
+ {
+ /* bitchx (and ircii?) sends this */
+ msg += 11;
+ reply = TRUE;
+ dcc->mirc_ctcp = FALSE;
+ }
+ }
+ else
+ {
+ /* bitchx (and ircii?) sends this */
+ msg += 13;
+ dcc->mirc_ctcp = FALSE;
+ }
+
+ /* Handle only DCC CTCPs */
+ if (*msg != 1)
+ return;
+
+ msg = g_strdup(msg+1);
+ /* remove the later \001 */
+ ptr = strrchr(msg, 1);
+ if (ptr != NULL) *ptr = '\0';
+
+ /* get ctcp command */
+ cmd = g_strconcat(reply ? "dcc reply " : "dcc ctcp ", msg, NULL);
+ ptr = strchr(cmd+9, ' ');
+ if (ptr != NULL) *ptr++ = '\0'; else ptr = "";
+
+ g_strdown(cmd+9);
+ if (!signal_emit(cmd, 2, ptr, dcc))
+ signal_emit(reply ? "default dcc reply" : "default dcc ctcp", 2, msg, dcc);
+
+ g_free(cmd);
+ g_free(msg);
+
+ signal_stop();
+}
+
+/* input function: DCC CHAT received some data.. */
+static void dcc_chat_input(DCC_REC *dcc)
+{
+ char tmpbuf[512], *str;
+ int recvlen, ret;
+
+ g_return_if_fail(dcc != NULL);
+
+ do {
+ recvlen = net_receive(dcc->handle, tmpbuf, sizeof(tmpbuf));
+
+ ret = line_split(tmpbuf, recvlen, &str, (LINEBUF_REC **) &dcc->databuf);
+ if (ret == -1) {
+ /* connection lost */
+ dcc->destroyed = TRUE;
+ signal_emit("dcc closed", 1, dcc);
+ dcc_destroy(dcc);
+ break;
+ }
+
+ if (ret > 0) {
+ dcc->transfd += ret;
+ signal_emit("dcc chat message", 2, dcc, str);
+ }
+ } while (ret > 0);
+}
+
+/* input function: DCC CHAT - someone tried to connect to our socket */
+static void dcc_chat_listen(DCC_REC *dcc)
+{
+ IPADDR ip;
+ gint handle, port;
+
+ g_return_if_fail(dcc != NULL);
+
+ /* accept connection */
+ handle = net_accept(dcc->handle, &ip, &port);
+ if (handle == -1)
+ return;
+
+ /* FIXME: add paranoia checking, check if host ip is the same as to who
+ we sent the DCC CHAT request.. */
+
+ g_source_remove(dcc->tagread);
+ close(dcc->handle);
+
+ dcc->starttime = time(NULL);
+ dcc->handle = handle;
+ memcpy(&dcc->addr, &ip, sizeof(IPADDR));
+ net_ip2host(&dcc->addr, dcc->addrstr);
+ dcc->port = port;
+ dcc->tagread = g_input_add(handle, G_INPUT_READ,
+ (GInputFunction) dcc_chat_input, dcc);
+
+ signal_emit("dcc connected", 1, dcc);
+}
+
+/* callback: DCC CHAT - net_connect_nonblock() finished */
+static void dcc_chat_connect(DCC_REC *dcc)
+{
+ g_return_if_fail(dcc != NULL);
+
+ g_source_remove(dcc->tagread);
+ if (net_geterror(dcc->handle) != 0)
+ {
+ /* error connecting */
+ signal_emit("dcc error connect", 1, dcc);
+ dcc_destroy(dcc);
+ return;
+ }
+
+ /* connect ok. */
+ dcc->starttime = time(NULL);
+ dcc->tagread = g_input_add(dcc->handle, G_INPUT_READ,
+ (GInputFunction) dcc_chat_input, dcc);
+
+ signal_emit("dcc connected", 1, dcc);
+}
+
+/* command: DCC CHAT */
+static void cmd_dcc_chat(gchar *data, IRC_SERVER_REC *server)
+{
+ DCC_REC *dcc;
+ IPADDR addr;
+ gchar *str;
+ gint port, handle;
+
+ g_return_if_fail(data != NULL);
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ dcc = dcc_find_item(DCC_TYPE_CHAT, data, NULL);
+ if (dcc != NULL)
+ {
+ if (dcc->addrstr[0] == '\0' || dcc->starttime != 0)
+ {
+ /* already sent a chat request / already chatting */
+ return;
+ }
+
+ /* found from dcc list - so we're the connecting side.. */
+ dcc->handle = net_connect_ip(&dcc->addr, dcc->port,
+ source_host_ok ? source_host_ip : NULL);
+ if (dcc->handle != -1)
+ {
+ dcc->tagread = g_input_add(dcc->handle, G_INPUT_WRITE,
+ (GInputFunction) dcc_chat_connect, dcc);
+ }
+ else
+ {
+ /* error connecting */
+ signal_emit("dcc error connect", 1, dcc);
+ dcc_destroy(dcc);
+ }
+
+ return;
+ }
+
+ /* send dcc chat request */
+ if (server == NULL || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (!net_getsockname(server->handle, &addr, NULL))
+ cmd_return_error(CMDERR_GETSOCKNAME);
+
+ port = settings_get_int("dcc_port");
+ handle = net_listen(&addr, &port);
+ if (handle == -1)
+ cmd_return_error(CMDERR_LISTEN);
+
+ dcc = dcc_create(DCC_TYPE_CHAT, handle, data, "chat", server, NULL);
+ dcc->tagread = g_input_add(dcc->handle, G_INPUT_READ,
+ (GInputFunction) dcc_chat_listen, dcc);
+
+ /* send the request */
+ str = g_strdup_printf("PRIVMSG %s :\001DCC CHAT CHAT %s %d\001",
+ data, dcc_make_address(&addr), port);
+ irc_send_cmd(server, str);
+ g_free(str);
+}
+
+static void cmd_mircdcc(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ DCC_REC *dcc;
+
+ g_return_if_fail(data != NULL);
+
+ dcc = irc_item_dcc_chat(item);
+ if (dcc == NULL) return;
+
+ dcc->mirc_ctcp = toupper(*data) == 'N' ? FALSE : TRUE;
+}
+
+static void dcc_ctcp_redirect(gchar *msg, DCC_REC *dcc)
+{
+ g_return_if_fail(msg != NULL);
+ g_return_if_fail(dcc != NULL);
+
+ signal_emit("ctcp msg dcc", 6, msg, dcc->server, dcc->nick, "dcc", dcc->mynick, dcc);
+}
+
+void dcc_chat_init(void)
+{
+ command_bind("msg", NULL, (SIGNAL_FUNC) dcc_chat_write);
+ command_bind("me", NULL, (SIGNAL_FUNC) dcc_chat_me);
+ command_bind("action", NULL, (SIGNAL_FUNC) dcc_chat_action);
+ command_bind("ctcp", NULL, (SIGNAL_FUNC) dcc_chat_ctcp);
+ command_bind("dcc chat", NULL, (SIGNAL_FUNC) cmd_dcc_chat);
+ signal_add_first("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg);
+ command_bind("mircdcc", NULL, (SIGNAL_FUNC) cmd_mircdcc);
+ signal_add("dcc ctcp dcc", (SIGNAL_FUNC) dcc_ctcp_redirect);
+}
+
+void dcc_chat_deinit(void)
+{
+ command_unbind("msg", (SIGNAL_FUNC) dcc_chat_write);
+ command_unbind("me", (SIGNAL_FUNC) dcc_chat_me);
+ command_unbind("action", (SIGNAL_FUNC) dcc_chat_action);
+ command_unbind("ctcp", (SIGNAL_FUNC) dcc_chat_ctcp);
+ command_unbind("dcc chat", (SIGNAL_FUNC) cmd_dcc_chat);
+ signal_remove("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg);
+ command_unbind("mircdcc", (SIGNAL_FUNC) cmd_mircdcc);
+ signal_remove("dcc ctcp dcc", (SIGNAL_FUNC) dcc_ctcp_redirect);
+}
diff --git a/src/irc/dcc/dcc-chat.h b/src/irc/dcc/dcc-chat.h
new file mode 100644
index 00000000..9ae9503f
--- /dev/null
+++ b/src/irc/dcc/dcc-chat.h
@@ -0,0 +1,7 @@
+#ifndef __DCC_CHAT_H
+#define __DCC_CHAT_H
+
+void dcc_chat_init(void);
+void dcc_chat_deinit(void);
+
+#endif
diff --git a/src/irc/dcc/dcc-files.c b/src/irc/dcc/dcc-files.c
new file mode 100644
index 00000000..23b1cdce
--- /dev/null
+++ b/src/irc/dcc/dcc-files.c
@@ -0,0 +1,577 @@
+/*
+ dcc-files.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "signals.h"
+#include "commands.h"
+#include "network.h"
+#include "line-split.h"
+#include "misc.h"
+#include "settings.h"
+
+#include "masks.h"
+#include "irc.h"
+#include "server-setup.h"
+
+#include "dcc.h"
+
+static gint dcc_file_create_mode;
+
+static gchar *dcc_prepare_path(gchar *fname)
+{
+ gchar *str, *ptr, *downpath;
+
+ /* strip all paths from file. */
+ ptr = strrchr(fname, '/');
+ if (ptr == NULL) ptr = fname; else ptr++;
+
+ downpath = convert_home(settings_get_str("dcc_download_path"));
+ str = g_strdup_printf("%s/%s", downpath, ptr);
+ g_free(downpath);
+
+ return str;
+}
+
+/* input function: DCC GET received data */
+static void dcc_receive(DCC_REC *dcc)
+{
+ guint32 recd;
+ gint len, ret;
+
+ g_return_if_fail(dcc != NULL);
+
+ for (;;)
+ {
+ len = net_receive(dcc->handle, dcc->databuf, dcc->databufsize);
+ if (len == 0) break;
+ if (len < 0)
+ {
+ /* socket closed - transmit complete (or other side died..) */
+ signal_emit("dcc closed", 1, dcc);
+ dcc_destroy(dcc);
+ return;
+ }
+
+ write(dcc->fhandle, dcc->databuf, len);
+ dcc->transfd += len;
+ }
+
+ /* send number of total bytes received - if for some reason we couldn't
+ send the 4 characters last time, try to somehow fix it this time by
+ sending missing amount of 0 characters.. */
+ if (dcc->trans_bytes != 0)
+ {
+ recd = (guint32) htonl(0);
+ dcc->trans_bytes += net_transmit(dcc->handle, ((gchar *) &recd)+dcc->trans_bytes, 4-dcc->trans_bytes);
+ if (dcc->trans_bytes == 4) dcc->trans_bytes = 0;
+ }
+
+ if (dcc->trans_bytes == 0)
+ {
+ recd = (guint32) htonl(dcc->transfd);
+ ret = net_transmit(dcc->handle, ((gchar *) &recd), 4);
+ if (ret > 0 && ret < 4) dcc->trans_bytes = ret;
+ }
+ signal_emit("dcc transfer update", 1, dcc);
+}
+
+/* callback: net_connect() finished for DCC GET */
+static void dcc_get_connect(DCC_REC *dcc)
+{
+ struct stat statbuf;
+
+ g_return_if_fail(dcc != NULL);
+
+ g_source_remove(dcc->tagread);
+ if (net_geterror(dcc->handle) != 0)
+ {
+ /* error connecting */
+ signal_emit("dcc error connect", 1, dcc);
+ dcc_destroy(dcc);
+ return;
+ }
+ dcc->file = dcc_prepare_path(dcc->arg);
+
+ /* if some plugin wants to change the file name/path here.. */
+ signal_emit("dcc get receive", 1, dcc);
+
+ if (stat(dcc->file, &statbuf) == 0 &&
+ (dcc->get_type == DCC_GET_RENAME || dcc->get_type == DCC_GET_DEFAULT))
+ {
+ /* file exists, rename.. */
+ GString *newname;
+ gint num;
+
+ newname = g_string_new(NULL);
+ for (num = 1; ; num++)
+ {
+ g_string_sprintf(newname, "%s.%d", dcc->file, num);
+ if (stat(newname->str, &statbuf) != 0) break;
+ }
+ g_free(dcc->file);
+ dcc->file = newname->str;
+ g_string_free(newname, FALSE);
+ }
+
+ if (dcc->get_type != DCC_GET_RESUME)
+ {
+ dcc->fhandle = open(dcc->file, O_WRONLY | O_TRUNC | O_CREAT, dcc_file_create_mode);
+ if (dcc->fhandle == -1)
+ {
+ signal_emit("dcc error file create", 2, dcc, dcc->file);
+ dcc_destroy(dcc);
+ return;
+ }
+ }
+
+ dcc->databufsize = settings_get_int("dcc_block_size") > 0 ? settings_get_int("dcc_block_size") : 2048;
+ dcc->databuf = g_malloc(dcc->databufsize);
+
+ dcc->starttime = time(NULL);
+ dcc->tagread = g_input_add(dcc->handle, G_INPUT_READ,
+ (GInputFunction) dcc_receive, dcc);
+ signal_emit("dcc connected", 1, dcc);
+}
+
+/* command: DCC GET */
+static void cmd_dcc_get(gchar *data)
+{
+ DCC_REC *dcc;
+ GSList *tmp, *next;
+ gchar *params, *nick, *fname;
+ gboolean found;
+
+ g_return_if_fail(data != NULL);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &nick, &fname);
+ if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ dcc = NULL; found = FALSE;
+ for (tmp = dcc_conns; tmp != NULL; tmp = next)
+ {
+ dcc = tmp->data;
+ next = tmp->next;
+
+ if (dcc->dcc_type == DCC_TYPE_GET && dcc->handle == -1 && g_strcasecmp(dcc->nick, nick) == 0 &&
+ (*fname == '\0' || strcmp(dcc->arg, fname) == 0))
+ {
+ /* found! */
+ found = TRUE;
+ dcc->handle = net_connect_ip(&dcc->addr, dcc->port,
+ source_host_ok ? source_host_ip : NULL);
+ if (dcc->handle != -1)
+ {
+ dcc->tagread = g_input_add(dcc->handle, G_INPUT_WRITE,
+ (GInputFunction) dcc_get_connect, dcc);
+ }
+ else
+ {
+ /* error connecting */
+ signal_emit("dcc error connect", 1, dcc);
+ dcc_destroy(dcc);
+ }
+ }
+ }
+
+ if (!found)
+ signal_emit("dcc error get not found", 1, nick);
+
+ g_free(params);
+}
+
+/* resume setup: DCC SEND - we either said resume on get, or when we sent,
+ someone chose resume */
+static void dcc_resume_setup(DCC_REC *dcc, gint port)
+{
+ gchar *str;
+
+ /* Check for DCC_SEND_RESUME */
+ if (dcc->dcc_type == DCC_TYPE_SEND)
+ {
+ if (lseek(dcc->fhandle, dcc->transfd, SEEK_SET) == -1)
+ {
+ signal_emit("dcc closed", 1, dcc);
+ dcc_destroy(dcc);
+ return;
+ }
+ else
+ {
+ str = g_strdup_printf("DCC ACCEPT %s %d %lu",
+ dcc->arg, port, dcc->transfd);
+ dcc_ctcp_message(dcc->nick, dcc->server, dcc->chat, FALSE, str);
+ g_free(str);
+ }
+ }
+
+ /* Check for DCC_GET_RESUME */
+ if (dcc->dcc_type == DCC_TYPE_GET && dcc->get_type == DCC_GET_RESUME)
+ {
+ dcc->handle = net_connect_ip(&dcc->addr, dcc->port,
+ source_host_ok ? source_host_ip : NULL);
+ if (dcc->handle != -1)
+ {
+ dcc->tagread = g_input_add(dcc->handle, G_INPUT_WRITE,
+ (GInputFunction) dcc_get_connect, dcc);
+ }
+ else
+ {
+ /* error connecting */
+ signal_emit("dcc error connect", 1, dcc);
+ dcc_destroy(dcc);
+ }
+ }
+}
+
+static void dcc_ctcp_msg(gchar *data, IRC_SERVER_REC *server, gchar *sender, gchar *sendaddr, gchar *target, DCC_REC *chat)
+{
+ gchar *params, *type, *arg, *portstr, *sizestr;
+ gulong size;
+ gint port;
+ DCC_REC *dcc;
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(sender != NULL);
+
+ params = cmd_get_params(data, 4, &type, &arg, &portstr, &sizestr);
+ if (g_strcasecmp(type, "RESUME") == 0 || g_strcasecmp(type, "ACCEPT") == 0)
+ {
+ if (sscanf(portstr, "%d", &port) != 1) port = 0;
+ if (sscanf(sizestr, "%lu", &size) != 1) size = 0;
+
+ dcc = dcc_find_by_port(sender, port);
+ if (dcc != NULL && (dcc->dcc_type == DCC_TYPE_GET || dcc->transfd == 0))
+ {
+ dcc->transfd = size;
+ dcc->skipped = size;
+ dcc_resume_setup(dcc, port);
+ }
+ }
+
+ g_free(params);
+}
+
+static void dcc_resume_rec(DCC_REC *dcc)
+{
+ gchar *str;
+
+ dcc->file = dcc_prepare_path(dcc->arg);
+
+ dcc->fhandle = open(dcc->file, O_WRONLY, dcc_file_create_mode);
+ if (dcc->fhandle == -1)
+ {
+ signal_emit("dcc error file not found", 2, dcc, dcc->file);
+ dcc_destroy(dcc);
+ }
+ else
+ {
+ dcc->transfd = lseek(dcc->fhandle, 0, SEEK_END);
+ if (dcc->transfd < 0) dcc->transfd = 0;
+ dcc->skipped = dcc->transfd;
+
+ str = g_strdup_printf("DCC RESUME %s %d %lu",
+ dcc->arg, dcc->port, dcc->transfd);
+ dcc_ctcp_message(dcc->nick, dcc->server, dcc->chat, FALSE, str);
+ g_free(str);
+ }
+}
+
+/* command: DCC RESUME */
+static void cmd_dcc_resume(gchar *data)
+{
+ DCC_REC *dcc;
+ GSList *tmp;
+ gchar *params, *nick, *fname;
+ gboolean found;
+
+ g_return_if_fail(data != NULL);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &nick, &fname);
+ if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ dcc = NULL; found = FALSE;
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next)
+ {
+ dcc = tmp->data;
+
+ if (dcc->dcc_type == DCC_TYPE_GET && dcc->handle == -1 && g_strcasecmp(dcc->nick, nick) == 0 &&
+ (*fname == '\0' || strcmp(dcc->arg, fname) == 0))
+ {
+ /* found! */
+ dcc->get_type = DCC_GET_RESUME;
+ dcc_resume_rec(dcc);
+ found = TRUE;
+ }
+ }
+
+ if (!found)
+ signal_emit("dcc error get not found", 1, nick);
+
+ g_free(params);
+}
+
+/* input function: DCC SEND send more data */
+static void dcc_send_data(DCC_REC *dcc)
+{
+ gint n;
+
+ g_return_if_fail(dcc != NULL);
+
+ if (!dcc->fastsend && !dcc->gotalldata)
+ {
+ /* haven't received everything we've send there yet.. */
+ return;
+ }
+
+ n = read(dcc->fhandle, dcc->databuf, dcc->databufsize);
+ if (n <= 0)
+ {
+ /* end of file .. or some error .. */
+ if (dcc->fastsend)
+ {
+ /* no need to call this function anymore.. in fact it just eats
+ all the cpu.. */
+ dcc->waitforend = TRUE;
+ g_source_remove(dcc->tagwrite);
+ dcc->tagwrite = -1;
+ }
+ else
+ {
+ signal_emit("dcc closed", 1, dcc);
+ dcc_destroy(dcc);
+ }
+ return;
+ }
+
+ dcc->transfd += net_transmit(dcc->handle, dcc->databuf, n);
+ dcc->gotalldata = FALSE;
+
+ lseek(dcc->fhandle, dcc->transfd, SEEK_SET);
+
+ signal_emit("dcc transfer update", 1, dcc);
+}
+
+/* input function: DCC SEND received some data */
+static void dcc_send_read_size(DCC_REC *dcc)
+{
+ guint32 bytes;
+ gint ret;
+
+ g_return_if_fail(dcc != NULL);
+
+ if (dcc->read_pos == 4)
+ return;
+
+ /* we need to get 4 bytes.. */
+ ret = net_receive(dcc->handle, dcc->read_buf+dcc->read_pos, 4-dcc->read_pos);
+ if (ret == -1)
+ {
+ signal_emit("dcc closed", 1, dcc);
+ dcc_destroy(dcc);
+ return;
+ }
+
+ dcc->read_pos += ret;
+
+ if (dcc->read_pos == 4)
+ {
+ bytes = 0; memcpy(&bytes, dcc->read_buf, 4);
+ bytes = (guint32) ntohl(bytes);
+
+ dcc->gotalldata = bytes == dcc->transfd;
+ dcc->read_pos = 0;
+
+ if (!dcc->fastsend)
+ {
+ /* send more data.. */
+ dcc_send_data(dcc);
+ }
+
+ if (dcc->waitforend && dcc->gotalldata)
+ {
+ /* file is sent */
+ signal_emit("dcc closed", 1, dcc);
+ dcc_destroy(dcc);
+ return;
+ }
+ }
+}
+
+/* input function: DCC SEND - someone tried to connect to our socket */
+static void dcc_send_init(DCC_REC *dcc)
+{
+ gint handle, port;
+ IPADDR addr;
+
+ g_return_if_fail(dcc != NULL);
+
+ /* accept connection */
+ handle = net_accept(dcc->handle, &addr, &port);
+ if (handle == -1)
+ return;
+
+ /* FIXME: add paranoia checking, check if host ip is the same as to who
+ we sent the DCC SEND request.. */
+
+ g_source_remove(dcc->tagread);
+ close(dcc->handle);
+
+ dcc->fastsend = settings_get_bool("toggle_dcc_fast_send");
+ dcc->handle = handle;
+ memcpy(&dcc->addr, &addr, sizeof(IPADDR));
+ net_ip2host(&dcc->addr, dcc->addrstr);
+ dcc->port = port;
+ dcc->databufsize = settings_get_int("dcc_block_size") > 0 ? settings_get_int("dcc_block_size") : 2048;
+ dcc->databuf = g_malloc(dcc->databufsize);
+ dcc->starttime = time(NULL);
+ dcc->tagread = g_input_add(handle, G_INPUT_READ,
+ (GInputFunction) dcc_send_read_size, dcc);
+ dcc->tagwrite = !dcc->fastsend ? -1 :
+ g_input_add(handle, G_INPUT_WRITE, (GInputFunction) dcc_send_data, dcc);
+
+ signal_emit("dcc connected", 1, dcc);
+
+ if (!dcc->fastsend)
+ {
+ /* send first block */
+ dcc->gotalldata = TRUE;
+ dcc_send_data(dcc);
+ }
+}
+
+/* command: DCC SEND */
+static void cmd_dcc_send(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ gchar *params, *target, *fname, *str, *ptr;
+ gint fh, h, port;
+ glong fsize;
+ DCC_REC *dcc, *chat;
+ IPADDR addr;
+
+ g_return_if_fail(data != NULL);
+
+ params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &fname);
+
+ /* if we're in dcc chat, send the request via it. */
+ chat = irc_item_dcc_chat(item);
+
+ if (chat != NULL && (chat->mirc_ctcp || g_strcasecmp(target, chat->nick) != 0))
+ chat = NULL;
+
+ if ((server == NULL || !server->connected) && chat == NULL)
+ cmd_param_error(CMDERR_NOT_CONNECTED);
+
+ if (*target == '\0' || *fname == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (dcc_find_item(DCC_TYPE_SEND, target, fname))
+ {
+ signal_emit("dcc error send exists", 2, target, fname);
+ g_free(params);
+ return;
+ }
+
+ str = convert_home(fname);
+ if (*str != '/')
+ {
+ gchar *path;
+
+ g_free(str);
+ path = convert_home(settings_get_str("dcc_upload_path"));
+ str = g_strconcat(path, "/", fname, NULL);
+ g_free(path);
+ }
+
+ fh = open(str, O_RDONLY);
+ g_free(str);
+
+ if (fh == -1)
+ {
+ signal_emit("dcc error file not found", 2, target, fname);
+ g_free(params);
+ return;
+ }
+ fsize = lseek(fh, 0, SEEK_END);
+ lseek(fh, 0, SEEK_SET);
+
+ /* get the IP address we use with IRC server */
+ if (!net_getsockname(chat != NULL ? chat->handle : server->handle, &addr, NULL))
+ {
+ close(fh);
+ cmd_param_error(CMDERR_GETSOCKNAME);
+ }
+
+ /* start listening */
+ port = settings_get_int("dcc_port");
+ h = net_listen(&addr, &port);
+ if (h == -1)
+ {
+ close(fh);
+ cmd_param_error(CMDERR_LISTEN);
+ }
+
+ /* skip path */
+ ptr = strrchr(fname, '/');
+ if (ptr != NULL) fname = ptr+1;
+
+ /* change all spaces to _ */
+ fname = g_strdup(fname);
+ for (ptr = fname; *ptr != '\0'; ptr++)
+ if (*ptr == ' ') *ptr = '_';
+
+ dcc = dcc_create(DCC_TYPE_SEND, h, target, fname, server, chat);
+ dcc->port = port;
+ dcc->size = fsize;
+ dcc->fhandle = fh;
+ dcc->tagread = g_input_add(h, G_INPUT_READ,
+ (GInputFunction) dcc_send_init, dcc);
+
+ /* send DCC request */
+ str = g_strdup_printf("DCC SEND %s %s %d %lu",
+ fname, dcc_make_address(&addr), port, fsize);
+ dcc_ctcp_message(target, server, chat, FALSE, str);
+ g_free(str);
+
+ g_free(fname);
+ g_free(params);
+}
+
+static void read_settings(void)
+{
+ dcc_file_create_mode = octal2dec(settings_get_int("dcc_file_create_mode"));
+}
+
+void dcc_files_init(void)
+{
+ signal_add("ctcp msg dcc", (SIGNAL_FUNC) dcc_ctcp_msg);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("irssi init finished", (SIGNAL_FUNC) read_settings);
+ command_bind("dcc send", NULL, (SIGNAL_FUNC) cmd_dcc_send);
+ command_bind("dcc get", NULL, (SIGNAL_FUNC) cmd_dcc_get);
+ command_bind("dcc resume", NULL, (SIGNAL_FUNC) cmd_dcc_resume);
+}
+
+void dcc_files_deinit(void)
+{
+ signal_remove("ctcp msg dcc", (SIGNAL_FUNC) dcc_ctcp_msg);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("irssi init finished", (SIGNAL_FUNC) read_settings);
+ command_unbind("dcc send", (SIGNAL_FUNC) cmd_dcc_send);
+ command_unbind("dcc get", (SIGNAL_FUNC) cmd_dcc_get);
+ command_unbind("dcc resume", (SIGNAL_FUNC) cmd_dcc_resume);
+}
diff --git a/src/irc/dcc/dcc-files.h b/src/irc/dcc/dcc-files.h
new file mode 100644
index 00000000..3d12ffc1
--- /dev/null
+++ b/src/irc/dcc/dcc-files.h
@@ -0,0 +1,7 @@
+#ifndef __DCC_FILES_H
+#define __DCC_FILES_H
+
+void dcc_files_init(void);
+void dcc_files_deinit(void);
+
+#endif
diff --git a/src/irc/dcc/dcc.c b/src/irc/dcc/dcc.c
new file mode 100644
index 00000000..41833744
--- /dev/null
+++ b/src/irc/dcc/dcc.c
@@ -0,0 +1,550 @@
+/*
+ dcc.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 "network.h"
+#include "line-split.h"
+#include "settings.h"
+
+#include "masks.h"
+#include "irc.h"
+
+#include "dcc.h"
+
+#define DCC_TYPES 5
+
+static gchar *dcc_types[] =
+{
+ "CHAT",
+ "SEND",
+ "GET",
+ "RESUME",
+ "ACCEPT"
+};
+
+GSList *dcc_conns;
+
+static gint dcc_timeouttag;
+
+/* Create new DCC record */
+DCC_REC *dcc_create(gint type, gint handle, gchar *nick, gchar *arg, IRC_SERVER_REC *server, DCC_REC *chat)
+{
+ DCC_REC *dcc;
+
+ g_return_val_if_fail(nick != NULL, NULL);
+ g_return_val_if_fail(arg != NULL, NULL);
+
+ dcc = g_new0(DCC_REC, 1);
+ dcc->type = type == DCC_TYPE_CHAT ? module_get_uniq_id("IRC", WI_IRC_DCC_CHAT) : -1;
+ dcc->mirc_ctcp = settings_get_bool("toggle_dcc_mirc_ctcp");
+ dcc->created = time(NULL);
+ dcc->chat = chat;
+ dcc->dcc_type = type;
+ dcc->arg = g_strdup(arg);
+ dcc->nick = g_strdup(nick);
+ dcc->handle = handle;
+ dcc->fhandle = -1;
+ dcc->tagread = dcc->tagwrite = -1;
+ dcc->server = server;
+ dcc->mynick = g_strdup(server != NULL ? server->nick :
+ chat != NULL ? chat->nick : "??");
+ dcc->ircnet = server == NULL ?
+ chat == NULL || chat->ircnet == NULL ? NULL : g_strdup(chat->ircnet) :
+ server->connrec->ircnet == NULL ? NULL : g_strdup(server->connrec->ircnet);
+ dcc_conns = g_slist_append(dcc_conns, dcc);
+
+ signal_emit("dcc created", 1, dcc);
+ return dcc;
+}
+
+/* Destroy DCC record */
+void dcc_destroy(DCC_REC *dcc)
+{
+ GSList *tmp;
+
+ g_return_if_fail(dcc != NULL);
+
+ dcc_conns = g_slist_remove(dcc_conns, dcc);
+
+ /* remove dcc chat references.. */
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next)
+ {
+ DCC_REC *rec = tmp->data;
+
+ if (rec->chat == dcc)
+ rec->chat = NULL;
+ }
+
+ signal_emit("dcc destroyed", 1, dcc);
+
+ if (dcc->fhandle != -1) close(dcc->fhandle);
+ if (dcc->handle != -1) net_disconnect(dcc->handle);
+ if (dcc->tagread != -1) g_source_remove(dcc->tagread);
+ if (dcc->tagwrite != -1) g_source_remove(dcc->tagwrite);
+
+ if (dcc->dcc_type == DCC_TYPE_CHAT)
+ line_split_free((LINEBUF_REC *) dcc->databuf);
+ else if (dcc->databuf != NULL) g_free(dcc->databuf);
+ if (dcc->file != NULL) g_free(dcc->file);
+ if (dcc->ircnet != NULL) g_free(dcc->ircnet);
+ g_free(dcc->mynick);
+ g_free(dcc->nick);
+ g_free(dcc->arg);
+ g_free(dcc);
+}
+
+gchar *dcc_make_address(IPADDR *ip)
+{
+ static gchar str[MAX_IP_LEN];
+ gulong addr;
+
+ if (is_ipv6_addr(ip))
+ {
+ /* IPv6 */
+ net_ip2host(ip, str);
+ }
+ else
+ {
+ memcpy(&addr, &ip->addr, 4);
+ sprintf(str, "%lu", (unsigned long) htonl(addr));
+ }
+
+ return str;
+}
+
+/* Find DCC record, arg can be NULL */
+DCC_REC *dcc_find_item(gint type, gchar *nick, gchar *arg)
+{
+ DCC_REC *dcc;
+ GSList *tmp;
+
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next)
+ {
+ dcc = tmp->data;
+
+ if (dcc->dcc_type == type && g_strcasecmp(dcc->nick, nick) == 0 &&
+ (arg == NULL || strcmp(dcc->arg, arg) == 0))
+ return dcc;
+ }
+
+ return NULL;
+}
+
+/* Find DCC record by port # */
+DCC_REC *dcc_find_by_port(gchar *nick, gint port)
+{
+ DCC_REC *dcc;
+ GSList *tmp;
+
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next)
+ {
+ dcc = tmp->data;
+
+ if (dcc->port == port && ((dcc->dcc_type == DCC_TYPE_GET || dcc->dcc_type == DCC_TYPE_SEND) && g_strcasecmp(dcc->nick, nick) == 0))
+ {
+ /* found! */
+ return dcc;
+ }
+ }
+
+ return NULL;
+}
+
+gchar *dcc_type2str(gint type)
+{
+ g_return_val_if_fail(type >= 1 && type <= DCC_TYPES, NULL);
+ return dcc_types[type-1];
+}
+
+gint dcc_str2type(gchar *type)
+{
+ gint num;
+
+ for (num = 0; num < DCC_TYPES; num++)
+ if (g_strcasecmp(dcc_types[num], type) == 0) return num+1;
+
+ return 0;
+}
+
+void dcc_ctcp_message(gchar *target, IRC_SERVER_REC *server, DCC_REC *chat, gboolean notice, gchar *msg)
+{
+ gchar *str;
+
+ if (chat != NULL)
+ {
+ /* send it via open DCC chat */
+ /* FIXME: we need output queue! */
+ str = g_strdup_printf("%s\001%s\001\n", chat->mirc_ctcp ? "" :
+ notice ? "CTCP_REPLY " : "CTCP_MESSAGE ", msg);
+ net_transmit(chat->handle, str, strlen(str));
+ }
+ else
+ {
+ str = g_strdup_printf("%s %s :\001%s\001",
+ notice ? "NOTICE" : "PRIVMSG", target, msg);
+ irc_send_cmd(server, str);
+ }
+
+ g_free(str);
+}
+
+/* Server connected, check if there's any open dcc sessions for this ircnet.. */
+static void dcc_server_connected(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ g_return_if_fail(server != NULL);
+
+ if (server->connrec->ircnet == NULL)
+ return;
+
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) {
+ DCC_REC *dcc = tmp->data;
+
+ if (dcc->server == NULL && dcc->ircnet != NULL &&
+ g_strcasecmp(dcc->ircnet, server->connrec->ircnet) == 0) {
+ dcc->server = server;
+ g_free(dcc->mynick);
+ dcc->mynick = g_strdup(server->nick);
+ }
+ }
+}
+
+/* Server disconnected, remove it from all dcc records */
+static void dcc_server_disconnected(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ g_return_if_fail(server != NULL);
+
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next)
+ {
+ DCC_REC *dcc = tmp->data;
+
+ if (dcc->server == server)
+ {
+ if (dcc->ircnet == NULL)
+ dcc->server = NULL;
+ else
+ {
+ dcc->server = (IRC_SERVER_REC *) server_find_ircnet(dcc->ircnet);
+ if (dcc->server != NULL)
+ {
+ g_free(dcc->mynick);
+ dcc->mynick = g_strdup(dcc->server->nick);
+ }
+ }
+ }
+ }
+}
+
+static void dcc_get_address(gchar *str, IPADDR *ip)
+{
+ gulong addr;
+
+ if (strchr(str, ':') == NULL)
+ {
+ /* normal IPv4 address */
+ if (sscanf(str, "%lu", &addr)!=1)
+ addr = 0;
+ ip->family = AF_INET;
+ addr = (gulong) ntohl(addr);
+ memcpy(&ip->addr, &addr, 4);
+ }
+ else
+ {
+ /* IPv6 */
+ net_host2ip(str, ip);
+ }
+}
+
+/* Handle incoming DCC CTCP messages */
+static void dcc_ctcp_msg(gchar *data, IRC_SERVER_REC *server, gchar *sender, gchar *sendaddr, gchar *target, DCC_REC *chat)
+{
+ gchar *params, *type, *arg, *addrstr, *portstr, *sizestr, *str;
+ const char *cstr;
+ DCC_REC *dcc;
+ gulong size;
+ gint port;
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(sender != NULL);
+
+ params = cmd_get_params(data, 5, &type, &arg, &addrstr, &portstr, &sizestr);
+
+ if (sscanf(portstr, "%d", &port) != 1) port = 0;
+ if (sscanf(sizestr, "%lu", &size) != 1) size = 0;
+
+ dcc = dcc_create(SWAP_SENDGET(dcc_str2type(type)), -1, sender, arg, server, chat);
+ dcc_get_address(addrstr, &dcc->addr);
+ net_ip2host(&dcc->addr, dcc->addrstr);
+ dcc->port = port;
+ dcc->size = size;
+
+ switch (dcc->dcc_type)
+ {
+ case DCC_TYPE_GET:
+ cstr = settings_get_str("dcc_autoget_masks");
+ /* check that autoget masks match */
+ if (settings_get_bool("toggle_dcc_autoget") && (*cstr == '\0' || irc_masks_match(cstr, sender, sendaddr)) &&
+ /* check file size limit, FIXME: it's possible to send a bogus file size and then just send what ever sized file.. */
+ (settings_get_int("dcc_max_autoget_size") <= 0 || (settings_get_int("dcc_max_autoget_size") > 0 && size <= settings_get_int("dcc_max_autoget_size")*1024)))
+ {
+ /* automatically get */
+ str = g_strdup_printf("GET %s %s", dcc->nick, dcc->arg);
+ signal_emit("command dcc", 2, str, server);
+ g_free(str);
+ }
+ else
+ {
+ /* send request */
+ signal_emit("dcc request", 1, dcc);
+ }
+ break;
+
+ case DCC_TYPE_CHAT:
+ cstr = settings_get_str("dcc_autochat_masks");
+ if (*cstr != '\0' && irc_masks_match(cstr, sender, sendaddr))
+ {
+ /* automatically accept chat */
+ str = g_strdup_printf("CHAT %s", dcc->nick);
+ signal_emit("command dcc", 2, str, server);
+ g_free(str);
+ }
+ else
+ {
+ /* send request */
+ signal_emit("dcc request", 1, dcc);
+ }
+ break;
+
+ case DCC_TYPE_RESUME:
+ case DCC_TYPE_ACCEPT:
+ /* handle this in dcc-files.c */
+ dcc_destroy(dcc);
+ break;
+
+ default:
+ /* unknown DCC command */
+ signal_emit("dcc unknown ctcp", 3, data, sender, sendaddr);
+ dcc_destroy(dcc);
+ break;
+ }
+
+ g_free(params);
+}
+
+/* Handle incoming DCC CTCP replies */
+static void dcc_ctcp_reply(gchar *data, IRC_SERVER_REC *server, gchar *sender, gchar *sendaddr)
+{
+ gchar *params, *cmd, *subcmd, *args;
+ gint type;
+ DCC_REC *dcc;
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(sender != NULL);
+
+ params = cmd_get_params(data, 3 | PARAM_FLAG_GETREST, &cmd, &subcmd, &args);
+
+ if (g_strcasecmp(cmd, "REJECT") == 0)
+ {
+ type = dcc_str2type(subcmd);
+ dcc = dcc_find_item(type, sender, type == DCC_TYPE_CHAT ? NULL : args);
+ if (dcc != NULL)
+ {
+ dcc->destroyed = TRUE;
+ signal_emit("dcc closed", 1, dcc);
+ dcc_destroy(dcc);
+ }
+ }
+ else
+ {
+ /* unknown dcc ctcp reply */
+ signal_emit("dcc unknown reply", 3, data, sender, sendaddr);
+ }
+
+ g_free(params);
+}
+
+static void dcc_reject(DCC_REC *dcc, IRC_SERVER_REC *server)
+{
+ gchar *str;
+
+ g_return_if_fail(dcc != NULL);
+
+ if (dcc->server != NULL) server = dcc->server;
+ if (server != NULL && (dcc->dcc_type != DCC_TYPE_CHAT || dcc->starttime == 0))
+ {
+ signal_emit("dcc rejected", 1, dcc);
+ str = g_strdup_printf("NOTICE %s :\001DCC REJECT %s %s\001",
+ dcc->nick, dcc_type2str(SWAP_SENDGET(dcc->dcc_type)), dcc->arg);
+
+ irc_send_cmd(server, str);
+ g_free(str);
+ }
+
+ dcc->destroyed = TRUE;
+ signal_emit("dcc closed", 1, dcc);
+ dcc_destroy(dcc);
+}
+
+/* command: DCC CLOSE */
+static void cmd_dcc_close(gchar *data, IRC_SERVER_REC *server)
+{
+ DCC_REC *dcc;
+ GSList *tmp, *next;
+ gchar *params, *type, *nick, *arg;
+ gboolean found;
+ gint itype;
+
+ g_return_if_fail(data != NULL);
+
+ params = cmd_get_params(data, 3, &type, &nick, &arg);
+
+ g_strup(type);
+ itype = dcc_str2type(type);
+ if (itype == 0)
+ {
+ signal_emit("dcc error unknown type", 1, type);
+ g_free(params);
+ return;
+ }
+
+ dcc = NULL; found = FALSE;
+ for (tmp = dcc_conns; tmp != NULL; tmp = next)
+ {
+ dcc = tmp->data;
+ next = tmp->next;
+
+ if (dcc->dcc_type == itype && g_strcasecmp(nick, dcc->nick) == 0)
+ {
+ dcc_reject(dcc, server);
+ found = TRUE;
+ }
+ }
+
+ if (!found)
+ signal_emit("dcc error close not found", 3, type, nick, arg);
+
+ g_free(params);
+}
+
+static void cmd_dcc(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item)
+{
+ command_runsub("dcc", data, server, item);
+}
+
+static int dcc_timeout_func(void)
+{
+ GSList *tmp, *next;
+ time_t now;
+
+ now = time(NULL)-settings_get_int("dcc_timeout");
+ for (tmp = dcc_conns; tmp != NULL; tmp = next)
+ {
+ DCC_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->tagread == -1 && now > rec->created)
+ {
+ /* timed out. */
+ dcc_reject(rec, NULL);
+ }
+ }
+ return 1;
+}
+
+static void event_no_such_nick(gchar *data, IRC_SERVER_REC *server)
+{
+ gchar *params, *nick;
+ GSList *tmp, *next;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &nick);
+
+ /* check if we've send any dcc requests to this nick.. */
+ for (tmp = dcc_conns; tmp != NULL; tmp = next)
+ {
+ DCC_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (g_strcasecmp(rec->nick, nick) == 0 && rec->starttime == 0)
+ {
+ /* timed out. */
+ rec->destroyed = TRUE;
+ signal_emit("dcc closed", 1, rec);
+ dcc_destroy(rec);
+ }
+ }
+
+ g_free(params);
+}
+
+void dcc_init(void)
+{
+ dcc_conns = NULL;
+ dcc_timeouttag = g_timeout_add(1000, (GSourceFunc) dcc_timeout_func, NULL);
+
+ settings_add_bool("dcc", "toggle_dcc_autorename", FALSE);
+ settings_add_bool("dcc", "toggle_dcc_autogete", FALSE);
+ settings_add_int("dcc", "dcc_max_autoget_size", 1000);
+ settings_add_str("dcc", "dcc_download_path", "~");
+ settings_add_int("dcc", "dcc_file_create_mode", 644);
+ settings_add_str("dcc", "dcc_autoget_masks", "");
+ settings_add_str("dcc", "dcc_autochat_masks", "");
+
+ settings_add_bool("dcc", "toggle_dcc_fast_send", TRUE);
+ settings_add_str("dcc", "dcc_upload_path", "~");
+
+ settings_add_bool("dcc", "toggle_dcc_mirc_ctcp", FALSE);
+ settings_add_bool("dcc", "toggle_dcc_autodisplay_dialog", TRUE);
+ settings_add_int("dcc", "dcc_block_size", 2048);
+ settings_add_int("dcc", "dcc_port", 0);
+ settings_add_int("dcc", "dcc_timeout", 300);
+
+ signal_add("server connected", (SIGNAL_FUNC) dcc_server_connected);
+ signal_add("server disconnected", (SIGNAL_FUNC) dcc_server_disconnected);
+ signal_add("ctcp reply dcc", (SIGNAL_FUNC) dcc_ctcp_reply);
+ signal_add("ctcp msg dcc", (SIGNAL_FUNC) dcc_ctcp_msg);
+ command_bind("dcc", NULL, (SIGNAL_FUNC) cmd_dcc);
+ command_bind("dcc close", NULL, (SIGNAL_FUNC) cmd_dcc_close);
+ signal_add("event 401", (SIGNAL_FUNC) event_no_such_nick);
+}
+
+void dcc_deinit(void)
+{
+ signal_remove("server connected", (SIGNAL_FUNC) dcc_server_connected);
+ signal_remove("server disconnected", (SIGNAL_FUNC) dcc_server_disconnected);
+ signal_remove("ctcp reply dcc", (SIGNAL_FUNC) dcc_ctcp_reply);
+ signal_remove("ctcp msg dcc", (SIGNAL_FUNC) dcc_ctcp_msg);
+ command_unbind("dcc", (SIGNAL_FUNC) cmd_dcc);
+ command_unbind("dcc close", (SIGNAL_FUNC) cmd_dcc_close);
+ signal_remove("event 401", (SIGNAL_FUNC) event_no_such_nick);
+
+ g_source_remove(dcc_timeouttag);
+
+ while (dcc_conns != NULL)
+ dcc_destroy(dcc_conns->data);
+}
diff --git a/src/irc/dcc/dcc.h b/src/irc/dcc/dcc.h
new file mode 100644
index 00000000..da640b41
--- /dev/null
+++ b/src/irc/dcc/dcc.h
@@ -0,0 +1,91 @@
+#ifndef __DCC_H
+#define __DCC_H
+
+#include "network.h"
+
+enum
+{
+ DCC_TYPE_CHAT = 1,
+ DCC_TYPE_SEND,
+ DCC_TYPE_GET,
+ DCC_TYPE_RESUME,
+ DCC_TYPE_ACCEPT
+};
+
+enum
+{
+ DCC_GET_DEFAULT = 0,
+ DCC_GET_OVERWRITE,
+ DCC_GET_RENAME,
+ DCC_GET_RESUME
+};
+
+#define SWAP_SENDGET(a) ((a) == DCC_TYPE_SEND ? DCC_TYPE_GET : \
+ (a) == DCC_TYPE_GET ? DCC_TYPE_SEND : (a))
+
+typedef struct DCC_REC
+{
+ int type;
+ GHashTable *module_data;
+
+ IRC_SERVER_REC *server;
+ gchar *nick;
+
+ struct DCC_REC *chat; /* if the request came through DCC chat */
+
+ gchar *ircnet;
+ gchar *mynick;
+
+ gchar *arg;
+ gchar *file; /* file name we're really moving, arg is just the reference.. */
+
+ time_t created;
+ gint dcc_type;
+
+ IPADDR addr; /* address we're connected in */
+ gchar addrstr[MAX_IP_LEN]; /* in readable form */
+ gint port; /* port we're connected in */
+
+ glong size, transfd, skipped; /* file size / bytes transferred / skipped at start */
+ gint handle; /* socket handle */
+ gint tagread, tagwrite;
+ gint fhandle; /* file handle */
+ time_t starttime; /* transfer start time */
+ gint trans_bytes;
+
+ gboolean fastsend; /* fastsending (just in case that global fastsend toggle changes while transferring..) */
+ gboolean waitforend; /* DCC fast send: file is sent, just wait for the replies from the other side */
+ gboolean gotalldata; /* DCC fast send: got all acks from the other end (needed to make sure the end of transfer works right) */
+ gint get_type; /* DCC get: what to do if file exists? */
+
+ gboolean mirc_ctcp; /* DCC chat: Send CTCPs without the CTCP_MESSAGE prefix */
+ gboolean destroyed; /* We're about to destroy this DCC recond */
+
+ /* read counter buffer */
+ gchar read_buf[4];
+ gint read_pos;
+
+ gchar *databuf; /* buffer for receiving/transmitting data */
+ gint databufsize;
+}
+DCC_REC;
+
+extern GSList *dcc_conns;
+
+void dcc_init(void);
+void dcc_deinit(void);
+
+/* Find DCC record, arg can be NULL */
+DCC_REC *dcc_find_item(gint type, gchar *nick, gchar *arg);
+DCC_REC *dcc_find_by_port(gchar *nick, gint port);
+
+gchar *dcc_type2str(gint type);
+gint dcc_str2type(gchar *type);
+gchar *dcc_make_address(IPADDR *ip);
+
+DCC_REC *dcc_create(gint type, gint handle, gchar *nick, gchar *arg, IRC_SERVER_REC *server, DCC_REC *chat);
+void dcc_destroy(DCC_REC *dcc);
+
+void dcc_ctcp_message(gchar *target, IRC_SERVER_REC *server, DCC_REC *chat, gboolean notice, gchar *msg);
+
+#endif
diff --git a/src/irc/dcc/module.h b/src/irc/dcc/module.h
new file mode 100644
index 00000000..2557ed0f
--- /dev/null
+++ b/src/irc/dcc/module.h
@@ -0,0 +1,3 @@
+#include "common.h"
+
+#define MODULE_NAME "irc/dcc"
diff --git a/src/irc/flood/Makefile.am b/src/irc/flood/Makefile.am
new file mode 100644
index 00000000..1ebebff4
--- /dev/null
+++ b/src/irc/flood/Makefile.am
@@ -0,0 +1,12 @@
+noinst_LTLIBRARIES = libirc_flood.la
+
+INCLUDES = $(GLIB_CFLAGS) \
+ -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ -I$(top_srcdir)/src/irc/core/
+
+libirc_flood_la_SOURCES = \
+ autoignore.c \
+ flood.c
+
+noinst_HEADERS = \
+ autoignore.h \
+ flood.h
diff --git a/src/irc/flood/autoignore.c b/src/irc/flood/autoignore.c
new file mode 100644
index 00000000..528ac618
--- /dev/null
+++ b/src/irc/flood/autoignore.c
@@ -0,0 +1,250 @@
+/*
+ autoignore.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 "modules.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "settings.h"
+#include "common-setup.h"
+
+#include "irc-server.h"
+#include "ignore.h"
+
+#include "autoignore.h"
+
+static int ignore_tag;
+
+GSList *server_autoignores(IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *rec;
+
+ g_return_val_if_fail(server != NULL, NULL);
+
+ rec = MODULE_DATA(server);
+ return rec->ignorelist;
+}
+
+static void autoignore_remove_rec(IRC_SERVER_REC *server, AUTOIGNORE_REC *rec)
+{
+ MODULE_SERVER_REC *mserver;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(rec != NULL);
+
+ signal_emit("autoignore remove", 2, server, rec);
+
+ g_free(rec->nick);
+ g_free(rec);
+
+ mserver = MODULE_DATA(server);
+ mserver->ignorelist = g_slist_remove(mserver->ignorelist, rec);
+}
+
+static AUTOIGNORE_REC *autoignore_find(IRC_SERVER_REC *server, const char *mask)
+{
+ MODULE_SERVER_REC *mserver;
+ GSList *tmp;
+
+ g_return_val_if_fail(server != NULL, NULL);
+ g_return_val_if_fail(mask != NULL, NULL);
+
+ mserver = MODULE_DATA(server);
+ for (tmp = mserver->ignorelist; tmp != NULL; tmp = tmp->next) {
+ AUTOIGNORE_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->nick, mask) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+/* timeout function: unignore old ignores.. */
+static void autoignore_timeout_server(IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+ GSList *tmp, *next;
+ time_t t;
+
+ g_return_if_fail(server != NULL);
+
+ mserver = MODULE_DATA(server);
+ t = time(NULL);
+ t -= mserver->ignore_lastcheck;
+
+ for (tmp = mserver->ignorelist; tmp != NULL; tmp = next) {
+ AUTOIGNORE_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->timeleft > t)
+ rec->timeleft -= t;
+ else
+ autoignore_remove_rec(server, rec);
+ }
+
+ mserver->ignore_lastcheck = time(NULL);
+}
+
+static int autoignore_timeout(void)
+{
+ g_slist_foreach(servers, (GFunc) autoignore_timeout_server, NULL);
+ return 1;
+}
+
+static void autoignore_init_server(IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+
+ g_return_if_fail(server != NULL);
+
+ mserver = MODULE_DATA(server);
+ mserver->ignorelist = NULL;
+ mserver->ignore_lastcheck = time(NULL)-AUTOIGNORE_TIMECHECK;
+}
+
+static void autoignore_deinit_server(IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+
+ g_return_if_fail(server != NULL);
+
+ mserver = MODULE_DATA(server);
+ while (mserver->ignorelist != NULL)
+ autoignore_remove_rec(server, (AUTOIGNORE_REC *) mserver->ignorelist->data);
+}
+
+IGNORE_REC *ignore_find_server(IRC_SERVER_REC *server, const char *mask)
+{
+ GSList *tmp;
+
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (rec->servertag != NULL &&
+ g_strcasecmp(rec->mask, mask) == 0 &&
+ g_strcasecmp(rec->servertag, server->tag) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+void autoignore_add(IRC_SERVER_REC *server, const char *nick, int level)
+{
+ MODULE_SERVER_REC *mserver;
+ AUTOIGNORE_REC *rec;
+ IGNORE_REC *irec;
+ int igtime;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(nick != NULL);
+ if (level == 0) return;
+
+ igtime = settings_get_int("autoignore_time");
+ if (igtime <= 0) return;
+
+ irec = ignore_find_server(server, nick);
+ if (irec == NULL) {
+ irec = g_new0(IGNORE_REC, 1);
+ irec->servertag = g_strdup(server->tag);
+ irec->mask = g_strdup(nick);
+ irec->level = level;
+ ignore_add_rec(irec);
+ } else {
+ irec->level |= level;
+ ignore_update_rec(irec);
+ }
+
+ rec = autoignore_find(server, nick);
+ if (rec != NULL) {
+ /* already being ignored */
+ rec->timeleft = igtime;
+ return;
+ }
+
+ rec = g_new(AUTOIGNORE_REC, 1);
+ rec->nick = g_strdup(nick);
+ rec->timeleft = igtime;
+ rec->level = level;
+
+ mserver = MODULE_DATA(server);
+ mserver->ignorelist = g_slist_append(mserver->ignorelist, rec);
+
+ signal_emit("autoignore new", 2, server, rec);
+}
+
+int autoignore_remove(IRC_SERVER_REC *server, const char *mask, int level)
+{
+ AUTOIGNORE_REC *rec;
+ IGNORE_REC *irec;
+
+ g_return_val_if_fail(server != NULL, FALSE);
+ g_return_val_if_fail(mask != NULL, FALSE);
+
+ irec = ignore_find_server(server, mask);
+ if (irec != NULL) {
+ irec->level &= ~level;
+ ignore_update_rec(irec);
+ }
+
+ rec = autoignore_find(server, mask);
+ if (rec != NULL && (level & rec->level)) {
+ rec->level &= ~level;
+ if (rec->level == 0) autoignore_remove_rec(server, rec);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void sig_flood(IRC_SERVER_REC *server, const char *nick, const char *host, const char *levelstr)
+{
+ int level, check_level;
+
+ level = level2bits(levelstr);
+ check_level = level2bits(settings_get_str("autoignore_levels"));
+
+ if (level & check_level)
+ autoignore_add(server, nick, level);
+}
+
+void autoignore_init(void)
+{
+ settings_add_int("flood", "autoignore_time", 300);
+ settings_add_str("flood", "autoignore_levels", "ctcps");
+
+ ignore_tag = g_timeout_add(AUTOIGNORE_TIMECHECK, (GSourceFunc) autoignore_timeout, NULL);
+
+ signal_add("server connected", (SIGNAL_FUNC) autoignore_init_server);
+ signal_add("server disconnected", (SIGNAL_FUNC) autoignore_deinit_server);
+ signal_add("flood", (SIGNAL_FUNC) sig_flood);
+}
+
+void autoignore_deinit(void)
+{
+ g_source_remove(ignore_tag);
+
+ signal_remove("server connected", (SIGNAL_FUNC) autoignore_init_server);
+ signal_remove("server disconnected", (SIGNAL_FUNC) autoignore_deinit_server);
+ signal_remove("flood", (SIGNAL_FUNC) sig_flood);
+}
diff --git a/src/irc/flood/autoignore.h b/src/irc/flood/autoignore.h
new file mode 100644
index 00000000..efa01d27
--- /dev/null
+++ b/src/irc/flood/autoignore.h
@@ -0,0 +1,18 @@
+#ifndef __AUTOIGNORE_H
+#define __AUTOIGNORE_H
+
+typedef struct {
+ char *nick;
+ int timeleft;
+ int level;
+} AUTOIGNORE_REC;
+
+GSList *server_autoignores(IRC_SERVER_REC *server);
+
+void autoignore_add(IRC_SERVER_REC *server, const char *nick, int level);
+int autoignore_remove(IRC_SERVER_REC *server, const char *mask, int level);
+
+void autoignore_init(void);
+void autoignore_deinit(void);
+
+#endif
diff --git a/src/irc/flood/flood.c b/src/irc/flood/flood.c
new file mode 100644
index 00000000..5522243e
--- /dev/null
+++ b/src/irc/flood/flood.c
@@ -0,0 +1,212 @@
+/*
+
+ flood.c : Flood protection (see also ctcp.c)
+
+ 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 "signals.h"
+#include "levels.h"
+#include "misc.h"
+#include "settings.h"
+
+#include "irc.h"
+#include "irc-server.h"
+#include "autoignore.h"
+#include "ignore.h"
+
+typedef struct {
+ char *nick;
+ int level;
+ int msgcount;
+} FLOOD_REC;
+
+static int flood_tag;
+static int flood_max_msgs;
+
+static int flood_hash_deinit(const char *key, FLOOD_REC *rec)
+{
+ g_return_val_if_fail(key != NULL, FALSE);
+ g_return_val_if_fail(rec != NULL, FALSE);
+
+ g_free(rec->nick);
+ g_free(rec);
+ return TRUE;
+}
+
+/* timeout function: flood protection */
+static int flood_timeout(void)
+{
+ MODULE_SERVER_REC *mserver;
+ GSList *tmp;
+
+ /* remove everyone from flood list */
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ IRC_SERVER_REC *rec = tmp->data;
+
+ mserver = MODULE_DATA(rec);
+ g_hash_table_foreach_remove(mserver->floodlist, (GHRFunc) flood_hash_deinit, NULL);
+ }
+ return 1;
+}
+
+/* Initialize flood protection */
+static void flood_init_server(IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *rec;
+
+ g_return_if_fail(server != NULL);
+
+ rec = g_new0(MODULE_SERVER_REC, 1);
+ MODULE_DATA_SET(server, rec);
+
+ rec->floodlist = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
+}
+
+/* Deinitialize flood protection */
+static void flood_deinit_server(IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+
+ g_return_if_fail(server != NULL);
+
+ mserver = MODULE_DATA(server);
+ if (mserver != NULL && mserver->floodlist != NULL) {
+ g_hash_table_freeze(mserver->floodlist);
+ g_hash_table_foreach(mserver->floodlist, (GHFunc) flood_hash_deinit, NULL);
+ g_hash_table_thaw(mserver->floodlist);
+ g_hash_table_destroy(mserver->floodlist);
+ }
+ g_free(mserver);
+}
+
+/* All messages should go through here.. */
+static void flood_newmsg(IRC_SERVER_REC *server, int level, const char *nick, const char *host, const char *target)
+{
+ MODULE_SERVER_REC *mserver;
+ FLOOD_REC *rec;
+ char *levelstr;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(nick != NULL);
+
+ mserver = MODULE_DATA(server);
+ rec = g_hash_table_lookup(mserver->floodlist, nick);
+ if (rec != NULL) {
+ if (++rec->msgcount > flood_max_msgs) {
+ /* flooding! */
+ levelstr = bits2level(rec->level);
+ signal_emit("flood", 5, server, nick, host, levelstr, target);
+ g_free(levelstr);
+ }
+ return;
+ }
+
+ rec = g_new(FLOOD_REC, 1);
+ rec->level = level;
+ rec->msgcount = 1;
+ rec->nick = g_strdup(nick);
+
+ g_hash_table_insert(mserver->floodlist, rec->nick, rec);
+}
+
+static void flood_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr)
+{
+ int publiclevel;
+ char *params, *target, *text;
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(server != NULL);
+
+ if (nick == NULL) {
+ /* don't try to ignore server messages.. */
+ return;
+ }
+
+ params = event_get_params(data, 2, &target, &text);
+
+ if (*text == 1) {
+ /* CTCP */
+ if (!ignore_check(server, nick, addr, target, text, MSGLEVEL_CTCPS))
+ flood_newmsg(server, MSGLEVEL_CTCPS, nick, addr, target);
+ } else {
+ publiclevel = ischannel(*target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS;
+
+ if (addr != NULL && !ignore_check(server, nick, addr, target, text, publiclevel))
+ flood_newmsg(server, publiclevel, nick, addr, target);
+ }
+
+ g_free(params);
+}
+
+static void flood_notice(const char *data, IRC_SERVER_REC *server, const char *nick, const char *addr)
+{
+ char *params, *target, *text;
+
+ g_return_if_fail(text != NULL);
+ g_return_if_fail(server != NULL);
+
+ if (nick == NULL) {
+ /* don't try to ignore server messages.. */
+ return;
+ }
+
+ params = event_get_params(data, 2, &target, &text);
+ if (addr != NULL && !ignore_check(server, nick, addr, target, text, MSGLEVEL_NOTICES))
+ flood_newmsg(server, MSGLEVEL_NOTICES | ischannel(*target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS, nick, addr, target);
+
+ g_free(params);
+}
+
+static void read_settings(void)
+{
+ if (flood_tag != -1) g_source_remove(flood_tag);
+ flood_tag = g_timeout_add(settings_get_int("flood_timecheck"), (GSourceFunc) flood_timeout, NULL);
+
+ flood_max_msgs = settings_get_int("flood_max_msgs");
+}
+
+void flood_init(void)
+{
+ settings_add_int("flood", "flood_timecheck", 1000);
+ settings_add_int("flood", "flood_max_msgs", 5);
+
+ flood_tag = -1;
+ read_settings();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add_first("server connected", (SIGNAL_FUNC) flood_init_server);
+ signal_add("server disconnected", (SIGNAL_FUNC) flood_deinit_server);
+ signal_add("event privmsg", (SIGNAL_FUNC) flood_privmsg);
+ signal_add("event notice", (SIGNAL_FUNC) flood_notice);
+
+ autoignore_init();
+}
+
+void flood_deinit(void)
+{
+ autoignore_deinit();
+
+ if (flood_tag != -1) g_source_remove(flood_tag);
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("server connected", (SIGNAL_FUNC) flood_init_server);
+ signal_remove("server disconnected", (SIGNAL_FUNC) flood_deinit_server);
+ signal_remove("event privmsg", (SIGNAL_FUNC) flood_privmsg);
+ signal_remove("event notice", (SIGNAL_FUNC) flood_notice);
+}
diff --git a/src/irc/flood/flood.h b/src/irc/flood/flood.h
new file mode 100644
index 00000000..e6454729
--- /dev/null
+++ b/src/irc/flood/flood.h
@@ -0,0 +1,7 @@
+#ifndef __FLOOD_H
+#define __FLOOD_H
+
+void flood_init(void);
+void flood_deinit(void);
+
+#endif
diff --git a/src/irc/flood/module.h b/src/irc/flood/module.h
new file mode 100644
index 00000000..9abdbf41
--- /dev/null
+++ b/src/irc/flood/module.h
@@ -0,0 +1,12 @@
+#include "common.h"
+
+typedef struct {
+ /* Flood protection */
+ GHashTable *floodlist;
+
+ /* Auto ignore list */
+ GSList *ignorelist;
+ time_t ignore_lastcheck;
+} MODULE_SERVER_REC;
+
+#define MODULE_NAME "irc/flood"
diff --git a/src/irc/irc.c b/src/irc/irc.c
new file mode 100644
index 00000000..609e239b
--- /dev/null
+++ b/src/irc/irc.c
@@ -0,0 +1,27 @@
+void irc_core_init(void);
+void irc_core_deinit(void);
+
+void dcc_init(void);
+void dcc_deinit(void);
+
+void flood_init(void);
+void flood_deinit(void);
+
+void notifylist_init(void);
+void notifylist_deinit(void);
+
+void irc_init(void)
+{
+ irc_core_init();
+ dcc_init();
+ flood_init();
+ notifylist_init();
+}
+
+void irc_deinit(void)
+{
+ notifylist_deinit();
+ flood_deinit();
+ dcc_deinit();
+ irc_core_deinit();
+}
diff --git a/src/irc/notifylist/Makefile.am b/src/irc/notifylist/Makefile.am
new file mode 100644
index 00000000..48dd5640
--- /dev/null
+++ b/src/irc/notifylist/Makefile.am
@@ -0,0 +1,15 @@
+noinst_LTLIBRARIES = libirc_notifylist.la
+
+INCLUDES = $(GLIB_CFLAGS) \
+ -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ -I$(top_srcdir)/src/irc/core/
+
+libirc_notifylist_la_SOURCES = \
+ notifylist.c \
+ notify-commands.c \
+ notify-ison.c \
+ notify-setup.c \
+ notify-whois.c
+
+noinst_HEADERS = \
+ notifylist.h \
+ notify-setup.h
diff --git a/src/irc/notifylist/module.h b/src/irc/notifylist/module.h
new file mode 100644
index 00000000..ac0eadac
--- /dev/null
+++ b/src/irc/notifylist/module.h
@@ -0,0 +1,44 @@
+#include "common.h"
+
+#define MODULE_NAME "irc/notifylist"
+
+#define ISON_EVENT "event 303"
+
+typedef struct {
+ char *nick;
+ char *user, *host, *realname, *awaymsg;
+ time_t idle_time;
+
+ int host_ok:1; /* host matches the one in notifylist = this is the right person*/
+ int away_ok:1; /* not away, or we don't care about it */
+ int idle_ok:1; /* idle time is low enough, or we don't care about it */
+
+ int away:1; /* nick is away */
+ int join_announced:1; /* join to IRC has been announced */
+ int idle_changed:1; /* idle time is lower than in last check */
+
+ time_t last_whois;
+} NOTIFY_NICK_REC;
+
+typedef struct {
+ GSList *notify_users; /* NOTIFY_NICK_REC's of notifylist people who are in IRC */
+ GSList *ison_tempusers; /* Temporary list for saving /ISON events.. */
+} MODULE_SERVER_REC;
+
+#include "irc-server.h"
+
+NOTIFY_NICK_REC *notify_nick_create(IRC_SERVER_REC *server, const char *nick);
+void notify_nick_destroy(NOTIFY_NICK_REC *rec);
+NOTIFY_NICK_REC *notify_nick_find(IRC_SERVER_REC *server, const char *nick);
+
+void notifylist_left(IRC_SERVER_REC *server, NOTIFY_NICK_REC *rec);
+void notifylist_destroy_all(void);
+
+void notifylist_commands_init(void);
+void notifylist_commands_deinit(void);
+
+void notifylist_whois_init(void);
+void notifylist_whois_deinit(void);
+
+void notifylist_ison_init(void);
+void notifylist_ison_deinit(void);
diff --git a/src/irc/notifylist/notify-commands.c b/src/irc/notifylist/notify-commands.c
new file mode 100644
index 00000000..9ae5a076
--- /dev/null
+++ b/src/irc/notifylist/notify-commands.c
@@ -0,0 +1,81 @@
+/*
+ notify-commands.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "signals.h"
+#include "commands.h"
+#include "misc.h"
+#include "settings.h"
+
+#include "notifylist.h"
+
+#define DEFAULT_NOTIFY_IDLE_TIME 60
+
+static void cmd_notify(gchar *data)
+{
+ char *params, *mask, *ircnets, *args, *idletime;
+ int away_check, idle_check_time;
+
+ g_return_if_fail(data != NULL);
+
+ args = "@idle";
+ params = cmd_get_params(data, 4 | PARAM_FLAG_MULTIARGS | PARAM_FLAG_GETREST, &args, &idletime, &mask, &ircnets);
+ if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (stristr(args, "-idle") == NULL)
+ idle_check_time = 0;
+ else {
+ idle_check_time = is_numeric(idletime, 0) ? (atol(idletime)*60) :
+ (settings_get_int("notify_idle_time")*60);
+ }
+
+ away_check = stristr(args, "-away") != NULL;
+ notifylist_remove(mask);
+ notifylist_add(mask, ircnets, away_check, idle_check_time);
+
+ g_free(params);
+}
+
+static void cmd_unnotify(const char *data)
+{
+ char *params, *mask;
+
+ g_return_if_fail(data != NULL);
+
+ params = cmd_get_params(data, 1, &mask);
+ if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ notifylist_remove(mask);
+
+ g_free(params);
+}
+
+void notifylist_commands_init(void)
+{
+ settings_add_int("misc", "notify_idle_time", DEFAULT_NOTIFY_IDLE_TIME);
+ command_bind("notify", NULL, (SIGNAL_FUNC) cmd_notify);
+ command_bind("unnotify", NULL, (SIGNAL_FUNC) cmd_unnotify);
+}
+
+void notifylist_commands_deinit(void)
+{
+ command_unbind("notify", (SIGNAL_FUNC) cmd_notify);
+ command_unbind("unnotify", (SIGNAL_FUNC) cmd_unnotify);
+}
diff --git a/src/irc/notifylist/notify-ison.c b/src/irc/notifylist/notify-ison.c
new file mode 100644
index 00000000..46aaa3b6
--- /dev/null
+++ b/src/irc/notifylist/notify-ison.c
@@ -0,0 +1,354 @@
+/*
+ notify-ison.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 "settings.h"
+
+#include "irc.h"
+#include "irc-server.h"
+#include "server-redirect.h"
+
+#include "notifylist.h"
+
+#define DEFAULT_NOTIFY_CHECK_TIME 60
+#define DEFAULT_NOTIFY_WHOIS_TIME (60*5)
+
+typedef struct {
+ char *nick;
+ int hostok;
+} ISON_REC;
+
+static int notify_tag;
+static int notify_whois_time;
+
+NOTIFY_NICK_REC *notify_nick_create(IRC_SERVER_REC *server, const char *nick)
+{
+ MODULE_SERVER_REC *mserver;
+ NOTIFY_NICK_REC *rec;
+
+ mserver = MODULE_DATA(server);
+
+ rec = g_new0(NOTIFY_NICK_REC, 1);
+ rec->nick = g_strdup(nick);
+
+ mserver->notify_users = g_slist_append(mserver->notify_users, rec);
+ return rec;
+}
+
+void notify_nick_destroy(NOTIFY_NICK_REC *rec)
+{
+ g_free(rec->nick);
+ g_free_not_null(rec->user);
+ g_free_not_null(rec->host);
+ g_free_not_null(rec->realname);
+ g_free_not_null(rec->awaymsg);
+ g_free(rec);
+}
+
+NOTIFY_NICK_REC *notify_nick_find(IRC_SERVER_REC *server, const char *nick)
+{
+ MODULE_SERVER_REC *mserver;
+ NOTIFY_NICK_REC *rec;
+ GSList *tmp;
+
+ mserver = MODULE_DATA(server);
+ for (tmp = mserver->notify_users; tmp != NULL; tmp = tmp->next) {
+ rec = tmp->data;
+
+ if (g_strcasecmp(rec->nick, nick) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static int is_ison_queue_empty(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ tmp = server_redirect_getqueue((SERVER_REC *) server, ISON_EVENT, NULL);
+ for (; tmp != NULL; tmp = tmp->next) {
+ REDIRECT_REC *rec = tmp->data;
+
+ if (strcmp(rec->name, "notifylist event") == 0)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void ison_send(IRC_SERVER_REC *server, GString *cmd)
+{
+ g_string_truncate(cmd, cmd->len-1);
+ g_string_prepend(cmd, "ISON :");
+
+ irc_send_cmd(server, cmd->str);
+ server_redirect_event((SERVER_REC *) server, NULL, 1, ISON_EVENT, "notifylist event", -1, NULL);
+
+ g_string_truncate(cmd, 0);
+}
+
+/* timeout function: send /ISON commands to server to check if someone in
+ notify list is in IRC */
+static void notifylist_timeout_server(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+ GString *cmd;
+ char *nick, *ptr;
+ int len;
+
+ g_return_if_fail(server != NULL);
+
+ if (!is_ison_queue_empty(server)) {
+ /* still not received all replies to previous /ISON commands.. */
+ return;
+ }
+
+ cmd = g_string_new(NULL);
+ for (tmp = notifies; tmp != NULL; tmp = tmp->next) {
+ NOTIFYLIST_REC *rec = tmp->data;
+
+ if (!notify_ircnets_match(rec, server->connrec->ircnet))
+ continue;
+
+ nick = g_strdup(rec->mask);
+ ptr = strchr(nick, '!');
+ if (ptr != NULL) *ptr = '\0';
+
+ len = strlen(nick);
+
+ if (cmd->len+len+1 > 510)
+ ison_send(server, cmd);
+
+ g_string_sprintfa(cmd, "%s ", nick);
+ g_free(nick);
+ }
+
+ if (cmd->len > 0)
+ ison_send(server, cmd);
+ g_string_free(cmd, TRUE);
+}
+
+static int notifylist_timeout_func(void)
+{
+ g_slist_foreach(servers, (GFunc) notifylist_timeout_server, NULL);
+ return 1;
+}
+
+static void ison_save_users(MODULE_SERVER_REC *mserver, char *online)
+{
+ char *ptr;
+
+ while (online != NULL && *online != '\0') {
+ ptr = strchr(online, ' ');
+ if (ptr != NULL) *ptr++ = '\0';
+
+ mserver->ison_tempusers =
+ g_slist_append(mserver->ison_tempusers, g_strdup(online));
+ online = ptr;
+ }
+}
+
+static void whois_send(IRC_SERVER_REC *server, char *nicks)
+{
+ char *p, *str;
+
+ irc_send_cmdv(server, "WHOIS %s", nicks);
+
+ /* "nick1,nick2" -> "nick1,nick2 nick1 nick2" because
+ End of WHOIS give nick1,nick2 while other whois events give
+ nick1 or nick2 */
+ str = g_strconcat(nicks, " ", nicks, NULL);
+ for (p = str+strlen(nicks)+1; *p != '\0'; p++)
+ if (*p == ',') *p = ' ';
+
+ server_redirect_event((SERVER_REC *) server, str, 2,
+ "event 318", "notifylist event whois end", 1,
+ "event 402", "event empty", -1,
+ "event 401", "event empty", 1,
+ "event 311", "notifylist event whois", 1,
+ "event 301", "notifylist event whois away", 1,
+ "event 312", "event empty", 1,
+ "event 313", "event empty", 1,
+ "event 317", "notifylist event whois idle", 1,
+ "event 319", "event empty", 1, NULL);
+ g_free(str);
+}
+
+static void whois_send_server(IRC_SERVER_REC *server, char *nick)
+{
+ char *str;
+
+ str = g_strdup_printf("%s %s", nick, nick);
+ whois_send(server, str);
+ g_free(str);
+}
+
+/* try to send as many nicks in one WHOIS as possible */
+static void whois_list_send(IRC_SERVER_REC *server, GSList *nicks)
+{
+ GSList *tmp;
+ GString *str;
+ char *nick;
+ int count;
+
+ str = g_string_new(NULL);
+ count = 0;
+
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ nick = tmp->data;
+
+ count++;
+ g_string_sprintfa(str, "%s,", nick);
+
+ if (count >= server->max_whois_in_cmd) {
+ g_string_truncate(str, str->len-1);
+ whois_send(server, str->str);
+ count = 0;
+ }
+ }
+
+ if (str->len > 0) {
+ g_string_truncate(str, str->len-1);
+ whois_send(server, str->str);
+ }
+
+ g_string_free(str, TRUE);
+}
+
+static void ison_check_joins(IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+ NOTIFYLIST_REC *notify;
+ NOTIFY_NICK_REC *rec;
+ GSList *tmp, *newnicks;
+ int send_whois;
+ time_t now;
+
+ mserver = MODULE_DATA(server);
+
+ now = time(NULL);
+ newnicks = NULL;
+ for (tmp = mserver->ison_tempusers; tmp != NULL; tmp = tmp->next) {
+ char *nick = tmp->data;
+
+ notify = notifylist_find(nick, server->connrec->ircnet);
+ send_whois = notify != NULL &&
+ (notify->away_check || notify->idle_check_time > 0);
+
+ rec = notify_nick_find(server, nick);
+ if (rec != NULL) {
+ /* check if we want to send WHOIS yet.. */
+ if (now-rec->last_whois < notify_whois_time)
+ continue;
+ } else {
+ rec = notify_nick_create(server, nick);
+ if (!send_whois) newnicks = g_slist_append(newnicks, nick);
+ }
+
+ if (send_whois) {
+ /* we need away message or idle time -
+ send the WHOIS reply to the nick's server */
+ rec->last_whois = now;
+ whois_send_server(server, nick);
+ }
+ }
+
+ whois_list_send(server, newnicks);
+ g_slist_free(newnicks);
+}
+
+static void ison_check_parts(IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+ GSList *tmp, *next;
+
+ mserver = MODULE_DATA(server);
+ for (tmp = mserver->notify_users; tmp != NULL; tmp = next) {
+ NOTIFY_NICK_REC *rec = tmp->data;
+ next = tmp->next;
+
+ if (gslist_find_icase_string(mserver->ison_tempusers, rec->nick) != NULL)
+ continue;
+
+ notifylist_left(server, rec);
+ notify_nick_destroy(rec);
+ }
+}
+
+static void event_ison(const char *data, IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+ char *params, *online;
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(server != NULL);
+
+ params = event_get_params(data, 2, NULL, &online);
+
+ mserver = MODULE_DATA(server);
+ ison_save_users(mserver, online);
+
+ if (!is_ison_queue_empty(server)) {
+ /* wait for the rest of the /ISON replies */
+ g_free(params);
+ return;
+ }
+
+ ison_check_joins(server);
+ ison_check_parts(server);
+
+ /* free memory used by temp list */
+ g_slist_foreach(mserver->ison_tempusers, (GFunc) g_free, NULL);
+ g_slist_free(mserver->ison_tempusers);
+ mserver->ison_tempusers = NULL;
+
+ g_free(params);
+}
+
+static void read_settings(void)
+{
+ if (notify_tag != -1) g_source_remove(notify_tag);
+ notify_tag = g_timeout_add(1000*settings_get_int("notify_check_time"), (GSourceFunc) notifylist_timeout_func, NULL);
+
+ notify_whois_time = settings_get_int("notify_whois_time");
+}
+
+void notifylist_ison_init(void)
+{
+ settings_add_int("misc", "notify_check_time", DEFAULT_NOTIFY_CHECK_TIME);
+ settings_add_int("misc", "notify_whois_time", DEFAULT_NOTIFY_WHOIS_TIME);
+
+ notify_tag = -1;
+ read_settings();
+
+ signal_add("notifylist event", (SIGNAL_FUNC) event_ison);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void notifylist_ison_deinit(void)
+{
+ g_source_remove(notify_tag);
+
+ signal_remove("notifylist event", (SIGNAL_FUNC) event_ison);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/irc/notifylist/notify-setup.c b/src/irc/notifylist/notify-setup.c
new file mode 100644
index 00000000..6ecbfa27
--- /dev/null
+++ b/src/irc/notifylist/notify-setup.c
@@ -0,0 +1,84 @@
+/*
+ notify-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 "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "irc-server.h"
+#include "notifylist.h"
+
+void notifylist_add_config(NOTIFYLIST_REC *rec)
+{
+ CONFIG_NODE *node;
+
+ node = iconfig_node_traverse("notifies", TRUE);
+ node = config_node_section(node, rec->mask, NODE_TYPE_BLOCK);
+
+ if (rec->away_check)
+ config_node_set_bool(node, "away_check", TRUE);
+ else
+ config_node_set_str(node, "away_check", NULL);
+
+ if (rec->idle_check_time > 0)
+ config_node_set_int(node, "idle_check_time", rec->idle_check_time/60);
+ else
+ config_node_set_str(node, "idle_check_time", NULL);
+
+ config_node_set_str(node, "ircnets", NULL);
+ if (rec->ircnets != NULL && *rec->ircnets != NULL) {
+ node = config_node_section(node, "ircnets", NODE_TYPE_LIST);
+ config_node_add_list(node, rec->ircnets);
+ }
+}
+
+void notifylist_remove_config(NOTIFYLIST_REC *rec)
+{
+ iconfig_set_str("notifies", rec->mask, NULL);
+}
+
+void notifylist_read_config(void)
+{
+ CONFIG_NODE *node;
+ NOTIFYLIST_REC *rec;
+ GSList *tmp;
+
+ notifylist_destroy_all();
+
+ node = iconfig_node_traverse("notifies", 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(NOTIFYLIST_REC, 1);
+ notifies = g_slist_append(notifies, rec);
+
+ rec->mask = g_strdup(node->key);
+ rec->away_check = config_node_get_bool(node, "away_check", FALSE);
+ rec->idle_check_time = config_node_get_int(node, "idle_check_time", 0)*60;
+
+ node = config_node_section(node, "ircnets", -1);
+ if (node != NULL) rec->ircnets = config_node_get_list(node);
+ }
+}
diff --git a/src/irc/notifylist/notify-setup.h b/src/irc/notifylist/notify-setup.h
new file mode 100644
index 00000000..bfaef0c8
--- /dev/null
+++ b/src/irc/notifylist/notify-setup.h
@@ -0,0 +1,9 @@
+#ifndef __NOTIFY_SETUP_H
+#define __NOTIFY_SETUP_H
+
+void notifylist_add_config(NOTIFYLIST_REC *rec);
+void notifylist_remove_config(NOTIFYLIST_REC *rec);
+
+void notifylist_read_config(void);
+
+#endif
diff --git a/src/irc/notifylist/notify-whois.c b/src/irc/notifylist/notify-whois.c
new file mode 100644
index 00000000..439a8af8
--- /dev/null
+++ b/src/irc/notifylist/notify-whois.c
@@ -0,0 +1,186 @@
+/*
+ notify-whois.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 "special-vars.h"
+
+#include "irc.h"
+#include "irc-server.h"
+#include "masks.h"
+
+#include "notifylist.h"
+
+static char *last_notify_nick;
+
+static void event_whois(const char *data, IRC_SERVER_REC *server)
+{
+ char *params, *nick, *user, *host, *realname;
+ NOTIFY_NICK_REC *nickrec;
+ NOTIFYLIST_REC *notify;
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(server != NULL);
+
+ params = event_get_params(data, 6, NULL, &nick, &user, &host, NULL, &realname);
+
+ notify = notifylist_find(nick, server->connrec->ircnet);
+ if (notify != NULL && !irc_mask_match(notify->mask, nick, user, host)) {
+ /* user or host didn't match */
+ g_free(params);
+ return;
+ }
+
+ nickrec = notify_nick_find(server, nick);
+ if (nickrec != NULL) {
+ g_free_not_null(last_notify_nick);
+ last_notify_nick = g_strdup(nick);
+
+ g_free_not_null(nickrec->user);
+ g_free_not_null(nickrec->host);
+ g_free_not_null(nickrec->realname);
+ g_free_and_null(nickrec->awaymsg);
+ nickrec->user = g_strdup(user);
+ nickrec->host = g_strdup(host);
+ nickrec->realname = g_strdup(realname);
+
+ nickrec->away = FALSE;
+ nickrec->host_ok = TRUE;
+ nickrec->idle_ok = TRUE;
+ }
+ g_free(params);
+}
+
+static void event_whois_idle(const char *data, IRC_SERVER_REC *server)
+{
+ NOTIFY_NICK_REC *nickrec;
+ NOTIFYLIST_REC *notify;
+ char *params, *nick, *secstr;
+ long secs;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &secstr);
+ secs = atol(secstr);
+
+ notify = notifylist_find(nick, server->connrec->ircnet);
+ nickrec = notify_nick_find(server, nick);
+ if (notify != NULL && nickrec != NULL) {
+ time_t now = time(NULL);
+ nickrec->idle_changed = secs < now-nickrec->idle_time &&
+ now-nickrec->idle_time > notify->idle_check_time;
+
+ nickrec->idle_time = now-secs;
+ }
+
+ g_free(params);
+}
+
+static void event_whois_away(const char *data, IRC_SERVER_REC *server)
+{
+ NOTIFY_NICK_REC *nickrec;
+ char *params, *nick, *awaymsg;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &awaymsg);
+
+ nickrec = notify_nick_find(server, nick);
+ if (nickrec != NULL) {
+ nickrec->awaymsg = g_strdup(awaymsg);
+ nickrec->away = TRUE;
+ }
+
+ g_free(params);
+}
+
+/* All WHOIS replies got, now announce all the changes at once. */
+static void event_whois_end(const char *data, IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+ NOTIFYLIST_REC *notify;
+ NOTIFY_NICK_REC *rec;
+ GSList *tmp;
+ const char *event;
+ int away_ok;
+ time_t now;
+
+ now = time(NULL);
+ mserver = MODULE_DATA(server);
+ for (tmp = mserver->notify_users; tmp != NULL; tmp = tmp->next) {
+ rec = tmp->data;
+
+ if (rec->realname == NULL)
+ continue;
+
+ notify = notifylist_find(rec->nick, server->connrec->ircnet);
+ if (notify == NULL) continue;
+
+ away_ok = !notify->away_check || !rec->away;
+
+ event = NULL;
+ if (!rec->join_announced) {
+ rec->join_announced = TRUE;
+ rec->idle_time = now;
+ if (away_ok) event = "notifylist joined";
+ } else if (notify->away_check && rec->away_ok == rec->away)
+ event = "notifylist away changed";
+ else if (notify->idle_check_time > 0 && rec->idle_changed)
+ event = "notifylist unidle";
+
+ if (event != NULL) {
+ signal_emit(event, 6, server, rec->nick,
+ rec->user, rec->host,
+ rec->realname, rec->awaymsg);
+ }
+ rec->idle_ok = notify->idle_check_time <= 0 ||
+ now-rec->idle_time <= notify->idle_check_time;
+ rec->idle_changed = FALSE;
+ rec->away_ok = away_ok;
+ }
+}
+
+/* last person that NOTIFY detected a signon for */
+static char *expando_lastnotify(void *server, void *item, int *free_ret)
+{
+ return last_notify_nick;
+}
+
+void notifylist_whois_init(void)
+{
+ last_notify_nick = NULL;
+
+ signal_add("notifylist event whois", (SIGNAL_FUNC) event_whois);
+ signal_add("notifylist event whois away", (SIGNAL_FUNC) event_whois_away);
+ signal_add("notifylist event whois idle", (SIGNAL_FUNC) event_whois_idle);
+ signal_add("notifylist event whois end", (SIGNAL_FUNC) event_whois_end);
+ expando_create("D", expando_lastnotify);
+}
+
+void notifylist_whois_deinit(void)
+{
+ g_free_not_null(last_notify_nick);
+
+ signal_remove("notifylist event whois", (SIGNAL_FUNC) event_whois);
+ signal_remove("notifylist event whois away", (SIGNAL_FUNC) event_whois_away);
+ signal_remove("notifylist event whois idle", (SIGNAL_FUNC) event_whois_idle);
+ signal_remove("notifylist event whois end", (SIGNAL_FUNC) event_whois_end);
+ expando_destroy("D", expando_lastnotify);
+}
diff --git a/src/irc/notifylist/notifylist.c b/src/irc/notifylist/notifylist.c
new file mode 100644
index 00000000..00561dc9
--- /dev/null
+++ b/src/irc/notifylist/notifylist.c
@@ -0,0 +1,356 @@
+/*
+ notifylist.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 "modules.h"
+#include "signals.h"
+
+#include "irc.h"
+#include "irc-server.h"
+#include "server-redirect.h"
+#include "masks.h"
+#include "nicklist.h"
+
+#include "notifylist.h"
+#include "notify-setup.h"
+
+GSList *notifies;
+
+NOTIFYLIST_REC *notifylist_add(const char *mask, const char *ircnets,
+ int away_check, int idle_check_time)
+{
+ NOTIFYLIST_REC *rec;
+
+ g_return_val_if_fail(mask != NULL, NULL);
+
+ rec = g_new0(NOTIFYLIST_REC, 1);
+ rec->mask = g_strdup(mask);
+ rec->ircnets = ircnets == NULL || *ircnets == '\0' ? NULL :
+ g_strsplit(ircnets, " ", -1);
+ rec->away_check = away_check;
+ rec->idle_check_time = idle_check_time;
+
+ notifylist_add_config(rec);
+
+ notifies = g_slist_append(notifies, rec);
+ signal_emit("notifylist new", 1, rec);
+ return rec;
+}
+
+static void notify_destroy(NOTIFYLIST_REC *rec)
+{
+ if (rec->ircnets != NULL) g_strfreev(rec->ircnets);
+ g_free(rec->mask);
+ g_free(rec);
+}
+
+void notifylist_destroy_all(void)
+{
+ g_slist_foreach(notifies, (GFunc) notify_destroy, NULL);
+ g_slist_free(notifies);
+
+ notifies = NULL;
+}
+
+void notifylist_remove(const char *mask)
+{
+ NOTIFYLIST_REC *rec;
+
+ g_return_if_fail(mask != NULL);
+
+ rec = notifylist_find(mask, "*");
+ if (rec == NULL) return;
+
+ notifylist_remove_config(rec);
+ notifies = g_slist_remove(notifies, rec);
+ signal_emit("notifylist remove", 1, rec);
+
+ notify_destroy(rec);
+}
+
+int notify_ircnets_match(NOTIFYLIST_REC *rec, const char *ircnet)
+{
+ char **tmp;
+
+ if (rec->ircnets == NULL) return TRUE;
+ if (ircnet == NULL) return FALSE;
+ if (strcmp(ircnet, "*") == 0) return TRUE;
+
+ for (tmp = rec->ircnets; *tmp != NULL; tmp++) {
+ if (g_strcasecmp(*tmp, ircnet) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+NOTIFYLIST_REC *notifylist_find(const char *mask, const char *ircnet)
+{
+ NOTIFYLIST_REC *best;
+ GSList *tmp;
+ int len;
+
+ best = NULL;
+ len = strlen(mask);
+ for (tmp = notifies; tmp != NULL; tmp = tmp->next) {
+ NOTIFYLIST_REC *rec = tmp->data;
+
+ /* check mask */
+ if (g_strncasecmp(rec->mask, mask, len) != 0 ||
+ (rec->mask[len] != '\0' && rec->mask[len] != '!')) continue;
+
+ /* check ircnet */
+ if (rec->ircnets == NULL) {
+ best = rec;
+ continue;
+ }
+
+ if (notify_ircnets_match(rec, ircnet))
+ return rec;
+ }
+
+ return best;
+}
+
+int notifylist_ison_server(IRC_SERVER_REC *server, const char *nick)
+{
+ NOTIFY_NICK_REC *rec;
+
+ g_return_val_if_fail(nick != NULL, FALSE);
+ g_return_val_if_fail(server != NULL, FALSE);
+
+ rec = notify_nick_find(server, nick);
+ return rec != NULL && rec->host_ok && rec->away_ok && rec->idle_ok;
+}
+
+static IRC_SERVER_REC *notifylist_ison_serverlist(const char *nick, const char *taglist)
+{
+ IRC_SERVER_REC *server;
+ char **list, **tmp;
+
+ list = g_strsplit(taglist, " ", -1);
+
+ server = NULL;
+ for (tmp = list; *tmp != NULL; tmp++) {
+ server = (IRC_SERVER_REC *) server_find_ircnet(*tmp);
+
+ if (server != NULL && notifylist_ison_server(server, nick))
+ break;
+ }
+ g_strfreev(list);
+
+ return tmp == NULL ? NULL : server;
+}
+
+IRC_SERVER_REC *notifylist_ison(const char *nick, const char *serverlist)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(nick != NULL, FALSE);
+ g_return_val_if_fail(serverlist != NULL, FALSE);
+
+ if (*serverlist != '\0')
+ return notifylist_ison_serverlist(nick, serverlist);
+
+ /* any server.. */
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ if (notifylist_ison_server(tmp->data, nick))
+ return tmp->data;
+ }
+
+ return NULL;
+}
+
+static void notifylist_init_server(IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *rec;
+
+ g_return_if_fail(server != NULL);
+
+ rec = g_new0(MODULE_SERVER_REC,1 );
+ MODULE_DATA_SET(server, rec);
+
+ server_redirect_init((SERVER_REC *) server, "command ison", 1, ISON_EVENT, NULL);
+}
+
+static void notifylist_deinit_server(IRC_SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+ NOTIFY_NICK_REC *rec;
+
+ g_return_if_fail(server != NULL);
+
+ mserver = MODULE_DATA(server);
+ while (mserver->notify_users != NULL) {
+ rec = mserver->notify_users->data;
+
+ mserver->notify_users = g_slist_remove(mserver->notify_users, rec);
+ notify_nick_destroy(rec);
+ }
+ g_free(mserver);
+}
+
+void notifylist_left(IRC_SERVER_REC *server, NOTIFY_NICK_REC *rec)
+{
+ MODULE_SERVER_REC *mserver;
+
+ mserver = MODULE_DATA(server);
+ mserver->notify_users = g_slist_remove(mserver->notify_users, rec);
+
+ if (rec->host_ok && rec->away_ok) {
+ signal_emit("notifylist left", 6,
+ server, rec->nick,
+ rec->user, rec->host,
+ rec->realname, rec->awaymsg);
+ }
+}
+
+static void notifylist_idle_reset(IRC_SERVER_REC *server, const char *nick)
+{
+ NOTIFY_NICK_REC *rec;
+ NOTIFYLIST_REC *notify;
+
+ notify = notifylist_find(nick, server->connrec->ircnet);
+ rec = notify_nick_find(server, nick);
+
+ if (notify != NULL && rec != NULL && notify->idle_check_time > 0 &&
+ time(NULL)-rec->idle_time > notify->idle_check_time) {
+ rec->idle_time = time(NULL);
+ signal_emit("notifylist unidle", 6,
+ server, rec->nick,
+ rec->user, rec->host,
+ rec->realname, rec->awaymsg);
+ }
+}
+
+static void event_quit(const char *data, IRC_SERVER_REC *server, const char *nick)
+{
+ NOTIFY_NICK_REC *rec;
+
+ if (*data == ':') data++; /* quit message */
+
+ rec = notify_nick_find(server, nick);
+ if (rec != NULL) notifylist_left(server, rec);
+}
+
+static void notifylist_check_join(IRC_SERVER_REC *server, const char *nick,
+ const char *userhost, const char *realname, int away)
+{
+ NOTIFYLIST_REC *notify;
+ NOTIFY_NICK_REC *rec;
+ char *user, *host;
+
+ notify = notifylist_find(nick, server->connrec->ircnet);
+ if (notify == NULL) return;
+
+ rec = notify_nick_find(server, nick);
+ if (rec != NULL && rec->join_announced) return;
+ if (rec == NULL) rec = notify_nick_create(server, nick);
+
+ user = g_strdup(userhost);
+ host = strchr(user, '@');
+ if (host != NULL) *host++ = '\0'; else host = "";
+
+ if (!irc_mask_match(notify->mask, nick, user, host)) {
+ g_free(user);
+ return;
+ }
+
+ if (notify->away_check && away == -1) {
+ /* we need to know if the nick is away */
+ g_free(user);
+ return;
+ }
+
+ g_free_not_null(rec->user);
+ g_free_not_null(rec->host);
+ g_free_not_null(rec->realname);
+ rec->user = g_strdup(user);
+ rec->host = g_strdup(host);
+ rec->realname = *realname == '\0' ? NULL : g_strdup(realname);
+
+ if (away != -1) rec->away = away;
+ rec->host_ok = TRUE;
+ rec->join_announced = TRUE;
+ rec->idle_time = time(NULL);
+
+ signal_emit("notifylist joined", 6,
+ server, rec->nick, rec->user, rec->host, realname, NULL);
+ g_free(user);
+}
+
+static void event_privmsg(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address)
+{
+ if (nick != NULL) {
+ notifylist_check_join(server, nick, address, "", -1);
+ notifylist_idle_reset(server, nick);
+ }
+}
+
+static void event_join(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address)
+{
+ notifylist_check_join(server, nick, address, "", -1);
+}
+
+static void sig_channel_wholist(CHANNEL_REC *channel)
+{
+ GSList *nicks, *tmp;
+
+ nicks = nicklist_getnicks(channel);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *rec = tmp->data;
+
+ notifylist_check_join(channel->server, rec->nick, rec->host, rec->realname, rec->gone);
+ }
+ g_slist_free(nicks);
+}
+
+void notifylist_init(void)
+{
+ notifylist_read_config();
+
+ notifylist_commands_init();
+ notifylist_ison_init();
+ notifylist_whois_init();
+ signal_add("server connected", (SIGNAL_FUNC) notifylist_init_server);
+ signal_add("server disconnected", (SIGNAL_FUNC) notifylist_deinit_server);
+ signal_add("event quit", (SIGNAL_FUNC) event_quit);
+ signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_add("event join", (SIGNAL_FUNC) event_join);
+ signal_add("channel wholist", (SIGNAL_FUNC) sig_channel_wholist);
+ signal_add("setup reread", (SIGNAL_FUNC) notifylist_read_config);
+}
+
+void notifylist_deinit(void)
+{
+ notifylist_commands_deinit();
+ notifylist_ison_deinit();
+ notifylist_whois_deinit();
+
+ signal_remove("server connected", (SIGNAL_FUNC) notifylist_init_server);
+ signal_remove("server disconnected", (SIGNAL_FUNC) notifylist_deinit_server);
+ signal_remove("event quit", (SIGNAL_FUNC) event_quit);
+ signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_remove("event join", (SIGNAL_FUNC) event_join);
+ signal_remove("channel wholist", (SIGNAL_FUNC) sig_channel_wholist);
+ signal_remove("setup reread", (SIGNAL_FUNC) notifylist_read_config);
+
+ notifylist_destroy_all();
+}
diff --git a/src/irc/notifylist/notifylist.h b/src/irc/notifylist/notifylist.h
new file mode 100644
index 00000000..0d4f3739
--- /dev/null
+++ b/src/irc/notifylist/notifylist.h
@@ -0,0 +1,32 @@
+#ifndef __NOTIFYLIST_H
+#define __NOTIFYLIST_H
+
+typedef struct {
+ char *mask; /* nick part must not contain wildcards */
+ char **ircnets; /* if non-NULL, check only from these irc networks */
+
+ /* notify when AWAY status changes (uses /USERHOST) */
+ int away_check:1;
+ /* notify when idle time is reset and it was bigger than this
+ (uses /WHOIS and PRIVMSG events) */
+ int idle_check_time;
+} NOTIFYLIST_REC;
+
+extern GSList *notifies;
+
+void notifylist_init(void);
+void notifylist_deinit(void);
+
+NOTIFYLIST_REC *notifylist_add(const char *mask, const char *ircnets,
+ int away_check, int idle_check_time);
+void notifylist_remove(const char *mask);
+
+IRC_SERVER_REC *notifylist_ison(const char *nick, const char *serverlist);
+int notifylist_ison_server(IRC_SERVER_REC *server, const char *nick);
+
+/* If `ircnet' is "*", it doesn't matter at all. */
+NOTIFYLIST_REC *notifylist_find(const char *mask, const char *ircnet);
+
+int notify_ircnets_match(NOTIFYLIST_REC *rec, const char *ircnet);
+
+#endif