From 76605ad0aed7e53c4a9dab686474235f547a5837 Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Thu, 25 May 2000 11:30:47 +0000 Subject: [PATCH] Added bot plugin, it also has almost-functional botnet. Changed configure.in's functionality so that you could tell what modules you want to build in main irssi binary and it will create automatically the .c files that need to call the module_init()/deinit() functions. Fixed several minor things.. git-svn-id: http://svn.irssi.org/repos/irssi/trunk@230 dbcabf3a-b0e7-0310-adc4-f8d773084564 --- configure.in | 24 +- docs/botnet.txt | 393 ++++++++---- src/core/misc.c | 2 +- src/core/net-nonblock.c | 18 +- src/core/net-nonblock.h | 14 +- src/core/network.c | 83 ++- src/core/network.h | 5 +- src/core/server.c | 6 +- src/fe-common/irc/Makefile.am | 1 + src/fe-common/irc/dcc/fe-dcc.c | 4 +- src/fe-common/irc/fe-common-irc.c | 22 +- src/fe-common/irc/flood/fe-flood.c | 4 +- src/fe-common/irc/notifylist/fe-notifylist.c | 4 +- src/fe-none/irssi.c | 1 - src/fe-text/irssi.c | 1 - src/irc/Makefile.am | 2 +- src/irc/bot/.cvsignore | 8 + src/irc/bot/Makefile.am | 24 + src/irc/bot/bot-commands.c | 165 +++++ src/irc/bot/bot-events.c | 199 ++++++ src/irc/bot/bot-users.c | 503 +++++++++++++++ src/irc/bot/bot-users.h | 47 ++ src/irc/bot/bot.c | 49 ++ src/irc/bot/bot.h | 26 + src/irc/bot/botnet-connection.c | 553 +++++++++++++++++ src/irc/bot/botnet.c | 622 +++++++++++++++++++ src/irc/bot/botnet.h | 124 ++++ src/irc/bot/botnets.sample | 15 + src/irc/bot/module.h | 3 + src/irc/bot/users.sample | 18 + src/irc/core/Makefile.am | 1 - src/irc/core/irc-commands.c | 2 +- src/irc/core/irc-core.h | 7 - src/irc/core/modes.c | 8 +- src/irc/core/modes.h | 3 + src/irc/core/nicklist.h | 1 + src/irc/core/server-setup.c | 4 +- src/irc/dcc/dcc.c | 4 +- src/irc/flood/flood.c | 4 +- src/irc/irc.c | 27 - src/irc/notifylist/notifylist.c | 4 +- 41 files changed, 2784 insertions(+), 221 deletions(-) create mode 100644 src/irc/bot/.cvsignore create mode 100644 src/irc/bot/Makefile.am create mode 100644 src/irc/bot/bot-commands.c create mode 100644 src/irc/bot/bot-events.c create mode 100644 src/irc/bot/bot-users.c create mode 100644 src/irc/bot/bot-users.h create mode 100644 src/irc/bot/bot.c create mode 100644 src/irc/bot/bot.h create mode 100644 src/irc/bot/botnet-connection.c create mode 100644 src/irc/bot/botnet.c create mode 100644 src/irc/bot/botnet.h create mode 100644 src/irc/bot/botnets.sample create mode 100644 src/irc/bot/module.h create mode 100644 src/irc/bot/users.sample delete mode 100644 src/irc/core/irc-core.h delete mode 100644 src/irc/irc.c diff --git a/configure.in b/configure.in index 42a022ef..baf20e95 100644 --- a/configure.in +++ b/configure.in @@ -296,8 +296,29 @@ for c in $CHAT_MODULES; do FE_COMMON_LIBS="$FE_COMMON_LIBS ../fe-common/$c/libfe_common_$c.la" for s in `eval echo \\$${c}_MODULES`; do CHAT_LIBS="$CHAT_LIBS ../$c/$s/lib${c}_$s.la" - FE_COMMON_LIBS="$FE_COMMON_LIBS ../fe-common/$c/$s/libfe_common_${c}_$s.la" + module_inits="$module_inits ${c}_${s}_init();" + module_deinits="${c}_${s}_deinit(); $module_deinits" + if test -d $srcdir/src/fe-common/$c/$s; then + FE_COMMON_LIBS="$FE_COMMON_LIBS ../fe-common/$c/$s/libfe_common_${c}_$s.la" + fe_module_inits="$fe_module_inits fe_${c}_${s}_init();" + fe_module_deinits="fe_${c}_${s}_deinit(); $fe_module_deinits" + fi done + + file="$srcdir/src/$c/$c.c" + echo "/* this file is automatically generated by configure - don't change */" > $file + echo "void ${c}_core_init(void); void ${c}_core_deinit(void);" >> $file + echo "$module_inits" | $sedpath -e 's/()/(void)/g' -e 's/ /void /g' >> $file + echo "$module_deinits" | $sedpath -e 's/[ ]*$//' -e 's/()/(void)/g' -e 's/ /void /g' -e 's/^/void /' >> $file + echo "void ${c}_init(void) { ${c}_core_init(); $module_inits }" >> $file + echo "void ${c}_deinit(void) { $module_deinits ${c}_core_deinit(); }" >> $file + + file="$srcdir/src/fe-common/$c/${c}-modules.c" + echo "/* this file is automatically generated by configure - don't change */" > $file + echo "$fe_module_inits" | $sedpath -e 's/()/(void)/g' -e 's/ /void /g' >> $file + echo "$fe_module_deinits" | $sedpath -e 's/[ ]*$//' -e 's/()/(void)/g' -e 's/ /void /g' -e 's/^/void /' >> $file + echo "void fe_${c}_modules_init(void) { $fe_module_inits }" >> $file + echo "void fe_${c}_modules_deinit(void) { $fe_module_deinits }" >> $file done dnl ** common libraries needed by frontends @@ -338,6 +359,7 @@ src/Makefile src/core/Makefile src/irc/Makefile src/irc/core/Makefile +src/irc/bot/Makefile src/irc/dcc/Makefile src/irc/notifylist/Makefile src/irc/flood/Makefile diff --git a/docs/botnet.txt b/docs/botnet.txt index 963ddcfd..8b5f9ba7 100644 --- a/docs/botnet.txt +++ b/docs/botnet.txt @@ -1,163 +1,316 @@ -HISTORY + Irssi's botnet description + + Copyright (c) 1999-2000 Timo Sirainen + + + 0. History draft v0.1 : 21.8.1999 - Just a first draft of my botnet design I did on a boring friday - work afternoon :) I'll try to implement this to irssi some day, it - feels pretty interesting now so it might be pretty soon even. Any - comments are welcome :) + Just a first draft of my botnet design I did on a boring friday + work afternoon :) I'll try to implement this to irssi some day, it + feels pretty interesting now so it might be pretty soon even. Any + comments are welcome :) draft v0.2 : 21.11.1999 - Exactly three months since the first draft :) Now I actually have - some code done, just committed pretty simple botnet to irssi CVS. - Made several changes to this document.. Still missing much details - but the basic idea should be clear. + Exactly three months since the first draft :) Now I actually have + some code done, just committed pretty simple botnet to irssi CVS. + Made several changes to this document.. Still missing much details + but the basic idea should be clear. ------- + draft v0.3 : 21.05.2000 -A small description of what botnet would do: A group of bots -efficiently working together to perform their tasks. Like when -someone's trying to take over your channel, bots will quickly decide -who deops/kicks whom instead of multiple bots deopping or trying to -kick the same people.. + Strange, again the same day. I really didn't plan this :) + Reformatted the text, added lots of text, implemented more of the + stuff. -config file: -mybotnet = -{ - priority=n; - nick=mybot; - bots= - ( - { host="another.org"; port=5567; password="blah"; - valid_addrs=("*.another.org"); }, - { host="blah.ircnetthing.net"; password="blah"; - valid_addrs=("*.ircnetthing.net", "*.blah.org"); }, - { host="some.thing.net"; password="blah"; - valid_addrs=("some.thing.net"); } - ); -} + 1. General -When connecting to botnet, it first tries to connect to the first bot -in bots list, then the second, etc. Setting port to 0 will prevent -connecting to the bot. + 1.1 Description -Login: + A small description of what botnet would do: A group of bots + efficiently working together to perform their tasks. Like when + someone's trying to take over your channel, bots will quickly + decide who deops/kicks whom instead of multiple bots deopping or + trying to kick the same people. -First host checks what client is connecting from bots' valid_addrs. If -there's no matches it just disconnects the client. + Irssi's botnet is pretty much based on trust. Some malicious bot + can quite well mess up the whole botnet. Connecting the bots to + each other via ssh would be a good idea. -CLIENT: PASS blah -HOST : (if error, disconnect) -CLIENT: NICK nick -HOST : (if nick already in use) NICKERROR -CLIENT: PRIORITY=n -HOST : MASTER nick -HOST : CONNECTED + 1.2 Configuration -Then host sends a list of all connected bots in botnet: - BOTINFO nick connected-to-nick address priority + example config file: -Now we're connected to botnet, rest of the commands will be send to -everyone. Commands are the following format (I won't write the nick -from now on): + mybotnet = + { + priority=5; + nick=mybot; + uplinks = ( + { host = "main.botnet.org"; password = "mypass"; }, + { host = "alter.botnet.org"; password = "pass2"; } + ); + downlinks = ( + { password = "thepass"; valid_addrs = ( "192.168.0.*" ); }, + { password = "blah"; valid_addrs = ( "*.botnet.org" ); }, + { password = "localpass"; valid_addrs = ( "127.*" ); } + ); + } -nick COMMAND [command specific data..] + When connecting to botnet, the bot first tries to connect to the + first bot in uplinks list, then the second, etc. Setting port to -1 + will prevent connecting to the bot, 0 uses the default. -After connection is established with the client, host sends (except to -the connected client): - BOTNEW client_nick client_address client_priority + 1.3 Botnet master -Master is the client with the highest priority, if there's multiple -with the same priority, the one who's nick is the first in alphabet -"wins" and says: + To avoid total chaos inside botnet, the bots shouldn't do (almost) + anything without a command from botnet's master. The master should + know everything, and give commands to clients that can perform the + task best. - MASTER + Master is the bot with the highest priority. If there's multiple + with the same priority, the one that already was the master will + stay master. When joining two botnets to one, the uplink's master + stays. If link to master breaks, the closest bot to it will choose + a new one. -Also after connecting, client could check if it's priority is bigger than -current master's and make itself the master. + The priorities should be given so that the bots that have the + fastest connections and are in the middle of the botnet have the + highest priorities. -Bots should every now and then check if their connections are active by -sending PING, the other side replies with PONG. If PONG isn't received -for a while (3 min?), the connection should be closed. If there's more -bots behind the lost bot, either side should try to reconnect to the -other one. Also if there's too much lag (>30sec) for some bots, their -priority could be temporarily lowered. When something urgent happens -and there's a lot of lag, each subset of bots should try to behave -independently. + 1.4 Command format -When connection is closed to some bot, a notice is sent: - BOTQUIT nick + Commands that are sent inside botnet are in the following format: -After bot is (dis)connected to some irc network and is ready to take -commands, it sends notice: - BOTCONNECT ircnet (where ircnet is network's name, like IRCNet, EFNet, ..) - BOTDISCONNECT ircnet + COMMAND [command specific data..] -After joining/leaving channels, bot sends notice: - BOTJOIN ircnet #channel - BOTPART ircnet #channel + If to_nick is '-', the command should be sent to everyone. -After BOTJOIN, master tries to op the bot. When bot receives +o, it replies: - BOTOP ircnet #channel -If it's the first opped bot in channel, master orders the bot to op the rest -of the bots. + 2. Handshake -Or after kicked or when being unable to join..: - BOTKICK ircnet #channel - BOTBANNED ircnet #channel - BOTCANTJOIN ircnet #channel + First host checks from bots' valid_addrs who is connecting. If + there's no matches it just disconnects the client. -When master notices that bot is kicked, it first checks if there's any other -opped bots in channel. If not, it waits for a random pause, 5-10sec before -joining (so it won't get autorejoin ban). When received BOTBANNED, master -tries to unban bot, BOTCANTJOIN results as invite to channel. + CLIENT: PASS + HOST : (if error, disconnect) -When master notices that bot is the first one joined to channel, it asks bot -for channel information: + CLIENT: NICK + HOST : NICKERROR | CONNECTED - BOTCMD nick ircnet NAMES #channel - BOTCMD nick ircnet WHO #channel - BOTCMD nick ircnet MODE #channel - BOTCMD nick ircnet MODE b #channel - BOTCMD nick ircnet MODE e #channel - BOTCMD nick ircnet MODE I #channel + If nick is already in use, the host sends NICKERROR and waits for + new nick. -Next command is sent right after getting answer from the last query. It's also -possible that if several bots join immediately after the first bot, the -commands are shared between all the bots. After getting the results, the bot -replies: + Now we're connected to botnet. The commands from now on use the + format specified in section 1.4. - BOTREPLY ircnet + Both the client and the host sends information to other side of + all the clients they serve (if any): -Bots should cache the information as much as possible, at least NAMES command. + BOTINFO -Every channel has a priority: LOW, NORMAL, HIGH. + BOTINFOs must be sent sorted so that connected_to_nick bot is + always known. Like first comes the bots connected to the + host/client, then the bots connected to them etc. -Normally LOW operates just as NORMAL channels, except when some channel -has HIGH priority and bots are really busy, LOW channels just wait -until there's time for them. + If the client had downlinks, nick collisions might happen. The + uplink is responsible for noticing them from BOTINFO commands. + It should automatically replace the nicks with new ones and + send nick change command to client and all it's downlinks. For + example if host received: -In NORMAL channels, the most urgent operations (kicks, ops, deops) are -performed quite soon even while bots are busy handling HIGH priority -commands. + BOTINFO bot highbot 10 -Channels shouldn't normally be HIGH priority, but if attack against -channel is detected (like someone comes from split, gets ops and gets -to op someone else), it's priority is set to HIGH. When channel's -priority is HIGH, botnet does everything it can to get rid of -unauthorized opped people as fast as possible. + And the bot already exists, the BOTINFO is changed to: -LOW channel's priority can also be raised to HIGH, but it's priority is -dropped back to LOW if some NORMAL channel's priority is raised to HIGH -too. + BOTINFO bot2 highbot 10 -Channel's priority can also be set manually: - CHPRIORITY ircnet #channel + And the client and it's downlinks are notified: ------- + BOTNICK bot2 bot + + After sending BOTINFOs, the host tells the current master: + + MASTER + + The client now checks if it's priority is higher than the current + master's. If it is, it will send the MASTER command without any + parameters. + + + 3. Bot connections + + 3.1 General + + Everyone's connections should be kept in n-way tree. Example: + + + [highuplink] + _____________/ | | \ + / | [h5] [h6] + [h1] | / | \ + / \ | [h7] | [h8] + [h2] [h3] | | \ + | [uplink] [h9] [h10] + [h4] / | \ + [up2] | [up1] + / | | | + [up3] [up4] | [up5] + | + [we] + / \ + [client1] [client2] + / \ + [c3] [c4] + + + Botnet should try to keep together even if some hub in the middle + crashes. Each bot should have at least two uplinks in case one + dies. For example if [uplink] dies, [we] and [up1] could connect + to [up2], and [up2] could connect to [highuplink]. + + When connection is closed to some bot, a notice is sent by the + bot's uplink: + + BOTQUIT + + The quit notice should be sent only about the bot that was + disconnected. Bots should figure out themselves the other bots and + remove them too from their lists. + + 3.2 Lag + + Each bot should send PING commands to their up/downlinks every + now and then (1min?) to check that the connection is still active. + If the PONG isn't received in 10 seconds, it's priority should be + temporarily lowered to -1. If the PONG isn't received in 3 + minutes, the whole connection should be closed. + + Master should know lag times of every bots. It could then + automatically raise/lower bots' priorities depending on how big + their lag is. Master could also lower it's own priority and pass + the master status to someone else with lower lag. + + If there's lot of lag (>3sec?) somewhere and something urgent + happens, the botnet could split and behave independently. + + + 4. IRC networks + + 4.1 Server connections + + When bot is connected to some irc server and is ready to take + commands, it says: + + IRCJOIN + + Tag is the bot specific unique tag of the server, so that the bot + can connect multiple times to same IRC network. All IRC related + commands should specify the server tag where it should be sent. + + If bot quits an irc network, it says: + + IRCQUIT + + 4.2 IRC commands + + Master asks a bot to send some command to IRC server by saying: + + CMD + + can't really be anything, since the bot should also be + able to reply to it. The is for identifying the command/reply + pair. Master should keep the command in memory until it receives + the reply: + + CMDREPLY + + The command could get a reply of multiple lines, so + specifies if the reply is the last line (1 or 0). + + If the command failed for some reason, the bot will reply with + + CMDFAIL + + and master should send the command to some other bot. + + 4.3 Channels + + When joined/left channels, the bot says: + + CHANJOIN + CHANPART + + After BOTJOIN, master tries to op the bot. When bot receives +o, + it says: + + CHANOP + + If it is the first opped bot in channel, master orders the bot to + op the rest of the bots. + + If the bot is kicked, it says: + + CHANKICK + + When master notices that bot is kicked, it first checks if there's + any other opped bots in channel. If not, it waits for a random + pause, 5-10sec before letting the bot join the channel again so + that it won't get autorejoin ban. + + If bot can't join channel, it says: + + CHANBANNED + (or) + CHANCANTJOIN + + When received BOTBANNED, master tries to unban bot or set a ban + exception. BOTCANTJOIN results as invite to channel. + + 4.4 Channel information + + When master notices that bot is the first one joined to channel, + it asks the bot for some channel information: + + CMD NAMES + CMD WHO + CMD MODE + CMD MODE b + CMD MODE e (if IRC network supports this) + CMD MODE I (if IRC network supports this) + + It's also possible that if several bots join immediately after the + first bot, the commands are shared between all the bots. + + Bots should cache the information as much as possible, at least + NAMES command. + + 4.5 Channel priorities + + Every channel has a priority: LOW, NORMAL, HIGH. + + Normally LOW operates just as NORMAL channels, except when some + channel has HIGH priority and bots are really busy, LOW channels + just wait until there's time for them. + + In NORMAL channels, the most urgent operations (kicks, ops, deops) + are performed quite soon even while bots are busy handling HIGH + priority commands. + + Channels shouldn't normally be HIGH priority, but if attack + against channel is detected (like someone comes from split, gets + ops and gets to op someone else), it's priority is set to HIGH. + When channel's priority is HIGH, botnet does everything it can to + get rid of unauthorized opped people as fast as possible. + + LOW channel's priority can also be raised to HIGH, but it's + priority is dropped back to LOW if some NORMAL channel's priority + is raised to HIGH too. + + Master notifies about channel's priority change by saying: + + CHANPRIORITY -Copyright (c) 1999 Timo Sirainen 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();