simpler newmont

This commit is contained in:
Neale Pickett 2012-11-16 12:46:16 -07:00
parent ed125f385b
commit 8dcfc5deb7
3 changed files with 80 additions and 99 deletions

98
README
View File

@ -1,22 +1,18 @@
bot bot
=== ===
This is a simple C program to assist writing an IRC bot in whatever language(s) This is a simple C program to assist writing an IRC bot in whatever
you choose. It is based on the Unix principle that one program should do one language(s) you choose. It is based on the Unix principle that one
thing. 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.
Getting Started Quickly Getting Started Quickly
======================= =======================
The example handler script, called `newmont`, will get you started right away. The example handler script, called `newmont`, will get you started right
It will set its nickname to `newmont`, join the channel `#newmont`, and respond away. It will set its nickname to `newmont`, join the channel
to any channel message containing the substring "strawberry". `#newmont`, and respond to any channel message containing the substring
"strawberry".
Start it like so: Start it like so:
@ -32,66 +28,74 @@ There are three pieces involved.
tcpclient tcpclient
--------- ---------
`tcpclient' is a program that connects to a TCP port, sets file descriptors 6 `tcpclient' is a program that connects to a TCP port, sets file
and 7 to be the input and output channels to that connection, and hands off descriptors 6 and 7 to be the input and output channels to that
control to whatever program you specify (in the example above, the `./bot` connection, and hands off control to whatever program you specify (in
program with argument `./newmont`). There also exist a `udpclient`, the example above, the `./bot` program with argument `./newmont`).
`sslclient`, and probably others. The advantage to this method is that There also exist a `udpclient`, `sslclient`, and probably others. The
your network client doesn't have to care about the transport mechanism: advantage to this method is that your network client doesn't have to
TCP, UDP, TCP6, SSL, or whatever else. care about the transport mechanism: TCP, UDP, TCP6, SSL, or whatever
else.
bot bot
--- ---
`bot` reads one line at a time from fd6 (or 0 as a fallback), parses it up, `bot` reads one line at a time from fd6 (or 0 as a fallback), parses it
forks, sets some environment variables, and runs the "handler" program provided up, forks, sets some environment variables, and runs the "handler"
as the first argument. Whatever that program prints to stdout is sent back to program provided as the first argument. Whatever that program prints to
the server, verbatim. As a convenience, it automatically responds to PING stdout is sent back to the server, verbatim. As a convenience, it
messages from the server. It can also rate-limit messages to the server, so automatically responds to PING messages from the server. It can also
your bot doesn't flood itself off IRC. Lastly, it can monitor a directory and rate-limit messages to the server, so your bot doesn't flood itself off
send the contents of any new file to the server, deleting the file after. This IRC. Lastly, it can monitor a directory and send the contents of any
allows you to write to IRC from a cron job, git post-update hook, or whatever new file to the server, deleting the file after. This allows you to
else you dream up. write to IRC from a cron job, git post-update hook, or whatever else you
dream up.
`bot` sets the following environment variables: `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 command IRC command or numeric
sender nickname to send "private" replies to this message sender nickname to send "private" replies to this message
forum nickname to send "public" 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 Any additional parameters of the message, like with the MODE command,
passed in as arguments to the handler. are passed in as arguments to the handler.
handler handler
------- -------
The handler is launched once for each incoming message. It should decide The handler is launched once for each incoming message. It should
what to do based on the environment variables and argv, possibly writing decide what to do based on the environment variables and argv, possibly
something to stdout, and then it should exit. writing something to stdout, and then it should exit.
Handlers are launched in parallel to each other. IRC is an asynchronous 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 protocol, and while messages do tend to arrive in a particular order,
count on it, especially with this framework. don't count on it, especially with this framework.
`newmont` is a very simple handler script to reply to any PRIVMSG with the `newmont` is a very simple handler script to reply to any PRIVMSG with
substring "strawberry", in the (public) forum it was sent. the substring "strawberry", in the (public) forum it was sent.
Caution Caution
======= =======
Your handler is getting input provided by a potentially malicious adversary. Your handler is getting input provided by a potentially malicious
If you're not careful, you could create a remote exploit: a path through your adversary. If you're not careful, you could create a remote exploit: a
handler script that allows anyone on IRC to do whatever they want on your path through your handler script that allows anyone on IRC to do
local computer. whatever they want on your local computer.
You can write handlers in bourne shell: it's really easy. It's equally as You can write handlers in bourne shell: it's really easy. It's equally
easy to accidentally allow remote control. There's nothing I can do in the as easy to accidentally allow remote control. There's nothing I can do
code I provide to prevent you from really hurting yourself, all I can do is in my code I provide to prevent you from creating a remote exploit, all
warn you. 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 <neale@woozle.org> Neale Pickett <neale@woozle.org>

11
bot.c
View File

@ -22,6 +22,7 @@
#define max(a,b) ((a)>(b)?(a):(b)) #define max(a,b) ((a)>(b)?(a):(b))
bool running = true;
char *handler = NULL; char *handler = NULL;
char *msgdir = NULL; char *msgdir = NULL;
struct timeval output_interval = {0}; struct timeval output_interval = {0};
@ -295,6 +296,9 @@ void
handle_input() handle_input()
{ {
handle_file(stdin, dispatch); handle_file(stdin, dispatch);
if (feof(stdin)) {
running = false;
}
} }
void void
@ -462,11 +466,14 @@ main(int argc, char *argv[])
signal(SIGCHLD, sigchld); signal(SIGCHLD, sigchld);
// Let handler know we're starting up // Let handler know we're starting up
dispatch("INIT"); dispatch("_INIT_");
while (1) { while (running) {
loop(); loop();
} }
// Let handler know we're shutting down
dispatch("_END_");
return 0; return 0;
} }

View File

@ -1,70 +1,42 @@
#! /usr/bin/lua #! /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") prefix = os.getenv("prefix")
forum = os.getenv("forum") forum = os.getenv("forum")
sender = os.getenv("sender") sender = os.getenv("sender")
command = os.getenv("command") command = os.getenv("command")
text = os.getenv("text") text = os.getenv("text")
-- io.stderr:write(">>> [" .. command .. "] " ..
-- Write text to stderr (for debugging) (sender or "-") .. "/" ..
-- (forum or "-") .. " " ..
function log(text) (text or "") .. "\n")
io.stderr:write(text .. "\n")
end
-- -- Our behavior depends on what the command is
-- Send a raw IRC command to the server if (command == "_INIT_") then
--
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
-- bot sends this when it first starts up, so we can log in -- bot sends this when it first starts up, so we can log in
raw("NICK nemont") print("NICK nemont")
raw("USER newmont newmont newmont :Sample bot") print("USER newmont newmont newmont :Sample bot")
elseif (command == "433") then elseif (command == "433") then
-- Couldn't get the nickname we asked for -- Couldn't get the nickname we asked for
raw("NICK bot_" .. (os.time() % 500)) print("NICK bot_" .. (os.time() % 500))
elseif (command == "001") then elseif (command == "001") then
-- IRC server sends this after successful login -- IRC server sends this after successful login
raw("JOIN #newmont") print("JOIN #newmont")
elseif (command == "PRIVMSG") then elseif (command == "PRIVMSG") then
-- Somebody said something! -- Somebody said something!
if (text:find("strawberry")) then 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
end end