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();