From 7e4f523347aec0795c44d41c1db72535354f94d4 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 13 Sep 2010 17:32:51 -0600 Subject: [PATCH] Remove more cruft, add polish --- Makefile | 77 +------- bin/badmathbot | 265 -------------------------- bin/in.flagd | 96 ---------- bin/in.heartbeatd | 5 - bin/in.pointsd | 19 -- bin/kevin | 136 -------------- bin/pointscli | 5 - bin/pollster | 285 ---------------------------- {src => bin}/register | 0 bin/run-ctf | 31 ++- bin/run-tanks | 61 ------ bin/scoreboard | 376 ++++++++++++++++++++++--------------- {src => bin}/teams.sh | 0 mdwn/mdwn.mk | 16 -- mdwn/src/index.mdwn | 15 -- mdwn/src/intro.mdwn | 103 ---------- mdwn/src/table.mdwn | 161 ---------------- mdwn/src/tanks/docs.mdwn | 332 -------------------------------- mdwn/src/tanks/links.xml | 10 - mdwn/src/tanks/submit.mdwn | 14 -- src/Makefile | 4 +- src/scoreboard | 240 ----------------------- src/src.mk | 2 + src/test.sh => test.sh | 37 ++-- 24 files changed, 265 insertions(+), 2025 deletions(-) delete mode 100755 bin/badmathbot delete mode 100755 bin/in.flagd delete mode 100755 bin/in.heartbeatd delete mode 100755 bin/in.pointsd delete mode 100755 bin/kevin delete mode 100755 bin/pointscli delete mode 100755 bin/pollster rename {src => bin}/register (100%) delete mode 100755 bin/run-tanks rename {src => bin}/teams.sh (100%) delete mode 100644 mdwn/mdwn.mk delete mode 100644 mdwn/src/index.mdwn delete mode 100644 mdwn/src/intro.mdwn delete mode 100644 mdwn/src/table.mdwn delete mode 100644 mdwn/src/tanks/docs.mdwn delete mode 100644 mdwn/src/tanks/links.xml delete mode 100644 mdwn/src/tanks/submit.mdwn delete mode 100755 src/scoreboard create mode 100644 src/src.mk rename src/test.sh => test.sh (59%) diff --git a/Makefile b/Makefile index a5e3f9a..65a9e15 100644 --- a/Makefile +++ b/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)) + diff --git a/bin/badmathbot b/bin/badmathbot deleted file mode 100755 index f6d0075..0000000 --- a/bin/badmathbot +++ /dev/null @@ -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() diff --git a/bin/in.flagd b/bin/in.flagd deleted file mode 100755 index 7a16261..0000000 --- a/bin/in.flagd +++ /dev/null @@ -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() diff --git a/bin/in.heartbeatd b/bin/in.heartbeatd deleted file mode 100755 index 3157e96..0000000 --- a/bin/in.heartbeatd +++ /dev/null @@ -1,5 +0,0 @@ -#! /bin/sh - -ip=$(echo $UDPREMOTEADDR | cut -d: -f1) -touch $1/$ip -echo 'Hello.' diff --git a/bin/in.pointsd b/bin/in.pointsd deleted file mode 100755 index 0b7b7fb..0000000 --- a/bin/in.pointsd +++ /dev/null @@ -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 diff --git a/bin/kevin b/bin/kevin deleted file mode 100755 index f0e533c..0000000 --- a/bin/kevin +++ /dev/null @@ -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() diff --git a/bin/pointscli b/bin/pointscli deleted file mode 100755 index fecb8ff..0000000 --- a/bin/pointscli +++ /dev/null @@ -1,5 +0,0 @@ -#! /usr/bin/python - -from ctf import pointscli - -pointscli.main() diff --git a/bin/pollster b/bin/pollster deleted file mode 100755 index 5b11c46..0000000 --- a/bin/pollster +++ /dev/null @@ -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 : using the - specified , send the specified and return the - response or None if something went wrong. 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 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('

%s

\n' % ip) - out.write('\n') - out.write('\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('\n' % (service, team)) - - pointscli.award('svc.' + service, team, 1) - - if out is not None: - out.write('
Service NameFlag Holder
%s%s
\n') - - if opts.debug: - print('+-----------------------------------------+') - - time_str = time.strftime('%a, %d %b %Y %H:%M:%S %Z') - out.write(''' -

This page was generated on %s. That was ? seconds ago.

- - ''' % (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) - diff --git a/src/register b/bin/register similarity index 100% rename from src/register rename to bin/register diff --git a/bin/run-ctf b/bin/run-ctf index 0a989e1..5198c64 100755 --- a/bin/run-ctf +++ b/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) diff --git a/bin/run-tanks b/bin/run-tanks deleted file mode 100755 index 2aa64fc..0000000 --- a/bin/run-tanks +++ /dev/null @@ -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() diff --git a/bin/scoreboard b/bin/scoreboard index e2654f6..216ea03 100755 --- a/bin/scoreboard +++ b/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("
\n" \ + " %s: %s\n" \ + "
", + 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" + + # New point at least every 2.5 minutes + interval = 150 + tslen = 0 - def write_js(self, f): - start = self.log[0][0] - end = self.log[-1][0] + while (1 == getline) { + time = $1 + team = $2 + cat = $3 + points = int($4) - # Calculate high score - high_score = reduce(max, [self.team_points(t) for t in self.teams]) + if (! start) { + start = time + } - width = end - start - height = high_score * 1.1 + if (time > (outtime + interval)) { + outtime = time + output() + } + lasttime = 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('}') + teams[team] = team + points_by_cat[cat] += points + points_by_cat_team[cat, team] += points + } - def make_table(self): - body = [] - body.append('') - body.append('') - body.append('') - for cat in self.cats: - points = self.points_by_cat[cat] - if not points: - continue - body.append('') - body.append('') + output() - body.append('') - body.append('') - for cat in self.cats: - total = self.points_by_cat[cat] - if not total: - continue - body.append('') - body.append('') - body.append('
Overall') - body.append(' %s (%d)' % (cat, points)) - try: - fn = os.path.join(flags_dir, cat) - team = open(fn).read().strip() or teams.house - body.append('
') - body.append(' ' % teams.color(team)) - body.append(' %s\n' % (cat, escape(team[:15]))) - body.append(' ') - except IOError: - pass - body.append('
    ') - 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('
  1. %s (%0.3f)
  2. ' - % (teams.color(team), escape(team[:15]), total)) - body.append('
') - 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('
' % (float(score * 100)/total, color)) - body.append(' %s: %d' % (cat, escape(team[:15]), score)) - body.append('
') - body.append('
') + # Get team colors and names + for (team in teams) { + fn = base "/teams/colors/" team + getline colors_by_team[team] < fn + close(fn) - return '\n'.join(body) + fn = base "/teams/names/" team + getline names_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') + # Sort categories + ncats = 0 + for (cat in points_by_cat) { + cats[ncats++] = cat + } + qsort(cats, 0, ncats-1) - opts, args = p.parse_args() - if args: - return p.print_help() + # 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) - 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)) - if opts.html: - hdr = ('' - '' - '') - body = chart.make_table() - body += '\n' - 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) + # Now we can start writing the document + print "" + print "" + print " " + print " Scoreboard" + print " " + print " " -if __name__ == '__main__': - main() + # Provide raw data for the chart + print " " + + # Reload every minute + print " " + + # Set up team colors and a few page-specific styles + print " " + + print " " + print " " + print "

Scoreboard

" + print "

" + print " " + print " " + print " " + + # Print out category names + for (i = 0; i < ncats; i += 1) { + cat = cats[i] + points = points_by_cat[cat] + if (0 == points) continue + printf("\n", cat, points) + } + + print " " + print " " + + # Print out teams, ranked by score + print " " + + # 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 "" + } + print " " + + print "
Overall%s (%d)
" + 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 " " + + # 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 "
" + print " " + print " " + print "" +} diff --git a/src/teams.sh b/bin/teams.sh similarity index 100% rename from src/teams.sh rename to bin/teams.sh diff --git a/mdwn/mdwn.mk b/mdwn/mdwn.mk deleted file mode 100644 index 12667a5..0000000 --- a/mdwn/mdwn.mk +++ /dev/null @@ -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) \ No newline at end of file diff --git a/mdwn/src/index.mdwn b/mdwn/src/index.mdwn deleted file mode 100644 index 222ec5f..0000000 --- a/mdwn/src/index.mdwn +++ /dev/null @@ -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) diff --git a/mdwn/src/intro.mdwn b/mdwn/src/intro.mdwn deleted file mode 100644 index 8ce18bb..0000000 --- a/mdwn/src/intro.mdwn +++ /dev/null @@ -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.x.x.x. **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 even if you never solve the puzzle. 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 dirtbags. People pay us -money to do the sorts of things you'll be doing in this contest. diff --git a/mdwn/src/table.mdwn b/mdwn/src/table.mdwn deleted file mode 100644 index 8d33420..0000000 --- a/mdwn/src/table.mdwn +++ /dev/null @@ -1,161 +0,0 @@ -Title: Counting in different bases - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Base
20110111001011101111000100110101011110011011110111110000100011001010011101001010110110101111100011001110101101111100111011111011111100000100001100010100011
80123456710111213141516172021222324252627303132333435363740414243
1001234567891011121314151617181920212223242526272829303132333435
160123456789abcdef101112131415161718191a1b1c1d1e1f20212223
\ No newline at end of file diff --git a/mdwn/src/tanks/docs.mdwn b/mdwn/src/tanks/docs.mdwn deleted file mode 100644 index 22756a9..0000000 --- a/mdwn/src/tanks/docs.mdwn +++ /dev/null @@ -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. - -
-
addsensor(range, angle, width, [turretAttached])
-
-

Add a new sensor to the tank.

-

- Sensors are an arc (pie slice) centered on the tank that - detect other tanks within their sweep.
- A sensor is 'on' if another tank is within this arc. -

-

- Sensors are numbered, starting at 0, in the order they are - added. -

-

- range - The range of the sensor, as a percent of the tanks max - range.
- angle - The angle of the center of the sensor, in degrees.
- width - The width of the sensor, in degrees.
- turretAttached - Normally, the angle is relative to the front of - the - tank.
When this is set, the angle is relative to the current - turret - direction.
-

-
- -
addtimer(timeout)
-
-

- Add a new timer (they're numbered in the order added, starting from 0), - with the given timeout. -

-

- The timeout is in number of turns.
- The timer - is created in inactive mode.
You'll need to do a starttimer() - action - to reset and start the timer.
When the timer expires, the - timer() - condition will begin to return True. -

-
- -
addtoggle([state])
-
-

Add a toggle to the tank.

-

- The state of the toggle defaults to 0 (False).
-ese essentially act as a single bit of memory.
-e the toggle() condition to check its state and the settoggle, -eartoggle, -d toggle actions to change the state.
Toggles are named -merically, -arting at 0. -

-
-
- -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. - -
-
cos(T)
-
-

- A cos wave with period T (in turns). -

- -

- Returns True when the wave is - positive.
A wave of period 1 is always True.
Period - 2 is True every - other turn, etc. -

-
- -
firenotready()
-
-

- True when the tank can not fire. -

-
- -
fireready()
-
-

- True when the tank can fire. -

-
- -
random(n,m)
-
-

Generate a random number.

- -

- Takes two - arguments, n and m.
Generates a random number between 1 - and m (inclusive) each time it's checked.
If the random - number is less - than or equal - to n, then the condition returns True.
Returns False - otherwise. -

-
- -
sense(#, [invert])
-
-

True when a sensor is activated.

- -

- Takes a Sensor number as an argument.
- - Returns True if the given sensor is currently activated, False - otherwise.
- 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.
Invert is false by default. -

-
- -
sin(T)
-
-

A sin wave of period T (in turns).

- -

- Returns True when the wave is positive.
- 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. -

-
- -
timer(#, [invert])
- -
-

Checks the state of timer # 'key'.

- -

- Returns True if time has run - out.
- - If invert is given (and true), then True is returned if the - timer has - yet to expire. -

-
- -
toggle(#)
-
-

Returns True if the given toggle is set, False - otherwise.

-
-
- -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. - - -
-
cleartimer(#)
-
-

Clear the given timer such that it is no longer active (inactive timers - are always False).

-
- -
fire()
-
-

Attempt to fire the tanks laser cannon.

-

- Its range is 50% of your sensor range. -

-
- -
led(state)
-
-

Set the tank's LED to state (true is on, false is off).

-

- The led is a light that appears behind the tanks turret.
- It remains on for a single turn. -

-
- -
move(left tread speed, right tread speed)
-
-

Set the speeds for the tanks right and left treads.

- -

- The speeds should - be a number (percent power) between -100 and - 100. -

-
- -
settoggle(key, state)
-
-

Set toggle 'key' to 'state'.

-
- -
starttimer(#)
-
-

Start (and reset) the given timer, but only if it is - inactive.

-
- -
toggle(key)
-
-

Toggle the value of toggle 'key'.

-
- -
turretccw([percent speed])
-
-

Rotate the turret counter clockwise as a - percentage of the max speed.

-
- -
turretcw([percent speed])
-
-

Rotate the turret clockwise at a rate - preportional to speed.

-
- -
turretset([angle])
-
-

Set the turret to the given angle, in degrees, relative to the - front of the tank.

-

- Angles increase counterclockwise.
The angle can be left - out; in that case, this locks the turret to its current - position. -

-
-
diff --git a/mdwn/src/tanks/links.xml b/mdwn/src/tanks/links.xml deleted file mode 100644 index c86c136..0000000 --- a/mdwn/src/tanks/links.xml +++ /dev/null @@ -1,10 +0,0 @@ -

Tanks

- - - - diff --git a/mdwn/src/tanks/submit.mdwn b/mdwn/src/tanks/submit.mdwn deleted file mode 100644 index c576413..0000000 --- a/mdwn/src/tanks/submit.mdwn +++ /dev/null @@ -1,14 +0,0 @@ -Title: Tanks Submission - -
-
- Your program: - Team: -
- Password: -
-
- - -
-
diff --git a/src/Makefile b/src/Makefile index 08c2f03..6a7ac44 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 diff --git a/src/scoreboard b/src/scoreboard deleted file mode 100755 index 216ea03..0000000 --- a/src/scoreboard +++ /dev/null @@ -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("
\n" \ - " %s: %s\n" \ - "
", - 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 "" - print "" - print " " - print " Scoreboard" - print " " - print " " - - # Provide raw data for the chart - print " " - - # Reload every minute - print " " - - # Set up team colors and a few page-specific styles - print " " - - print " " - print " " - print "

Scoreboard

" - print "

" - print " " - print " " - print " " - - # Print out category names - for (i = 0; i < ncats; i += 1) { - cat = cats[i] - points = points_by_cat[cat] - if (0 == points) continue - printf("\n", cat, points) - } - - print " " - print " " - - # Print out teams, ranked by score - print " " - - # 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 "" - } - print " " - - print "
Overall%s (%d)
" - 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 " " - - # 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 "
" - print " " - print " " - print "" -} diff --git a/src/src.mk b/src/src.mk new file mode 100644 index 0000000..bbbe4bf --- /dev/null +++ b/src/src.mk @@ -0,0 +1,2 @@ +src-%: + $(MAKE) -C src $* diff --git a/src/test.sh b/test.sh similarity index 59% rename from src/test.sh rename to test.sh index c19228b..f43b4a8 100755 --- a/src/test.sh +++ b/test.sh @@ -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!"