From c95034c6de1bf72536595e1e3431d8ec64b9880e Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Wed, 26 Apr 2000 08:03:38 +0000 Subject: ..adding new files.. git-svn-id: http://svn.irssi.org/repos/irssi/trunk@171 dbcabf3a-b0e7-0310-adc4-f8d773084564 --- src/irc/Makefile.am | 5 + src/irc/core/Makefile.am | 61 +++ src/irc/core/bans.c | 218 +++++++++ src/irc/core/bans.h | 15 + src/irc/core/channel-events.c | 241 ++++++++++ src/irc/core/channel-events.h | 7 + src/irc/core/channels-query.c | 594 ++++++++++++++++++++++++ src/irc/core/channels-query.h | 7 + src/irc/core/channels-setup.c | 215 +++++++++ src/irc/core/channels-setup.h | 27 ++ src/irc/core/channels.c | 231 +++++++++ src/irc/core/channels.h | 73 +++ src/irc/core/ctcp.c | 193 ++++++++ src/irc/core/ctcp.h | 10 + src/irc/core/ignore.c | 296 ++++++++++++ src/irc/core/ignore.h | 30 ++ src/irc/core/irc-commands.c | 878 +++++++++++++++++++++++++++++++++++ src/irc/core/irc-commands.h | 7 + src/irc/core/irc-core.c | 63 +++ src/irc/core/irc-core.h | 7 + src/irc/core/irc-log.c | 70 +++ src/irc/core/irc-rawlog.c | 77 +++ src/irc/core/irc-rawlog.h | 7 + src/irc/core/irc-server.c | 457 ++++++++++++++++++ src/irc/core/irc-server.h | 146 ++++++ src/irc/core/irc-special-vars.c | 332 +++++++++++++ src/irc/core/irc-special-vars.h | 7 + src/irc/core/irc.c | 440 ++++++++++++++++++ src/irc/core/irc.h | 90 ++++ src/irc/core/ircnet-setup.c | 116 +++++ src/irc/core/ircnet-setup.h | 23 + src/irc/core/lag.c | 178 +++++++ src/irc/core/lag.h | 7 + src/irc/core/masks.c | 170 +++++++ src/irc/core/masks.h | 15 + src/irc/core/massjoin.c | 254 ++++++++++ src/irc/core/massjoin.h | 7 + src/irc/core/mode-lists.c | 234 ++++++++++ src/irc/core/mode-lists.h | 24 + src/irc/core/modes.c | 451 ++++++++++++++++++ src/irc/core/modes.h | 18 + src/irc/core/module.h | 4 + src/irc/core/netsplit.c | 270 +++++++++++ src/irc/core/netsplit.h | 27 ++ src/irc/core/nicklist.c | 566 ++++++++++++++++++++++ src/irc/core/nicklist.h | 41 ++ src/irc/core/query.c | 158 +++++++ src/irc/core/query.h | 30 ++ src/irc/core/server-idle.c | 252 ++++++++++ src/irc/core/server-idle.h | 24 + src/irc/core/server-reconnect.c | 398 ++++++++++++++++ src/irc/core/server-reconnect.h | 16 + src/irc/core/server-setup.c | 317 +++++++++++++ src/irc/core/server-setup.h | 40 ++ src/irc/dcc/Makefile.am | 14 + src/irc/dcc/dcc-chat.c | 371 +++++++++++++++ src/irc/dcc/dcc-chat.h | 7 + src/irc/dcc/dcc-files.c | 577 +++++++++++++++++++++++ src/irc/dcc/dcc-files.h | 7 + src/irc/dcc/dcc.c | 550 ++++++++++++++++++++++ src/irc/dcc/dcc.h | 91 ++++ src/irc/dcc/module.h | 3 + src/irc/flood/Makefile.am | 12 + src/irc/flood/autoignore.c | 250 ++++++++++ src/irc/flood/autoignore.h | 18 + src/irc/flood/flood.c | 212 +++++++++ src/irc/flood/flood.h | 7 + src/irc/flood/module.h | 12 + src/irc/irc.c | 27 ++ src/irc/notifylist/Makefile.am | 15 + src/irc/notifylist/module.h | 44 ++ src/irc/notifylist/notify-commands.c | 81 ++++ src/irc/notifylist/notify-ison.c | 354 ++++++++++++++ src/irc/notifylist/notify-setup.c | 84 ++++ src/irc/notifylist/notify-setup.h | 9 + src/irc/notifylist/notify-whois.c | 186 ++++++++ src/irc/notifylist/notifylist.c | 356 ++++++++++++++ src/irc/notifylist/notifylist.h | 32 ++ 78 files changed, 11763 insertions(+) create mode 100644 src/irc/Makefile.am create mode 100644 src/irc/core/Makefile.am create mode 100644 src/irc/core/bans.c create mode 100644 src/irc/core/bans.h create mode 100644 src/irc/core/channel-events.c create mode 100644 src/irc/core/channel-events.h create mode 100644 src/irc/core/channels-query.c create mode 100644 src/irc/core/channels-query.h create mode 100644 src/irc/core/channels-setup.c create mode 100644 src/irc/core/channels-setup.h create mode 100644 src/irc/core/channels.c create mode 100644 src/irc/core/channels.h create mode 100644 src/irc/core/ctcp.c create mode 100644 src/irc/core/ctcp.h create mode 100644 src/irc/core/ignore.c create mode 100644 src/irc/core/ignore.h create mode 100644 src/irc/core/irc-commands.c create mode 100644 src/irc/core/irc-commands.h create mode 100644 src/irc/core/irc-core.c create mode 100644 src/irc/core/irc-core.h create mode 100644 src/irc/core/irc-log.c create mode 100644 src/irc/core/irc-rawlog.c create mode 100644 src/irc/core/irc-rawlog.h create mode 100644 src/irc/core/irc-server.c create mode 100644 src/irc/core/irc-server.h create mode 100644 src/irc/core/irc-special-vars.c create mode 100644 src/irc/core/irc-special-vars.h create mode 100644 src/irc/core/irc.c create mode 100644 src/irc/core/irc.h create mode 100644 src/irc/core/ircnet-setup.c create mode 100644 src/irc/core/ircnet-setup.h create mode 100644 src/irc/core/lag.c create mode 100644 src/irc/core/lag.h create mode 100644 src/irc/core/masks.c create mode 100644 src/irc/core/masks.h create mode 100644 src/irc/core/massjoin.c create mode 100644 src/irc/core/massjoin.h create mode 100644 src/irc/core/mode-lists.c create mode 100644 src/irc/core/mode-lists.h create mode 100644 src/irc/core/modes.c create mode 100644 src/irc/core/modes.h create mode 100644 src/irc/core/module.h create mode 100644 src/irc/core/netsplit.c create mode 100644 src/irc/core/netsplit.h create mode 100644 src/irc/core/nicklist.c create mode 100644 src/irc/core/nicklist.h create mode 100644 src/irc/core/query.c create mode 100644 src/irc/core/query.h create mode 100644 src/irc/core/server-idle.c create mode 100644 src/irc/core/server-idle.h create mode 100644 src/irc/core/server-reconnect.c create mode 100644 src/irc/core/server-reconnect.h create mode 100644 src/irc/core/server-setup.c create mode 100644 src/irc/core/server-setup.h create mode 100644 src/irc/dcc/Makefile.am create mode 100644 src/irc/dcc/dcc-chat.c create mode 100644 src/irc/dcc/dcc-chat.h create mode 100644 src/irc/dcc/dcc-files.c create mode 100644 src/irc/dcc/dcc-files.h create mode 100644 src/irc/dcc/dcc.c create mode 100644 src/irc/dcc/dcc.h create mode 100644 src/irc/dcc/module.h create mode 100644 src/irc/flood/Makefile.am create mode 100644 src/irc/flood/autoignore.c create mode 100644 src/irc/flood/autoignore.h create mode 100644 src/irc/flood/flood.c create mode 100644 src/irc/flood/flood.h create mode 100644 src/irc/flood/module.h create mode 100644 src/irc/irc.c create mode 100644 src/irc/notifylist/Makefile.am create mode 100644 src/irc/notifylist/module.h create mode 100644 src/irc/notifylist/notify-commands.c create mode 100644 src/irc/notifylist/notify-ison.c create mode 100644 src/irc/notifylist/notify-setup.c create mode 100644 src/irc/notifylist/notify-setup.h create mode 100644 src/irc/notifylist/notify-whois.c create mode 100644 src/irc/notifylist/notifylist.c create mode 100644 src/irc/notifylist/notifylist.h (limited to 'src/irc') 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 - 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 -- cgit v1.2.3