mirror of
https://github.com/irssi/irssi.git
synced 2026-08-09 03:40:16 +02:00
try again. git-svn-id: http://svn.irssi.org/repos/irssi/trunk@240 dbcabf3a-b0e7-0310-adc4-f8d773084564
570 lines
14 KiB
C
570 lines
14 KiB
C
/*
|
|
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 int reconnect_tag;
|
|
|
|
static int sig_reconnect(void)
|
|
{
|
|
GSList *tmp;
|
|
|
|
for (tmp = botnets; tmp != NULL; tmp = tmp->next) {
|
|
BOTNET_REC *rec = tmp->data;
|
|
|
|
if (rec->reconnect)
|
|
botnet_connect(rec);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
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);
|
|
}
|
|
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);
|
|
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);
|
|
}
|
|
|
|
void botnet_connect(BOTNET_REC *botnet)
|
|
{
|
|
BOT_REC *bot;
|
|
BOT_UPLINK_REC *uplink, *best;
|
|
GSList *tmp;
|
|
time_t now;
|
|
|
|
g_return_if_fail(botnet != NULL);
|
|
|
|
botnet->reconnect = 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) {
|
|
/* reconnect later */
|
|
botnet->reconnect = TRUE;
|
|
}
|
|
|
|
/* connect to uplink */
|
|
best->last_connect = time(NULL);
|
|
net_connect_nonblock(best->host, best->port, NULL, (NET_CALLBACK) sig_botnet_connected, best);
|
|
}
|
|
|
|
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)
|
|
{
|
|
reconnect_tag = g_timeout_add(BOTNET_RECONNECT_TIME*1000, (GSourceFunc) sig_reconnect, NULL);
|
|
|
|
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)
|
|
{
|
|
g_source_remove(reconnect_tag);
|
|
|
|
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);
|
|
}
|