diff --git a/README b/README index 01ff14d..8c83a9b 100644 --- a/README +++ b/README @@ -1,22 +1,18 @@ bot === -This is a simple C program to assist writing an IRC bot in whatever language(s) -you choose. It is based on the Unix principle that one program should do one -thing. - -This framework gives you plenty of opportunity to shoot yourself in the foot. -If you're not a seasoned programmer, please stick to a language like Python or -Lua for your handler. Leave the arcane trivia of proper Bourne Shell quoting -rules to the seasoned experts who've learned the hard way how to do it. +This is a simple C program to assist writing an IRC bot in whatever +language(s) you choose. It is based on the Unix principle that one +program should do one thing. Getting Started Quickly ======================= -The example handler script, called `newmont`, will get you started right away. -It will set its nickname to `newmont`, join the channel `#newmont`, and respond -to any channel message containing the substring "strawberry". +The example handler script, called `newmont`, will get you started right +away. It will set its nickname to `newmont`, join the channel +`#newmont`, and respond to any channel message containing the substring +"strawberry". Start it like so: @@ -32,66 +28,74 @@ There are three pieces involved. tcpclient --------- -`tcpclient' is a program that connects to a TCP port, sets file descriptors 6 -and 7 to be the input and output channels to that connection, and hands off -control to whatever program you specify (in the example above, the `./bot` -program with argument `./newmont`). There also exist a `udpclient`, -`sslclient`, and probably others. The advantage to this method is that -your network client doesn't have to care about the transport mechanism: -TCP, UDP, TCP6, SSL, or whatever else. +`tcpclient' is a program that connects to a TCP port, sets file +descriptors 6 and 7 to be the input and output channels to that +connection, and hands off control to whatever program you specify (in +the example above, the `./bot` program with argument `./newmont`). +There also exist a `udpclient`, `sslclient`, and probably others. The +advantage to this method is that your network client doesn't have to +care about the transport mechanism: TCP, UDP, TCP6, SSL, or whatever +else. + bot --- -`bot` reads one line at a time from fd6 (or 0 as a fallback), parses it up, -forks, sets some environment variables, and runs the "handler" program provided -as the first argument. Whatever that program prints to stdout is sent back to -the server, verbatim. As a convenience, it automatically responds to PING -messages from the server. It can also rate-limit messages to the server, so -your bot doesn't flood itself off IRC. Lastly, it can monitor a directory and -send the contents of any new file to the server, deleting the file after. This -allows you to write to IRC from a cron job, git post-update hook, or whatever -else you dream up. +`bot` reads one line at a time from fd6 (or 0 as a fallback), parses it +up, forks, sets some environment variables, and runs the "handler" +program provided as the first argument. Whatever that program prints to +stdout is sent back to the server, verbatim. As a convenience, it +automatically responds to PING messages from the server. It can also +rate-limit messages to the server, so your bot doesn't flood itself off +IRC. Lastly, it can monitor a directory and send the contents of any +new file to the server, deleting the file after. This allows you to +write to IRC from a cron job, git post-update hook, or whatever else you +dream up. `bot` sets the following environment variables: - prefix IRC line prefix: you probably don't care about this + prefix you probably don't care about this command IRC command or numeric sender nickname to send "private" replies to this message forum nickname to send "public" replies to this message - text text of this message, like what's sent to the channel + text command text, like what's sent to the channel -Any additional parameters of the message, like with the MODE command, are -passed in as arguments to the handler. +Any additional parameters of the message, like with the MODE command, +are passed in as arguments to the handler. handler ------- -The handler is launched once for each incoming message. It should decide -what to do based on the environment variables and argv, possibly writing -something to stdout, and then it should exit. +The handler is launched once for each incoming message. It should +decide what to do based on the environment variables and argv, possibly +writing something to stdout, and then it should exit. Handlers are launched in parallel to each other. IRC is an asynchronous -protocol, and while messages do tend to arrive in a particular order, don't -count on it, especially with this framework. +protocol, and while messages do tend to arrive in a particular order, + don't count on it, especially with this framework. -`newmont` is a very simple handler script to reply to any PRIVMSG with the -substring "strawberry", in the (public) forum it was sent. +`newmont` is a very simple handler script to reply to any PRIVMSG with +the substring "strawberry", in the (public) forum it was sent. Caution ======= -Your handler is getting input provided by a potentially malicious adversary. -If you're not careful, you could create a remote exploit: a path through your -handler script that allows anyone on IRC to do whatever they want on your -local computer. +Your handler is getting input provided by a potentially malicious +adversary. If you're not careful, you could create a remote exploit: a +path through your handler script that allows anyone on IRC to do +whatever they want on your local computer. -You can write handlers in bourne shell: it's really easy. It's equally as -easy to accidentally allow remote control. There's nothing I can do in the -code I provide to prevent you from really hurting yourself, all I can do is -warn you. +You can write handlers in bourne shell: it's really easy. It's equally +as easy to accidentally allow remote control. There's nothing I can do +in my code I provide to prevent you from creating a remote exploit, all +I can do is warn you. + +If you're not confident in your mastery of bourne shell quoting rules, +you should build off of the provided lua example, which will make it +much more difficult to accidentally create an exploit. Or create a new +handler in Python, Ruby, etc. @@ -100,5 +104,3 @@ Author Neale Pickett - - diff --git a/bot.c b/bot.c index 5299f18..dadee95 100644 --- a/bot.c +++ b/bot.c @@ -22,6 +22,7 @@ #define max(a,b) ((a)>(b)?(a):(b)) +bool running = true; char *handler = NULL; char *msgdir = NULL; struct timeval output_interval = {0}; @@ -295,6 +296,9 @@ void handle_input() { handle_file(stdin, dispatch); + if (feof(stdin)) { + running = false; + } } void @@ -462,11 +466,14 @@ main(int argc, char *argv[]) signal(SIGCHLD, sigchld); // Let handler know we're starting up - dispatch("INIT"); + dispatch("_INIT_"); - while (1) { + while (running) { loop(); } + // Let handler know we're shutting down + dispatch("_END_"); + return 0; } diff --git a/contrib/newmont b/contrib/newmont index 0e9efb9..0cb3f89 100755 --- a/contrib/newmont +++ b/contrib/newmont @@ -1,70 +1,42 @@ #! /usr/bin/lua -- --- Set global variables from environment +-- A very simple bot which will join IRC, join #newmont, and +-- respond to any messages with "strawberry" in them. It also +-- has some naïve nickname collision avoidance. -- +-- This is a good place to start if you're not going to write +-- your handler in lua. If you *do* want to use lua, you should +-- take a look at bot.lua instead. +-- + prefix = os.getenv("prefix") forum = os.getenv("forum") sender = os.getenv("sender") command = os.getenv("command") text = os.getenv("text") --- --- Write text to stderr (for debugging) --- -function log(text) - io.stderr:write(text .. "\n") -end +io.stderr:write(">>> [" .. command .. "] " .. + (sender or "-") .. "/" .. + (forum or "-") .. " " .. + (text or "") .. "\n") --- --- Send a raw IRC command to the server --- -function raw(text) - log("< " .. text) - print(text) -end - - --- --- Send a message to the forum; if we've sent 4 lines --- already, start sending directly to the sender, to --- avoid spamming channels. --- -msgs_sent = 0 -msg_recip = forum - -function msg(text) - msgs_sent = msgs_sent + 1 - if ((msgs_sent == 5) and (forum ~= sender)) then - raw("PRIVMSG " .. forum .. " :Sending the rest in private") - msg_recip = sender - end - raw("PRIVMSG " .. msg_recip .. " :" .. text) -end - --- --- --- Main program --- --- - --- Log what we got -log(" > " .. (prefix or "") .. " " .. (sender or "-") .. "/" .. (forum or "-") .. " [" .. command .. "] :" .. (text or "")) - --- Our action depends on what the command is -if (command == "INIT") then +-- Our behavior depends on what the command is +if (command == "_INIT_") then -- bot sends this when it first starts up, so we can log in - raw("NICK nemont") - raw("USER newmont newmont newmont :Sample bot") + print("NICK nemont") + print("USER newmont newmont newmont :Sample bot") elseif (command == "433") then -- Couldn't get the nickname we asked for - raw("NICK bot_" .. (os.time() % 500)) + print("NICK bot_" .. (os.time() % 500)) elseif (command == "001") then -- IRC server sends this after successful login - raw("JOIN #newmont") + print("JOIN #newmont") elseif (command == "PRIVMSG") then -- Somebody said something! if (text:find("strawberry")) then - msg("Strawberries are delicious.") + print("PRIVMSG " .. forum .. " :Strawberries are delicious.") + elseif (text:find("die")) then + print("QUIT :goodbye") end end