summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/misc.c2
-rw-r--r--src/core/net-nonblock.c18
-rw-r--r--src/core/net-nonblock.h14
-rw-r--r--src/core/network.c83
-rw-r--r--src/core/network.h5
-rw-r--r--src/core/server.c6
-rw-r--r--src/fe-common/irc/Makefile.am1
-rw-r--r--src/fe-common/irc/dcc/fe-dcc.c4
-rw-r--r--src/fe-common/irc/fe-common-irc.c22
-rw-r--r--src/fe-common/irc/flood/fe-flood.c4
-rw-r--r--src/fe-common/irc/notifylist/fe-notifylist.c4
-rw-r--r--src/fe-none/irssi.c1
-rw-r--r--src/fe-text/irssi.c1
-rw-r--r--src/irc/Makefile.am2
-rw-r--r--src/irc/bot/.cvsignore8
-rw-r--r--src/irc/bot/Makefile.am24
-rw-r--r--src/irc/bot/bot-commands.c165
-rw-r--r--src/irc/bot/bot-events.c199
-rw-r--r--src/irc/bot/bot-users.c503
-rw-r--r--src/irc/bot/bot-users.h47
-rw-r--r--src/irc/bot/bot.c49
-rw-r--r--src/irc/bot/bot.h26
-rw-r--r--src/irc/bot/botnet-connection.c553
-rw-r--r--src/irc/bot/botnet.c622
-rw-r--r--src/irc/bot/botnet.h124
-rw-r--r--src/irc/bot/botnets.sample15
-rw-r--r--src/irc/bot/module.h3
-rw-r--r--src/irc/bot/users.sample18
-rw-r--r--src/irc/core/Makefile.am1
-rw-r--r--src/irc/core/irc-commands.c2
-rw-r--r--src/irc/core/irc-core.h7
-rw-r--r--src/irc/core/modes.c8
-rw-r--r--src/irc/core/modes.h3
-rw-r--r--src/irc/core/nicklist.h1
-rw-r--r--src/irc/core/server-setup.c4
-rw-r--r--src/irc/dcc/dcc.c4
-rw-r--r--src/irc/flood/flood.c4
-rw-r--r--src/irc/irc.c27
-rw-r--r--src/irc/notifylist/notifylist.c4
39 files changed, 2488 insertions, 100 deletions
diff --git a/src/core/misc.c b/src/core/misc.c
index 516c6dc3..78190db2 100644
--- a/src/core/misc.c
+++ b/src/core/misc.c
@@ -409,7 +409,7 @@ int match_wildcards(const char *cmask, const char *data)
}
ret = data != NULL && *data == '\0' && *mask == '\0';
- free(newmask);
+ g_free(newmask);
return ret;
}
diff --git a/src/core/net-nonblock.c b/src/core/net-nonblock.c
index b6e9264f..86d0f263 100644
--- a/src/core/net-nonblock.c
+++ b/src/core/net-nonblock.c
@@ -39,7 +39,7 @@ SIMPLE_THREAD_REC;
/* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is
written to pipe when found PID of the resolver child is returned */
-int net_gethostname_nonblock(const char *addr, int pipe)
+int net_gethostbyname_nonblock(const char *addr, int pipe)
{
RESOLVED_IP_REC rec;
const char *errorstr;
@@ -60,7 +60,8 @@ int net_gethostname_nonblock(const char *addr, int pipe)
}
/* child */
- rec.error = net_gethostname(addr, &rec.ip);
+ memset(&rec, 0, sizeof(rec));
+ rec.error = net_gethostbyname(addr, &rec.ip);
if (rec.error == 0) {
errorstr = NULL;
} else {
@@ -105,7 +106,7 @@ int net_gethostbyname_return(int pipe, RESOLVED_IP_REC *rec)
if (rec->error) {
/* read error string */
- rec->errorstr = g_malloc(rec->errlen);
+ rec->errorstr = g_malloc(rec->errlen+1);
len = 0;
do {
ret = read(pipe, rec->errorstr+len, rec->errlen-len);
@@ -122,6 +123,13 @@ int net_gethostbyname_return(int pipe, RESOLVED_IP_REC *rec)
return 0;
}
+/* Get host name, call func when finished */
+int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data)
+{
+ /*FIXME*/
+ return FALSE;
+}
+
/* Kill the resolver child */
void net_disconnect_nonblock(int pid)
{
@@ -173,7 +181,7 @@ static void simple_readpipe(SIMPLE_THREAD_REC *rec, int pipe)
return;
}
- rec->tag = g_input_add(handle, G_INPUT_WRITE,
+ rec->tag = g_input_add(handle, G_INPUT_READ | G_INPUT_WRITE,
(GInputFunction) simple_init, rec);
}
@@ -192,7 +200,7 @@ int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip, NET_
}
/* start nonblocking host name lookup */
- net_gethostname_nonblock(server, fd[1]);
+ net_gethostbyname_nonblock(server, fd[1]);
rec = g_new0(SIMPLE_THREAD_REC, 1);
rec->port = port;
diff --git a/src/core/net-nonblock.h b/src/core/net-nonblock.h
index 6ae05e82..6e4e10f4 100644
--- a/src/core/net-nonblock.h
+++ b/src/core/net-nonblock.h
@@ -11,10 +11,22 @@ typedef struct {
need to free() it yourself unless it's NULL */
} RESOLVED_IP_REC;
+typedef struct {
+ int namelen;
+ char *name;
+
+ int error;
+ int errlen;
+ char *errorstr;
+} RESOLVED_NAME_REC;
+
typedef void (*NET_CALLBACK) (int, void *);
+typedef void (*NET_HOST_CALLBACK) (RESOLVED_NAME_REC *, void *);
/* nonblocking gethostbyname(), PID of the resolver child is returned. */
-int net_gethostname_nonblock(const char *addr, int pipe);
+int net_gethostbyname_nonblock(const char *addr, int pipe);
+/* Get host's name, call func when finished */
+int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data);
/* get the resolved IP address. returns -1 if some error occured with read() */
int net_gethostbyname_return(int pipe, RESOLVED_IP_REC *rec);
diff --git a/src/core/network.c b/src/core/network.c
index d962f35f..55431971 100644
--- a/src/core/network.c
+++ b/src/core/network.c
@@ -50,7 +50,18 @@ int net_ip_compare(IPADDR *ip1, IPADDR *ip2)
/* copy IP to sockaddr */
inline void sin_set_ip(union sockaddr_union *so, const IPADDR *ip)
{
- so->sin.sin_family = ip->family;
+ if (ip == NULL) {
+#ifdef HAVE_IPV6
+ so->sin6.sin6_family = AF_INET6;
+ so->sin6.sin6_addr.s_addr = in6addr_any;
+#else
+ so->sin.sin_family = AF_INET;
+ so->sin.sin_addr.s_addr = INADDR_ANY;
+#endif
+ return;
+ }
+
+ so->sin.sin_family = ip->family;
#ifdef HAVE_IPV6
if (ip->family == AF_INET6)
memcpy(&so->sin6.sin6_addr, &ip->addr, sizeof(ip->addr.ip6));
@@ -97,7 +108,7 @@ int net_connect(const char *addr, int port, IPADDR *my_ip)
g_return_val_if_fail(addr != NULL, -1);
- if (net_gethostname(addr, &ip) == -1)
+ if (net_gethostbyname(addr, &ip) == -1)
return -1;
return net_connect_ip(&ip, port, my_ip);
@@ -149,21 +160,22 @@ void net_disconnect(int handle)
close(handle);
}
-/* Listen for connections on a socket */
+/* Listen for connections on a socket. if `my_ip' is NULL, listen in any
+ address. */
int net_listen(IPADDR *my_ip, int *port)
{
union sockaddr_union so;
int ret, handle, opt = 1;
socklen_t len = sizeof(so);
- g_return_val_if_fail(my_ip != NULL, -1);
g_return_val_if_fail(port != NULL, -1);
- /* create the socket */
memset(&so, 0, sizeof(so));
- so.sin.sin_family = my_ip->family;
- handle = socket(my_ip->family, SOCK_STREAM, 0);
+ sin_set_port(&so, *port);
+ sin_set_ip(&so, my_ip);
+ /* create the socket */
+ handle = socket(so.sin.sin_family, SOCK_STREAM, 0);
if (handle == -1)
return -1;
@@ -173,7 +185,6 @@ int net_listen(IPADDR *my_ip, int *port)
setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt));
/* specify the address/port we want to listen in */
- sin_set_port(&so, *port);
ret = bind(handle, &so.sa, sizeof(so));
if (ret < 0) {
close(handle);
@@ -190,8 +201,7 @@ int net_listen(IPADDR *my_ip, int *port)
*port = sin_get_port(&so);
/* start listening */
- if (listen(handle, 1) < 0)
- {
+ if (listen(handle, 1) < 0) {
close(handle);
return -1;
}
@@ -207,8 +217,6 @@ int net_accept(int handle, IPADDR *addr, int *port)
socklen_t addrlen;
g_return_val_if_fail(handle != -1, -1);
- g_return_val_if_fail(addr != NULL, -1);
- g_return_val_if_fail(port != NULL, -1);
addrlen = sizeof(so);
ret = accept(handle, &so.sa, &addrlen);
@@ -216,8 +224,8 @@ int net_accept(int handle, IPADDR *addr, int *port)
if (ret < 0)
return -1;
- sin_get_ip(&so, addr);
- *port = sin_get_port(&so);
+ if (addr != NULL) sin_get_ip(&so, addr);
+ if (port != NULL) *port = sin_get_port(&so);
fcntl(ret, F_SETFL, O_NONBLOCK);
return ret;
@@ -297,7 +305,7 @@ int net_getsockname(int handle, IPADDR *addr, int *port)
/* Get IP address for host, returns 0 = ok,
others = error code for net_gethosterror() */
-int net_gethostname(const char *addr, IPADDR *ip)
+int net_gethostbyname(const char *addr, IPADDR *ip)
{
#ifdef HAVE_IPV6
union sockaddr_union *so;
@@ -310,7 +318,6 @@ int net_gethostname(const char *addr, IPADDR *ip)
g_return_val_if_fail(addr != NULL, -1);
- /* host name */
#ifdef HAVE_IPV6
memset(ip, 0, sizeof(IPADDR));
memset(&req, 0, sizeof(struct addrinfo));
@@ -338,6 +345,50 @@ int net_gethostname(const char *addr, IPADDR *ip)
return 0;
}
+/* Get name for host, *name should be g_free()'d unless it's NULL.
+ Return values are the same as with net_gethostbyname() */
+int net_gethostbyaddr(IPADDR *ip, char **name)
+{
+#ifdef HAVE_IPV6
+ struct addrinfo req, *ai;
+ char hbuf[NI_MAXHOST];
+ int host_error;
+#else
+ struct hostent *hp;
+#endif
+ char ipname[MAX_IP_LEN];
+
+ g_return_val_if_fail(ip != NULL, -1);
+ g_return_val_if_fail(name != NULL, -1);
+
+ *name = NULL;
+#ifdef HAVE_IPV6
+ memset(&req, 0, sizeof(struct addrinfo));
+ req.ai_socktype = SOCK_STREAM;
+
+ /* save error to host_error for later use */
+ host_error = getaddrinfo(addr, NULL, &req, &ai);
+ if (host_error != 0)
+ return host_error;
+
+ if (getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, 0))
+ return 1;
+
+ /*FIXME: how does this work? *name = g_strdup(ai->???);*/
+ freeaddrinfo(ai);
+ return 1;
+#else
+ net_ip2host(ip, ipname);
+
+ hp = gethostbyaddr(ipname, strlen(ipname), AF_INET);
+ if (hp == NULL) return -1;
+
+ *name = g_strdup(hp->h_name);
+#endif
+
+ return 0;
+}
+
int net_ip2host(IPADDR *ip, char *host)
{
#ifdef HAVE_IPV6
diff --git a/src/core/network.h b/src/core/network.h
index a206da45..b7e6a30f 100644
--- a/src/core/network.h
+++ b/src/core/network.h
@@ -52,7 +52,10 @@ int net_transmit(int handle, const char *data, int len);
/* Get IP address for host, returns 0 = ok,
others = error code for net_gethosterror() */
-int net_gethostname(const char *addr, IPADDR *ip);
+int net_gethostbyname(const char *addr, IPADDR *ip);
+/* Get name for host, *name should be g_free()'d unless it's NULL.
+ Return values are the same as with net_gethostbyname() */
+int net_gethostbyaddr(IPADDR *ip, char **name);
/* get error of net_gethostname() */
const char *net_gethosterror(int error);
diff --git a/src/core/server.c b/src/core/server.c
index a600e74a..07da183e 100644
--- a/src/core/server.c
+++ b/src/core/server.c
@@ -175,9 +175,9 @@ int server_connect(SERVER_REC *server)
server->handle = -1;
server->connect_pid =
- net_gethostname_nonblock(server->connrec->proxy != NULL ?
- server->connrec->proxy : server->connrec->address,
- server->connect_pipe[1]);
+ net_gethostbyname_nonblock(server->connrec->proxy != NULL ?
+ server->connrec->proxy : server->connrec->address,
+ server->connect_pipe[1]);
server->connect_tag =
g_input_add(server->connect_pipe[0], G_INPUT_READ,
diff --git a/src/fe-common/irc/Makefile.am b/src/fe-common/irc/Makefile.am
index d4ca3f70..662caf9d 100644
--- a/src/fe-common/irc/Makefile.am
+++ b/src/fe-common/irc/Makefile.am
@@ -25,6 +25,7 @@ libfe_common_irc_la_SOURCES = \
fe-common-irc.c \
irc-window-activity.c \
irc-hilight-text.c \
+ irc-modules.c \
module-formats.c
noinst_HEADERS = \
diff --git a/src/fe-common/irc/dcc/fe-dcc.c b/src/fe-common/irc/dcc/fe-dcc.c
index 5cdcd28d..1ca4b167 100644
--- a/src/fe-common/irc/dcc/fe-dcc.c
+++ b/src/fe-common/irc/dcc/fe-dcc.c
@@ -403,7 +403,7 @@ static void dcc_chat_closed(WINDOW_REC *window, WI_IRC_REC *item)
}
}
-void fe_dcc_init(void)
+void fe_irc_dcc_init(void)
{
signal_add("dcc connected", (SIGNAL_FUNC) dcc_connected);
signal_add("dcc rejected", (SIGNAL_FUNC) dcc_rejected);
@@ -432,7 +432,7 @@ void fe_dcc_init(void)
theme_register(fecommon_irc_dcc_formats);
}
-void fe_dcc_deinit(void)
+void fe_irc_dcc_deinit(void)
{
theme_unregister();
diff --git a/src/fe-common/irc/fe-common-irc.c b/src/fe-common/irc/fe-common-irc.c
index 87a0655e..63430a69 100644
--- a/src/fe-common/irc/fe-common-irc.c
+++ b/src/fe-common/irc/fe-common-irc.c
@@ -31,6 +31,9 @@
#include "themes.h"
#include "completion.h"
+void fe_irc_modules_init(void);
+void fe_irc_modules_deinit(void);
+
void fe_channels_init(void);
void fe_channels_deinit(void);
@@ -43,9 +46,6 @@ void fe_irc_server_deinit(void);
void fe_ctcp_init(void);
void fe_ctcp_deinit(void);
-void fe_dcc_init(void);
-void fe_dcc_deinit(void);
-
void fe_events_init(void);
void fe_events_deinit(void);
@@ -61,12 +61,6 @@ void fe_query_deinit(void);
void irc_window_activity_init(void);
void irc_window_activity_deinit(void);
-void fe_notifylist_init(void);
-void fe_notifylist_deinit(void);
-
-void fe_flood_init(void);
-void fe_flood_deinit(void);
-
void fe_netsplit_init(void);
void fe_netsplit_deinit(void);
@@ -108,30 +102,28 @@ void fe_common_irc_init(void)
fe_irc_commands_init();
fe_irc_server_init();
fe_ctcp_init();
- fe_dcc_init();
fe_events_init();
fe_events_numeric_init();
fe_ignore_init();
- fe_notifylist_init();
- fe_flood_init();
fe_netsplit_init();
fe_query_init();
completion_init();
irc_window_activity_init();
+
+ fe_irc_modules_init();
}
void fe_common_irc_deinit(void)
{
+ fe_irc_modules_deinit();
+
fe_channels_deinit();
fe_irc_commands_deinit();
fe_irc_server_deinit();
fe_ctcp_deinit();
- fe_dcc_deinit();
fe_events_deinit();
fe_events_numeric_deinit();
fe_ignore_deinit();
- fe_notifylist_deinit();
- fe_flood_deinit();
fe_netsplit_deinit();
fe_query_deinit();
completion_deinit();
diff --git a/src/fe-common/irc/flood/fe-flood.c b/src/fe-common/irc/flood/fe-flood.c
index b7937fe9..eca1a3b3 100644
--- a/src/fe-common/irc/flood/fe-flood.c
+++ b/src/fe-common/irc/flood/fe-flood.c
@@ -43,7 +43,7 @@ static void event_autoignore_remove(IRC_SERVER_REC *server, AUTOIGNORE_REC *igno
printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_AUTOUNIGNORE, ignore->nick);
}
-void fe_flood_init(void)
+void fe_irc_flood_init(void)
{
signal_add("autoignore new", (SIGNAL_FUNC) event_autoignore_new);
signal_add("autoignore remove", (SIGNAL_FUNC) event_autoignore_remove);
@@ -51,7 +51,7 @@ void fe_flood_init(void)
theme_register(fecommon_irc_flood_formats);
}
-void fe_flood_deinit(void)
+void fe_irc_flood_deinit(void)
{
theme_unregister();
diff --git a/src/fe-common/irc/notifylist/fe-notifylist.c b/src/fe-common/irc/notifylist/fe-notifylist.c
index 3a799de3..77a25fe0 100644
--- a/src/fe-common/irc/notifylist/fe-notifylist.c
+++ b/src/fe-common/irc/notifylist/fe-notifylist.c
@@ -224,7 +224,7 @@ static void notifylist_unidle(IRC_SERVER_REC *server, const char *nick,
server->connrec->ircnet == NULL ? "IRC" : server->connrec->ircnet);
}
-void fe_notifylist_init(void)
+void fe_irc_notifylist_init(void)
{
theme_register(fecommon_irc_notifylist_formats);
@@ -235,7 +235,7 @@ void fe_notifylist_init(void)
signal_add("notifylist unidle", (SIGNAL_FUNC) notifylist_unidle);
}
-void fe_notifylist_deinit(void)
+void fe_irc_notifylist_deinit(void)
{
theme_unregister();
diff --git a/src/fe-none/irssi.c b/src/fe-none/irssi.c
index 09f5a94f..3c449e22 100644
--- a/src/fe-none/irssi.c
+++ b/src/fe-none/irssi.c
@@ -22,7 +22,6 @@
#include "args.h"
#include "signals.h"
#include "core.h"
-#include "irc-core.h"
void irc_init(void);
void irc_deinit(void);
diff --git a/src/fe-text/irssi.c b/src/fe-text/irssi.c
index ee8e3bf5..4b6f408d 100644
--- a/src/fe-text/irssi.c
+++ b/src/fe-text/irssi.c
@@ -24,7 +24,6 @@
#include "signals.h"
#include "core.h"
-#include "irc-core.h"
#include "fe-common-core.h"
#include "fe-common-irc.h"
#include "themes.h"
diff --git a/src/irc/Makefile.am b/src/irc/Makefile.am
index b2972564..dbfe6997 100644
--- a/src/irc/Makefile.am
+++ b/src/irc/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = core dcc flood notifylist
+SUBDIRS = core bot dcc flood notifylist
noinst_LTLIBRARIES = libirc.la
diff --git a/src/irc/bot/.cvsignore b/src/irc/bot/.cvsignore
new file mode 100644
index 00000000..8553e9e9
--- /dev/null
+++ b/src/irc/bot/.cvsignore
@@ -0,0 +1,8 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
diff --git a/src/irc/bot/Makefile.am b/src/irc/bot/Makefile.am
new file mode 100644
index 00000000..fa7058e7
--- /dev/null
+++ b/src/irc/bot/Makefile.am
@@ -0,0 +1,24 @@
+plugindir = $(libdir)/irssi/plugins
+plugin_LTLIBRARIES = libirc_bot.la
+
+INCLUDES = $(GLIB_CFLAGS) \
+ -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ -I$(top_srcdir)/src/irc/core/
+
+libirc_bot_la_LIBADD = -lcrypt
+
+libirc_bot_la_SOURCES = \
+ bot.c \
+ bot-commands.c \
+ bot-events.c \
+ bot-users.c \
+ botnet.c \
+ botnet-connection.c
+
+noinst_HEADERS = \
+ bot.h \
+ botnet.h \
+ bot-users.h
+
+EXTRA_DIST = \
+ users.sample \
+ botnets.sample
diff --git a/src/irc/bot/bot-commands.c b/src/irc/bot/bot-commands.c
new file mode 100644
index 00000000..9c8e7ee6
--- /dev/null
+++ b/src/irc/bot/bot-commands.c
@@ -0,0 +1,165 @@
+/*
+ bot-commands.c : IRC bot plugin for 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 "irc.h"
+#include "irc-server.h"
+#include "channels.h"
+#include "nicklist.h"
+#include "masks.h"
+
+#include "bot-users.h"
+
+static void event_privmsg(const char *data, IRC_SERVER_REC *server,
+ const char *nick, const char *address)
+{
+ char *params, *target, *msg, *args, *str;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg);
+ if (nick == NULL) nick = server->real_address;
+
+ if (*msg == 1 || ischannel(*target)) {
+ g_free(params);
+ return;
+ }
+
+ /* private message for us */
+ str = g_strconcat("bot command ", msg, NULL);
+ args = strchr(str+12, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+
+ g_strdown(str);
+ if (signal_emit(str, 4, args, server, nick, address)) {
+ /* msg was a command - the msg event. */
+ signal_stop();
+ }
+ g_free(str);
+ g_free(params);
+}
+
+static void botcmd_op(const char *data, IRC_SERVER_REC *server,
+ const char *nick, const char *address)
+{
+ CHANNEL_REC *channel;
+ USER_REC *user;
+ USER_CHAN_REC *userchan;
+ GSList *tmp;
+
+ g_return_if_fail(data != NULL);
+
+ if (*data == '\0') {
+ /* no password given? .. */
+ return;
+ }
+
+ user = botuser_find(nick, address);
+ if (user == NULL || (user->not_flags & USER_OP) ||
+ !botuser_verify_password(user, data)) {
+ /* not found, can't op with this mask or failed password */
+ return;
+ }
+
+ /* find the channels where to op.. */
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ channel = tmp->data;
+
+ userchan = g_hash_table_lookup(user->channels, channel->name);
+ if ((user->flags & USER_OP) || (userchan->flags & USER_OP))
+ signal_emit("command op", 3, nick, server, channel);
+ }
+}
+
+static void botcmd_ident(const char *data, IRC_SERVER_REC *server,
+ const char *nick, const char *address)
+{
+ USER_REC *user;
+ char *mask;
+
+ g_return_if_fail(data != NULL);
+
+ user = botuser_find(nick, address);
+ if (user != NULL) {
+ /* Already know this host */
+ return;
+ }
+
+ user = botuser_find(nick, NULL);
+ if (user == NULL || !botuser_verify_password(user, data)) {
+ /* failed password */
+ return;
+ }
+
+ /* add the new mask */
+ mask = irc_get_mask(nick, address, IRC_MASK_USER | IRC_MASK_DOMAIN);
+ botuser_add_mask(user, mask);
+
+ irc_send_cmdv(server, "NOTICE %s :Added new mask %s", nick, mask);
+ g_free(mask);
+}
+
+static void botcmd_pass(const char *data, IRC_SERVER_REC *server,
+ const char *nick, const char *address)
+{
+ USER_REC *user;
+ char *params, *pass, *newpass;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, &pass, &newpass);
+
+ user = botuser_find(nick, address);
+ if (user == NULL || *pass == '\0') {
+ g_free(params);
+ return;
+ }
+
+ if (user->password != NULL &&
+ (*newpass == '\0' || !botuser_verify_password(user, pass))) {
+ g_free(params);
+ return;
+ }
+
+ /* change the password */
+ botuser_set_password(user, user->password == NULL ? pass : newpass);
+ irc_send_cmdv(server, "NOTICE %s :Password changed", nick);
+
+ g_free(params);
+}
+
+void bot_commands_init(void)
+{
+ signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_add_last("bot command op", (SIGNAL_FUNC) botcmd_op);
+ signal_add_last("bot command ident", (SIGNAL_FUNC) botcmd_ident);
+ signal_add_last("bot command pass", (SIGNAL_FUNC) botcmd_pass);
+}
+
+void bot_commands_deinit(void)
+{
+ signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_remove("bot command op", (SIGNAL_FUNC) botcmd_op);
+ signal_remove("bot command ident", (SIGNAL_FUNC) botcmd_ident);
+ signal_remove("bot command pass", (SIGNAL_FUNC) botcmd_pass);
+}
diff --git a/src/irc/bot/bot-events.c b/src/irc/bot/bot-events.c
new file mode 100644
index 00000000..ebbe0660
--- /dev/null
+++ b/src/irc/bot/bot-events.c
@@ -0,0 +1,199 @@
+/*
+ bot-events.c : IRC bot plugin for 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 "irc.h"
+#include "irc-server.h"
+#include "channels.h"
+#include "nicklist.h"
+#include "modes.h"
+#include "netsplit.h"
+
+#include "bot-users.h"
+
+static int get_flags(USER_REC *user, CHANNEL_REC *channel)
+{
+ USER_CHAN_REC *userchan;
+
+ g_return_val_if_fail(user != NULL, 0);
+ g_return_val_if_fail(channel != NULL, 0);
+
+ userchan = g_hash_table_lookup(user->channels, channel->name);
+ return (user->flags | (userchan == NULL ? 0 : userchan->flags)) &
+ (~user->not_flags);
+}
+
+static void event_massjoin(CHANNEL_REC *channel, GSList *users)
+{
+ USER_REC *user;
+ USER_CHAN_REC *userchan;
+ NICK_REC *nick;
+ GString *modestr, *nickstr;
+ int flags;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(users != NULL);
+
+ modestr = g_string_new(NULL);
+ nickstr = g_string_new(NULL);
+
+ for (; users != NULL; users = users->next) {
+ user = users->data;
+ userchan = g_hash_table_lookup(user->channels, channel->name);
+ nick = userchan->nickrec;
+
+ flags = get_flags(user, channel);
+ if (!nick->op && (flags & USER_AUTO_OP)) {
+ g_string_sprintfa(modestr, "+o");
+ g_string_sprintfa(nickstr, "%s,", nick->nick);
+ }
+
+ if (!nick->voice && !nick->op && (flags & USER_AUTO_VOICE)) {
+ g_string_sprintfa(modestr, "+v");
+ g_string_sprintfa(nickstr, "%s,", nick->nick);
+ }
+ }
+
+ if (nickstr->len > 0) {
+ g_string_truncate(nickstr, nickstr->len-1);
+ g_string_sprintfa(modestr, " %s", nickstr->str);
+
+ channel_set_mode(channel->server, channel->name, modestr->str);
+ }
+
+ g_string_free(modestr, TRUE);
+ g_string_free(nickstr, TRUE);
+}
+
+/* Parse channel mode string */
+static void parse_channel_mode(CHANNEL_REC *channel, const char *mode,
+ const char *nick, const char *address)
+{
+ NICK_REC *nickrec, *splitnick;
+ USER_REC *user;
+ GString *str;
+ char *ptr, *curmode, type, *dup, *modestr;
+ int flags;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nick != NULL);
+ g_return_if_fail(modestr != NULL);
+
+ user = botuser_find(nick, address);
+ flags = user == NULL ? 0 : get_flags(user, channel);
+
+ if (!channel->chanop || (flags & USER_MASTER) ||
+ g_strcasecmp(nick, channel->server->nick) == 0) {
+ /* can't do anything or we/master did mode change,
+ don't bother checking what */
+ return;
+ }
+
+ str = g_string_new(NULL);
+ dup = modestr = g_strdup(mode);
+
+ type = '+';
+ curmode = cmd_get_param(&modestr);
+ for (; *curmode != '\0'; curmode++) {
+ if (*curmode == '+' || *curmode == '-') {
+ type = *curmode;
+ continue;
+ }
+
+ if (!HAS_MODE_ARG(*curmode))
+ ptr = NULL;
+ else {
+ ptr = cmd_get_param(&modestr);
+ if (*ptr == '\0') continue;
+ }
+
+ if (*curmode != 'o')
+ continue;
+
+ if (type == '-' && strcmp(channel->server->nick, ptr) == 0) {
+ /* we aren't chanop anymore .. */
+ g_string_truncate(str, 0);
+ break;
+ }
+
+ if (type != '+')
+ continue;
+
+ /* check that op is valid */
+ nickrec = nicklist_find(channel, ptr);
+ if (nickrec == NULL || nickrec->host == NULL)
+ continue;
+
+ user = botuser_find(ptr, nickrec->host);
+ flags = user == NULL ? 0 : get_flags(user, channel);
+ if (flags & USER_OP)
+ continue;
+
+ if (address == NULL) {
+ /* server opped, check if user was opped before netsplit. */
+ splitnick = netsplit_find_channel(channel->server, nickrec->nick, nickrec->host, channel->name);
+ if (splitnick != NULL && splitnick->op)
+ continue;
+ }
+
+ /* this one isn't supposed to get ops! */
+ g_string_sprintfa(str, "%s ", ptr);
+ }
+ g_free(dup);
+
+ if (str->len != 0)
+ signal_emit("command deop", 3, str->str, channel->server, channel);
+ g_string_free(str, TRUE);
+}
+
+static void event_mode(const char *data, IRC_SERVER_REC *server,
+ const char *nick, const char *address)
+{
+ 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)) {
+ /* channel mode change */
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL)
+ parse_channel_mode(chanrec, mode, nick, address);
+ }
+
+ g_free(params);
+}
+
+void bot_events_init(void)
+{
+ signal_add_last("bot massjoin", (SIGNAL_FUNC) event_massjoin);
+ signal_add("event mode", (SIGNAL_FUNC) event_mode);
+}
+
+void bot_events_deinit(void)
+{
+ signal_remove("bot massjoin", (SIGNAL_FUNC) event_massjoin);
+ signal_remove("event mode", (SIGNAL_FUNC) event_mode);
+}
diff --git a/src/irc/bot/bot-users.c b/src/irc/bot/bot-users.c
new file mode 100644
index 00000000..5db917a1
--- /dev/null
+++ b/src/irc/bot/bot-users.c
@@ -0,0 +1,503 @@
+/*
+ bot-users.c : IRC bot plugin for irssi - user handling
+
+ 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
+*/
+
+#define _XOPEN_SOURCE /* for crypt() */
+#include "module.h"
+#include "signals.h"
+#include "misc.h"
+#include "lib-config/iconfig.h"
+
+#include "channels.h"
+#include "nicklist.h"
+#include "masks.h"
+
+#include "bot-users.h"
+
+#define WRITE_USERS_INTERVAL (60*15)
+
+static char *user_flags = "oavm"; /* Keep these in the same order as USER_xxx flags */
+
+static CONFIG_REC *userconfig;
+static GHashTable *users;
+
+static int writeusers_tag;
+static time_t last_write;
+
+int botuser_flags2value(const char *flags)
+{
+ char *pos;
+ int val;
+
+ g_return_val_if_fail(flags != NULL, 0);
+
+ val = 0;
+ while (*flags != '\0') {
+ pos = strchr(user_flags, *flags);
+ if (pos != NULL)
+ val |= 1 << (int) (pos-user_flags);
+ flags++;
+ }
+
+ return val;
+}
+
+char *botuser_value2flags(int value)
+{
+ char *str, *p;
+ int n;
+
+ p = str = g_malloc(USER_FLAG_COUNT+1);
+ for (n = 0; n < USER_FLAG_COUNT; n++) {
+ if (value & (1 << n))
+ *p++ = user_flags[n];
+ }
+ *p = '\0';
+
+ return str;
+}
+
+/* save channel specific user record */
+static void botuser_save_chan(const char *key, USER_CHAN_REC *rec, CONFIG_NODE *node)
+{
+ CONFIG_NODE *noderec;
+ char *str;
+
+ noderec = config_node_section(node, rec->channel, NODE_TYPE_BLOCK);
+
+ str = rec->flags == 0 ? NULL :
+ botuser_value2flags(rec->flags);
+ config_node_set_str(userconfig, noderec, "flags", str);
+ g_free_not_null(str);
+}
+
+static void botuser_config_save(USER_REC *user)
+{
+ CONFIG_NODE *node, *subnode, *noderec;
+ GSList *tmp;
+ char *str;
+
+ node = config_node_traverse(userconfig, "users", TRUE);
+ node = config_node_section(node, user->nick, NODE_TYPE_BLOCK);
+
+ str = user->flags == 0 ? NULL :
+ botuser_value2flags(user->flags);
+ config_node_set_str(userconfig, node, "flags", str);
+ g_free_not_null(str);
+
+ config_node_set_str(userconfig, node, "password", user->password);
+
+ /* Save masks */
+ if (user->masks == NULL)
+ config_node_set_str(userconfig, node, "masks", NULL);
+ else {
+ subnode = config_node_section(node, "masks", NODE_TYPE_LIST);
+
+ for (tmp = user->masks; tmp != NULL; tmp = tmp->next) {
+ USER_MASK_REC *rec = tmp->data;
+
+ noderec = config_node_section(subnode, NULL, NODE_TYPE_BLOCK);
+ config_node_set_str(userconfig, noderec, "mask", rec->mask);
+
+ str = user->flags == 0 ? NULL :
+ botuser_value2flags(rec->not_flags);
+ config_node_set_str(userconfig, noderec, "not_flags", str);
+ g_free_not_null(str);
+ }
+ }
+
+ /* Save channels */
+ if (g_hash_table_size(user->channels) == 0)
+ config_node_set_str(userconfig, node, "channels", NULL);
+ else {
+ subnode = config_node_section(node, "channels", NODE_TYPE_LIST);
+ g_hash_table_foreach(user->channels, (GHFunc) botuser_save_chan, subnode);
+ }
+}
+
+static USER_MASK_REC *botuser_create_mask(USER_REC *user, const char *mask)
+{
+ USER_MASK_REC *rec;
+
+ rec = g_new0(USER_MASK_REC, 1);
+ rec->mask = g_strdup(mask);
+
+ user->masks = g_slist_append(user->masks, rec);
+ return rec;
+}
+
+USER_MASK_REC *botuser_add_mask(USER_REC *user, const char *mask)
+{
+ USER_MASK_REC *rec;
+
+ rec = botuser_create_mask(user, mask);
+ botuser_config_save(user);
+ return rec;
+}
+
+static int botuser_find_mask(USER_REC *user, const char *nick, const char *host)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(user != NULL, FALSE);
+ g_return_val_if_fail(nick != NULL, FALSE);
+ g_return_val_if_fail(host != NULL, FALSE);
+
+ /* Check that masks match */
+ for (tmp = user->masks; tmp != NULL; tmp = tmp->next) {
+ USER_MASK_REC *rec = tmp->data;
+
+ if (irc_mask_match_address(rec->mask, nick, host)) {
+ user->not_flags = rec->not_flags;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void botuser_getusers_hash(void *key, USER_REC *user, GList **list)
+{
+ *list = g_list_append(*list, user);
+}
+
+USER_REC *botuser_find(const char *nick, const char *host)
+{
+ USER_REC *user;
+ char *stripnick;
+ GList *list, *tmp;
+
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ /* First check for user with same nick */
+ stripnick = nick_strip(nick);
+ user = g_hash_table_lookup(users, stripnick);
+ g_free(stripnick);
+
+ if (user != NULL && host != NULL &&
+ !botuser_find_mask(user, nick, host)) {
+ /* mask didn't match, check for more.. */
+ user = NULL;
+ }
+
+ if (user != NULL || host == NULL)
+ return user;
+
+ /* Check for different nicks.. */
+ list = NULL;
+ g_hash_table_foreach(users, (GHFunc) botuser_getusers_hash, &list);
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ if (botuser_find_mask(tmp->data, nick, host)) {
+ user = tmp->data;
+ break;
+ }
+ }
+ g_list_free(list);
+
+ return user;
+}
+
+USER_REC *botuser_find_rec(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ USER_REC *user, *rec;
+ USER_CHAN_REC *userchan;
+ GList *list, *tmp;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ user = NULL; list = NULL;
+ g_hash_table_foreach(users, (GHFunc) botuser_getusers_hash, &list);
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ rec = tmp->data;
+
+ userchan = g_hash_table_lookup(rec->channels, channel->name);
+ if (userchan != NULL && userchan->nickrec == nick) {
+ user = rec;
+ break;
+ }
+ }
+ g_list_free(list);
+
+ return user;
+}
+
+static USER_CHAN_REC *botuser_channel(USER_REC *user, const char *channel)
+{
+ USER_CHAN_REC *rec;
+
+ g_return_val_if_fail(user != NULL, NULL);
+ g_return_val_if_fail(channel != NULL, NULL);
+
+ rec = g_hash_table_lookup(user->channels, channel);
+ if (rec != NULL) return rec;
+
+ rec = g_new0(USER_CHAN_REC, 1);
+ rec->channel = g_strdup(channel);
+ g_hash_table_insert(user->channels, rec->channel, rec);
+ return rec;
+}
+
+void botuser_set_password(USER_REC *user, const char *password)
+{
+ char *pass, salt[3];
+
+ g_return_if_fail(user != NULL);
+ g_return_if_fail(password != NULL);
+
+ salt[0] = rand()%20 + 'A';
+ salt[1] = rand()%20 + 'A';
+ salt[2] = '\0';
+ pass = crypt(password, salt);
+
+ if (user->password != NULL) g_free(user->password);
+ user->password = g_strdup(pass);
+ botuser_config_save(user);
+}
+
+int botuser_verify_password(USER_REC *user, const char *password)
+{
+ char *pass, salt[3];
+
+ g_return_val_if_fail(user != NULL, FALSE);
+ g_return_val_if_fail(password != NULL, FALSE);
+
+ if (user->password == NULL || strlen(user->password) < 3)
+ return FALSE;
+
+ salt[0] = user->password[0];
+ salt[1] = user->password[1];
+ salt[2] = '\0';
+ pass = crypt(password, salt);
+ return strcmp(user->password, pass) == 0;
+}
+
+static void event_massjoin(CHANNEL_REC *channel, GSList *nicks)
+{
+ USER_REC *user;
+ USER_CHAN_REC *userchan;
+ GSList *users;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nicks != NULL);
+
+ users = NULL;
+ for (; nicks != NULL; nicks = nicks->next) {
+ NICK_REC *rec = nicks->data;
+
+ user = botuser_find(rec->nick, rec->host);
+ if (user != NULL) {
+ userchan = botuser_channel(user, channel->name);
+ userchan->nickrec = rec;
+ users = g_slist_append(users, user);
+ }
+ }
+
+ if (users != NULL) {
+ signal_emit("bot massjoin", 2, channel, users);
+ g_slist_free(users);
+ }
+}
+
+/* channel synced - find everyone's NICK_REC's */
+static void sig_channel_sync(CHANNEL_REC *channel)
+{
+ USER_REC *user;
+ USER_CHAN_REC *userchan;
+ GSList *tmp, *nicks;
+
+ g_return_if_fail(channel != NULL);
+
+ nicks = nicklist_getnicks(channel);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *rec = tmp->data;
+
+ if (rec->send_massjoin)
+ continue; /* This will be checked in "massjoin" signal */
+
+ user = botuser_find(rec->nick, rec->host);
+ if (user != NULL) {
+ userchan = botuser_channel(user, channel->name);
+ userchan->nickrec = rec;
+ }
+ }
+ g_slist_free(nicks);
+}
+
+/* user left channel - remove from users record */
+static void sig_nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ USER_REC *user;
+ USER_CHAN_REC *userchan;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nick != NULL);
+
+ user = botuser_find_rec(channel, nick);
+ userchan = user == NULL ? NULL :
+ g_hash_table_lookup(user->channels, channel->name);
+ if (userchan != NULL) userchan->nickrec = NULL;
+}
+
+/* Free memory used by user channel record */
+static void user_destroy_chan(const char *key, USER_CHAN_REC *rec)
+{
+ g_free(rec->channel);
+ g_free(rec);
+}
+
+static void usermask_destroy(USER_MASK_REC *rec)
+{
+ g_free(rec->mask);
+ g_free(rec);
+}
+
+/* Free memory used by user record */
+static void user_destroy(const char *key, USER_REC *user)
+{
+ g_slist_foreach(user->masks, (GFunc) usermask_destroy, NULL);
+ g_slist_free(user->masks);
+
+ g_hash_table_foreach(user->channels, (GHFunc) user_destroy_chan, NULL);
+ g_hash_table_destroy(user->channels);
+
+ g_free_not_null(user->password);
+ g_free(user->nick);
+ g_free(user);
+}
+
+static int sig_write_users(void)
+{
+ if (last_write + WRITE_USERS_INTERVAL <= time(NULL)) {
+ last_write = time(NULL);
+ config_write(userconfig, NULL, -1);
+ }
+ return 1;
+}
+
+static void botuser_config_read_user(CONFIG_NODE *node)
+{
+ USER_REC *user;
+ USER_CHAN_REC *userchan;
+ USER_MASK_REC *usermask;
+ CONFIG_NODE *subnode;
+ GSList *tmp;
+ char *value;
+
+ g_return_if_fail(node != NULL);
+
+ /* nick = { ... } */
+ if (node->key == NULL || node->value == NULL)
+ return;
+
+ /* Add new user */
+ user = g_new0(USER_REC, 1);
+ user->nick = g_strdup(node->key);
+ g_hash_table_insert(users, user->nick, user);
+
+ /* password, flags */
+ user->password = g_strdup(config_node_get_str(node, "password", NULL));
+ user->flags = botuser_flags2value(config_node_get_str(node, "flags", ""));
+
+ /* get masks */
+ user->masks = NULL;
+ subnode = config_node_section(node, "masks", -1);
+ tmp = subnode == NULL ? NULL : subnode->value;
+ for (; tmp != NULL; tmp = tmp->next) {
+ subnode = tmp->data;
+
+ value = config_node_get_str(subnode, "mask", NULL);
+ if (value == NULL) continue; /* mask is required */
+
+ usermask = botuser_create_mask(user, value);
+ value = config_node_get_str(subnode, "not_flags", "");
+ usermask->not_flags = botuser_flags2value(value);
+ }
+
+ /* get channels - must be last, messes up pvalue */
+ user->channels = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
+ subnode = config_node_section(node, "channels", -1);
+ tmp = subnode == NULL ? NULL : subnode->value;
+ for (; tmp != NULL; tmp = tmp->next) {
+ subnode = tmp->data;
+
+ value = config_node_get_str(subnode, "channel", NULL);
+ if (value == NULL) continue; /* channel is required */
+
+ /* create user channel specific record */
+ userchan = g_new0(USER_CHAN_REC, 1);
+ userchan->channel = g_strdup(value);
+ g_hash_table_insert(user->channels, userchan->channel, userchan);
+
+ value = config_node_get_str(subnode, "flags", "");
+ userchan->flags = botuser_flags2value(value);
+ }
+}
+
+static void botuser_config_read(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+ char *fname;
+
+ /* Read users from ~/.irssi/users */
+ fname = g_strdup_printf("%s/.irssi/users", g_get_home_dir());
+ userconfig = config_open(fname, 0600);
+ g_free(fname);
+
+ if (userconfig == NULL)
+ return; /* access denied?! */
+
+ config_parse(userconfig);
+
+ node = config_node_traverse(userconfig, "users", FALSE);
+ tmp = node == NULL ? NULL : node->value;
+ for (; tmp != NULL; tmp = tmp->next)
+ botuser_config_read_user(tmp->data);
+}
+
+void bot_users_init(void)
+{
+ users = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
+
+ last_write = time(NULL);
+ writeusers_tag = g_timeout_add(10000, (GSourceFunc) sig_write_users, NULL);
+
+ botuser_config_read();
+ signal_add_last("massjoin", (SIGNAL_FUNC) event_massjoin);
+ signal_add_last("channel sync", (SIGNAL_FUNC) sig_channel_sync);
+ signal_add_last("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove);
+}
+
+void bot_users_deinit(void)
+{
+ if (userconfig != NULL) {
+ config_write(userconfig, NULL, -1);
+ config_close(userconfig);
+ }
+
+ g_source_remove(writeusers_tag);
+
+ g_hash_table_foreach(users, (GHFunc) user_destroy, NULL);
+ g_hash_table_destroy(users);
+
+ signal_remove("massjoin", (SIGNAL_FUNC) event_massjoin);
+ signal_remove("channel sync", (SIGNAL_FUNC) sig_channel_sync);
+ signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove);
+}
diff --git a/src/irc/bot/bot-users.h b/src/irc/bot/bot-users.h
new file mode 100644
index 00000000..c463f09e
--- /dev/null
+++ b/src/irc/bot/bot-users.h
@@ -0,0 +1,47 @@
+#ifndef __BOT_USERS_H
+#define __BOT_USERS_H
+
+#define USER_OP 0x0001
+#define USER_AUTO_OP 0x0002
+#define USER_AUTO_VOICE 0x0004
+#define USER_MASTER 0x0008
+
+#define USER_FLAG_COUNT 4
+
+/* Channel specific flags */
+typedef struct {
+ char *channel;
+ int flags;
+ NICK_REC *nickrec; /* Nick record in channel,
+ FIXME: User can be in channel with multiple nicks too! */
+} USER_CHAN_REC;
+
+typedef struct {
+ char *mask;
+ int not_flags; /* do not let this mask use these flags.. */
+} USER_MASK_REC;
+
+/* User specific flags */
+typedef struct {
+ char *nick;
+ int flags;
+ char *password;
+
+ GSList *masks;
+ GHashTable *channels;
+
+ int not_flags; /* active not_flags based on current host mask,
+ botuser_find() updates this */
+} USER_REC;
+
+int botuser_flags2value(const char *flags);
+char *botuser_value2flags(int value);
+
+USER_REC *botuser_find(const char *nick, const char *host);
+USER_REC *botuser_find_rec(CHANNEL_REC *channel, NICK_REC *nick);
+USER_MASK_REC *botuser_add_mask(USER_REC *user, const char *mask);
+
+void botuser_set_password(USER_REC *user, const char *password);
+int botuser_verify_password(USER_REC *user, const char *password);
+
+#endif
diff --git a/src/irc/bot/bot.c b/src/irc/bot/bot.c
new file mode 100644
index 00000000..5bcf7dd0
--- /dev/null
+++ b/src/irc/bot/bot.c
@@ -0,0 +1,49 @@
+/*
+ bot.c : IRC bot plugin for 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"
+
+void bot_commands_deinit(void);
+void bot_commands_init(void);
+
+void bot_events_init(void);
+void bot_events_deinit(void);
+
+void bot_users_init(void);
+void bot_users_deinit(void);
+
+void botnet_init(void);
+void botnet_deinit(void);
+
+void irc_bot_init(void)
+{
+ bot_users_init();
+ bot_commands_init();
+ bot_events_init();
+ botnet_init();
+}
+
+void irc_bot_deinit(void)
+{
+ bot_users_deinit();
+ bot_commands_deinit();
+ bot_events_deinit();
+ botnet_deinit();
+}
diff --git a/src/irc/bot/bot.h b/src/irc/bot/bot.h
new file mode 100644
index 00000000..a518d4bd
--- /dev/null
+++ b/src/irc/bot/bot.h
@@ -0,0 +1,26 @@
+#ifndef __BOT_H
+#define __BOT_H
+
+typedef struct
+{
+ PLUGIN_REC *plugin;
+ gboolean loaded;
+
+ GHashTable *users;
+ GSList *botnets;
+
+ gchar *nick;
+ gint rank;
+
+ time_t last_write;
+}
+PLUGIN_DATA;
+
+void plugin_bot_events(PLUGIN_REC *plugin);
+
+#include "botnet.h"
+#include "users.h"
+
+#define MODULE_NAME "bot"
+
+#endif
diff --git a/src/irc/bot/botnet-connection.c b/src/irc/bot/botnet-connection.c
new file mode 100644
index 00000000..fa0d10c5
--- /dev/null
+++ b/src/irc/bot/botnet-connection.c
@@ -0,0 +1,553 @@
+/*
+ botnet-connection.c : IRC bot plugin for 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 "net-nonblock.h"
+#include "signals.h"
+#include "commands.h"
+#include "misc.h"
+#include "line-split.h"
+#include "lib-config/iconfig.h"
+
+#include "botnet.h"
+
+#define BOTNET_RECONNECT_TIME (60*5)
+
+static void sig_bot_read(BOT_REC *bot)
+{
+ BOTNET_REC *botnet;
+ char tmpbuf[1024], *str;
+ int ret, recvlen, reconnect;
+
+ botnet = bot->botnet;
+ for (;;) {
+ recvlen = bot->handle == -1 ? -1 :
+ net_receive(bot->handle, tmpbuf, sizeof(tmpbuf));
+ ret = line_split(tmpbuf, recvlen, &str, (LINEBUF_REC **) &bot->buffer);
+
+ if (ret == 0)
+ break;
+ if (ret == -1) {
+ /* connection lost */
+ reconnect = !bot->disconnect && bot->uplink;
+ bot_destroy(bot);
+
+ if (reconnect) {
+ /* wasn't intentional disconnection from
+ our uplink, reconnect */
+ botnet_connect(botnet->name);
+ }
+ break;
+ }
+
+ fprintf(stderr, "%s\r\n", str);
+ signal_emit("botnet event", 2, bot, str);
+ }
+}
+
+static void connect_downlink(BOTNET_REC *botnet, int handle,
+ IPADDR *ip, const char *host)
+{
+ BOT_DOWNLINK_REC *downlink;
+ BOT_REC *bot;
+
+ g_return_if_fail(botnet != NULL);
+
+ /* identify the bot who's trying to connect.. */
+ downlink = bot_downlink_find(botnet, ip, host);
+ if (downlink == NULL || downlink->password == NULL) {
+ /* unknown bot, close connection /
+ bot didn't have password, don't let it connect to us */
+ net_disconnect(handle);
+ return;
+ }
+
+ bot = g_new0(BOT_REC, 1);
+ bot->botnet = botnet;
+ bot->link = downlink;
+ g_node_append_data(botnet->bots, bot);
+
+ /* connected.. */
+ bot->handle = handle;
+ bot->read_tag = g_input_add(handle, G_INPUT_READ, (GInputFunction) sig_bot_read, bot);
+}
+
+typedef struct {
+ char *botnet;
+ IPADDR ip;
+ int handle;
+} BOT_CONNECT_REC;
+
+static void sig_host_got(RESOLVED_NAME_REC *name, BOT_CONNECT_REC *rec)
+{
+ BOTNET_REC *botnet;
+
+ botnet = botnet_find(rec->botnet);
+ if (botnet == NULL || !botnet->connected) {
+ /* this botnet isn't connected anymore.. */
+ net_disconnect(rec->handle);
+ } else {
+ connect_downlink(botnet, rec->handle, &rec->ip,
+ name->error ? NULL : name->name);
+ }
+ g_free(rec->botnet);
+ g_free(rec);
+}
+
+static void sig_botnet_listen(BOTNET_REC *botnet)
+{
+ BOT_CONNECT_REC *rec;
+ IPADDR ip;
+ int handle;
+
+ g_return_if_fail(botnet != NULL);
+
+ /* accept connection */
+ handle = net_accept(botnet->listen_handle, &ip, NULL);
+ if (handle == -1)
+ return;
+
+ rec = g_new0(BOT_CONNECT_REC, 1);
+ rec->botnet = g_strdup(botnet->name);
+ memcpy(&rec->ip, &ip, sizeof(IPADDR));
+ rec->handle = handle;
+
+ if (!net_gethostbyaddr_nonblock(&ip, (NET_HOST_CALLBACK) sig_host_got, rec)) {
+ /* failed for some reason, try without host */
+ connect_downlink(botnet, handle, &ip, NULL);
+ g_free(rec->botnet);
+ g_free(rec);
+ }
+}
+
+static int botnet_listen(BOTNET_REC *botnet)
+{
+ IPADDR addr;
+ int port;
+
+ g_return_val_if_fail(botnet != NULL, FALSE);
+
+ if (botnet->port <= 0)
+ return FALSE;
+
+ port = botnet->port;
+ if (botnet->addr == NULL)
+ botnet->listen_handle = net_listen(NULL, &port);
+ else {
+ net_host2ip(botnet->addr, &addr);
+ botnet->listen_handle = net_listen(&addr, &port);
+ }
+
+ if (botnet->listen_handle == -1) {
+ g_warning("Couldn't start listening botnet\n");
+ return FALSE;
+ }
+
+ botnet->listen_tag = g_input_add(botnet->listen_handle, G_INPUT_READ,
+ (GInputFunction) sig_botnet_listen, botnet);
+
+ return TRUE;
+}
+
+static void sig_botnet_connected(int handle, BOT_UPLINK_REC *uplink)
+{
+ BOTNET_REC *botnet;
+ BOT_REC *bot;
+
+ g_return_if_fail(uplink != NULL);
+
+ botnet = uplink->botnet;
+
+ if (handle == -1) {
+ /* error, try another bot */
+ botnet_connect(botnet->name);
+ return;
+ }
+
+ /* connected to bot */
+ bot = g_new0(BOT_REC, 1);
+ bot->botnet = botnet;
+ bot->link = uplink;
+ bot->uplink = TRUE;
+
+ bot->handle = handle;
+ bot->read_tag = g_input_add(handle, G_INPUT_READ, (GInputFunction) sig_bot_read, bot);
+
+ botnet->uplink = bot;
+ g_node_append_data(botnet->bots, bot);
+
+ /* send nick/pass */
+ bot_send_cmdv(bot, "PASS %s", uplink->password);
+ bot_send_cmdv(bot, "NICK %s", botnet->nick);
+}
+
+int botnet_connect(const char *network)
+{
+ BOTNET_REC *botnet;
+ BOT_REC *bot;
+ BOT_UPLINK_REC *uplink, *best;
+ GSList *tmp;
+ time_t now;
+
+ g_return_val_if_fail(network != NULL, FALSE);
+
+ /* find botnet */
+ botnet = botnet_find(network);
+ if (botnet == NULL) return FALSE;
+
+ if (botnet->bots == NULL) {
+ /* create bot record for us */
+ bot = g_new0(BOT_REC, 1);
+ bot->botnet = botnet;
+ bot->nick = g_strdup(botnet->nick);
+ bot->priority = botnet->priority;
+ bot->connected = TRUE;
+ bot->master = TRUE;
+
+ bot->handle = -1;
+ bot->read_tag = -1;
+
+ botnet->connected = TRUE;
+ botnet->master = bot;
+
+ botnet->bots = g_node_new(bot);
+ }
+
+ if (botnet->listen_handle == -1) {
+ /* start listening */
+ botnet_listen(botnet);
+ }
+
+ /* find some bot where we can try to connect to */
+ now = time(NULL);
+ uplink = best = NULL;
+ for (tmp = botnet->uplinks; tmp != NULL; tmp = tmp->next) {
+ uplink = tmp->data;
+
+ if (uplink->last_connect+BOTNET_RECONNECT_TIME > now)
+ continue;
+
+ if (uplink->last_connect == 0) {
+ /* haven't yet tried to connect to this bot */
+ best = uplink;
+ break;
+ }
+
+ if (best == NULL || uplink->last_connect < best->last_connect)
+ best = uplink;
+ }
+
+ if (best == NULL)
+ return FALSE;
+
+ /* connect to uplink */
+ best->last_connect = time(NULL);
+ net_connect_nonblock(best->host, best->port, NULL, (NET_CALLBACK) sig_botnet_connected, best);
+ return TRUE;
+}
+
+static int botnet_send_botinfo(GNode *node, BOT_REC *client)
+{
+ BOT_REC *parent, *bot;
+
+ bot = node->data;
+ parent = node->parent == NULL ? NULL : node->parent->data;
+ if (parent == NULL && client->uplink) parent = client;
+
+ bot_send_cmdv(client, "%s - BOTINFO %s %s %d", bot->botnet->nick, bot->nick,
+ parent != NULL ? parent->nick : "-", bot->priority);
+ return FALSE;
+}
+
+/* send botnet links to specified bot */
+static void botnet_send_links(BOT_REC *bot, int downlinks)
+{
+ GNode *node;
+
+ if (!downlinks) {
+ /* send uplinks */
+ if (bot->botnet->uplink == NULL)
+ return;
+
+ node = g_node_find(bot->botnet->bots, G_IN_ORDER,
+ G_TRAVERSE_ALL, bot->botnet->uplink);
+ if (node == NULL)
+ return;
+
+ g_node_traverse(node, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1,
+ (GNodeTraverseFunc) botnet_send_botinfo, bot);
+ return;
+ }
+
+ /* send downlinks = all non-uplink nodes */
+ for (node = bot->botnet->bots->children; node != NULL; node = node->next) {
+ BOT_REC *rec = node->data;
+
+ if (rec == bot || rec->uplink || !rec->connected)
+ continue;
+
+ g_node_traverse(node, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1,
+ (GNodeTraverseFunc) botnet_send_botinfo, bot);
+ }
+}
+
+static void botnet_connect_event_uplink(BOT_REC *bot, const char *data)
+{
+ BOTNET_REC *botnet;
+ BOT_REC *ownbot;
+ char *str, *p;
+ int num;
+
+ botnet = bot->botnet;
+ g_return_if_fail(botnet != NULL);
+
+ if (g_strcasecmp(data, "NICKERROR") == 0) {
+ /* nick already in use, change it by adding a number
+ at the end of it */
+ p = botnet->nick+strlen(botnet->nick);
+ while (p > botnet->nick && isdigit(p[-1])) p--;
+ num = *p == '\0' ? 2 : atoi(p)+1; *p = '\0';
+ str = g_strdup_printf("%s%d", botnet->nick, num);
+ g_free(botnet->nick); botnet->nick = str;
+
+ ownbot = botnet->bots->data;
+ g_free(ownbot->nick); ownbot->nick = g_strdup(str);
+
+ /* try again.. */
+ bot_send_cmdv(bot, "NICK %s", botnet->nick);
+
+ return;
+ }
+
+ if (g_strcasecmp(data, "CONNECTED") == 0) {
+ /* connected, wait for SYNC command */
+ bot->connected = TRUE;
+ return;
+ }
+
+ /* error? what? */
+}
+
+static void botnet_event(BOT_REC *bot, const char *data)
+{
+ BOT_DOWNLINK_REC *downlink;
+
+ g_return_if_fail(bot != NULL);
+ g_return_if_fail(data != NULL);
+
+ if (bot->connected)
+ return;
+
+ if (bot->uplink) {
+ botnet_connect_event_uplink(bot, data);
+ return;
+ }
+
+ downlink = bot->link;
+
+ if (!bot->pass_ok && g_strncasecmp(data, "PASS ", 5) == 0) {
+ /* password sent, check that it matches */
+ if (strcmp(data+5, downlink->password) == 0) {
+ /* ok, connected! */
+ bot->pass_ok = TRUE;
+ } else {
+ /* wrong password, disconnect */
+ bot_disconnect(bot);
+ }
+ return;
+ }
+
+ if (g_strncasecmp(data, "NICK ", 5) == 0) {
+ /* set bot's nick */
+ if (!bot->pass_ok) {
+ /* password has to be sent before nick, kill the
+ stupid bot. */
+ bot_disconnect(bot);
+ return;
+ }
+
+ if (g_strcasecmp(bot->botnet->nick, data+5) == 0 ||
+ bot_find_nick(bot->botnet, data+5) != NULL) {
+ /* nick already exists */
+ bot_send_cmd(bot, "NICKERROR");
+ return;
+ }
+
+ /* set the nick */
+ bot->nick = g_strdup(data+5);
+ bot->connected = TRUE;
+ bot_send_cmd(bot, "CONNECTED");
+
+ /* send info about all the bots that are connected now
+ to this botnet */
+ botnet_send_botinfo(bot->botnet->bots, bot);
+ botnet_send_links(bot, FALSE);
+ botnet_send_links(bot, TRUE);
+ bot_send_cmdv(bot, "%s - MASTER %s", bot->botnet->nick, bot->botnet->master->nick);
+ bot_send_cmdv(bot, "%s - SYNC", bot->botnet->nick);
+ return;
+ }
+
+ /* pass/nick not sent yet */
+ bot_send_cmd(bot, "ERROR");
+}
+
+static void botnet_event_sync(BOT_REC *bot)
+{
+ /* send our record to host */
+ botnet_send_botinfo(bot->botnet->bots, bot);
+
+ /* send our downlinks to host */
+ botnet_send_links(bot, TRUE);
+}
+
+static BOT_REC *bot_add(BOTNET_REC *botnet, const char *nick, const char *parent)
+{
+ GNode *node;
+ BOT_REC *rec;
+
+ g_return_val_if_fail(botnet != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ node = bot_find_nick(botnet, nick);
+ if (node != NULL) return node->data;
+
+ node = bot_find_nick(botnet, parent);
+ if (node == NULL) return NULL;
+
+ rec = g_new0(BOT_REC, 1);
+ rec->botnet = botnet;
+ rec->nick = g_strdup(nick);
+
+ rec->handle = -1;
+ rec->read_tag = -1;
+ rec->connected = TRUE;
+
+ g_node_append_data(node, rec);
+ return rec;
+}
+
+static void botnet_event_botinfo(BOT_REC *bot, const char *data, const char *sender)
+{
+ char *str, *params, *nick, *parent, *priority;
+ BOT_REC *rec;
+
+ str = g_strdup_printf("BOTINFO %s", data);
+ botnet_broadcast(bot->botnet, bot, sender, str);
+ g_free(str);
+
+ params = cmd_get_params(data, 3, &nick, &parent, &priority);
+ if (*parent == '-' && parent[1] == '\0')
+ parent = NULL;
+
+ if (parent == NULL && bot->botnet->uplink != NULL &&
+ bot->botnet->uplink == bot) {
+ /* our uplink */
+ if (bot->nick == NULL) bot->nick = g_strdup(nick);
+ rec = bot;
+ } else {
+ rec = bot_add(bot->botnet, nick, parent);
+ }
+
+ if (rec != NULL) {
+ rec->priority = atoi(priority);
+ }
+ g_free(params);
+}
+
+static void botnet_event_botquit(BOT_REC *bot, const char *data)
+{
+ GNode *node;
+
+ node = bot_find_nick(bot->botnet, data);
+ if (node != NULL) bot_destroy(node->data);
+}
+
+static void sig_bot_disconnected(BOT_REC *bot)
+{
+ BOT_REC *master, *tmpbot;
+ GNode *node;
+ char *str;
+
+ if (!bot->botnet->connected)
+ return;
+
+ if (bot->connected && bot->handle != -1) {
+ /* send notice to rest of the botnet about quit */
+ str = g_strdup_printf("BOTQUIT %s", bot->nick);
+ botnet_broadcast(bot->botnet, bot, NULL, str);
+ g_free(str);
+ }
+
+ if (bot->master) {
+ /* master quit */
+ node = bot_find_path(bot->botnet, bot->nick);
+ tmpbot = node == NULL ? NULL : node->data;
+
+ if (tmpbot != NULL && tmpbot->disconnect) {
+ /* we lost the connection to master - find new
+ master for the botnet*/
+ master = botnet_find_master(bot->botnet, NULL);
+ botnet_set_master(bot->botnet, master);
+
+ str = g_strdup_printf("MASTER %s", master->nick);
+ botnet_broadcast(bot->botnet, bot, NULL, str);
+ g_free(str);
+ }
+ }
+}
+
+static int print_bot(GNode *node)
+{
+ BOT_REC *bot = node->data;
+
+ fprintf(stderr, "%s %d %d\r\n", bot->nick, bot->connected, bot->disconnect);
+ return FALSE;
+}
+
+static void cmd_bots(void)
+{
+ BOTNET_REC *botnet = botnet_find("ircnet");
+
+ fprintf(stderr, "\r\n");
+ g_node_traverse(botnet->bots, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1,
+ (GNodeTraverseFunc) print_bot, NULL);
+}
+
+void botnet_connection_init(void)
+{
+ signal_add("botnet event", (SIGNAL_FUNC) botnet_event);
+ signal_add("botnet event sync", (SIGNAL_FUNC) botnet_event_sync);
+ signal_add("botnet event botinfo", (SIGNAL_FUNC) botnet_event_botinfo);
+ signal_add("botnet event botquit", (SIGNAL_FUNC) botnet_event_botquit);
+ signal_add("bot disconnected", (SIGNAL_FUNC) sig_bot_disconnected);
+ command_bind("bots", NULL, (SIGNAL_FUNC) cmd_bots);
+}
+
+void botnet_connection_deinit(void)
+{
+ signal_remove("botnet event", (SIGNAL_FUNC) botnet_event);
+ signal_remove("botnet event sync", (SIGNAL_FUNC) botnet_event_sync);
+ signal_remove("botnet event botinfo", (SIGNAL_FUNC) botnet_event_botinfo);
+ signal_remove("botnet event botquit", (SIGNAL_FUNC) botnet_event_botquit);
+ signal_remove("bot disconnected", (SIGNAL_FUNC) sig_bot_disconnected);
+ command_unbind("bots", (SIGNAL_FUNC) cmd_bots);
+}
diff --git a/src/irc/bot/botnet.c b/src/irc/bot/botnet.c
new file mode 100644
index 00000000..9331e1d4
--- /dev/null
+++ b/src/irc/bot/botnet.c
@@ -0,0 +1,622 @@
+/*
+ botnet.c : IRC bot plugin for 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 "net-nonblock.h"
+#include "signals.h"
+#include "commands.h"
+#include "misc.h"
+#include "line-split.h"
+#include "lib-config/iconfig.h"
+
+#include "botnet.h"
+
+void botnet_connection_init(void);
+void botnet_connection_deinit(void);
+
+static GSList *botnets;
+
+void bot_send_cmd(BOT_REC *bot, char *data)
+{
+ g_return_if_fail(bot != NULL);
+ g_return_if_fail(data != NULL);
+
+ net_transmit(bot->handle, data, strlen(data));
+ net_transmit(bot->handle, "\n", 1);
+}
+
+void bot_send_cmdv(BOT_REC *bot, char *format, ...)
+{
+ va_list args;
+ char *str;
+
+ va_start(args, format);
+
+ str = g_strdup_vprintf(format, args);
+ bot_send_cmd(bot, str);
+ g_free(str);
+
+ va_end(args);
+}
+
+/* broadcast a message to everyone in bot network, except for `except_bot'
+ if it's not NULL */
+void botnet_broadcast(BOTNET_REC *botnet, BOT_REC *except_bot,
+ const char *source, const char *data)
+{
+ GNode *node;
+ char *str;
+
+ g_return_if_fail(botnet != NULL);
+ g_return_if_fail(data != NULL);
+
+ str = g_strdup_printf("%s - %s", source != NULL ? source :
+ botnet->nick, data);
+ for (node = botnet->bots->children; node != NULL; node = node->next) {
+ BOT_REC *rec = node->data;
+
+ if (rec != except_bot && rec->handle != -1)
+ bot_send_cmd(rec, str);
+ }
+ g_free(str);
+}
+
+BOTNET_REC *botnet_find(const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = botnets; tmp != NULL; tmp = tmp->next) {
+ BOTNET_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+typedef struct {
+ gconstpointer key;
+ int priority;
+ GNode *node;
+} BOT_FIND_REC;
+
+static int gnode_find_nick(GNode *node, BOT_FIND_REC *rec)
+{
+ BOT_REC *bot = node->data;
+
+ if (bot == NULL) return FALSE;
+
+ if (bot->nick != NULL && g_strcasecmp(bot->nick, rec->key) == 0) {
+ rec->node = node;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+GNode *bot_find_nick(BOTNET_REC *botnet, const char *nick)
+{
+ BOT_FIND_REC rec;
+
+ g_return_val_if_fail(botnet != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec.key = nick;
+ rec.node = NULL;
+ g_node_traverse(botnet->bots, 0, G_TRAVERSE_ALL, -1,
+ (GNodeTraverseFunc) gnode_find_nick, &rec);
+ return rec.node;
+}
+
+/* Return the bot who we should send the message if we wanted `nick' to get it. */
+GNode *bot_find_path(BOTNET_REC *botnet, const char *nick)
+{
+ BOT_FIND_REC rec;
+ GNode *node;
+
+ g_return_val_if_fail(botnet != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec.key = nick;
+ rec.node = NULL;
+ for (node = botnet->bots->children; node != NULL; node = node->next) {
+ g_node_traverse(node, 0, G_TRAVERSE_ALL, -1,
+ (GNodeTraverseFunc) gnode_find_nick, &rec);
+ if (rec.node != NULL) return node;
+ }
+ return rec.node;
+}
+
+/* check if `addr' is an IP address - this is checked to make sure that
+ if we have an address like "192.168.0.*", it wouldn't match to host name
+ 192.168.0.host.org */
+static int is_ip_mask(const char *addr)
+{
+ while (*addr != '\0') {
+ if (!isdigit(*addr) && *addr != '.' &&
+ *addr != '*' && *addr != '?') return FALSE;
+ addr++;
+ }
+
+ return TRUE;
+}
+
+BOT_DOWNLINK_REC *bot_downlink_find(BOTNET_REC *botnet, IPADDR *ip, const char *host)
+{
+ GSList *tmp, *tmp2;
+ char ipname[MAX_IP_LEN];
+
+ g_return_val_if_fail(botnet != NULL, NULL);
+ g_return_val_if_fail(ip != NULL, NULL);
+
+ net_ip2host(ip, ipname);
+
+ for (tmp = botnet->downlinks; tmp != NULL; tmp = tmp->next) {
+ BOT_DOWNLINK_REC *rec = tmp->data;
+
+ for (tmp2 = rec->valid_addrs; tmp2 != NULL; tmp2 = tmp2->next) {
+ if (match_wildcards(tmp2->data, ipname))
+ return rec;
+ if (match_wildcards(tmp2->data, host) &&
+ !is_ip_mask(tmp2->data))
+ return rec;
+ }
+ }
+
+ return NULL;
+}
+
+static int gnode_find_master(GNode *node, BOT_FIND_REC *rec)
+{
+ BOT_REC *bot = node->data;
+
+ if (bot == NULL) return FALSE;
+
+ if (!bot->disconnect && bot->priority > rec->priority) {
+ rec->node = node;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOT_REC *botnet_find_master(BOTNET_REC *botnet, BOT_REC *old_master)
+{
+ BOT_FIND_REC rec;
+
+ g_return_val_if_fail(botnet != NULL, NULL);
+
+ rec.node = NULL;
+ rec.priority = old_master == NULL ? -1 : old_master->priority;
+ g_node_traverse(botnet->bots, 0, G_TRAVERSE_ALL, -1,
+ (GNodeTraverseFunc) gnode_find_master, &rec);
+ return rec.node == NULL ? old_master : rec.node->data;
+}
+
+void botnet_set_master(BOTNET_REC *botnet, BOT_REC *bot)
+{
+ g_return_if_fail(botnet != NULL);
+ g_return_if_fail(bot != NULL);
+
+ if (botnet->master != NULL)
+ botnet->master->master = FALSE;
+
+ bot->master = TRUE;
+ botnet->master = bot;
+}
+
+void bot_nick_destroy(BOT_CHANNEL_REC *rec, NICK_REC *nick)
+{
+ g_return_if_fail(rec != NULL);
+ g_return_if_fail(nick != NULL);
+
+ rec->nicks = g_slist_remove(rec->nicks, nick);
+
+ g_free(nick->nick);
+ g_free_not_null(nick->realname);
+ g_free_not_null(nick->host);
+ g_free(nick);
+}
+
+void bot_channel_destroy(BOT_IRCNET_REC *ircnet, BOT_CHANNEL_REC *rec)
+{
+ g_return_if_fail(ircnet != NULL);
+ g_return_if_fail(rec != NULL);
+
+ ircnet->channels = g_slist_remove(ircnet->channels, rec);
+
+ while (rec->nicks != NULL)
+ bot_nick_destroy(rec, rec->nicks->data);
+
+ g_slist_foreach(rec->banlist, (GFunc) g_free, NULL);
+ g_slist_foreach(rec->ebanlist, (GFunc) g_free, NULL);
+ g_slist_foreach(rec->invitelist, (GFunc) g_free, NULL);
+
+ g_slist_free(rec->banlist);
+ g_slist_free(rec->ebanlist);
+ g_slist_free(rec->invitelist);
+
+ g_free_not_null(rec->mode);
+ g_free_not_null(rec->key);
+ g_free(rec->name);
+ g_free(rec);
+}
+
+void bot_ircnet_destroy(BOT_REC *bot, BOT_IRCNET_REC *rec)
+{
+ g_return_if_fail(bot != NULL);
+ g_return_if_fail(rec != NULL);
+
+ bot->ircnets = g_slist_remove(bot->ircnets, bot);
+
+ while (rec->channels != NULL)
+ bot_channel_destroy(rec, rec->channels->data);
+
+ g_free(rec->tag);
+ g_free(rec->ircnet);
+ g_free(rec->server);
+ g_free(rec->nick);
+ g_free(rec);
+}
+
+void bot_disconnect(BOT_REC *bot)
+{
+ bot->disconnect = TRUE;
+
+ signal_emit("bot disconnected", 1, bot);
+
+ if (bot->read_tag != -1) {
+ g_source_remove(bot->read_tag);
+ bot->read_tag = -1;
+ }
+ if (bot->handle != -1) {
+ net_disconnect(bot->handle);
+ bot->handle = -1;
+ }
+}
+
+static void bot_mark_disconnect(GNode *node)
+{
+ BOT_REC *bot = node->data;
+
+ bot->disconnect = TRUE;
+}
+
+#define bot_mark_disconnects(node) \
+ g_node_traverse(node, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, \
+ (GNodeTraverseFunc) bot_mark_disconnect, NULL)
+
+void bot_destroy(BOT_REC *bot)
+{
+ GNode *node;
+
+ g_return_if_fail(bot != NULL);
+
+ node = g_node_find(bot->botnet->bots, 0, G_TRAVERSE_ALL, bot);
+ if (node != NULL) {
+ if (!bot->disconnect)
+ bot_mark_disconnects(node);
+ }
+
+ bot_disconnect(bot);
+
+ if (node != NULL) {
+ while (node->children != NULL)
+ bot_destroy(node->children->data);
+ g_node_destroy(node);
+ }
+
+ if (bot->botnet->uplink == bot)
+ bot->botnet->uplink = NULL;
+ if (bot->botnet->master == bot)
+ bot->botnet->master = NULL;
+
+ while (bot->ircnets != NULL)
+ bot_ircnet_destroy(bot, bot->ircnets->data);
+
+ line_split_free(bot->buffer);
+ g_free_not_null(bot->nick);
+ g_free(bot);
+}
+
+void bot_downlink_destroy(BOT_DOWNLINK_REC *rec)
+{
+ rec->botnet->downlinks = g_slist_remove(rec->botnet->downlinks, rec);
+
+ g_slist_foreach(rec->valid_addrs, (GFunc) g_free, NULL);
+ g_slist_free(rec->valid_addrs);
+
+ g_free_not_null(rec->password);
+ g_free(rec);
+}
+
+void bot_uplink_destroy(BOT_UPLINK_REC *rec)
+{
+ rec->botnet->uplinks = g_slist_remove(rec->botnet->uplinks, rec);
+
+ g_free(rec->host);
+ g_free_not_null(rec->password);
+ g_free(rec);
+}
+
+void botnet_disconnect(BOTNET_REC *botnet)
+{
+ botnet->connected = FALSE;
+
+ bot_destroy(botnet->bots->data);
+ botnet->bots = NULL;
+
+ if (botnet->listen_tag != -1) {
+ g_source_remove(botnet->listen_tag);
+ botnet->listen_tag = -1;
+ }
+ if (botnet->listen_handle != -1) {
+ net_disconnect(botnet->listen_handle);
+ botnet->listen_handle = -1;
+ }
+}
+
+static void botnet_destroy(BOTNET_REC *botnet)
+{
+ botnets = g_slist_remove(botnets, botnet);
+
+ while (botnet->uplinks != NULL)
+ bot_uplink_destroy(botnet->uplinks->data);
+ while (botnet->downlinks != NULL)
+ bot_downlink_destroy(botnet->downlinks->data);
+
+ botnet_disconnect(botnet);
+
+ g_free_not_null(botnet->addr);
+ g_free(botnet->name);
+ g_free(botnet->nick);
+ g_free(botnet);
+}
+
+static void botnet_event(BOT_REC *bot, const char *data)
+{
+ char *params, *source, *target, *command, *args, *event;
+
+ if (!bot->connected)
+ return;
+
+ params = cmd_get_params(data, 4 | PARAM_FLAG_GETREST,
+ &source, &target, &command, &args);
+
+ if (*target == '-' && target[1] == '\0')
+ target = NULL;
+ g_strdown(command);
+
+ event = g_strconcat("botnet event ", command, NULL);
+ signal_emit(event, 4, bot, args, source, target);
+ g_free(event);
+
+ g_free(params);
+}
+
+static void botnet_event_bcast(BOT_REC *bot, const char *data, const char *sender)
+{
+ char *str;
+
+ /* broadcast message to all bots */
+ str = g_strdup_printf("BCAST %s", data);
+ botnet_broadcast(bot->botnet, bot, sender, str);
+ g_free(str);
+}
+
+static void botnet_event_master(BOT_REC *bot, const char *data, const char *sender)
+{
+ BOTNET_REC *botnet;
+ BOT_REC *master;
+ GNode *node;
+ char *str;
+
+ botnet = bot->botnet;
+
+ node = bot_find_nick(bot->botnet, data);
+ master = node == NULL ? NULL : node->data;
+ master = botnet_find_master(bot->botnet, master);
+ g_return_if_fail(master != NULL);
+
+ if (node == NULL || node->data != master) {
+ /* no, we don't agree with that master -
+ send our own to everyone. */
+ bot = NULL;
+ }
+
+ botnet_set_master(botnet, master);
+
+ str = g_strdup_printf("MASTER %s", master->nick);
+ botnet_broadcast(botnet, bot, sender, str);
+ g_free(str);
+}
+
+static void botnet_config_read_ips(BOT_DOWNLINK_REC *rec, CONFIG_NODE *node)
+{
+ GSList *tmp;
+
+ g_return_if_fail(rec != NULL);
+ g_return_if_fail(node != NULL);
+
+ node = config_node_section(node, "valid_addrs", -1);
+ tmp = node == NULL ? NULL : node->value;
+ for (; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+ rec->valid_addrs = g_slist_append(rec->valid_addrs, g_strdup(node->value));
+ }
+}
+
+static void botnet_config_read_uplink(BOTNET_REC *botnet, CONFIG_NODE *node)
+{
+ BOT_UPLINK_REC *rec;
+ char *value;
+
+ g_return_if_fail(botnet != NULL);
+ g_return_if_fail(node != NULL);
+
+ value = config_node_get_str(node, "host", NULL);
+ if (value == NULL) return; /* host required */
+
+ rec = g_new0(BOT_UPLINK_REC, 1);
+ rec->botnet = botnet;
+ rec->host = g_strdup(value);
+ rec->port = config_node_get_int(node, "port", DEFAULT_BOTNET_PORT);
+ rec->password = g_strdup(config_node_get_str(node, "password", NULL));
+
+ botnet->uplinks = g_slist_append(botnet->uplinks, rec);
+}
+
+static void botnet_config_read_downlink(BOTNET_REC *botnet, CONFIG_NODE *node)
+{
+ BOT_DOWNLINK_REC *rec;
+
+ g_return_if_fail(botnet != NULL);
+ g_return_if_fail(node != NULL);
+
+ rec = g_new0(BOT_DOWNLINK_REC, 1);
+
+ botnet_config_read_ips(rec, node);
+ if (rec->valid_addrs == NULL) {
+ g_free(rec);
+ return;
+ }
+
+ rec->botnet = botnet;
+ rec->password = g_strdup(config_node_get_str(node, "password", NULL));
+ botnet->downlinks = g_slist_append(botnet->downlinks, rec);
+}
+
+static void botnet_config_read_botnet(CONFIG_NODE *node)
+{
+ CONFIG_NODE *subnode;
+ BOTNET_REC *botnet;
+ GSList *tmp;
+
+ g_return_if_fail(node != NULL);
+
+ if (node->key == NULL || node->value == NULL)
+ return;
+
+ /* New botnet */
+ botnet = g_new0(BOTNET_REC, 1);
+ botnet->name = g_strdup(node->key);
+ botnet->nick = g_strdup(config_node_get_str(node, "nick", "bot"));
+ botnet->priority = config_node_get_int(node, "priority", DEFAULT_BOTNET_PRIORITY);
+ botnet->autoconnect = config_node_get_bool(node, "autoconnect", FALSE);
+
+ botnet->addr = g_strdup(config_node_get_str(node, "listen_addr", NULL));
+ botnet->port = config_node_get_int(node, "listen_port", DEFAULT_BOTNET_PORT);
+
+ botnet->listen_handle = -1;
+ botnet->listen_tag = -1;
+
+ /* read uplinks */
+ subnode = config_node_section(node, "uplinks", -1);
+ tmp = subnode == NULL ? NULL : subnode->value;
+ for (; tmp != NULL; tmp = tmp->next)
+ botnet_config_read_uplink(botnet, tmp->data);
+
+ /* read downlinks */
+ subnode = config_node_section(node, "downlinks", -1);
+ tmp = subnode == NULL ? NULL : subnode->value;
+ for (; tmp != NULL; tmp = tmp->next)
+ botnet_config_read_downlink(botnet, tmp->data);
+
+ botnets = g_slist_append(botnets, botnet);
+}
+
+static void botnet_config_read(void)
+{
+ CONFIG_REC *config;
+ CONFIG_NODE *node;
+ GSList *tmp;
+ char *fname;
+
+ /* Read botnets from ~/.irssi/botnets */
+ fname = g_strdup_printf("%s/.irssi/botnets", g_get_home_dir());
+ config = config_open(fname, -1);
+ g_free(fname);
+
+ if (config == NULL)
+ return;
+
+ config_parse(config);
+
+ node = config_node_traverse(config, "botnets", FALSE);
+ tmp = node == NULL ? NULL : node->value;
+ for (; tmp != NULL; tmp = tmp->next)
+ botnet_config_read_botnet(tmp->data);
+ config_close(config);
+}
+
+/* FIXME: this command is just temporary */
+static void cmd_botnet(const char *data)
+{
+ BOTNET_REC *botnet;
+ char *str;
+
+ botnet = botnets->data;
+
+ str = g_strdup_printf("BCAST %s", data);
+ botnet_broadcast(botnet, NULL, NULL, str);
+ g_free(str);
+}
+
+static void autoconnect_botnets(void)
+{
+ GSList *tmp;
+
+ for (tmp = botnets; tmp != NULL; tmp = tmp->next) {
+ BOTNET_REC *rec = tmp->data;
+
+ if (rec->autoconnect)
+ botnet_connect(rec->name);
+ }
+}
+
+void botnet_init(void)
+{
+ botnet_config_read();
+ botnet_connection_init();
+
+ signal_add("botnet event", (SIGNAL_FUNC) botnet_event);
+ signal_add("botnet event bcast", (SIGNAL_FUNC) botnet_event_bcast);
+ signal_add("botnet event master", (SIGNAL_FUNC) botnet_event_master);
+ command_bind("botnet", NULL, (SIGNAL_FUNC) cmd_botnet);
+
+ autoconnect_botnets();
+}
+
+void botnet_deinit(void)
+{
+ while (botnets)
+ botnet_destroy(botnets->data);
+
+ botnet_connection_deinit();
+
+ signal_remove("botnet event", (SIGNAL_FUNC) botnet_event);
+ signal_remove("botnet event bcast", (SIGNAL_FUNC) botnet_event_bcast);
+ signal_remove("botnet event master", (SIGNAL_FUNC) botnet_event_master);
+ command_unbind("botnet", (SIGNAL_FUNC) cmd_botnet);
+}
diff --git a/src/irc/bot/botnet.h b/src/irc/bot/botnet.h
new file mode 100644
index 00000000..309d6745
--- /dev/null
+++ b/src/irc/bot/botnet.h
@@ -0,0 +1,124 @@
+#ifndef __BOT_BOTNET_H
+#define __BOT_BOTNET_H
+
+#include "nicklist.h"
+
+#define DEFAULT_BOTNET_PORT 2255
+#define DEFAULT_BOTNET_PRIORITY 5
+
+typedef struct _botnet_rec BOTNET_REC;
+
+typedef struct {
+ char *name;
+ GSList *nicks; /* NICK_RECs */
+ int chanop:1;
+
+ GSList *banlist;
+ GSList *ebanlist;
+ GSList *invitelist;
+
+ char *mode;
+ int limit;
+ char *key;
+} BOT_CHANNEL_REC;
+
+typedef struct {
+ char *tag; /* same as server->tag */
+ char *ircnet;
+ char *server;
+ char *nick;
+
+ GSList *channels;
+} BOT_IRCNET_REC;
+
+typedef struct {
+ BOTNET_REC *botnet;
+ void *link; /* NULL, BOT_UPLINK_REC or BOT_DOWNLINK_REC */
+
+ int uplink:1; /* this is our uplink */
+ int pass_ok:1; /* downlink's password was ok */
+ int connected:1; /* bot is in this botnet now */
+ int disconnect:1; /* just disconnecting this bot.. */
+ int master:1; /* this bot is the bot network's current master */
+
+ char *nick; /* bot's unique nick in botnet */
+ int priority;
+
+ int handle;
+ int read_tag;
+ void *buffer;
+
+ GSList *ircnets;
+} BOT_REC;
+
+typedef struct {
+ BOTNET_REC *botnet;
+
+ char *host;
+ int port;
+ char *password;
+
+ time_t last_connect;
+} BOT_UPLINK_REC;
+
+typedef struct {
+ BOTNET_REC *botnet;
+
+ GSList *valid_addrs; /* IP/host masks where this bot is allowed to connect */
+ char *password;
+} BOT_DOWNLINK_REC;
+
+struct _botnet_rec {
+ int connected:1;
+ int autoconnect:1;
+
+ char *name; /* botnet name */
+ char *nick; /* our nick in botnet */
+ int priority; /* our priority in botnet */
+
+ char *addr; /* in what address we should listen, NULL = all */
+ int port; /* what port we should listen, 0 = default, -1 = don't listen */
+
+ int listen_handle;
+ int listen_tag;
+
+ GSList *uplinks;
+ GSList *downlinks;
+
+ GNode *bots;
+ BOT_REC *uplink; /* our current uplink */
+ BOT_REC *master; /* link to current master */
+};
+
+void bot_send_cmd(BOT_REC *bot, char *data);
+void bot_send_cmdv(BOT_REC *bot, char *format, ...);
+
+/* broadcast a message to everyone in bot network, except for `except_bot'
+ if it's not NULL */
+void botnet_broadcast(BOTNET_REC *botnet, BOT_REC *except_bot,
+ const char *source, const char *data);
+
+BOT_REC *botnet_find_master(BOTNET_REC *botnet, BOT_REC *old_master);
+void botnet_set_master(BOTNET_REC *botnet, BOT_REC *bot);
+
+BOTNET_REC *botnet_find(const char *name);
+GNode *bot_find_nick(BOTNET_REC *botnet, const char *nick);
+/* Return the bot who we should send the message if we wanted `nick' to get it. */
+GNode *bot_find_path(BOTNET_REC *botnet, const char *nick);
+
+BOT_DOWNLINK_REC *bot_downlink_find(BOTNET_REC *botnet, IPADDR *ip, const char *host);
+
+void bot_nick_destroy(BOT_CHANNEL_REC *rec, NICK_REC *nick);
+void bot_channel_destroy(BOT_IRCNET_REC *ircnet, BOT_CHANNEL_REC *rec);
+void bot_ircnet_destroy(BOT_REC *bot, BOT_IRCNET_REC *rec);
+
+void bot_disconnect(BOT_REC *bot);
+void bot_destroy(BOT_REC *bot);
+
+void bot_downlink_destroy(BOT_DOWNLINK_REC *rec);
+void bot_uplink_destroy(BOT_UPLINK_REC *rec);
+
+int botnet_connect(const char *network);
+void botnet_disconnect(BOTNET_REC *botnet);
+
+#endif
diff --git a/src/irc/bot/botnets.sample b/src/irc/bot/botnets.sample
new file mode 100644
index 00000000..01ce9684
--- /dev/null
+++ b/src/irc/bot/botnets.sample
@@ -0,0 +1,15 @@
+botnets = {
+ irssinet = {
+ nick = irssibot;
+ priority = 5;
+ autoconnect = yes;
+ uplinks = (
+ { host = "main.botnet.org"; password = "mypass"; }
+ );
+ downlinks = (
+ { password = "thepass"; valid_addrs = ( "192.168.0.*" ); },
+ { password = "blah"; valid_addrs = ( "*.botnet.org" ); },
+ { password = "localpass"; valid_addrs = ( "127.*" ); }
+ );
+ };
+};
diff --git a/src/irc/bot/module.h b/src/irc/bot/module.h
new file mode 100644
index 00000000..3ae857fa
--- /dev/null
+++ b/src/irc/bot/module.h
@@ -0,0 +1,3 @@
+#include "common.h"
+
+#define MODULE_NAME "irc/bot"
diff --git a/src/irc/bot/users.sample b/src/irc/bot/users.sample
new file mode 100644
index 00000000..72e534a8
--- /dev/null
+++ b/src/irc/bot/users.sample
@@ -0,0 +1,18 @@
+users =
+{
+ mynick = {
+ flags = oa;
+ masks = (
+ { mask="*!*@somewhere" },
+ { mask="*!*@somewhere.else"; not_flags=a; }
+ );
+ };
+
+ other = {
+ masks = ( { mask="*!nick@home.org"; } );
+ channels = (
+ { channel = "#irssi";flags = oa; },
+ { channel = "#chan";flags = oa; }
+ );
+ };
+};
diff --git a/src/irc/core/Makefile.am b/src/irc/core/Makefile.am
index fa5f5f1f..aa0b87ea 100644
--- a/src/irc/core/Makefile.am
+++ b/src/irc/core/Makefile.am
@@ -41,7 +41,6 @@ noinst_HEADERS = \
channels-setup.h \
ignore.h \
irc.h \
- irc-core.h \
irc-server.h \
ircnet-setup.h \
masks.h \
diff --git a/src/irc/core/irc-commands.c b/src/irc/core/irc-commands.c
index 2e4bfa53..3405ef99 100644
--- a/src/irc/core/irc-commands.c
+++ b/src/irc/core/irc-commands.c
@@ -70,7 +70,7 @@ static IRC_SERVER_REC *connect_server(const char *data)
if (*host != '\0') {
IPADDR ip;
- if (net_gethostname(host, &ip) == 0) {
+ if (net_gethostbyname(host, &ip) == 0) {
if (conn->own_ip == NULL)
conn->own_ip = g_new(IPADDR, 1);
memcpy(conn->own_ip, &ip, sizeof(IPADDR));
diff --git a/src/irc/core/irc-core.h b/src/irc/core/irc-core.h
deleted file mode 100644
index 31b1fc26..00000000
--- a/src/irc/core/irc-core.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#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/modes.c b/src/irc/core/modes.c
index de4939cf..ba3d9a76 100644
--- a/src/irc/core/modes.c
+++ b/src/irc/core/modes.c
@@ -23,6 +23,7 @@
#include "signals.h"
#include "irc.h"
+#include "modes.h"
#include "mode-lists.h"
#include "nicklist.h"
@@ -322,9 +323,6 @@ void channel_set_singlemode(IRC_SERVER_REC *server, const char *channel, const c
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, *orig;
@@ -343,7 +341,7 @@ void channel_set_mode(IRC_SERVER_REC *server, const char *channel, const char *m
curmode = cmd_get_param(&modestr);
for (; *curmode != '\0'; curmode++) {
- if (count == server->connrec->max_modes && MODE_HAS_ARG(*curmode)) {
+ if (count == server->connrec->max_modes && HAS_MODE_ARG(*curmode)) {
irc_send_cmdv(server, "MODE %s %s%s", channel, tmode->str, targs->str);
count = 0;
@@ -353,7 +351,7 @@ void channel_set_mode(IRC_SERVER_REC *server, const char *channel, const char *m
g_string_append_c(tmode, *curmode);
- if (MODE_HAS_ARG(*curmode)) {
+ if (HAS_MODE_ARG(*curmode)) {
char *arg;
count++;
diff --git a/src/irc/core/modes.h b/src/irc/core/modes.h
index 10ea28d5..d72e6a3c 100644
--- a/src/irc/core/modes.h
+++ b/src/irc/core/modes.h
@@ -4,6 +4,9 @@
#include "server.h"
#include "channels.h"
+#define HAS_MODE_ARG(c) ((c) == 'b' || (c) == 'e' || (c) == 'I' || \
+ (c) == 'v' || (c) == 'o' || (c) == 'l' || (c) == 'k')
+
void modes_init(void);
void modes_deinit(void);
diff --git a/src/irc/core/nicklist.h b/src/irc/core/nicklist.h
index b5250d18..27fde067 100644
--- a/src/irc/core/nicklist.h
+++ b/src/irc/core/nicklist.h
@@ -1,6 +1,7 @@
#ifndef __NICKLIST_H
#define __NICKLIST_H
+#include "irc-server.h"
#include "channels.h"
typedef struct {
diff --git a/src/irc/core/server-setup.c b/src/irc/core/server-setup.c
index 91bafd81..1a8e9d7b 100644
--- a/src/irc/core/server-setup.c
+++ b/src/irc/core/server-setup.c
@@ -41,7 +41,7 @@ static void get_source_host_ip(void)
/* FIXME: This will block! */
if (!source_host_ok) {
source_host_ok = *settings_get_str("hostname") != '\0' &&
- net_gethostname(settings_get_str("hostname"), &ip) == 0;
+ net_gethostbyname(settings_get_str("hostname"), &ip) == 0;
if (source_host_ok) {
source_host_ip = g_new(IPADDR, 1);
memcpy(source_host_ip, &ip, sizeof(IPADDR));
@@ -100,7 +100,7 @@ create_addr_conn(const char *address, int port, const char *password,
/* resolve the IP and use it */
IPADDR ip;
- if (net_gethostname(sserver->own_host, &ip) == 0) {
+ if (net_gethostbyname(sserver->own_host, &ip) == 0) {
if (conn->own_ip == NULL)
conn->own_ip = g_new(IPADDR, 1);
memcpy(conn->own_ip, &ip, sizeof(IPADDR));
diff --git a/src/irc/dcc/dcc.c b/src/irc/dcc/dcc.c
index 1c02e65a..5bfc417f 100644
--- a/src/irc/dcc/dcc.c
+++ b/src/irc/dcc/dcc.c
@@ -508,7 +508,7 @@ static void event_no_such_nick(gchar *data, IRC_SERVER_REC *server)
g_free(params);
}
-void dcc_init(void)
+void irc_dcc_init(void)
{
dcc_conns = NULL;
dcc_timeouttag = g_timeout_add(1000, (GSourceFunc) dcc_timeout_func, NULL);
@@ -542,7 +542,7 @@ void dcc_init(void)
dcc_files_init();
}
-void dcc_deinit(void)
+void irc_dcc_deinit(void)
{
dcc_chat_deinit();
dcc_files_deinit();
diff --git a/src/irc/flood/flood.c b/src/irc/flood/flood.c
index b9617157..3e66afa6 100644
--- a/src/irc/flood/flood.c
+++ b/src/irc/flood/flood.c
@@ -258,7 +258,7 @@ static void read_settings(void)
}
}
-void flood_init(void)
+void irc_flood_init(void)
{
settings_add_int("flood", "flood_timecheck", 5000);
settings_add_int("flood", "flood_max_msgs", 4);
@@ -272,7 +272,7 @@ void flood_init(void)
autoignore_init();
}
-void flood_deinit(void)
+void irc_flood_deinit(void)
{
autoignore_deinit();
diff --git a/src/irc/irc.c b/src/irc/irc.c
deleted file mode 100644
index 609e239b..00000000
--- a/src/irc/irc.c
+++ /dev/null
@@ -1,27 +0,0 @@
-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/notifylist.c b/src/irc/notifylist/notifylist.c
index 17adb3da..6b46ff3d 100644
--- a/src/irc/notifylist/notifylist.c
+++ b/src/irc/notifylist/notifylist.c
@@ -322,7 +322,7 @@ static void sig_channel_wholist(CHANNEL_REC *channel)
g_slist_free(nicks);
}
-void notifylist_init(void)
+void irc_notifylist_init(void)
{
notifylist_read_config();
@@ -338,7 +338,7 @@ void notifylist_init(void)
signal_add("setup reread", (SIGNAL_FUNC) notifylist_read_config);
}
-void notifylist_deinit(void)
+void irc_notifylist_deinit(void)
{
notifylist_commands_deinit();
notifylist_ison_deinit();