/* 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" #include "bot-users.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 == NULL ? -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, GIOChannel *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; GIOChannel *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; GIOChannel *handle; g_return_if_fail(botnet != NULL); /* accept connection */ handle = net_accept(botnet->listen_handle, &ip, NULL); if (handle == NULL) 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 == NULL) { 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(GIOChannel *handle, BOT_UPLINK_REC *uplink) { BOTNET_REC *botnet; BOT_REC *bot; g_return_if_fail(uplink != NULL); botnet = uplink->botnet; if (handle == NULL) { /* 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->read_tag = -1; botnet->connected = TRUE; botnet->master = bot; botnet->bots = g_node_new(bot); } if (botnet->listen_handle == NULL) { /* start listening */ botnet_listen(botnet); } if (botnet->uplinks == NULL) { /* we have no uplinks */ return; } /* 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; return; } /* 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; char *fname; g_return_if_fail(bot != NULL); g_return_if_fail(data != NULL); if (bot->connected) return; signal_stop_by_name("botnet event"); 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); /* send our current user configuration */ fname = g_strdup_printf("%s/.irssi/users.temp", g_get_home_dir()); botuser_save(fname); botnet_send_file(bot->botnet, bot->nick, fname); g_free(fname); /* send sync msg */ 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); signal_stop_by_name("botnet event"); } 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->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 *nick, *parent, *priority; void *free_arg; BOT_REC *rec; /*str = g_strdup_printf("BOTINFO %s", data); botnet_broadcast(bot->botnet, bot, sender, str); g_free(str);*/ if (!cmd_get_params(data, &free_arg, 3, &nick, &parent, &priority)) return; 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); } cmd_params_free(free_arg); } 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); signal_stop_by_name("botnet event"); } 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 != NULL) { /* 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); }