mirror of https://github.com/dirtbags/moth.git
Add new "kevin" category
This commit is contained in:
parent
36a430e9a1
commit
4b2ebe9418
|
@ -0,0 +1,26 @@
|
||||||
|
FAKE = fakeroot -s fake -i fake
|
||||||
|
INSTALL = $(FAKE) install
|
||||||
|
|
||||||
|
all: kevin.tce
|
||||||
|
|
||||||
|
kevin.tce: target
|
||||||
|
$(FAKE) sh -c 'cd target && tar -czf - --exclude=placeholder --exclude=*~ .' > $@
|
||||||
|
|
||||||
|
|
||||||
|
target: kevin.py irc.pyc run log.run
|
||||||
|
$(INSTALL) -d target/usr/lib/ctf
|
||||||
|
$(INSTALL) kevin.py irc.py target/usr/lib/ctf
|
||||||
|
|
||||||
|
$(INSTALL) --owner=100 -d target/var/lib/ctf/kevin/tokens
|
||||||
|
|
||||||
|
$(INSTALL) -d target/var/service/kevin
|
||||||
|
$(INSTALL) run target/var/service/kevin/run
|
||||||
|
|
||||||
|
$(INSTALL) -d target/var/service/kevin/log
|
||||||
|
$(INSTALL) log.run target/var/service/kevin/log/run
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf target kevin.tce fake
|
||||||
|
|
||||||
|
%.pyc: %.py
|
||||||
|
python3 -c 'import $*'
|
|
@ -0,0 +1,528 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import asynchat
|
||||||
|
import asyncore
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
import time
|
||||||
|
|
||||||
|
class IRCHandler(asynchat.async_chat):
|
||||||
|
"""IRC Server connection.
|
||||||
|
|
||||||
|
This is the one you want to derive your connection classes from.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
debug = False
|
||||||
|
heartbeat_interval = 1 # seconds per heartbeat
|
||||||
|
|
||||||
|
def __init__(self, host=None, nick=None, gecos=None):
|
||||||
|
asynchat.async_chat.__init__(self)
|
||||||
|
self.line = b''
|
||||||
|
self.timers = []
|
||||||
|
self.last_heartbeat = 0
|
||||||
|
self.set_terminator(b'\r\n')
|
||||||
|
if host:
|
||||||
|
self.open_connection(host, nick, gecos)
|
||||||
|
|
||||||
|
def dbg(self, msg):
|
||||||
|
if self.debug:
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
def open_connection(self, host, nick, gecos):
|
||||||
|
self.nick = nick
|
||||||
|
self.gecos = gecos
|
||||||
|
self.host = host
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.connect(host)
|
||||||
|
|
||||||
|
def handle_connect(self):
|
||||||
|
self.write(['NICK', self.nick])
|
||||||
|
self.write(['USER', self.nick, '+iw', self.nick], self.gecos)
|
||||||
|
|
||||||
|
def connect(self, host):
|
||||||
|
self.waiting = False
|
||||||
|
asynchat.async_chat.connect(self, host)
|
||||||
|
|
||||||
|
def heartbeat(self):
|
||||||
|
"""Invoke all timers."""
|
||||||
|
|
||||||
|
if not self.timers:
|
||||||
|
return
|
||||||
|
timers, self.timers = self.timers, []
|
||||||
|
now = time.time()
|
||||||
|
for t, cb in timers:
|
||||||
|
if t > now:
|
||||||
|
self.timers.append((t, cb))
|
||||||
|
else:
|
||||||
|
cb()
|
||||||
|
|
||||||
|
def add_timer(self, secs, callback):
|
||||||
|
"""After secs seconds, call callback"""
|
||||||
|
self.timers.append((time.time() + secs, callback))
|
||||||
|
|
||||||
|
def readable(self):
|
||||||
|
"""Called by asynchat to see if we're readable.
|
||||||
|
|
||||||
|
We hook our heartbeat in here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
if now > self.last_heartbeat + self.heartbeat_interval:
|
||||||
|
self.heartbeat()
|
||||||
|
self.last_heartbeat = now
|
||||||
|
|
||||||
|
if self.connected:
|
||||||
|
return asynchat.async_chat.readable(self)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def collect_incoming_data(self, data):
|
||||||
|
"""Called by asynchat when data arrives"""
|
||||||
|
self.line += data
|
||||||
|
|
||||||
|
def found_terminator(self):
|
||||||
|
"""Called by asynchat when it finds the terminating character.
|
||||||
|
"""
|
||||||
|
line = self.line.decode('utf-8')
|
||||||
|
self.line = b''
|
||||||
|
self.parse_line(line)
|
||||||
|
|
||||||
|
def write(self, args, text=None):
|
||||||
|
"""Send out an IRC command
|
||||||
|
|
||||||
|
This function helps to prevent you from shooting yourself in the
|
||||||
|
foot, by forcing you to send commands that are in a valid format
|
||||||
|
(although it doesn't check the validity of the actual commands).
|
||||||
|
|
||||||
|
As we all know, IRC commands take the form
|
||||||
|
|
||||||
|
:COMMAND ARG1 ARG2 ARG3 ... :text string
|
||||||
|
|
||||||
|
where 'text string' is optional. Well, that's exactly how this
|
||||||
|
function works. Args is a list of length at least one, and text
|
||||||
|
string is a string.
|
||||||
|
|
||||||
|
write(['PRIVMSG', nick], 'Hello 12')
|
||||||
|
|
||||||
|
will send
|
||||||
|
|
||||||
|
PRIVMSG nick :Hello 12
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
cmdstr = ' '.join(args)
|
||||||
|
if text:
|
||||||
|
cmdstr = '%s :%s' % (cmdstr, text)
|
||||||
|
self.dbg('-> %s' % cmdstr)
|
||||||
|
try:
|
||||||
|
line = '%s\n' % cmdstr
|
||||||
|
self.send(line.encode('utf-8'))
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def parse_line(self, line):
|
||||||
|
"""Parse a server-provided line
|
||||||
|
|
||||||
|
This does all the magic of parsing those ill-formatted IRC
|
||||||
|
messages. It will also decide if a PRIVMSG or NOTICE is using
|
||||||
|
CTCP (the client-to-client protocol, which by convention is any
|
||||||
|
of the above messages with ^A on both ends of the text.
|
||||||
|
|
||||||
|
This function goes on to invoke self.eval_triggers on the parsed
|
||||||
|
data like this:
|
||||||
|
|
||||||
|
self.eval_triggers(operation, arguments, text)
|
||||||
|
|
||||||
|
where operation and text are strings, and arguments is a list.
|
||||||
|
|
||||||
|
It returns the same tuple (op, args, text).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if (line[0] == ':'):
|
||||||
|
with_uname = 1
|
||||||
|
line = line [1:]
|
||||||
|
else:
|
||||||
|
with_uname = 0
|
||||||
|
try:
|
||||||
|
[args, text] = line.split(' :', 1)
|
||||||
|
args = args.split()
|
||||||
|
except ValueError:
|
||||||
|
args = line.split()
|
||||||
|
text = ''
|
||||||
|
if (with_uname != 1):
|
||||||
|
op = args[0]
|
||||||
|
elif ((args[1] in ["PRIVMSG", "NOTICE"]) and
|
||||||
|
(text and (text[0] == '\001') and (text[-1] == '\001'))):
|
||||||
|
op = "C" + args[1]
|
||||||
|
text = text[1:-1]
|
||||||
|
else:
|
||||||
|
op = args[1]
|
||||||
|
self.dbg("<- %s %s %s" % (op, args, text))
|
||||||
|
self.handle(op, args, text)
|
||||||
|
return (op, args, text)
|
||||||
|
|
||||||
|
|
||||||
|
def handle(self, op, args, text):
|
||||||
|
"""Take action on a server message
|
||||||
|
|
||||||
|
Right now, just invokes
|
||||||
|
|
||||||
|
self.do_[op](args, text)
|
||||||
|
|
||||||
|
where [op] is the operation passed in.
|
||||||
|
|
||||||
|
This is a good method to overload if you want a really advanced
|
||||||
|
client supporting bindings.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
method = getattr(self, "do_" + lower(op))
|
||||||
|
except AttributeError:
|
||||||
|
self.dbg("Unhandled: %s" % (op, args, text))
|
||||||
|
return
|
||||||
|
method(args, text)
|
||||||
|
|
||||||
|
|
||||||
|
class Recipient:
|
||||||
|
"""Abstract recipient object"""
|
||||||
|
|
||||||
|
def __init__(self, interface, name):
|
||||||
|
self._interface = interface
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Recipient(%s)' % self.name()
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def is_channel(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def write(self, cmd, addl):
|
||||||
|
"""Write a raw IRC command to our interface"""
|
||||||
|
|
||||||
|
self._interface.write(cmd, addl)
|
||||||
|
|
||||||
|
def cmd(self, cmd, text):
|
||||||
|
"""Send a command to ourself"""
|
||||||
|
|
||||||
|
self.write([cmd, self._name], text)
|
||||||
|
|
||||||
|
def msg(self, text):
|
||||||
|
"""Tell the recipient something"""
|
||||||
|
|
||||||
|
self.cmd("PRIVMSG", text)
|
||||||
|
|
||||||
|
def notice(self, text):
|
||||||
|
"""Send a notice to the recipient"""
|
||||||
|
|
||||||
|
self.cmd("NOTICE", text)
|
||||||
|
|
||||||
|
def ctcp(self, command, text):
|
||||||
|
"""Send a CTCP command to the recipient"""
|
||||||
|
|
||||||
|
return self.msg("\001%s %s\001" % (command.upper(), text))
|
||||||
|
|
||||||
|
def act(self, text):
|
||||||
|
"""Send an action to the recipient"""
|
||||||
|
|
||||||
|
return self.ctcp("ACTION", text)
|
||||||
|
|
||||||
|
def cnotice(self, command, text):
|
||||||
|
"""Send a CTCP notice to the recipient"""
|
||||||
|
|
||||||
|
return self.notice("\001%s %s\001" % (command.upper(), text))
|
||||||
|
|
||||||
|
class Channel(Recipient):
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Channel(%s)' % self.name()
|
||||||
|
|
||||||
|
def is_channel(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
class User(Recipient):
|
||||||
|
def __init__(self, interface, name, user, host, op=False):
|
||||||
|
Recipient.__init__(self, interface, name)
|
||||||
|
self.user = user
|
||||||
|
self.host = host
|
||||||
|
self.op = op
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'User(%s, %s, %s)' % (self.name(), self.user, self.host)
|
||||||
|
|
||||||
|
def recipient(interface, name):
|
||||||
|
if name[0] in ["&", "#"]:
|
||||||
|
return Channel(interface, name)
|
||||||
|
else:
|
||||||
|
return User(interface, name, None, None)
|
||||||
|
|
||||||
|
class SmartIRCHandler(IRCHandler):
|
||||||
|
"""This is like the IRCHandler, except it creates Recipient objects
|
||||||
|
for IRC messages. The intent is to make it easier to write stuff
|
||||||
|
without knowledge of the IRC protocol.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def recipient(self, name):
|
||||||
|
return recipient(self, name)
|
||||||
|
|
||||||
|
def err(self, exception):
|
||||||
|
if self.debug:
|
||||||
|
traceback.print_exception(*exception)
|
||||||
|
|
||||||
|
def handle(self, op, args, text):
|
||||||
|
"""Parse more, creating objects and stuff
|
||||||
|
|
||||||
|
makes a call to self.handle_op(sender, forum, addl)
|
||||||
|
|
||||||
|
sender is always a Recipient object; if you want to reply
|
||||||
|
privately, you can send your reply to sender.
|
||||||
|
|
||||||
|
forum is a Recipient object corresponding with the forum over
|
||||||
|
which the message was carried. For user-to-user PRIVMSG and
|
||||||
|
NOTICE commands, this is the same as sender. For those same
|
||||||
|
commands sent to a channel, it is the channel. Thus, you can
|
||||||
|
always send a reply to forum, and it will be sent back in an
|
||||||
|
appropriate manner (ie. the way you expect).
|
||||||
|
|
||||||
|
addl is a tuple, containing additional information which might
|
||||||
|
be relelvant. Here's what it will contain, based on the server
|
||||||
|
operation:
|
||||||
|
|
||||||
|
op | addl
|
||||||
|
---------+----------------
|
||||||
|
PRIVMSG | text of the message
|
||||||
|
NOTICE | text of the notice
|
||||||
|
CPRIVMSG | CTCP command, text of the command
|
||||||
|
CNOTICE | CTCP response, text of the response
|
||||||
|
KICK * | victim of kick, kick text
|
||||||
|
MODE * | all mode args
|
||||||
|
JOIN * | empty
|
||||||
|
PART * | empty
|
||||||
|
QUIT | quit message
|
||||||
|
PING | ping text
|
||||||
|
NICK ! | old nickname
|
||||||
|
others | all arguments; text is last element
|
||||||
|
|
||||||
|
* The forum in these items is the channel to which the action
|
||||||
|
pertains.
|
||||||
|
! The sender for the NICK command is the *new* nickname. This
|
||||||
|
is so you can send messages to the sender object and they'll
|
||||||
|
go to the right place.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
sender = User(self, *unpack_nuhost(args))
|
||||||
|
except ValueError:
|
||||||
|
sender = None
|
||||||
|
forum = None
|
||||||
|
addl = ()
|
||||||
|
|
||||||
|
if op in ("PRIVMSG", "NOTICE"):
|
||||||
|
# PRIVMSG ['neale!~user@127.0.0.1', 'PRIVMSG', '#hydra'] firebot, foo
|
||||||
|
# PRIVMSG ['neale!~user@127.0.0.1', 'PRIVMSG', 'firebot'] firebot, foo
|
||||||
|
try:
|
||||||
|
if args[2][0] in '#&':
|
||||||
|
forum = self.recipient(args[2])
|
||||||
|
else:
|
||||||
|
forum = sender
|
||||||
|
addl = (text,)
|
||||||
|
except IndexError:
|
||||||
|
addl = (text, args[1])
|
||||||
|
elif op in ("CPRIVMSG", "CNOTICE"):
|
||||||
|
forum = self.recipient(args[2])
|
||||||
|
splits = text.split(" ")
|
||||||
|
if splits[0] == "DCC":
|
||||||
|
op = "DC" + op
|
||||||
|
addl = (splits[1],) + tuple(splits[2:])
|
||||||
|
else:
|
||||||
|
addl = (splits[0],) + tuple(splits[1:])
|
||||||
|
elif op in ("KICK",):
|
||||||
|
forum = self.recipient(args[2])
|
||||||
|
addl = (self.recipient(args[3]), text)
|
||||||
|
elif op in ("MODE",):
|
||||||
|
forum = self.recipient(args[2])
|
||||||
|
addl = args[3:]
|
||||||
|
elif op in ("JOIN", "PART"):
|
||||||
|
try:
|
||||||
|
forum = self.recipient(args[2])
|
||||||
|
except IndexError:
|
||||||
|
forum = self.recipient(text)
|
||||||
|
elif op in ("QUIT",):
|
||||||
|
addl = (text,)
|
||||||
|
elif op in ("PING", "PONG"):
|
||||||
|
# PING ['PING'] us.boogernet.org.
|
||||||
|
# PONG ['irc.foonet.com', 'PONG', 'irc.foonet.com'] 1114199424
|
||||||
|
addl = (text,)
|
||||||
|
elif op in ("NICK",):
|
||||||
|
# NICK ['brad!~brad@10.168.2.33', 'NICK'] bradaway
|
||||||
|
#
|
||||||
|
# The sender is the new nickname here, in case you want to
|
||||||
|
# send something to the sender.
|
||||||
|
|
||||||
|
# Apparently there are two different standards for this
|
||||||
|
# command.
|
||||||
|
if text:
|
||||||
|
sender = self.recipient(text)
|
||||||
|
else:
|
||||||
|
sender = self.recipient(args[2])
|
||||||
|
addl = (unpack_nuhost(args)[0],)
|
||||||
|
elif op in ("INVITE",):
|
||||||
|
# INVITE [u'pflarr!~pflarr@www.clanspum.net', u'INVITE', u'gallium', u'#mysterious']
|
||||||
|
# INVITE [u'pflarr!~pflarr@www.clanspum.net', u'INVITE', u'gallium'] #mysterious
|
||||||
|
if len(args) > 3:
|
||||||
|
forum = self.recipient(args[3])
|
||||||
|
else:
|
||||||
|
forum = self.recipient(text)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
int(op)
|
||||||
|
except ValueError:
|
||||||
|
self.dbg("WARNING: unknown server code: %s" % op)
|
||||||
|
addl = tuple(args[3:]) + (text,)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.handle_cooked(op, sender, forum, addl)
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
self.err(sys.exc_info())
|
||||||
|
|
||||||
|
def handle_cooked(self, op, sender, forum, addl):
|
||||||
|
try:
|
||||||
|
func = getattr(self, 'cmd_' + op.lower())
|
||||||
|
except AttributeError:
|
||||||
|
self.unhandled(op, sender, forum, addl)
|
||||||
|
return
|
||||||
|
func(sender, forum, addl)
|
||||||
|
|
||||||
|
def cmd_ping(self, sender, forum, addl):
|
||||||
|
self.write(['PONG'], addl[0])
|
||||||
|
|
||||||
|
def unhandled(self, op, sender, forum, addl):
|
||||||
|
"""Handle all the stuff that had no handler.
|
||||||
|
|
||||||
|
This is a special handler in that it also gets the server code
|
||||||
|
as the first argument.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.dbg("unhandled: %s" % ((op, sender, forum, addl),))
|
||||||
|
|
||||||
|
|
||||||
|
class Bot(SmartIRCHandler):
|
||||||
|
"""A simple bot.
|
||||||
|
|
||||||
|
This automatically joins the channels you pass to the constructor,
|
||||||
|
tries to use one of the nicks provided, and reconnects if it gets
|
||||||
|
booted. You can use this as a base for more sophisticated bots.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, host, nicks, gecos, channels):
|
||||||
|
self.nicks = nicks
|
||||||
|
self.channels = channels
|
||||||
|
self.waiting = True
|
||||||
|
self._spool = []
|
||||||
|
SmartIRCHandler.__init__(self, host, nicks[0], gecos)
|
||||||
|
|
||||||
|
def despool(self, target, lines):
|
||||||
|
"""Slowly despool a bunch of lines to a target
|
||||||
|
|
||||||
|
Since the IRC server will block all output if we send it too
|
||||||
|
fast, use this to send large multi-line responses.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._spool.append((target, list(lines)))
|
||||||
|
|
||||||
|
def heartbeat(self):
|
||||||
|
SmartIRCHandler.heartbeat(self)
|
||||||
|
|
||||||
|
# Despool data
|
||||||
|
if self._spool:
|
||||||
|
# Take the first one on the queue, and put it on the end
|
||||||
|
which = self._spool[0]
|
||||||
|
del self._spool[0]
|
||||||
|
self._spool.append(which)
|
||||||
|
|
||||||
|
# Despool a line
|
||||||
|
target, lines = which
|
||||||
|
if lines:
|
||||||
|
line = lines[0]
|
||||||
|
target.msg(line)
|
||||||
|
del lines[0]
|
||||||
|
else:
|
||||||
|
self._spool.remove(which)
|
||||||
|
|
||||||
|
def announce(self, text):
|
||||||
|
for c in self.channels:
|
||||||
|
self.write(['PRIVMSG', c], text)
|
||||||
|
|
||||||
|
def err(self, exception):
|
||||||
|
SmartIRCHandler.err(self, exception)
|
||||||
|
self.announce('*bzert*')
|
||||||
|
|
||||||
|
def cmd_001(self, sender, forum, addl):
|
||||||
|
for c in self.channels:
|
||||||
|
self.write(['JOIN'], c)
|
||||||
|
|
||||||
|
def writable(self):
|
||||||
|
if not self.waiting:
|
||||||
|
return asynchat.async_chat.writable(self)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def write(self, *args):
|
||||||
|
SmartIRCHandler.write(self, *args)
|
||||||
|
|
||||||
|
def close(self, final=False):
|
||||||
|
SmartIRCHandler.close(self)
|
||||||
|
if not final:
|
||||||
|
self.dbg("Connection closed, reconnecting...")
|
||||||
|
self.waiting = True
|
||||||
|
self.connected = 0
|
||||||
|
# Wait a bit and reconnect
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.add_timer(23, lambda : self.connect(self.host))
|
||||||
|
|
||||||
|
def handle_close(self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Miscellaneous IRC functions
|
||||||
|
##
|
||||||
|
|
||||||
|
def unpack_nuhost(nuhost):
|
||||||
|
"""Unpack nick!user@host
|
||||||
|
|
||||||
|
Frequently, the first argument in a server message is in
|
||||||
|
nick!user@host format. You can just pass your whole argument list
|
||||||
|
to this function and get back a tuple containing:
|
||||||
|
|
||||||
|
(nick, user, host)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
[nick, uhost] = nuhost[0].split('!', 1)
|
||||||
|
[user, host] = uhost.split('@', 1)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError("not in nick!user@host format")
|
||||||
|
return (nick, user, host)
|
||||||
|
|
||||||
|
def run_forever(timeout=2.0):
|
||||||
|
"""Run your clients forever.
|
||||||
|
|
||||||
|
Just a handy front-end to asyncore.loop, so you don't have to import
|
||||||
|
asyncore yourself.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
asyncore.loop(timeout)
|
|
@ -0,0 +1,147 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import irc
|
||||||
|
import os
|
||||||
|
import optparse
|
||||||
|
import asynchat
|
||||||
|
import socket
|
||||||
|
import asyncore
|
||||||
|
from urllib.parse import quote_plus as quote
|
||||||
|
|
||||||
|
nobody = '\002[nobody]\002'
|
||||||
|
|
||||||
|
class Flagger(asynchat.async_chat):
|
||||||
|
"""Connection to flagd"""
|
||||||
|
|
||||||
|
def __init__(self, addr, auth):
|
||||||
|
asynchat.async_chat.__init__(self)
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.connect((addr, 6668))
|
||||||
|
self.push(auth + b'\n')
|
||||||
|
self.flag = None
|
||||||
|
|
||||||
|
def handle_read(self):
|
||||||
|
msg = self.recv(4096)
|
||||||
|
raise ValueError("Flagger died: %r" % msg)
|
||||||
|
|
||||||
|
def handle_error(self):
|
||||||
|
# If we lose the connection to flagd, nobody can score any
|
||||||
|
# points. Terminate everything.
|
||||||
|
asyncore.close_all()
|
||||||
|
asynchat.async_chat.handle_error(self)
|
||||||
|
|
||||||
|
def set_flag(self, team):
|
||||||
|
if team:
|
||||||
|
eteam = team.encode('utf-8')
|
||||||
|
else:
|
||||||
|
eteam = b''
|
||||||
|
self.push(eteam + b'\n')
|
||||||
|
self.flag = team
|
||||||
|
|
||||||
|
|
||||||
|
class Kevin(irc.Bot):
|
||||||
|
def __init__(self, host, flagger, tokens, victims):
|
||||||
|
irc.Bot.__init__(self, host, ['kevin'], 'Kevin', ['#kevin'])
|
||||||
|
self.flagger = flagger
|
||||||
|
self.tokens = tokens
|
||||||
|
self.victims = victims
|
||||||
|
self.affiliation = {}
|
||||||
|
|
||||||
|
def cmd_join(self, sender, forum, addl):
|
||||||
|
if sender.name() in self.nicks:
|
||||||
|
self.tell_flag(forum)
|
||||||
|
|
||||||
|
def err(self, exception):
|
||||||
|
"""Save the traceback for later inspection"""
|
||||||
|
irc.Bot.err(self, exception)
|
||||||
|
t,v,tb = exception
|
||||||
|
info = []
|
||||||
|
while 1:
|
||||||
|
info.append('%s:%d(%s)' % (os.path.basename(tb.tb_frame.f_code.co_filename),
|
||||||
|
tb.tb_lineno,
|
||||||
|
tb.tb_frame.f_code.co_name))
|
||||||
|
tb = tb.tb_next
|
||||||
|
if not tb:
|
||||||
|
break
|
||||||
|
del tb # just to be safe
|
||||||
|
infostr = '[' + '] ['.join(info) + ']'
|
||||||
|
self.last_tb = '%s %s %s' % (t, v, infostr)
|
||||||
|
print(self.last_tb)
|
||||||
|
|
||||||
|
def tell_flag(self, forum):
|
||||||
|
forum.msg('%s has the flag.' % (self.flagger.flag or nobody))
|
||||||
|
|
||||||
|
def cmd_privmsg(self, sender, forum, addl):
|
||||||
|
text = addl[0]
|
||||||
|
if text.startswith('!'):
|
||||||
|
parts = text[1:].lower().split(' ', 1)
|
||||||
|
cmd = parts[0]
|
||||||
|
if len(parts) > 1:
|
||||||
|
args = parts[1]
|
||||||
|
else:
|
||||||
|
args = None
|
||||||
|
if cmd.startswith('r'):
|
||||||
|
# Register
|
||||||
|
who = sender.name()
|
||||||
|
if args:
|
||||||
|
self.affiliation[who] = args
|
||||||
|
team = self.affiliation.get(who, nobody)
|
||||||
|
forum.msg('%s is playing for %s' % (who, team))
|
||||||
|
elif cmd.startswith('e'):
|
||||||
|
# Embrace
|
||||||
|
forum.ctcp('ACTION', 'hugs %s' % sender.name())
|
||||||
|
elif cmd.startswith('f'):
|
||||||
|
# Flag
|
||||||
|
self.tell_flag(forum)
|
||||||
|
elif cmd.startswith('h'):
|
||||||
|
# Help
|
||||||
|
forum.msg('Goal: Obtain a token with social engineering.')
|
||||||
|
forum.msg('Commands: !help, !flag, !register [TEAM], !claim TOKEN, !victims, !embrace')
|
||||||
|
elif cmd.startswith('c') and args:
|
||||||
|
# Claim
|
||||||
|
sn = sender.name()
|
||||||
|
team = self.affiliation.get(sn)
|
||||||
|
token = quote(args, safe='')
|
||||||
|
fn = os.path.join(self.tokens, token)
|
||||||
|
if not team:
|
||||||
|
forum.msg('%s: register first (!register TEAM).' % sn)
|
||||||
|
elif self.flagger.flag == team:
|
||||||
|
forum.msg('%s: Greedy, greedy.' % sn)
|
||||||
|
elif not os.path.exists(fn):
|
||||||
|
forum.msg('%s: Token does not exist (possibly already claimed).' % sn)
|
||||||
|
else:
|
||||||
|
os.unlink(fn)
|
||||||
|
self.flagger.set_flag(team)
|
||||||
|
self.tell_flag(forum)
|
||||||
|
elif cmd.startswith('v'):
|
||||||
|
# Victims
|
||||||
|
# Open the file each time, so it can change
|
||||||
|
try:
|
||||||
|
for line in open(self.victims):
|
||||||
|
forum.msg(line.strip())
|
||||||
|
except IOError:
|
||||||
|
forum.msg('There are no victims!')
|
||||||
|
elif cmd == 'traceback':
|
||||||
|
forum.msg(self.last_tb or 'No traceback')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
p = optparse.OptionParser()
|
||||||
|
p.add_option('-t', '--tokens', dest='tokens', default='./tokens',
|
||||||
|
help='Directory containing tokens')
|
||||||
|
p.add_option('-v', '--victims', dest='victims', default='victims.txt',
|
||||||
|
help='File containing victims information')
|
||||||
|
p.add_option('-i', '--ircd', dest='ircd', default='localhost',
|
||||||
|
help='IRC server to connect to')
|
||||||
|
p.add_option('-f', '--flagd', dest='flagd', default='localhost',
|
||||||
|
help='Flag server to connect to')
|
||||||
|
p.add_option('-p', '--password', dest='password',
|
||||||
|
default='kevin:::7db3e44d53d4a466f8facd7b7e9aa2b7',
|
||||||
|
help='Flag server password')
|
||||||
|
opts, args = p.parse_args()
|
||||||
|
|
||||||
|
f = Flagger(opts.flagd, opts.password.encode('utf-8'))
|
||||||
|
k = Kevin((opts.ircd, 6667), f, opts.tokens, opts.victims)
|
||||||
|
irc.run_forever()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,3 @@
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
exec logger -t kevin
|
|
@ -0,0 +1,5 @@
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
[ -f /var/lib/ctf/disabled/kevin ] && exit 0
|
||||||
|
|
||||||
|
exec envuidgid ctf /usr/lib/ctf/kevin.py --victims=/var/lib/ctf/kevin/victims.txt --tokens=/var/lib/ctf/kevin/tokens
|
|
@ -0,0 +1,4 @@
|
||||||
|
Michael Smith (505)555-1212 SMS OK
|
||||||
|
Janet Berger (505)555-7382
|
||||||
|
Dimwit Flathead dimwit on irc.efnet.net
|
||||||
|
Jacob Schmidt jacob@dirtbags.net
|
Loading…
Reference in New Issue