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
|
SUBDIRS = src
|
||||||
VAR = $(BASE)/var
|
|
||||||
WWW = $(BASE)/www
|
|
||||||
LIB = $(BASE)/lib
|
|
||||||
BIN = $(BASE)/bin
|
|
||||||
SBIN = $(BASE)/sbin
|
|
||||||
BASE_URL = /
|
|
||||||
USERNAME = www-data
|
|
||||||
|
|
||||||
TEMPLATE = $(CURDIR)/template.html
|
all: build
|
||||||
MDWNTOHTML = $(CURDIR)/mdwntohtml.py --template=$(TEMPLATE) --base=$(BASE_URL)
|
|
||||||
|
|
||||||
default: install
|
|
||||||
|
|
||||||
SUBDIRS = mdwn
|
|
||||||
INSTALL_TARGETS = $(addsuffix -install, $(SUBDIRS))
|
|
||||||
include $(addsuffix /*.mk, $(SUBDIRS))
|
include $(addsuffix /*.mk, $(SUBDIRS))
|
||||||
|
|
||||||
install: base-install $(INSTALL_TARGETS)
|
test: build
|
||||||
install --directory --owner=$(USERNAME) $(VAR)/tanks
|
./test.sh
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
build: $(addsuffix -build, $(SUBDIRS))
|
||||||
clean: $(addsuffix -clean, $(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
|
#! /bin/sh
|
||||||
|
|
||||||
# First argument is seconds between running everything
|
# First argument is seconds between running everything
|
||||||
cycle=${1:-60}
|
period=${1:-60}
|
||||||
|
|
||||||
POINTS=var/points/log
|
CTF_BASE=${CTF_BASE:-/srv/ctf} export CTF_BASE
|
||||||
|
|
||||||
cd $(dirname $0)/..
|
|
||||||
|
|
||||||
|
POINTS=$CTF_BASE/points.log
|
||||||
|
SCOREBOARD=$CTF_BASE/www/scoreboard.html
|
||||||
|
PUZZLES=$CTF_BASE/www/puzzles.html
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
# Timestamp
|
|
||||||
start=$(date +%s)
|
start=$(date +%s)
|
||||||
next=$(expr $start + $cycle)
|
next=$(expr $start + $period)
|
||||||
|
|
||||||
# If enabled, run tanks
|
|
||||||
if ! [ -f var/disabled/tanks ]; then
|
|
||||||
sbin/run-tanks --no-barren-points --once var/tanks
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect any new points
|
# Collect any new points
|
||||||
for fn in var/points/cur/*; do
|
for fn in $CTF_BASE/points.new/*; do
|
||||||
[ -f $fn ] || continue
|
[ -f $fn ] || continue
|
||||||
cat $fn >> $POINTS || break
|
cat $fn >> $POINTS || break
|
||||||
rm $fn
|
rm $fn
|
||||||
done
|
done
|
||||||
|
|
||||||
# Update the scoreboard
|
if [ $POINTS -nt $SCOREBOARD ]; then
|
||||||
if [ -f $POINTS ]; then
|
$CTF_BASE/sbin/scoreboard < $POINTS > $SCOREBOARD.new
|
||||||
sbin/scoreboard -t www/scoreboard.html -j www/myplot.js < $POINTS
|
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
|
fi
|
||||||
|
|
||||||
# Wait until the next minute
|
|
||||||
now=$(date +%s)
|
now=$(date +%s)
|
||||||
if [ $now -lt $next ]; then
|
if [ $now -lt $next ]; then
|
||||||
sleep $(expr $next - $now)
|
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
|
## I'm not happy with how this code looks. I've
|
||||||
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
|
|
||||||
|
|
||||||
flags_dir = os.path.join(paths.VAR, 'flags')
|
function qsort(A, left, right, i, last) {
|
||||||
sys.stdin = codecs.getreader('utf-8')(sys.stdin)
|
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):
|
function escape(s) {
|
||||||
dict[key] = dict.get(key, 0) + amt
|
gsub("&", "&", s)
|
||||||
|
gsub("<", "<", s)
|
||||||
|
gsub(">", ">", s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
class Chart:
|
function print_bar(cat, team, n, d) {
|
||||||
def __init__(self):
|
printf("<div class=\"t%s score\"" \
|
||||||
self.points_by_cat = {}
|
" style=\"height: %.2f%%;\"" \
|
||||||
self.points_by_cat_team = {}
|
" onmouseover=\"highlight('%s')\"" \
|
||||||
self.high_score = 0.001
|
" onmouseout=\"restore('%s')\">\n" \
|
||||||
self.teams = set()
|
"<!-- %s --> %s: %s\n" \
|
||||||
self.cats = set()
|
"</div>",
|
||||||
self.log = []
|
team,
|
||||||
|
100 * n / d,
|
||||||
|
team,
|
||||||
|
team,
|
||||||
|
cat, escape(names_by_team[team]), n)
|
||||||
|
}
|
||||||
|
|
||||||
def add_points(self, when, cat, team, points):
|
function output( t, c) {
|
||||||
self.log.append((when, cat, team, points))
|
for (t in teams) {
|
||||||
self.teams.add(team)
|
score = 0;
|
||||||
self.cats.add(cat)
|
for (c in points_by_cat) {
|
||||||
incdict(self.points_by_cat, cat, points)
|
if (points_by_cat[c] > 0) {
|
||||||
incdict(self.points_by_cat_team, (cat, team), points)
|
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):
|
BEGIN {
|
||||||
points = 0
|
base = ENVIRON["CTF_BASE"]
|
||||||
for cat, tot in self.points_by_cat.items():
|
if (! base) {
|
||||||
if not tot:
|
base = "/srv/ctf"
|
||||||
continue
|
}
|
||||||
team_points = self.team_points_in_cat(cat, team)
|
|
||||||
points += team_points / float(tot)
|
|
||||||
return points
|
|
||||||
|
|
||||||
def team_points_in_cat(self, cat, team):
|
# Only display two decimal places
|
||||||
return self.points_by_cat_team.get((cat, team), 0)
|
CONVFMT = "%.2f"
|
||||||
|
|
||||||
def write_js(self, f):
|
# New point at least every 2.5 minutes
|
||||||
start = self.log[0][0]
|
interval = 150
|
||||||
end = self.log[-1][0]
|
tslen = 0
|
||||||
|
|
||||||
# Calculate high score
|
while (1 == getline) {
|
||||||
high_score = reduce(max, [self.team_points(t) for t in self.teams])
|
time = $1
|
||||||
|
team = $2
|
||||||
|
cat = $3
|
||||||
|
points = int($4)
|
||||||
|
|
||||||
width = end - start
|
if (! start) {
|
||||||
height = high_score * 1.1
|
start = time
|
||||||
|
}
|
||||||
|
|
||||||
f.write('function draw(id) {\n')
|
if (time > (outtime + interval)) {
|
||||||
f.write(' p = new Plot(id, %d, %.3f);\n' % (width, height))
|
outtime = time
|
||||||
for team in self.teams:
|
output()
|
||||||
f.write(' p.line("#%s",[' % teams.color(team))
|
}
|
||||||
score = 0
|
lasttime = time
|
||||||
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('}')
|
|
||||||
|
|
||||||
def make_table(self):
|
teams[team] = team
|
||||||
body = []
|
points_by_cat[cat] += points
|
||||||
body.append('<table class="scoreboard">')
|
points_by_cat_team[cat, team] += points
|
||||||
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>')
|
|
||||||
|
|
||||||
body.append('<tr>')
|
output()
|
||||||
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>')
|
|
||||||
|
|
||||||
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():
|
fn = base "/teams/names/" team
|
||||||
p = optparse.OptionParser(usage='%prog [options] < logfile')
|
getline names_by_team[team] < fn
|
||||||
p.add_option('-t', '--html', dest='html', default=None,
|
close(fn)
|
||||||
help='Write a web page to HTML')
|
}
|
||||||
p.add_option('-j', '--javascript', dest='js', default=None,
|
|
||||||
help='Write javascript params to JS')
|
|
||||||
|
|
||||||
opts, args = p.parse_args()
|
# Sort categories
|
||||||
if args:
|
ncats = 0
|
||||||
return p.print_help()
|
for (cat in points_by_cat) {
|
||||||
|
cats[ncats++] = cat
|
||||||
|
}
|
||||||
|
qsort(cats, 0, ncats-1)
|
||||||
|
|
||||||
chart = Chart()
|
# Create a sorted list of scores
|
||||||
for line in sys.stdin:
|
nteams = 0
|
||||||
line = line.strip()
|
for (team in teams) {
|
||||||
try:
|
scores[nteams++] = scores_by_team_time[team, lasttime]
|
||||||
date, qcat, qteam, points = line.split('\t')
|
}
|
||||||
except ValueError:
|
qsort(scores, 0, nteams-1)
|
||||||
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))
|
|
||||||
|
|
||||||
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__':
|
# Now we can start writing the document
|
||||||
main()
|
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
|
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
|
in.tokend: in.tokend.o xxtea.o common.o
|
||||||
pointscli: pointscli.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
|
#! /bin/sh -e
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo $*
|
echo "$*"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ done
|
||||||
mkdir -p $CTF_BASE/teams/names
|
mkdir -p $CTF_BASE/teams/names
|
||||||
mkdir -p $CTF_BASE/teams/colors
|
mkdir -p $CTF_BASE/teams/colors
|
||||||
for team in team1 team2 team3; do
|
for team in team1 team2 team3; do
|
||||||
hash=$(./register $team | awk '{print $NF;}')
|
hash=$(bin/register $team | awk '{print $NF;}')
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,35 +37,35 @@ done
|
||||||
## Puzzler tests
|
## Puzzler tests
|
||||||
##
|
##
|
||||||
|
|
||||||
if ./puzzles.cgi | grep 20; then
|
if src/puzzles.cgi | grep 20; then
|
||||||
die "20 points puzzles shouldn't show up here"
|
die "20 points puzzles shouldn't show up here"
|
||||||
fi
|
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"
|
die "Awarded points with wrong answer"
|
||||||
fi
|
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"
|
die "Awarded points with wrong category"
|
||||||
fi
|
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"
|
die "Awarded points with wrong point value"
|
||||||
fi
|
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"
|
die "Awarded points with bad team"
|
||||||
fi
|
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"
|
die "Didn't award points for correct answer"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! ./puzzles.cgi | grep -q 20; then
|
if ! src/puzzles.cgi | grep -q 20; then
|
||||||
die "20 point answer didn't show up"
|
die "20 point answer didn't show up"
|
||||||
fi
|
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"
|
die "Awarded same points twice"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -73,11 +73,11 @@ fi
|
||||||
## Scoreboard tests
|
## 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"
|
die "Scoreboard total incorrect"
|
||||||
fi
|
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"
|
die "Scoreboard cat1 points incorrect"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -89,25 +89,25 @@ mkdir -p $CTF_BASE/token.keys
|
||||||
echo -n '0123456789abcdef' > $CTF_BASE/token.keys/tokencat
|
echo -n '0123456789abcdef' > $CTF_BASE/token.keys/tokencat
|
||||||
|
|
||||||
# in.tokend uses a random number generator
|
# 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
|
if ! grep -q 'tokencat:x....-....x' $CTF_BASE/tokens.db; then
|
||||||
die "in.tokend didn't write to database"
|
die "in.tokend didn't write to database"
|
||||||
fi
|
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"
|
die "claim.cgi gave points to a bogus team"
|
||||||
fi
|
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"
|
die "claim.cgi gave points for a bogus token"
|
||||||
fi
|
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"
|
die "claim.cgi didn't give me any points"
|
||||||
fi
|
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"
|
die "claim.cgi gave me points twice for the same token"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -115,5 +115,4 @@ if ! [ -f $CTF_BASE/points.new/*.$hash.tokencat.1 ]; then
|
||||||
die "claim.cgi didn't actually record any points"
|
die "claim.cgi didn't actually record any points"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$0: All tests passed!"
|
echo "All tests passed! You're the best programmer ever!"
|
||||||
echo "$0: Aren't you just the best programmer ever?"
|
|
Loading…
Reference in New Issue