mirror of https://github.com/dirtbags/moth.git
Remove more cruft, add polish
This commit is contained in:
parent
14d7e3509d
commit
7e4f523347
77
Makefile
77
Makefile
|
@ -1,77 +1,12 @@
|
|||
BASE = /opt/ctf
|
||||
VAR = $(BASE)/var
|
||||
WWW = $(BASE)/www
|
||||
LIB = $(BASE)/lib
|
||||
BIN = $(BASE)/bin
|
||||
SBIN = $(BASE)/sbin
|
||||
BASE_URL = /
|
||||
USERNAME = www-data
|
||||
SUBDIRS = src
|
||||
|
||||
TEMPLATE = $(CURDIR)/template.html
|
||||
MDWNTOHTML = $(CURDIR)/mdwntohtml.py --template=$(TEMPLATE) --base=$(BASE_URL)
|
||||
all: build
|
||||
|
||||
default: install
|
||||
|
||||
SUBDIRS = mdwn
|
||||
INSTALL_TARGETS = $(addsuffix -install, $(SUBDIRS))
|
||||
include $(addsuffix /*.mk, $(SUBDIRS))
|
||||
|
||||
install: base-install $(INSTALL_TARGETS)
|
||||
install --directory --owner=$(USERNAME) $(VAR)/tanks
|
||||
install --directory --owner=$(USERNAME) $(VAR)/tanks/results
|
||||
install --directory --owner=$(USERNAME) $(VAR)/tanks/errors
|
||||
install --directory --owner=$(USERNAME) $(VAR)/tanks/ai
|
||||
install --directory --owner=$(USERNAME) $(VAR)/tanks/ai/players
|
||||
install --directory --owner=$(USERNAME) $(VAR)/tanks/ai/house
|
||||
|
||||
echo 'VAR = "$(VAR)"' > ctf/paths.py
|
||||
echo 'WWW = "$(WWW)"' >> ctf/paths.py
|
||||
echo 'LIB = "$(LIB)"' >> ctf/paths.py
|
||||
echo 'BIN = "$(BIN)"' >> ctf/paths.py
|
||||
echo 'SBIN = "$(SBIN)"' >> ctf/paths.py
|
||||
echo 'BASE_URL = "$(BASE_URL)"' >> ctf/paths.py
|
||||
|
||||
install bin/pointscli $(BIN)
|
||||
install bin/in.pointsd bin/in.flagd \
|
||||
bin/scoreboard bin/run-tanks \
|
||||
bin/run-ctf $(SBIN)
|
||||
cp -r lib/* $(LIB)
|
||||
cp -r www/* $(WWW)
|
||||
rm -f $(WWW)/tanks/results
|
||||
ln -s $(VAR)/tanks/results $(WWW)/tanks/results
|
||||
cp template.html $(LIB)
|
||||
|
||||
./mkpuzzles.py --base=$(BASE_URL) --puzzles=puzzles \
|
||||
--htmldir=$(WWW)/puzzler --keyfile=$(LIB)/puzzler.keys
|
||||
|
||||
install --directory $(VAR)/disabled
|
||||
touch $(VAR)/disabled/bletchley
|
||||
touch $(VAR)/disabled/compaq
|
||||
touch $(VAR)/disabled/crypto
|
||||
touch $(VAR)/disabled/forensics
|
||||
touch $(VAR)/disabled/hackme
|
||||
touch $(VAR)/disabled/hispaniola
|
||||
touch $(VAR)/disabled/net-re
|
||||
touch $(VAR)/disabled/skynet
|
||||
touch $(VAR)/disabled/survey
|
||||
|
||||
python setup.py install
|
||||
|
||||
|
||||
base-install:
|
||||
install --directory $(LIB) $(BIN) $(SBIN)
|
||||
install --directory --owner=$(USERNAME) $(VAR)
|
||||
install --directory --owner=$(USERNAME) $(WWW)
|
||||
install --directory --owner=$(USERNAME) $(WWW)/puzzler
|
||||
install --directory --owner=$(USERNAME) $(VAR)/points
|
||||
install --directory --owner=$(USERNAME) $(VAR)/points/tmp
|
||||
install --directory --owner=$(USERNAME) $(VAR)/points/cur
|
||||
install --directory --owner=$(USERNAME) $(VAR)/flags
|
||||
|
||||
|
||||
uninstall:
|
||||
rm -rf $(VAR) $(WWW) $(LIB) $(BIN) $(SBIN)
|
||||
rmdir $(BASE) || true
|
||||
|
||||
test: build
|
||||
./test.sh
|
||||
|
||||
build: $(addsuffix -build, $(SUBDIRS))
|
||||
clean: $(addsuffix -clean, $(SUBDIRS))
|
||||
|
||||
|
|
265
bin/badmathbot
265
bin/badmathbot
|
@ -1,265 +0,0 @@
|
|||
#! /usr/bin/python
|
||||
|
||||
import badmath
|
||||
import time
|
||||
import os
|
||||
import traceback
|
||||
import pickle
|
||||
|
||||
import irc
|
||||
from ctf import teams
|
||||
from ctf.flagger import Flagger
|
||||
|
||||
class Gyopi(irc.Bot):
|
||||
STATE_FN = 'badmath.state'
|
||||
|
||||
SALT = 'this is questionable.'
|
||||
|
||||
MAX_ATTEMPT_RATE = 3
|
||||
NOBODY = '\002[nobody]\002'
|
||||
|
||||
def __init__(self, host, channels, dataPath, flagger):
|
||||
irc.Bot.__init__(self, host, ['gyopi', 'gyopi_', '_gyopi', '[gyopi]'], 'Gyopi', channels)
|
||||
|
||||
self._dataPath = dataPath
|
||||
|
||||
self._flag = flagger
|
||||
|
||||
try:
|
||||
self._loadState()
|
||||
except:
|
||||
self._lvl = 0
|
||||
self._flag.set_flag(teams.house)
|
||||
|
||||
self._tokens = []
|
||||
self._lastAttempt = {}
|
||||
self._affiliations = {}
|
||||
self._newPuzzle()
|
||||
|
||||
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 cmd_JOIN(self, sender, forum, addl):
|
||||
"""On join, announce who has the flag."""
|
||||
if sender.name() in self.nicks:
|
||||
self._tellFlag(forum)
|
||||
self._tellPuzzle(forum)
|
||||
self.write(['TOPIC', '#badmath'], 'type !help')
|
||||
|
||||
def _newPuzzle(self):
|
||||
"""Create a new puzzle."""
|
||||
self._key, self._puzzle, self._banned = badmath.mkPuzzle(self._lvl)
|
||||
|
||||
def _loadState(self):
|
||||
"""Load the last state from the stateFile."""
|
||||
statePath = os.path.join(self._dataPath, self.STATE_FN)
|
||||
stateFile = open( statePath, 'rb' )
|
||||
state = pickle.load(stateFile)
|
||||
self._lvl = state['lvl']
|
||||
self._flag.set_flag( state['flag'] )
|
||||
self._lastAttempt = state['lastAttempt']
|
||||
self._affiliations = state['affiliations']
|
||||
self._puzzle = state['puzzle']
|
||||
self._key = state['key']
|
||||
self._banned = state['banned']
|
||||
self._tokens = state.get('tokens', [])
|
||||
|
||||
def _saveState(self):
|
||||
"""Write the current state to file."""
|
||||
state = {'lvl': self._lvl,
|
||||
'flag': self._flag.flag,
|
||||
'lastAttempt': self._lastAttempt,
|
||||
'affiliations': self._affiliations,
|
||||
'puzzle': self._puzzle,
|
||||
'key': self._key,
|
||||
'banned': self._banned,
|
||||
'tokens': self._tokens}
|
||||
|
||||
# Do the write as an atomic move operation
|
||||
statePath = os.path.join(self._dataPath, self.STATE_FN)
|
||||
stateFile = open(statePath + '.tmp', 'wb')
|
||||
pickle.dump(state, stateFile)
|
||||
stateFile.close()
|
||||
os.rename( statePath + '.tmp', statePath)
|
||||
|
||||
def _tellFlag(self, forum):
|
||||
"""Announce who owns the flag."""
|
||||
forum.msg('%s has the flag.' % (self._flag.flag))
|
||||
|
||||
def _tellPuzzle(self, forum):
|
||||
"""Announce the current puzzle."""
|
||||
forum.msg('Difficulty level is %d' % self._lvl)
|
||||
forum.msg('The problem is: %s' % ' '.join( map(str, self._puzzle)))
|
||||
|
||||
def _getStations(self):
|
||||
stations = {}
|
||||
file = open(os.path.join(STORAGE, 'stations.txt'))
|
||||
lines = file.readlines()
|
||||
for line in lines:
|
||||
try:
|
||||
name, file = line.split(':')
|
||||
except:
|
||||
continue
|
||||
stations[name] = file
|
||||
|
||||
return stations
|
||||
|
||||
def _giveToken(self, user, forum):
|
||||
"""Hand a Jukebox token to the user."""
|
||||
|
||||
token = self._jukebox.mkToken(user)
|
||||
|
||||
forum.msg('You get a jukebox token: %s' % token)
|
||||
forum.msg('Use this with the !set command to change the music.')
|
||||
forum.msg('This token is specific to your user name, and is only '
|
||||
'useable once.')
|
||||
|
||||
def _useToken(self, user, forum, token, station):
|
||||
"""Use the given token, and change the current station to station."""
|
||||
try:
|
||||
station = int(station)
|
||||
stations = self._getStations()
|
||||
assert station in stations
|
||||
except:
|
||||
forum.msg('%s: Invalid Station (%s)' % station)
|
||||
return
|
||||
|
||||
if token in self._tokens[user]:
|
||||
self._tokens[user].remove(token)
|
||||
|
||||
|
||||
def cmd_PRIVMSG(self, sender, forum, addl):
|
||||
text = addl[0]
|
||||
who = sender.name()
|
||||
if text.startswith('!'):
|
||||
parts = text[1:].split(' ', 1)
|
||||
cmd = parts[0]
|
||||
if len(parts) > 1:
|
||||
args = parts[1]
|
||||
else:
|
||||
args = None
|
||||
if cmd.startswith('r'):
|
||||
# Register
|
||||
if args:
|
||||
self._affiliations[who] = args
|
||||
team = self._affiliations.get(who, self.NOBODY)
|
||||
forum.msg('%s is playing for %s' % (who, team))
|
||||
elif cmd.startswith('w'):
|
||||
forum.msg('Teams:')
|
||||
for player in self._affiliations:
|
||||
forum.msg('%s: %s' % (player, self._affiliations[player]))
|
||||
elif cmd.startswith('embrace'):
|
||||
# Embrace
|
||||
forum.ctcp('ACTION', 'is devoid of emotion.')
|
||||
elif cmd.startswith('f'):
|
||||
# Flag
|
||||
self._tellFlag(forum)
|
||||
elif cmd.startswith('h'):
|
||||
# Help
|
||||
forum.msg('''Goal: Help me with my math homework, FROM ANOTHER DIMENSION! Order of operations is always left to right in that dimension, but the operators are alien.''')
|
||||
forum.msg('Order of operations is always left to right.')
|
||||
#forum.msg('Goal: The current winner gets to control the contest music.')
|
||||
forum.msg('Commands: !help, !flag, !register [TEAM], !solve SOLUTION,!? EQUATION, !ops, !problem, !who')
|
||||
elif cmd.startswith('prob'):
|
||||
self._tellPuzzle(forum)
|
||||
elif cmd.startswith('solve') and args:
|
||||
# Solve
|
||||
team = self._affiliations.get(who)
|
||||
lastAttempt = time.time() - self._lastAttempt.get(team, 0)
|
||||
#UN-COMMENT AFTER NMT CTF
|
||||
# self._lastAttempt[team] = time.time()
|
||||
answer = badmath.solve(self._key, self._puzzle)
|
||||
try:
|
||||
attempt = int(''.join(args).strip())
|
||||
except:
|
||||
forum.msg("%s: Answers are always integers.")
|
||||
if not team:
|
||||
forum.msg('%s: register first (!register TEAM).' % who)
|
||||
elif self._flag.flag == team:
|
||||
forum.msg('%s: Greedy, greedy.' % who)
|
||||
elif lastAttempt < self.MAX_ATTEMPT_RATE:
|
||||
forum.msg('%s: Wait at least %d seconds between attempts' %
|
||||
(team, self.MAX_ATTEMPT_RATE))
|
||||
elif answer == attempt:
|
||||
self._flag.set_flag( team )
|
||||
self._lvl = self._lvl + 1
|
||||
self._tellFlag(forum)
|
||||
self._newPuzzle()
|
||||
self._tellPuzzle(forum)
|
||||
# self._giveToken(who, sender)
|
||||
self._saveState()
|
||||
else:
|
||||
forum.msg('%s: That is not correct.' % who)
|
||||
|
||||
# Test a simple one op command.
|
||||
elif cmd.startswith('?'):
|
||||
if not args:
|
||||
forum.msg('%s: Give me an easier problem, and I\'ll '
|
||||
'give you the answer.' % who)
|
||||
return
|
||||
|
||||
try:
|
||||
tokens = badmath.parse(''.join(args))
|
||||
except (ValueError), msg:
|
||||
forum.msg('%s: %s' % (who, msg))
|
||||
return
|
||||
|
||||
if len(tokens) > 3:
|
||||
forum.msg('%s: You can only test one op at a time.' % who)
|
||||
return
|
||||
|
||||
for num in self._banned:
|
||||
if num in tokens:
|
||||
forum.msg('%s: You can\'t test numbers in the '
|
||||
'puzzle.' % who)
|
||||
return
|
||||
|
||||
try:
|
||||
result = badmath.solve(self._key, tokens)
|
||||
forum.msg('%s: %s -> %d' % (who, ''.join(args), result))
|
||||
except Exception, msg:
|
||||
forum.msg("%s: That doesn't work at all: %s" % (who, msg))
|
||||
|
||||
elif cmd == 'birdzerk':
|
||||
self._saveState()
|
||||
|
||||
elif cmd == 'traceback':
|
||||
forum.msg(self.last_tb or 'No traceback')
|
||||
|
||||
if __name__ == '__main__':
|
||||
import optparse
|
||||
|
||||
p = optparse.OptionParser()
|
||||
p.add_option('-i', '--irc', dest='ircHost', default='localhost',
|
||||
help='IRC Host 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='badmath:::a41c6753210c0bdafd84b3b62d7d1666',
|
||||
help='Flag server password')
|
||||
p.add_option('-d', '--path', dest='path', default='/var/lib/ctf/badmath',
|
||||
help='Path to where we can store state info.')
|
||||
p.add_option('-c', '--channel', dest='channel', default='#badmath',
|
||||
help='Which channel to join')
|
||||
|
||||
opts, args = p.parse_args()
|
||||
channels = [opts.channel]
|
||||
|
||||
flagger = Flagger(opts.flagd, opts.password.encode('utf-8'))
|
||||
gyopi = Gyopi((opts.ircHost, 6667), channels, opts.path, flagger)
|
||||
irc.run_forever()
|
96
bin/in.flagd
96
bin/in.flagd
|
@ -1,96 +0,0 @@
|
|||
#! /usr/bin/python
|
||||
|
||||
import sys
|
||||
import optparse
|
||||
import hmac
|
||||
import time
|
||||
import select
|
||||
from ctf import teams, pointscli
|
||||
import os
|
||||
from urllib import quote
|
||||
|
||||
basedir = None
|
||||
flagsdir = None
|
||||
|
||||
key = 'My First Shared Secret (tm)'
|
||||
def hexdigest(s):
|
||||
return hmac.new(key, s.encode('utf-8')).hexdigest()
|
||||
|
||||
def auth():
|
||||
# Pretend to be in.tcpmuxd
|
||||
while True:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
return
|
||||
line = line.strip().lower()
|
||||
|
||||
if line == 'tcpmux':
|
||||
sys.stdout.write('+Okay, fine.\r\n')
|
||||
sys.stdout.flush()
|
||||
continue
|
||||
elif line == 'help':
|
||||
sys.stdout.write('tcpmux\r\n')
|
||||
elif ':::' in line:
|
||||
# Authentication
|
||||
cat, passwd = line.split(':::')
|
||||
if passwd == hexdigest(cat):
|
||||
return cat
|
||||
else:
|
||||
sys.stdout.write('-Blow me.\r\n')
|
||||
else:
|
||||
sys.stdout.write('-Blow me.\r\n')
|
||||
return
|
||||
|
||||
def award(cat, team):
|
||||
qcat = quote(cat, '')
|
||||
fn = os.path.join(flagsdir, qcat)
|
||||
f = open(fn, 'w')
|
||||
f.write(team)
|
||||
f.close()
|
||||
pointscli.award(cat, team, 1)
|
||||
print('+%s' % team)
|
||||
sys.stdout.flush()
|
||||
|
||||
def run():
|
||||
cat = auth()
|
||||
if not cat:
|
||||
return
|
||||
|
||||
now = time.time()
|
||||
next_award = now - (now % 60)
|
||||
flag = teams.house
|
||||
|
||||
while True:
|
||||
now = time.time()
|
||||
while now >= next_award:
|
||||
next_award += 60
|
||||
award(cat, flag)
|
||||
|
||||
timeout = next_award - now
|
||||
r, w, x = select.select([sys.stdin], [], [], timeout)
|
||||
if r:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
break
|
||||
new_flag = line.strip() or teams.house
|
||||
if new_flag != flag:
|
||||
# Award a point if the flag is captured
|
||||
flag = new_flag
|
||||
award(cat, flag)
|
||||
|
||||
def main():
|
||||
p = optparse.OptionParser(usage='%prog [options] FLAGSDIR')
|
||||
p.add_option('-a', '--auth', dest='cat', default=None,
|
||||
help='Generate authentication for the given category')
|
||||
opts, args = p.parse_args()
|
||||
if opts.cat:
|
||||
print('%s:::%s' % (opts.cat, hexdigest(opts.cat.encode('utf-8'))))
|
||||
elif len(args) != 1:
|
||||
p.error('Wrong number of arguments')
|
||||
else:
|
||||
global flagsdir
|
||||
flagsdir = args[0]
|
||||
run()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,5 +0,0 @@
|
|||
#! /bin/sh
|
||||
|
||||
ip=$(echo $UDPREMOTEADDR | cut -d: -f1)
|
||||
touch $1/$ip
|
||||
echo 'Hello.'
|
|
@ -1,19 +0,0 @@
|
|||
#! /bin/sh
|
||||
|
||||
##
|
||||
## This is meant to be run from inotifyd like so:
|
||||
##
|
||||
## inotifyd in.pointsd $base/cur:y
|
||||
##
|
||||
## inotifyd runs in.pointsd serially, so all we have to do is just echo
|
||||
## each file to the log, and then remove it. This allows a log message
|
||||
## to be entered by writing a file into tmp, moving it to cur, and then
|
||||
## moving along. Even if pointsd dies, everybody is still able to score
|
||||
## points asynchronously without losing anything: it'll just get picked
|
||||
## up when pointsd restarts.
|
||||
##
|
||||
|
||||
# Args: flag dir file
|
||||
fn=$2/$3
|
||||
|
||||
cat $fn >> $2/../log && rm $fn
|
136
bin/kevin
136
bin/kevin
|
@ -1,136 +0,0 @@
|
|||
#! /usr/bin/python
|
||||
|
||||
import os
|
||||
import optparse
|
||||
import asynchat
|
||||
import socket
|
||||
import asyncore
|
||||
from urllib import quote_plus as quote
|
||||
|
||||
import irc
|
||||
from ctf.flagger import Flagger
|
||||
|
||||
nobody = '\002[nobody]\002'
|
||||
|
||||
class Kevin(irc.Bot):
|
||||
def __init__(self, host, flagger, tokens, victims):
|
||||
irc.Bot.__init__(self, host,
|
||||
['kevin', 'kev', 'kevin_', 'kev_', 'kevinm', 'kevinm_'],
|
||||
'Kevin',
|
||||
['+kevin'])
|
||||
self.flagger = flagger
|
||||
self.tokens = tokens
|
||||
self.victims = victims
|
||||
self.affiliation = {}
|
||||
|
||||
def cmd_001(self, sender, forum, addl):
|
||||
self.write(['OPER', 'bot', 'BottyMcBotpants'])
|
||||
irc.Bot.cmd_001(self, sender, forum, addl)
|
||||
|
||||
def cmd_JOIN(self, sender, forum, addl):
|
||||
if sender.name == self.nick:
|
||||
self.write(['TOPIC', '#badmath'], 'type !help')
|
||||
self.tell_flag(forum)
|
||||
|
||||
def cmd_381(self, sender, forum, addl):
|
||||
# You are now an IRC Operator
|
||||
if self.nick != 'kevin':
|
||||
self.write(['KILL', 'kevin'], 'You are not kevin. I am kevin.')
|
||||
self.write(['NICK', 'kevin'])
|
||||
|
||||
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:].split(' ', 1)
|
||||
cmd = parts[0].lower()
|
||||
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')
|
||||
p.add_option('-c', '--channel', dest='channel',
|
||||
help='Channel to join')
|
||||
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()
|
|
@ -1,5 +0,0 @@
|
|||
#! /usr/bin/python
|
||||
|
||||
from ctf import pointscli
|
||||
|
||||
pointscli.main()
|
285
bin/pollster
285
bin/pollster
|
@ -1,285 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import socket
|
||||
import traceback
|
||||
import subprocess
|
||||
import random
|
||||
import httplib
|
||||
import optparse
|
||||
import cStringIO as io
|
||||
|
||||
from ctf import pointscli, html
|
||||
|
||||
ifconfig = '/sbin/ifconfig'
|
||||
udhcpc = '/sbin/udhcpc'
|
||||
|
||||
class BoundHTTPConnection(httplib.HTTPConnection):
|
||||
''' http.client.HTTPConnection doesn't support binding to a particular
|
||||
address, which is something we need. '''
|
||||
|
||||
def __init__(self, bindip, host, port=None, strict=None, timeout=None):
|
||||
httplib.HTTPConnection.__init__(self, host, port, strict)
|
||||
self.bindip = bindip
|
||||
self.timeout = timeout
|
||||
|
||||
def connect(self):
|
||||
''' Connect to the host and port specified in __init__, but
|
||||
also bind first. '''
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.bind((self.bindip, 0))
|
||||
self.sock.settimeout(self.timeout)
|
||||
self.sock.connect((self.host, self.port))
|
||||
|
||||
def random_mac():
|
||||
''' Set a random mac on the poll interface. '''
|
||||
retcode = subprocess.call((ifconfig, opts.iface, 'down'))
|
||||
mac = ':'.join([opts.mac_vendor] + ['%02x' % random.randint(0,255) for i in range(3)])
|
||||
retcode = subprocess.call((ifconfig, opts.iface, 'hw', 'ether', mac, 'up'))
|
||||
|
||||
def dhcp_request():
|
||||
''' Request a new IP on the poll interface. '''
|
||||
retcode = subprocess.call((udhcpc, '-i', opts.iface, '-q'))
|
||||
|
||||
def get_ip():
|
||||
''' Return the IP of the poll interface. '''
|
||||
ip_match = re.compile(r'inet addr:([0-9.]+)')
|
||||
p = subprocess.Popen((ifconfig, opts.iface), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = p.communicate()
|
||||
for line in out.splitlines():
|
||||
m = ip_match.search(line)
|
||||
if m is not None:
|
||||
return m.group(1).decode('utf-8')
|
||||
return '10.1.1.1'
|
||||
|
||||
def socket_poll(srcip, ip, port, msg, prot, max_recv=1):
|
||||
''' Connect via socket to the specified <ip>:<port> using the
|
||||
specified <prot>, send the specified <msg> and return the
|
||||
response or None if something went wrong. <max_recvs> specifies
|
||||
how many times to read from the socket (defaults to once). '''
|
||||
|
||||
# create a socket
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, prot)
|
||||
except Exception, e:
|
||||
print('pollster: create socket failed (%s)' % e)
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
sock.bind((srcip, 0))
|
||||
sock.settimeout(opts.timeout)
|
||||
|
||||
# connect
|
||||
try:
|
||||
sock.connect((ip, port))
|
||||
except socket.timeout, e:
|
||||
print('pollster: attempt to connect to %s:%d timed out (%s)' % (ip, port, e))
|
||||
traceback.print_exc()
|
||||
return None
|
||||
except Exception, e:
|
||||
print('pollster: attempt to connect to %s:%d failed (%s)' % (ip, port, e))
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
# send something
|
||||
sock.send(msg)
|
||||
|
||||
# get a response
|
||||
resp = []
|
||||
try:
|
||||
# read from the socket until <max_recv> responses or read,
|
||||
# a timeout occurs, the socket closes, or some other exception
|
||||
# is raised
|
||||
for i in range(max_recv):
|
||||
data = sock.recv(1024)
|
||||
if len(data) == 0:
|
||||
break
|
||||
resp.append(data)
|
||||
|
||||
except socket.timeout, e:
|
||||
print('pollster: timed out waiting for a response from %s:%d (%s)' % (ip, port, e))
|
||||
traceback.print_exc()
|
||||
except Exception, e:
|
||||
print('pollster: receive from %s:%d failed (%s)' % (ip, port, e))
|
||||
traceback.print_exc()
|
||||
|
||||
sock.close()
|
||||
|
||||
if len(resp) == 0:
|
||||
return None
|
||||
|
||||
return ''.join(resp)
|
||||
|
||||
# PUT POLLS FUNCTIONS HERE
|
||||
# Each function should take an IP address and return a team name or None
|
||||
# if (a) the service is not up, (b) it doesn't return a valid team name.
|
||||
|
||||
def poll_fingerd(srcip, ip):
|
||||
''' Poll the fingerd service. Returns None or a team name. '''
|
||||
resp = socket_poll(srcip, ip, 79, 'flag\n', socket.SOCK_STREAM)
|
||||
if resp is None:
|
||||
return None
|
||||
return resp.split('\n')[0]
|
||||
|
||||
def poll_noted(srcip, ip):
|
||||
''' Poll the noted service. Returns None or a team name. '''
|
||||
resp = socket_poll(srcip, ip, 4000, 'rflag\n', socket.SOCK_STREAM)
|
||||
if resp is None:
|
||||
return None
|
||||
return resp.split('\n')[0]
|
||||
|
||||
def poll_catcgi(srcip, ip):
|
||||
''' Poll the cat.cgi web service. Returns None or a team name. '''
|
||||
|
||||
try:
|
||||
conn = BoundHTTPConnection(srcip, ip, timeout=opts.timeout)
|
||||
conn.request('GET', '/var/www/flag')
|
||||
except Exception, e:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
resp = conn.getresponse()
|
||||
if resp.status != 200:
|
||||
conn.close()
|
||||
return None
|
||||
|
||||
data = resp.read()
|
||||
conn.close()
|
||||
return data.split('\n')[0]
|
||||
|
||||
def poll_tftpd(srcip, ip):
|
||||
''' Poll the tftp service. Returns None or a team name. '''
|
||||
resp = socket_poll(srcip, ip, 69, '\x00\x01' + 'flag' + '\x00' + 'octet' + '\x00', socket.SOCK_DGRAM)
|
||||
if resp is None:
|
||||
return None
|
||||
|
||||
if len(resp) <= 5:
|
||||
return None
|
||||
|
||||
resp = resp.split('\n')[0]
|
||||
|
||||
# ack
|
||||
_ = socket_poll(srcip, ip, 69, '\x00\x04\x00\x01' + '\x00' * 14, socket.SOCK_DGRAM, 0)
|
||||
|
||||
return resp[4:].split('\n')[0]
|
||||
|
||||
# PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED
|
||||
POLLS = {
|
||||
'fingerd' : poll_fingerd,
|
||||
'noted' : poll_noted,
|
||||
'catcgi' : poll_catcgi,
|
||||
'tftpd' : poll_tftpd,
|
||||
}
|
||||
|
||||
|
||||
|
||||
p = optparse.OptionParser()
|
||||
p.add_option('-d', '--debug', action='store_true', dest='debug',
|
||||
default=False,
|
||||
help='Turn on debugging output')
|
||||
p.add_option('-s', '--interval', type='float', dest='interval',
|
||||
default=60,
|
||||
help='Time between polls, in seconds (default: %default)')
|
||||
p.add_option('-t', '--timeout', type='float', dest='timeout',
|
||||
default=0.5,
|
||||
help='Poll timeout, in seconds (default: %default)')
|
||||
p.add_option('-b', '--heartbeat-dir', dest='heartbeat_dir',
|
||||
default='/var/lib/ctf/heartbeat',
|
||||
help='Where in.heartbeatd writes its files')
|
||||
p.add_option('-r', '--results-page', dest='results',
|
||||
default='/tmp/services.html',
|
||||
help='Where to write results file')
|
||||
p.add_option('-i', '--interface', dest='iface',
|
||||
default='eth1',
|
||||
help='Interface to bind to')
|
||||
p.add_option('-m', '--mac-vendor', dest='mac_vendor',
|
||||
default='00:01:c0',
|
||||
help='MAC vendor to look for (default: %default)')
|
||||
opts, args = p.parse_args()
|
||||
|
||||
socket.setdefaulttimeout(opts.timeout)
|
||||
|
||||
ip_re = re.compile('(\d{1,3}\.){3}\d{1,3}')
|
||||
# loop forever
|
||||
while True:
|
||||
|
||||
random_mac()
|
||||
dhcp_request()
|
||||
|
||||
srcip = get_ip()
|
||||
|
||||
t_start = time.time()
|
||||
|
||||
# gather the list of IPs to poll
|
||||
ips = os.listdir(opts.heartbeat_dir)
|
||||
|
||||
out = io.StringIO()
|
||||
for ip in ips:
|
||||
# check file name format is ip
|
||||
if ip_re.match(ip) is None:
|
||||
continue
|
||||
|
||||
# remove the file
|
||||
fn = os.path.join(opts.heartbeat_dir, ip)
|
||||
try:
|
||||
os.remove(fn)
|
||||
except Exception, e:
|
||||
print('pollster: could not remove %s' % fn)
|
||||
traceback.print_exc()
|
||||
|
||||
results = {}
|
||||
|
||||
if opts.debug:
|
||||
print('ip: %s' % ip)
|
||||
|
||||
if out is not None:
|
||||
out.write('<h2>%s</h2>\n' % ip)
|
||||
out.write('<table class="pollster">\n<thead><tr><td>Service Name</td></td>')
|
||||
out.write('<td>Flag Holder</td></tr></thead>\n')
|
||||
|
||||
# perform polls
|
||||
for service,func in POLLS.items():
|
||||
try:
|
||||
team = func(srcip, ip).decode('utf-8')
|
||||
if len(team) == 0:
|
||||
team = 'dirtbags'
|
||||
except:
|
||||
team = 'dirtbags'
|
||||
|
||||
if opts.debug:
|
||||
print('\t%s - %s' % (service, team))
|
||||
|
||||
if out is not None:
|
||||
out.write('<tr><td>%s</td><td>%s</td>\n' % (service, team))
|
||||
|
||||
pointscli.award('svc.' + service, team, 1)
|
||||
|
||||
if out is not None:
|
||||
out.write('</table>\n')
|
||||
|
||||
if opts.debug:
|
||||
print('+-----------------------------------------+')
|
||||
|
||||
time_str = time.strftime('%a, %d %b %Y %H:%M:%S %Z')
|
||||
out.write('''
|
||||
<p>This page was generated on %s. That was <span id="diff">?</span> seconds ago.</p>
|
||||
<script type="text/javascript">
|
||||
var gen_time = new Date(%f);
|
||||
var cur_time = new Date();
|
||||
var diff = (cur_time.getTime() - gen_time.getTime())/1000;
|
||||
document.getElementById("diff").innerHTML = diff;
|
||||
</script>
|
||||
''' % (time_str, time.time()*1000))
|
||||
|
||||
t_end = time.time()
|
||||
exec_time = int(t_end - t_start)
|
||||
sleep_time = opts.interval - exec_time
|
||||
|
||||
html.write(opts.results,'Team Service Availability', out.getvalue())
|
||||
|
||||
# sleep until its time to poll again
|
||||
time.sleep(sleep_time)
|
||||
|
31
bin/run-ctf
31
bin/run-ctf
|
@ -1,36 +1,35 @@
|
|||
#! /bin/sh
|
||||
|
||||
# First argument is seconds between running everything
|
||||
cycle=${1:-60}
|
||||
period=${1:-60}
|
||||
|
||||
POINTS=var/points/log
|
||||
|
||||
cd $(dirname $0)/..
|
||||
CTF_BASE=${CTF_BASE:-/srv/ctf} export CTF_BASE
|
||||
|
||||
POINTS=$CTF_BASE/points.log
|
||||
SCOREBOARD=$CTF_BASE/www/scoreboard.html
|
||||
PUZZLES=$CTF_BASE/www/puzzles.html
|
||||
|
||||
while true; do
|
||||
# Timestamp
|
||||
start=$(date +%s)
|
||||
next=$(expr $start + $cycle)
|
||||
|
||||
# If enabled, run tanks
|
||||
if ! [ -f var/disabled/tanks ]; then
|
||||
sbin/run-tanks --no-barren-points --once var/tanks
|
||||
fi
|
||||
next=$(expr $start + $period)
|
||||
|
||||
# Collect any new points
|
||||
for fn in var/points/cur/*; do
|
||||
for fn in $CTF_BASE/points.new/*; do
|
||||
[ -f $fn ] || continue
|
||||
cat $fn >> $POINTS || break
|
||||
rm $fn
|
||||
done
|
||||
|
||||
# Update the scoreboard
|
||||
if [ -f $POINTS ]; then
|
||||
sbin/scoreboard -t www/scoreboard.html -j www/myplot.js < $POINTS
|
||||
if [ $POINTS -nt $SCOREBOARD ]; then
|
||||
$CTF_BASE/sbin/scoreboard < $POINTS > $SCOREBOARD.new
|
||||
mv $SCOREBOARD.new $SCOREBOARD
|
||||
fi
|
||||
|
||||
if [ $CTF_BASE/puzzler.db -nt $PUZZLES ]; then
|
||||
$CTF_BASE/sbin/puzzles.cgi > $PUZZLES.new
|
||||
mv $PUZZLES.new $PUZZLES
|
||||
fi
|
||||
|
||||
# Wait until the next minute
|
||||
now=$(date +%s)
|
||||
if [ $now -lt $next ]; then
|
||||
sleep $(expr $next - $now)
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
#! /usr/bin/python
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import time
|
||||
from ctf import pointscli, teams, paths
|
||||
from tanks import Pflanzarr
|
||||
|
||||
running = True
|
||||
|
||||
def run_tanks(basedir, turns, barren_points=True):
|
||||
try:
|
||||
p = Pflanzarr.Pflanzarr(basedir)
|
||||
p.run(turns)
|
||||
winner = p.winner or teams.house
|
||||
except Pflanzarr.NotEnoughPlayers:
|
||||
if not barren_points:
|
||||
return
|
||||
winner = teams.house
|
||||
pointscli.award('tanks', winner, 1)
|
||||
|
||||
winnerFile = open(os.path.join(basedir, 'winner'),'w')
|
||||
winnerFile.write(winner)
|
||||
winnerFile.close()
|
||||
|
||||
# Fake being a flag, so the most recent winner shows up on the
|
||||
# scoreboard.
|
||||
try:
|
||||
open(os.path.join(paths.VAR, 'flags', 'tanks'), 'w').write(winner)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
parser = optparse.OptionParser('%prog [options] DATA_DIR')
|
||||
parser.add_option('-1', '--once',
|
||||
action='store_true', dest='once',
|
||||
help='Run only once')
|
||||
parser.add_option('-b', '--no-barren-points',
|
||||
action='store_false', dest='barren', default=True,
|
||||
help="Don't award points if there aren't enough players")
|
||||
parser.add_option('-t', '--max-turns',
|
||||
type='int', dest='turns', default=500,
|
||||
help='Maximum number of turns per round')
|
||||
parser.add_option('-s', '--sleep-time',
|
||||
type='int', dest='sleep', default=60,
|
||||
help='Wait SLEEP seconds between turns (default %default)')
|
||||
opts, args = parser.parse_args()
|
||||
if (len(args) != 1):
|
||||
parser.error('Wrong number of arguments')
|
||||
|
||||
while running:
|
||||
run_tanks(args[0], opts.turns, opts.barren)
|
||||
if opts.once:
|
||||
break
|
||||
time.sleep(opts.sleep)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
376
bin/scoreboard
376
bin/scoreboard
|
@ -1,174 +1,240 @@
|
|||
#! /usr/bin/python
|
||||
#! /usr/bin/awk -f
|
||||
|
||||
import sys
|
||||
import codecs
|
||||
import time
|
||||
import optparse
|
||||
import string
|
||||
import os
|
||||
from urllib import unquote
|
||||
from ctf import teams, html, paths
|
||||
from codecs import open
|
||||
from sets import Set as set
|
||||
from cgi import escape
|
||||
##
|
||||
##
|
||||
## I'm not happy with how this code looks. I've
|
||||
##
|
||||
##
|
||||
|
||||
flags_dir = os.path.join(paths.VAR, 'flags')
|
||||
sys.stdin = codecs.getreader('utf-8')(sys.stdin)
|
||||
function qsort(A, left, right, i, last) {
|
||||
if (left >= right)
|
||||
return
|
||||
swap(A, left, left+int((right-left+1)*rand()))
|
||||
last = left
|
||||
for (i = left+1; i <= right; i++)
|
||||
if (A[i] < A[left])
|
||||
swap(A, ++last, i)
|
||||
swap(A, left, last)
|
||||
qsort(A, left, last-1)
|
||||
qsort(A, last+1, right)
|
||||
}
|
||||
function swap(A, i, j, t) {
|
||||
t = A[i]; A[i] = A[j]; A[j] = t
|
||||
}
|
||||
|
||||
def incdict(dict, key, amt=1):
|
||||
dict[key] = dict.get(key, 0) + amt
|
||||
function escape(s) {
|
||||
gsub("&", "&", s)
|
||||
gsub("<", "<", s)
|
||||
gsub(">", ">", s)
|
||||
return s
|
||||
}
|
||||
|
||||
class Chart:
|
||||
def __init__(self):
|
||||
self.points_by_cat = {}
|
||||
self.points_by_cat_team = {}
|
||||
self.high_score = 0.001
|
||||
self.teams = set()
|
||||
self.cats = set()
|
||||
self.log = []
|
||||
function print_bar(cat, team, n, d) {
|
||||
printf("<div class=\"t%s score\"" \
|
||||
" style=\"height: %.2f%%;\"" \
|
||||
" onmouseover=\"highlight('%s')\"" \
|
||||
" onmouseout=\"restore('%s')\">\n" \
|
||||
"<!-- %s --> %s: %s\n" \
|
||||
"</div>",
|
||||
team,
|
||||
100 * n / d,
|
||||
team,
|
||||
team,
|
||||
cat, escape(names_by_team[team]), n)
|
||||
}
|
||||
|
||||
def add_points(self, when, cat, team, points):
|
||||
self.log.append((when, cat, team, points))
|
||||
self.teams.add(team)
|
||||
self.cats.add(cat)
|
||||
incdict(self.points_by_cat, cat, points)
|
||||
incdict(self.points_by_cat_team, (cat, team), points)
|
||||
function output( t, c) {
|
||||
for (t in teams) {
|
||||
score = 0;
|
||||
for (c in points_by_cat) {
|
||||
if (points_by_cat[c] > 0) {
|
||||
score += points_by_cat_team[c, t] / points_by_cat[c];
|
||||
}
|
||||
}
|
||||
if (score > maxscore) {
|
||||
maxscore = score
|
||||
}
|
||||
if (score > maxscores_by_team[t]) {
|
||||
maxscores_by_team[t] = score
|
||||
}
|
||||
scores_by_team_time[t, lasttime] = score
|
||||
}
|
||||
timestamps[tslen++] = lasttime
|
||||
}
|
||||
|
||||
def team_points(self, team):
|
||||
points = 0
|
||||
for cat, tot in self.points_by_cat.items():
|
||||
if not tot:
|
||||
continue
|
||||
team_points = self.team_points_in_cat(cat, team)
|
||||
points += team_points / float(tot)
|
||||
return points
|
||||
BEGIN {
|
||||
base = ENVIRON["CTF_BASE"]
|
||||
if (! base) {
|
||||
base = "/srv/ctf"
|
||||
}
|
||||
|
||||
def team_points_in_cat(self, cat, team):
|
||||
return self.points_by_cat_team.get((cat, team), 0)
|
||||
# Only display two decimal places
|
||||
CONVFMT = "%.2f"
|
||||
|
||||
def write_js(self, f):
|
||||
start = self.log[0][0]
|
||||
end = self.log[-1][0]
|
||||
# New point at least every 2.5 minutes
|
||||
interval = 150
|
||||
tslen = 0
|
||||
|
||||
# Calculate high score
|
||||
high_score = reduce(max, [self.team_points(t) for t in self.teams])
|
||||
while (1 == getline) {
|
||||
time = $1
|
||||
team = $2
|
||||
cat = $3
|
||||
points = int($4)
|
||||
|
||||
width = end - start
|
||||
height = high_score * 1.1
|
||||
if (! start) {
|
||||
start = time
|
||||
}
|
||||
|
||||
f.write('function draw(id) {\n')
|
||||
f.write(' p = new Plot(id, %d, %.3f);\n' % (width, height))
|
||||
for team in self.teams:
|
||||
f.write(' p.line("#%s",[' % teams.color(team))
|
||||
score = 0
|
||||
for when, cat, t, points in self.log:
|
||||
if t == team:
|
||||
cat_points = self.points_by_cat[cat]
|
||||
if not cat_points:
|
||||
continue
|
||||
pct = float(points) / cat_points
|
||||
score += pct
|
||||
f.write('[%d,%.2f],' % (when - start, score))
|
||||
f.write(']); // %s\n' % team)
|
||||
f.write('}')
|
||||
if (time > (outtime + interval)) {
|
||||
outtime = time
|
||||
output()
|
||||
}
|
||||
lasttime = time
|
||||
|
||||
def make_table(self):
|
||||
body = []
|
||||
body.append('<table class="scoreboard">')
|
||||
body.append('<tr>')
|
||||
body.append('<th>Overall</th>')
|
||||
for cat in self.cats:
|
||||
points = self.points_by_cat[cat]
|
||||
if not points:
|
||||
continue
|
||||
body.append('<th>')
|
||||
body.append(' %s (%d)' % (cat, points))
|
||||
try:
|
||||
fn = os.path.join(flags_dir, cat)
|
||||
team = open(fn).read().strip() or teams.house
|
||||
body.append(' <br/>')
|
||||
body.append(' <span style="color: #%s" title="flag holder">' % teams.color(team))
|
||||
body.append(' <!-- flag: %s --> %s\n' % (cat, escape(team[:15])))
|
||||
body.append(' </span>')
|
||||
except IOError:
|
||||
pass
|
||||
body.append('</th>')
|
||||
body.append('</tr>')
|
||||
teams[team] = team
|
||||
points_by_cat[cat] += points
|
||||
points_by_cat_team[cat, team] += points
|
||||
}
|
||||
|
||||
body.append('<tr>')
|
||||
body.append('<td><ol>')
|
||||
totals = []
|
||||
for team in self.teams:
|
||||
total = self.team_points(team)
|
||||
totals.append((total, team))
|
||||
totals.sort()
|
||||
totals.reverse()
|
||||
for total, team in totals:
|
||||
if total < 0.1:
|
||||
break
|
||||
body.append('<li><span style="color: #%s;">%s (%0.3f)</span></li>'
|
||||
% (teams.color(team), escape(team[:15]), total))
|
||||
body.append('</ol></td>')
|
||||
for cat in self.cats:
|
||||
total = self.points_by_cat[cat]
|
||||
if not total:
|
||||
continue
|
||||
body.append('<td>')
|
||||
scores = sorted([(self.team_points_in_cat(cat, team), team) for team in self.teams])
|
||||
for score, team in scores:
|
||||
if not score:
|
||||
continue
|
||||
color = teams.color(team)
|
||||
body.append('<div style="height: %f%%; overflow: hidden; background: #%s; color: black;">' % (float(score * 100)/total, color))
|
||||
body.append('<!-- category: %s --> %s: %d' % (cat, escape(team[:15]), score))
|
||||
body.append('</div>')
|
||||
body.append('</td>')
|
||||
body.append('</tr>')
|
||||
body.append('</table>')
|
||||
output()
|
||||
|
||||
return '\n'.join(body)
|
||||
# Get team colors and names
|
||||
for (team in teams) {
|
||||
fn = base "/teams/colors/" team
|
||||
getline colors_by_team[team] < fn
|
||||
close(fn)
|
||||
|
||||
def main():
|
||||
p = optparse.OptionParser(usage='%prog [options] < logfile')
|
||||
p.add_option('-t', '--html', dest='html', default=None,
|
||||
help='Write a web page to HTML')
|
||||
p.add_option('-j', '--javascript', dest='js', default=None,
|
||||
help='Write javascript params to JS')
|
||||
fn = base "/teams/names/" team
|
||||
getline names_by_team[team] < fn
|
||||
close(fn)
|
||||
}
|
||||
|
||||
opts, args = p.parse_args()
|
||||
if args:
|
||||
return p.print_help()
|
||||
# Sort categories
|
||||
ncats = 0
|
||||
for (cat in points_by_cat) {
|
||||
cats[ncats++] = cat
|
||||
}
|
||||
qsort(cats, 0, ncats-1)
|
||||
|
||||
chart = Chart()
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
try:
|
||||
date, qcat, qteam, points = line.split('\t')
|
||||
except ValueError:
|
||||
print 'Possible line corruption: %s' % (repr(line)[:40])
|
||||
cat = unquote(qcat)
|
||||
team = unquote(qteam)
|
||||
when = time.strptime(date, '%Y-%m-%dT%H:%M:%S')
|
||||
chart.add_points(time.mktime(when),
|
||||
cat,
|
||||
team,
|
||||
int(points))
|
||||
# Create a sorted list of scores
|
||||
nteams = 0
|
||||
for (team in teams) {
|
||||
scores[nteams++] = scores_by_team_time[team, lasttime]
|
||||
}
|
||||
qsort(scores, 0, nteams-1)
|
||||
|
||||
if opts.html:
|
||||
hdr = ('<script type="application/javascript" src="plot.js"></script>'
|
||||
'<script type="application/javascript" src="myplot.js"></script>'
|
||||
'<meta http-equiv="refresh" content="60" />')
|
||||
body = chart.make_table()
|
||||
body += '\n<canvas id="history"></canvas>'
|
||||
html.write(opts.html,
|
||||
'Scoreboard',
|
||||
body,
|
||||
hdr=hdr,
|
||||
body_class='wide',
|
||||
onload="draw('history')")
|
||||
if opts.js:
|
||||
f = open(opts.js, 'w', encoding='utf-8')
|
||||
chart.write_js(f)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
# Now we can start writing the document
|
||||
print "<!DOCTYPE html>"
|
||||
print "<html>"
|
||||
print " <head>"
|
||||
print " <title>Scoreboard</title>"
|
||||
print " <link rel=\"stylesheet\" href=\"ctf.css\" type=\"text/css\">"
|
||||
print " <script type=\"application/javascript\" src=\"scoreboard.js\"></script>"
|
||||
|
||||
# Provide raw data for the chart
|
||||
print " <script type=\"application/javascript\">"
|
||||
print "function init() {"
|
||||
printf(" plot(\"chart\", %d, %.2f, {\n", tslen, maxscore)
|
||||
c = 0
|
||||
for (team in teams) {
|
||||
if (maxscores_by_team[team] / maxscore < 0.01) continue
|
||||
printf(" \"%s\": [\"#%s\",[", team, colors_by_team[team])
|
||||
for (i = 1; i < tslen; i += 1) {
|
||||
time = timestamps[i]
|
||||
printf("[%d,%.2f],",
|
||||
i, scores_by_team_time[team, time])
|
||||
}
|
||||
printf("]],\n");
|
||||
}
|
||||
print " });"
|
||||
print " if (location.hash) {"
|
||||
print " cycle();"
|
||||
print " setInterval(cycle, 10000);"
|
||||
print " }"
|
||||
print "}"
|
||||
print "window.onload = init;"
|
||||
print " </script>"
|
||||
|
||||
# Reload every minute
|
||||
print " <meta http-equiv=\"refresh\" content=\"60\">"
|
||||
|
||||
# Set up team colors and a few page-specific styles
|
||||
print " <style type=\"text/css\">"
|
||||
print " body { width: 100%; }"
|
||||
print " .score { overflow: hidden; color: black; }"
|
||||
for (team in teams) {
|
||||
printf(" .t%s { background-color: #%s; }\n",
|
||||
team, colors_by_team[team])
|
||||
}
|
||||
print " </style>"
|
||||
|
||||
print " </head>"
|
||||
print " <body>"
|
||||
print " <h1>Scoreboard</h1>"
|
||||
print "<p id=\"debug\"></p>"
|
||||
print " <table id=\"scoreboard\">"
|
||||
print " <tr>"
|
||||
print " <th>Overall</th>"
|
||||
|
||||
# Print out category names
|
||||
for (i = 0; i < ncats; i += 1) {
|
||||
cat = cats[i]
|
||||
points = points_by_cat[cat]
|
||||
if (0 == points) continue
|
||||
printf("<th>%s (%d)</th>\n", cat, points)
|
||||
}
|
||||
|
||||
print " </tr>"
|
||||
print " <tr>"
|
||||
|
||||
# Print out teams, ranked by score
|
||||
print " <td>"
|
||||
for (i = 0; i < nteams; i += 1) {
|
||||
if (scores[i] == scores[i-1]) continue;
|
||||
for (team in teams) {
|
||||
if (scores[i] == scores_by_team_time[team, lasttime]) {
|
||||
name = names_by_team[team]
|
||||
print_bar("total", team, scores[i], ncats)
|
||||
}
|
||||
}
|
||||
}
|
||||
print " </td>"
|
||||
|
||||
# Print out scores within each category
|
||||
for (i = 0; i < ncats; i += 1) {
|
||||
cat = cats[i]
|
||||
points = points_by_cat[cat]
|
||||
if (0 == points) break;
|
||||
|
||||
print "<td>"
|
||||
|
||||
# Create sorted list of scores in this category
|
||||
n = 0
|
||||
for (team in teams) {
|
||||
l[n++] = points_by_cat_team[cat, team];
|
||||
}
|
||||
qsort(l, 0, n-1)
|
||||
|
||||
# Print out teams, ranked by points
|
||||
for (j = 0; j < n; j += 1) {
|
||||
if (l[j] == l[j-1]) continue;
|
||||
if (0 == l[j]) break;
|
||||
for (team in teams) {
|
||||
points = points_by_cat_team[cat, team]
|
||||
if (l[j] == points) {
|
||||
name = names_by_team[team]
|
||||
print_bar(cat, team, points, points_by_cat[cat])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print "</td>"
|
||||
}
|
||||
print " </tr>"
|
||||
|
||||
print " </table>"
|
||||
print " <canvas id=\"chart\" width=\"800\" height=\"400\"></canvas>"
|
||||
print " </body>"
|
||||
print "</html>"
|
||||
}
|
||||
|
|
16
mdwn/mdwn.mk
16
mdwn/mdwn.mk
|
@ -1,16 +0,0 @@
|
|||
MDWN_DIR = mdwn
|
||||
|
||||
MDWN_SRC += $(wildcard $(MDWN_DIR)/src/*.mdwn)
|
||||
MDWN_SRC += $(wildcard $(MDWN_DIR)/src/*/*.mdwn)
|
||||
MDWN_SRC += $(wildcard $(MDWN_DIR)/src/*/*/*.mdwn)
|
||||
|
||||
MDWN_OUT = $(subst $(MDWN_DIR)/src/, $(WWW)/, $(MDWN_SRC:.mdwn=.html))
|
||||
|
||||
mdwn-install: $(MDWN_OUT)
|
||||
|
||||
$(WWW)/%.html: $(MDWN_DIR)/src/%.mdwn
|
||||
install -d $(@D)
|
||||
$(MDWNTOHTML) $< $@
|
||||
|
||||
mdwn-clean:
|
||||
rm -f $(MDWN_OUT)
|
|
@ -1,15 +0,0 @@
|
|||
Title: Welcome
|
||||
|
||||
Welcome to Café Scientifique Capture The Flag.
|
||||
|
||||
1. [Register](register.cgi) your team
|
||||
2. [View the score board](scoreboard.html)
|
||||
3. Check out the [base conversion table](table.html)
|
||||
4. Start playing
|
||||
|
||||
The following categories exist for this version of CTF:
|
||||
|
||||
* [Sequence](puzzler.cgi?c=sequence)
|
||||
* [Code Breaking](puzzler.cgi?c=codebreaking)
|
||||
* [Webapp](puzzler.cgi?c=webapp)
|
||||
* [Tanks](tanks/results.html)
|
|
@ -1,103 +0,0 @@
|
|||
Title: Introduction
|
||||
|
||||
Welcome to Capture The Flag.
|
||||
|
||||
|
||||
What This Is
|
||||
============
|
||||
|
||||
* A hacking contest
|
||||
* A chance to experience the nature of cyber incident response
|
||||
* An environment to safely experiment with offensive techniques
|
||||
|
||||
|
||||
What This Is Not
|
||||
================
|
||||
|
||||
* An arena for purely malicious attacks
|
||||
* A rave
|
||||
|
||||
|
||||
Rules
|
||||
=====
|
||||
|
||||
Important Rules
|
||||
---------------
|
||||
|
||||
* The contest network is 10.<i>x</i>.<i>x</i>.<i>x</i>. **Do
|
||||
not attack machines outside the contest network**. All
|
||||
federal, state, and school laws still apply to the outside
|
||||
network.
|
||||
* If the "outside network" requires you to plug into a different
|
||||
switch, do not connect any machine that has been on the contest
|
||||
network.
|
||||
* Consider this network hostile: your machine may be
|
||||
compromised.
|
||||
* We expect you to be disruptive within the framework of the
|
||||
game (malicious code, network scanning, social engineering,
|
||||
etc.). Disruptive behavior outside the game will result in a
|
||||
public and humiliating ejection from the contest area.
|
||||
* No ARP attacks. While cute, they are not particularly clever
|
||||
given our network topology, and would require expensive and
|
||||
bulky equipment to prevent. Find something else to do.
|
||||
|
||||
Less-Important Rules
|
||||
--------------------
|
||||
|
||||
* If IRC is up, you should use it to communicate with the
|
||||
contest staff. Staff will have operator status in #ctf.
|
||||
* If you think something is wrong with the game, you are
|
||||
expected to demonstrate the problem and explain what you think
|
||||
is the correct behavior.
|
||||
|
||||
|
||||
Scoring
|
||||
=======
|
||||
|
||||
The contest is made up of multiple categories. Each category is worth
|
||||
one point toward the total score; the percentage of the total points
|
||||
held by your team is the percentage of one point your team has for that
|
||||
category. The team that has 30% of the points in each of five
|
||||
categories has 1.5 points, whereas the team that has 80% of the points
|
||||
in only one category has 0.8 points. It is typically better to have a
|
||||
few points in many categories, than many points in a few categories.
|
||||
|
||||
There are two kinds of categories: *flags* and *puzzles*.
|
||||
|
||||
|
||||
Flags
|
||||
-----
|
||||
|
||||
Flag categories are challenges with a notion of a *winner* or *service
|
||||
availability*. In these categories, the flag-holder (the winner, or
|
||||
each team with a running service) makes 1 point per minute for as long
|
||||
as they hold the flag. If there is a single flag-holder, and the flag
|
||||
changes hands, a point is awarded to the new winner at the moment the
|
||||
flag moves.
|
||||
|
||||
|
||||
Puzzles
|
||||
-------
|
||||
|
||||
Most of the categories come in the form of multiple *puzzles*: for each
|
||||
puzzle presented, a key (answer) must be found to recieve the amount of
|
||||
points that puzzle is worth. Any team may answer any puzzle question at
|
||||
any time. A new puzzle is revealed when a team correctly answers the
|
||||
highest-valued puzzle in that category.
|
||||
|
||||
|
||||
Hints
|
||||
=====
|
||||
|
||||
If you are really stuck, you can ask for a hint. It will cost you
|
||||
points, though. For puzzles, you will lose ¼ of the points for that
|
||||
puzzle <em>even if you never solve the puzzle</em>. For other events,
|
||||
the staff member will decide how many points it will cost. You can try
|
||||
to bribe or otherwise fanagle information out of us or other
|
||||
contestants. *It's a hacking contest.*
|
||||
|
||||
About Us
|
||||
========
|
||||
|
||||
We are the <a href="http://dirtbags.net/">dirtbags</a>. People pay us
|
||||
money to do the sorts of things you'll be doing in this contest.
|
|
@ -1,161 +0,0 @@
|
|||
Title: Counting in different bases
|
||||
|
||||
<table style="background-color: #444;">
|
||||
<tr><th>Base</th></tr>
|
||||
<tr>
|
||||
<th>2</th>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>10</td>
|
||||
<td>11</td>
|
||||
<td>100</td>
|
||||
<td>101</td>
|
||||
<td>110</td>
|
||||
<td>111</td>
|
||||
<td>1000</td>
|
||||
<td>1001</td>
|
||||
<td>1010</td>
|
||||
<td>1011</td>
|
||||
<td>1100</td>
|
||||
<td>1101</td>
|
||||
<td>1110</td>
|
||||
<td>1111</td>
|
||||
<td>10000</td>
|
||||
<td>10001</td>
|
||||
<td>10010</td>
|
||||
<td>10011</td>
|
||||
<td>10100</td>
|
||||
<td>10101</td>
|
||||
<td>10110</td>
|
||||
<td>10111</td>
|
||||
<td>11000</td>
|
||||
<td>11001</td>
|
||||
<td>11010</td>
|
||||
<td>11011</td>
|
||||
<td>11100</td>
|
||||
<td>11101</td>
|
||||
<td>11110</td>
|
||||
<td>11111</td>
|
||||
<td>100000</td>
|
||||
<td>100001</td>
|
||||
<td>100010</td>
|
||||
<td>100011</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>8</th>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td>6</td>
|
||||
<td>7</td>
|
||||
<td>10</td>
|
||||
<td>11</td>
|
||||
<td>12</td>
|
||||
<td>13</td>
|
||||
<td>14</td>
|
||||
<td>15</td>
|
||||
<td>16</td>
|
||||
<td>17</td>
|
||||
<td>20</td>
|
||||
<td>21</td>
|
||||
<td>22</td>
|
||||
<td>23</td>
|
||||
<td>24</td>
|
||||
<td>25</td>
|
||||
<td>26</td>
|
||||
<td>27</td>
|
||||
<td>30</td>
|
||||
<td>31</td>
|
||||
<td>32</td>
|
||||
<td>33</td>
|
||||
<td>34</td>
|
||||
<td>35</td>
|
||||
<td>36</td>
|
||||
<td>37</td>
|
||||
<td>40</td>
|
||||
<td>41</td>
|
||||
<td>42</td>
|
||||
<td>43</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>10</th>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td>6</td>
|
||||
<td>7</td>
|
||||
<td>8</td>
|
||||
<td>9</td>
|
||||
<td>10</td>
|
||||
<td>11</td>
|
||||
<td>12</td>
|
||||
<td>13</td>
|
||||
<td>14</td>
|
||||
<td>15</td>
|
||||
<td>16</td>
|
||||
<td>17</td>
|
||||
<td>18</td>
|
||||
<td>19</td>
|
||||
<td>20</td>
|
||||
<td>21</td>
|
||||
<td>22</td>
|
||||
<td>23</td>
|
||||
<td>24</td>
|
||||
<td>25</td>
|
||||
<td>26</td>
|
||||
<td>27</td>
|
||||
<td>28</td>
|
||||
<td>29</td>
|
||||
<td>30</td>
|
||||
<td>31</td>
|
||||
<td>32</td>
|
||||
<td>33</td>
|
||||
<td>34</td>
|
||||
<td>35</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>16</th>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td>6</td>
|
||||
<td>7</td>
|
||||
<td>8</td>
|
||||
<td>9</td>
|
||||
<td>a</td>
|
||||
<td>b</td>
|
||||
<td>c</td>
|
||||
<td>d</td>
|
||||
<td>e</td>
|
||||
<td>f</td>
|
||||
<td>10</td>
|
||||
<td>11</td>
|
||||
<td>12</td>
|
||||
<td>13</td>
|
||||
<td>14</td>
|
||||
<td>15</td>
|
||||
<td>16</td>
|
||||
<td>17</td>
|
||||
<td>18</td>
|
||||
<td>19</td>
|
||||
<td>1a</td>
|
||||
<td>1b</td>
|
||||
<td>1c</td>
|
||||
<td>1d</td>
|
||||
<td>1e</td>
|
||||
<td>1f</td>
|
||||
<td>20</td>
|
||||
<td>21</td>
|
||||
<td>22</td>
|
||||
<td>23</td>
|
||||
</tr>
|
||||
</table>
|
|
@ -1,332 +0,0 @@
|
|||
Title: Tanks Documentation
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
You are the proud new operator of a M-375 Pflanzarr Tank. Your tank is
|
||||
equipped with a powerful laser cannon, independently rotating turret
|
||||
section, up to 10 enemy detection sensors, and a standard issue NATO
|
||||
hull. Unfortunately, it lacks seats, and thus must rely own its own
|
||||
wits and your skills at designing those wits to survive.
|
||||
|
||||
|
||||
An Example Tank
|
||||
===============
|
||||
|
||||
You can to paste this tank code into the [submit page](submit.html) page
|
||||
to get started. Then, start changing things to see how it affects your
|
||||
tank's behavior.
|
||||
|
||||
>addsensor(50, 0, 5, 1); # Sensor 0: Fire Sensor
|
||||
>addsensor(30, 0, 50); # Sensor 1: Anti-collision sensor
|
||||
|
||||
# Commands
|
||||
: move(90, 100) . turretset(0); # Always do this
|
||||
sense(0) : fire(); # If sensor 0 is active, fire
|
||||
sense(1) : move(-100, 100) # If sensor 1 is active, turn
|
||||
|
||||
|
||||
Programming Your Tank
|
||||
=====================
|
||||
|
||||
Your tanks are programmed using the Super Useful Command and Kontrol
|
||||
language, the very best in laser tank AI languages. It includes amazing
|
||||
features such as comments (Started by a #, ended at EOL), logic,
|
||||
versatility, and semi-colons (all lines must end in one). As with all
|
||||
new military systems it utilizes only integers; we must never rest in
|
||||
our diligence against the communist floating point conspiracy.
|
||||
Whitespace is provided by trusted contractors, and should never
|
||||
interfere with operations.
|
||||
|
||||
Your program should be separated into Setup and AI commands. The
|
||||
definitions section lets you designated the behaviors of its
|
||||
sensors and memory. Each setup command must begin with a
|
||||
'>'. Placing setup commands after the first AI command is a
|
||||
violation of protocol. Here are some examples of correct setup
|
||||
commands:
|
||||
|
||||
>addsensor(80, 90, 33);
|
||||
>addsensor(50, 0, 10, 1);
|
||||
>addtimer(3);
|
||||
|
||||
The AI section will act as the brain of your tank. Each AI line
|
||||
is separated into a group of conditions functions and a group of
|
||||
action functions. If all the conditions are satisfactory (true),
|
||||
all of the actions are given as orders. Conditions are separated
|
||||
by ampersands, actions separated by periods. Here are some
|
||||
examples of AI commands:
|
||||
|
||||
sense(1) & sense(2) & fireready() : fire();
|
||||
sense(0,0)&sin(5): move(40, 30) . turretcw(50);
|
||||
sense(4) & random(4,5) : led(1).settoggle(0,1);
|
||||
|
||||
Your tank will execute its program each turn(frame), and attempt
|
||||
to the best of its abilities to carry out its orders (or die
|
||||
trying). Like any military mind, your tank may receive a plethora
|
||||
of often conflicting orders and information. This a SMART TANK,
|
||||
however. It knows that the proper thing to do with each subsystem
|
||||
is to have that subsystem follow only the last order given each
|
||||
turn.
|
||||
|
||||
#Setup commands define your tank when your program
|
||||
#compiles
|
||||
>addsensor(50, 0, 5, 1); # 0-Fire Sensor
|
||||
>addsensor(30, 0, 180); # 1-Anti-collision sensor
|
||||
|
||||
# These commands execute each frame.
|
||||
# Blank condition sections are true.
|
||||
: move(90, 100).
|
||||
turretset(0);
|
||||
sense(0) : fire();
|
||||
sense(1) : move(-100, 100)
|
||||
|
||||
Setup Actions
|
||||
-------------
|
||||
|
||||
These functions can be used to setup your tank. Abuse of these
|
||||
functions has, in the past, resulted in mine sweeping duty. With
|
||||
a broom.
|
||||
|
||||
<dl>
|
||||
<dt>addsensor(range, angle, width, [turretAttached])</dt>
|
||||
<dd>
|
||||
<p>Add a new sensor to the tank.</p>
|
||||
<p>
|
||||
Sensors are an arc (pie slice) centered on the tank that
|
||||
detect other tanks within their sweep.<br/>
|
||||
A sensor is 'on' if another tank is within this arc.
|
||||
</p>
|
||||
<p>
|
||||
Sensors are numbered, starting at 0, in the order they are
|
||||
added.
|
||||
</p>
|
||||
<p>
|
||||
range - The range of the sensor, as a percent of the tanks max
|
||||
range.<br/>
|
||||
angle - The angle of the center of the sensor, in degrees.<br />
|
||||
width - The width of the sensor, in degrees.<br />
|
||||
turretAttached - Normally, the angle is relative to the front of
|
||||
the
|
||||
tank.<br /> When this is set, the angle is relative to the current
|
||||
turret
|
||||
direction.<br />
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>addtimer(timeout)</dt>
|
||||
<dd>
|
||||
<p>
|
||||
Add a new timer (they're numbered in the order added, starting from 0),
|
||||
with the given timeout.
|
||||
</p>
|
||||
<p>
|
||||
The timeout is in number of turns.<br />
|
||||
The timer
|
||||
is created in inactive mode.<br /> You'll need to do a starttimer()
|
||||
action
|
||||
to reset and start the timer.<br /> When the timer expires, the
|
||||
timer()
|
||||
condition will begin to return True.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>addtoggle([state])</dt>
|
||||
<dd>
|
||||
<p>Add a toggle to the tank.</p>
|
||||
<p>
|
||||
The state of the toggle defaults to 0 (False).<br />
|
||||
ese essentially act as a single bit of memory.<br />
|
||||
e the toggle() condition to check its state and the settoggle,
|
||||
eartoggle,
|
||||
d toggle actions to change the state.<br /> Toggles are named
|
||||
merically,
|
||||
arting at 0.
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
Conditions
|
||||
----------
|
||||
|
||||
These functions are used to check the state of reality. If reality
|
||||
stops being real, refer to chapter 5 in your girl scout handbook.
|
||||
|
||||
<dl>
|
||||
<dt>cos(T)</dt>
|
||||
<dd>
|
||||
<p>
|
||||
A cos wave with period T (in turns).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Returns True when the wave is
|
||||
positive.<br /> A wave of period 1 is always True.<br /> Period
|
||||
2 is True every
|
||||
other turn, etc.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>firenotready()</dt>
|
||||
<dd>
|
||||
<p>
|
||||
True when the tank can not fire.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>fireready()</dt>
|
||||
<dd>
|
||||
<p>
|
||||
True when the tank can fire.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>random(n,m)</dt>
|
||||
<dd>
|
||||
<p>Generate a random number.</p>
|
||||
|
||||
<p>
|
||||
Takes two
|
||||
arguments, n and m.<br /> Generates a random number between 1
|
||||
and m (inclusive) each time it's checked.<br /> If the random
|
||||
number is less
|
||||
than or equal
|
||||
to n, then the condition returns True.<br /> Returns False
|
||||
otherwise.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>sense(#, [invert])</dt>
|
||||
<dd>
|
||||
<p>True when a sensor is activated.</p>
|
||||
|
||||
<p>
|
||||
Takes a Sensor number as an argument.<br />
|
||||
|
||||
Returns True if the given sensor is currently activated, False
|
||||
otherwise.<br />
|
||||
If the option argument invert is set to true then logic is
|
||||
inverted,
|
||||
and then sensor returns True when it is NOT activated, and
|
||||
False when
|
||||
it is.<br /> Invert is false by default.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>sin(T)</dt>
|
||||
<dd>
|
||||
<p>A sin wave of period T (in turns).</p>
|
||||
|
||||
<p>
|
||||
Returns True when the wave is positive.<br />
|
||||
A wave with period 1 or 2 is always False (it's 0 each turn),
|
||||
only
|
||||
at periods of 3 or more does this become useful.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>timer(#, [invert])</dt>
|
||||
|
||||
<dd>
|
||||
<p>Checks the state of timer # 'key'.</p>
|
||||
|
||||
<p>
|
||||
Returns True if time has run
|
||||
out.<br />
|
||||
|
||||
If invert is given (and true), then True is returned if the
|
||||
timer has
|
||||
yet to expire.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>toggle(#)</dt>
|
||||
<dd>
|
||||
<p>Returns True if the given toggle is set, False
|
||||
otherwise.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
Actions
|
||||
-------
|
||||
|
||||
These actions are not for cowards. Remember, if actions contradict,
|
||||
your tank will simply do the last thing it was told in a turn. If
|
||||
ordered to hop on a plane to hell it will gladly do so. If order to
|
||||
make tea shortly afterwards, it will serve it politely and with cookies
|
||||
instead.
|
||||
|
||||
|
||||
<dl>
|
||||
<dt>cleartimer(#)</dt>
|
||||
<dd>
|
||||
<p>Clear the given timer such that it is no longer active (inactive timers
|
||||
are always False).</p>
|
||||
</dd>
|
||||
|
||||
<dt>fire()</dt>
|
||||
<dd>
|
||||
<p>Attempt to fire the tanks laser cannon.</p>
|
||||
<p>
|
||||
Its range is 50% of your sensor range.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>led(state)</dt>
|
||||
<dd>
|
||||
<p>Set the tank's LED to state (true is on, false is off).</p>
|
||||
<p>
|
||||
The led is a light that appears behind the tanks turret.<br/>
|
||||
It remains on for a single turn.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>move(left tread speed, right tread speed)</dt>
|
||||
<dd>
|
||||
<p>Set the speeds for the tanks right and left treads.</p>
|
||||
|
||||
<p>
|
||||
The speeds should
|
||||
be a number (percent power) between -100 and
|
||||
100.
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
<dt>settoggle(key, state)</dt>
|
||||
<dd>
|
||||
<p>Set toggle 'key' to 'state'.</p>
|
||||
</dd>
|
||||
|
||||
<dt>starttimer(#)</dt>
|
||||
<dd>
|
||||
<p>Start (and reset) the given timer, but only if it is
|
||||
inactive.</p>
|
||||
</dd>
|
||||
|
||||
<dt>toggle(key)</dt>
|
||||
<dd>
|
||||
<p>Toggle the value of toggle 'key'.</p>
|
||||
</dd>
|
||||
|
||||
<dt>turretccw([percent speed])</dt>
|
||||
<dd>
|
||||
<p>Rotate the turret counter clockwise as a
|
||||
percentage of the max speed.</p>
|
||||
</dd>
|
||||
|
||||
<dt>turretcw([percent speed])</dt>
|
||||
<dd>
|
||||
<p>Rotate the turret clockwise at a rate
|
||||
preportional to speed.</p>
|
||||
</dd>
|
||||
|
||||
<dt>turretset([angle])</dt>
|
||||
<dd>
|
||||
<p>Set the turret to the given angle, in degrees, relative to the
|
||||
front of the tank.</p>
|
||||
<p>
|
||||
Angles increase counterclockwise.<br/> The angle can be left
|
||||
out; in that case, this locks the turret to its current
|
||||
position.
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
|
@ -1,10 +0,0 @@
|
|||
<h3>Tanks</h3>
|
||||
<ul>
|
||||
<li><a href="docs.html">Docs</a></li>
|
||||
<li><a href="results.cgi">Results</a></li>
|
||||
<li><a href="submit.html">Submit</a></li>
|
||||
<li><a href="errors.cgi">My Errors</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
Title: Tanks Submission
|
||||
|
||||
<form action="submit.cgi" method="post">
|
||||
<fieldset>
|
||||
<legend>Your program:</legend>
|
||||
Team:
|
||||
<input type="text" name="team"/><br/>
|
||||
Password:
|
||||
<input type="password" name="passwd"/><br/>
|
||||
<textarea cols="80" rows="30" name="code"></textarea><br/>
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
</fieldset>
|
||||
</form>
|
|
@ -1,6 +1,8 @@
|
|||
TARGETS = in.tokend pointscli claim.cgi puzzler.cgi puzzles.cgi
|
||||
|
||||
all: $(TARGETS)
|
||||
all: build
|
||||
|
||||
build: $(TARGETS)
|
||||
|
||||
in.tokend: in.tokend.o xxtea.o common.o
|
||||
pointscli: pointscli.o common.o
|
||||
|
|
240
src/scoreboard
240
src/scoreboard
|
@ -1,240 +0,0 @@
|
|||
#! /usr/bin/awk -f
|
||||
|
||||
##
|
||||
##
|
||||
## I'm not happy with how this code looks. I've
|
||||
##
|
||||
##
|
||||
|
||||
function qsort(A, left, right, i, last) {
|
||||
if (left >= right)
|
||||
return
|
||||
swap(A, left, left+int((right-left+1)*rand()))
|
||||
last = left
|
||||
for (i = left+1; i <= right; i++)
|
||||
if (A[i] < A[left])
|
||||
swap(A, ++last, i)
|
||||
swap(A, left, last)
|
||||
qsort(A, left, last-1)
|
||||
qsort(A, last+1, right)
|
||||
}
|
||||
function swap(A, i, j, t) {
|
||||
t = A[i]; A[i] = A[j]; A[j] = t
|
||||
}
|
||||
|
||||
function escape(s) {
|
||||
gsub("&", "&", s)
|
||||
gsub("<", "<", s)
|
||||
gsub(">", ">", s)
|
||||
return s
|
||||
}
|
||||
|
||||
function print_bar(cat, team, n, d) {
|
||||
printf("<div class=\"t%s score\"" \
|
||||
" style=\"height: %.2f%%;\"" \
|
||||
" onmouseover=\"highlight('%s')\"" \
|
||||
" onmouseout=\"restore('%s')\">\n" \
|
||||
"<!-- %s --> %s: %s\n" \
|
||||
"</div>",
|
||||
team,
|
||||
100 * n / d,
|
||||
team,
|
||||
team,
|
||||
cat, escape(names_by_team[team]), n)
|
||||
}
|
||||
|
||||
function output( t, c) {
|
||||
for (t in teams) {
|
||||
score = 0;
|
||||
for (c in points_by_cat) {
|
||||
if (points_by_cat[c] > 0) {
|
||||
score += points_by_cat_team[c, t] / points_by_cat[c];
|
||||
}
|
||||
}
|
||||
if (score > maxscore) {
|
||||
maxscore = score
|
||||
}
|
||||
if (score > maxscores_by_team[t]) {
|
||||
maxscores_by_team[t] = score
|
||||
}
|
||||
scores_by_team_time[t, lasttime] = score
|
||||
}
|
||||
timestamps[tslen++] = lasttime
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
base = ENVIRON["CTF_BASE"]
|
||||
if (! base) {
|
||||
base = "/srv/ctf"
|
||||
}
|
||||
|
||||
# Only display two decimal places
|
||||
CONVFMT = "%.2f"
|
||||
|
||||
# New point at least every 2.5 minutes
|
||||
interval = 150
|
||||
tslen = 0
|
||||
|
||||
while (1 == getline) {
|
||||
time = $1
|
||||
team = $2
|
||||
cat = $3
|
||||
points = int($4)
|
||||
|
||||
if (! start) {
|
||||
start = time
|
||||
}
|
||||
|
||||
if (time > (outtime + interval)) {
|
||||
outtime = time
|
||||
output()
|
||||
}
|
||||
lasttime = time
|
||||
|
||||
teams[team] = team
|
||||
points_by_cat[cat] += points
|
||||
points_by_cat_team[cat, team] += points
|
||||
}
|
||||
|
||||
output()
|
||||
|
||||
# Get team colors and names
|
||||
for (team in teams) {
|
||||
fn = base "/teams/colors/" team
|
||||
getline colors_by_team[team] < fn
|
||||
close(fn)
|
||||
|
||||
fn = base "/teams/names/" team
|
||||
getline names_by_team[team] < fn
|
||||
close(fn)
|
||||
}
|
||||
|
||||
# Sort categories
|
||||
ncats = 0
|
||||
for (cat in points_by_cat) {
|
||||
cats[ncats++] = cat
|
||||
}
|
||||
qsort(cats, 0, ncats-1)
|
||||
|
||||
# Create a sorted list of scores
|
||||
nteams = 0
|
||||
for (team in teams) {
|
||||
scores[nteams++] = scores_by_team_time[team, lasttime]
|
||||
}
|
||||
qsort(scores, 0, nteams-1)
|
||||
|
||||
|
||||
# Now we can start writing the document
|
||||
print "<!DOCTYPE html>"
|
||||
print "<html>"
|
||||
print " <head>"
|
||||
print " <title>Scoreboard</title>"
|
||||
print " <link rel=\"stylesheet\" href=\"ctf.css\" type=\"text/css\">"
|
||||
print " <script type=\"application/javascript\" src=\"scoreboard.js\"></script>"
|
||||
|
||||
# Provide raw data for the chart
|
||||
print " <script type=\"application/javascript\">"
|
||||
print "function init() {"
|
||||
printf(" plot(\"chart\", %d, %.2f, {\n", tslen, maxscore)
|
||||
c = 0
|
||||
for (team in teams) {
|
||||
if (maxscores_by_team[team] / maxscore < 0.01) continue
|
||||
printf(" \"%s\": [\"#%s\",[", team, colors_by_team[team])
|
||||
for (i = 1; i < tslen; i += 1) {
|
||||
time = timestamps[i]
|
||||
printf("[%d,%.2f],",
|
||||
i, scores_by_team_time[team, time])
|
||||
}
|
||||
printf("]],\n");
|
||||
}
|
||||
print " });"
|
||||
print " if (location.hash) {"
|
||||
print " cycle();"
|
||||
print " setInterval(cycle, 10000);"
|
||||
print " }"
|
||||
print "}"
|
||||
print "window.onload = init;"
|
||||
print " </script>"
|
||||
|
||||
# Reload every minute
|
||||
print " <meta http-equiv=\"refresh\" content=\"60\">"
|
||||
|
||||
# Set up team colors and a few page-specific styles
|
||||
print " <style type=\"text/css\">"
|
||||
print " body { width: 100%; }"
|
||||
print " .score { overflow: hidden; color: black; }"
|
||||
for (team in teams) {
|
||||
printf(" .t%s { background-color: #%s; }\n",
|
||||
team, colors_by_team[team])
|
||||
}
|
||||
print " </style>"
|
||||
|
||||
print " </head>"
|
||||
print " <body>"
|
||||
print " <h1>Scoreboard</h1>"
|
||||
print "<p id=\"debug\"></p>"
|
||||
print " <table id=\"scoreboard\">"
|
||||
print " <tr>"
|
||||
print " <th>Overall</th>"
|
||||
|
||||
# Print out category names
|
||||
for (i = 0; i < ncats; i += 1) {
|
||||
cat = cats[i]
|
||||
points = points_by_cat[cat]
|
||||
if (0 == points) continue
|
||||
printf("<th>%s (%d)</th>\n", cat, points)
|
||||
}
|
||||
|
||||
print " </tr>"
|
||||
print " <tr>"
|
||||
|
||||
# Print out teams, ranked by score
|
||||
print " <td>"
|
||||
for (i = 0; i < nteams; i += 1) {
|
||||
if (scores[i] == scores[i-1]) continue;
|
||||
for (team in teams) {
|
||||
if (scores[i] == scores_by_team_time[team, lasttime]) {
|
||||
name = names_by_team[team]
|
||||
print_bar("total", team, scores[i], ncats)
|
||||
}
|
||||
}
|
||||
}
|
||||
print " </td>"
|
||||
|
||||
# Print out scores within each category
|
||||
for (i = 0; i < ncats; i += 1) {
|
||||
cat = cats[i]
|
||||
points = points_by_cat[cat]
|
||||
if (0 == points) break;
|
||||
|
||||
print "<td>"
|
||||
|
||||
# Create sorted list of scores in this category
|
||||
n = 0
|
||||
for (team in teams) {
|
||||
l[n++] = points_by_cat_team[cat, team];
|
||||
}
|
||||
qsort(l, 0, n-1)
|
||||
|
||||
# Print out teams, ranked by points
|
||||
for (j = 0; j < n; j += 1) {
|
||||
if (l[j] == l[j-1]) continue;
|
||||
if (0 == l[j]) break;
|
||||
for (team in teams) {
|
||||
points = points_by_cat_team[cat, team]
|
||||
if (l[j] == points) {
|
||||
name = names_by_team[team]
|
||||
print_bar(cat, team, points, points_by_cat[cat])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print "</td>"
|
||||
}
|
||||
print " </tr>"
|
||||
|
||||
print " </table>"
|
||||
print " <canvas id=\"chart\" width=\"800\" height=\"400\"></canvas>"
|
||||
print " </body>"
|
||||
print "</html>"
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
src-%:
|
||||
$(MAKE) -C src $*
|
|
@ -1,7 +1,7 @@
|
|||
#! /bin/sh -e
|
||||
|
||||
die () {
|
||||
echo $*
|
||||
echo "$*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ done
|
|||
mkdir -p $CTF_BASE/teams/names
|
||||
mkdir -p $CTF_BASE/teams/colors
|
||||
for team in team1 team2 team3; do
|
||||
hash=$(./register $team | awk '{print $NF;}')
|
||||
hash=$(bin/register $team | awk '{print $NF;}')
|
||||
done
|
||||
|
||||
|
||||
|
@ -37,35 +37,35 @@ done
|
|||
## Puzzler tests
|
||||
##
|
||||
|
||||
if ./puzzles.cgi | grep 20; then
|
||||
if src/puzzles.cgi | grep 20; then
|
||||
die "20 points puzzles shouldn't show up here"
|
||||
fi
|
||||
|
||||
if ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer20 | grep -q 'awarded'; then
|
||||
if src/puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer20 | grep -q 'awarded'; then
|
||||
die "Awarded points with wrong answer"
|
||||
fi
|
||||
|
||||
if ./puzzler.cgi t=$hash c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then
|
||||
if src/puzzler.cgi t=$hash c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then
|
||||
die "Awarded points with wrong category"
|
||||
fi
|
||||
|
||||
if ./puzzler.cgi t=$hash c=cat1 p=20 a=cat1answer10 | grep -q 'awarded'; then
|
||||
if src/puzzler.cgi t=$hash c=cat1 p=20 a=cat1answer10 | grep -q 'awarded'; then
|
||||
die "Awarded points with wrong point value"
|
||||
fi
|
||||
|
||||
if ./puzzler.cgi t=merfmerfmerfmerf c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then
|
||||
if src/puzzler.cgi t=merfmerfmerfmerf c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then
|
||||
die "Awarded points with bad team"
|
||||
fi
|
||||
|
||||
if ! ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then
|
||||
if ! src/puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then
|
||||
die "Didn't award points for correct answer"
|
||||
fi
|
||||
|
||||
if ! ./puzzles.cgi | grep -q 20; then
|
||||
if ! src/puzzles.cgi | grep -q 20; then
|
||||
die "20 point answer didn't show up"
|
||||
fi
|
||||
|
||||
if ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then
|
||||
if src/puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then
|
||||
die "Awarded same points twice"
|
||||
fi
|
||||
|
||||
|
@ -73,11 +73,11 @@ fi
|
|||
## Scoreboard tests
|
||||
##
|
||||
|
||||
if ! cat $CTF_BASE/points.new/* | ./scoreboard | grep -q 'total.*team3: 1'; then
|
||||
if ! cat $CTF_BASE/points.new/* | bin/scoreboard | grep -q 'total.*team3: 1'; then
|
||||
die "Scoreboard total incorrect"
|
||||
fi
|
||||
|
||||
if ! cat $CTF_BASE/points.new/* | ./scoreboard | grep -q 'cat1.*team3: 10'; then
|
||||
if ! cat $CTF_BASE/points.new/* | bin/scoreboard | grep -q 'cat1.*team3: 10'; then
|
||||
die "Scoreboard cat1 points incorrect"
|
||||
fi
|
||||
|
||||
|
@ -89,25 +89,25 @@ mkdir -p $CTF_BASE/token.keys
|
|||
echo -n '0123456789abcdef' > $CTF_BASE/token.keys/tokencat
|
||||
|
||||
# in.tokend uses a random number generator
|
||||
echo -n 'tokencat' | ./in.tokend > /dev/null
|
||||
echo -n 'tokencat' | src/in.tokend > /dev/null
|
||||
|
||||
if ! grep -q 'tokencat:x....-....x' $CTF_BASE/tokens.db; then
|
||||
die "in.tokend didn't write to database"
|
||||
fi
|
||||
|
||||
if ./claim.cgi t=lalalala k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
|
||||
if src/claim.cgi t=lalalala k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
|
||||
die "claim.cgi gave points to a bogus team"
|
||||
fi
|
||||
|
||||
if ./claim.cgi t=$hash k=tokencat:xanax-xanax | grep -q success; then
|
||||
if src/claim.cgi t=$hash k=tokencat:xanax-xanax | grep -q success; then
|
||||
die "claim.cgi gave points for a bogus token"
|
||||
fi
|
||||
|
||||
if ! ./claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
|
||||
if ! src/claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
|
||||
die "claim.cgi didn't give me any points"
|
||||
fi
|
||||
|
||||
if ./claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
|
||||
if src/claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
|
||||
die "claim.cgi gave me points twice for the same token"
|
||||
fi
|
||||
|
||||
|
@ -115,5 +115,4 @@ if ! [ -f $CTF_BASE/points.new/*.$hash.tokencat.1 ]; then
|
|||
die "claim.cgi didn't actually record any points"
|
||||
fi
|
||||
|
||||
echo "$0: All tests passed!"
|
||||
echo "$0: Aren't you just the best programmer ever?"
|
||||
echo "All tests passed! You're the best programmer ever!"
|
Loading…
Reference in New Issue