diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 703ccba..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "puzzles"] - path = puzzles - url = cfl:/var/projects/ctf-puzzles diff --git a/mdwntohtml.py b/mdwntohtml.py deleted file mode 100755 index bafe89f..0000000 --- a/mdwntohtml.py +++ /dev/null @@ -1,44 +0,0 @@ -#! /usr/bin/python - -import os -import shutil -import optparse -import string -import markdown -from codecs import open - -p = optparse.OptionParser('%prog [OPTIONS] infile outfile') -p.add_option('-t', '--template', dest='template', default='template.html', - help='Location of HTML template') -p.add_option('-b', '--base', dest='base', default='', - help='Base URL for contest') - -opts, args = p.parse_args() - -basedir = os.path.dirname(args[0]) -links_fn = os.path.join(basedir, 'links.xml') -try: - links = open(links_fn, encoding='utf-8').read() -except IOError: - links = '' - -f = open(args[0], encoding='utf-8') -title = '' -for line in f: - line = line.strip() - if not line: - break - k, v = line.split(': ') - if k.lower() == 'title': - title = v -body = markdown.markdown(f.read(99999)) -template = string.Template(open(opts.template, encoding='utf-8').read()) -page = template.substitute(hdr='', - title=title, - base=opts.base, - links=links, - body_class='', - onload='', - body=body) - -open(args[1], 'w', encoding='utf-8').write(page) diff --git a/mkpuzzles.py b/mkpuzzles.py deleted file mode 100755 index c1ce50e..0000000 --- a/mkpuzzles.py +++ /dev/null @@ -1,150 +0,0 @@ -#! /usr/bin/python - -import os -import shutil -import optparse -import string -import markdown -import rfc822 -from codecs import open - -p = optparse.OptionParser() -p.add_option('-t', '--template', dest='template', default='template.html', - help='Location of HTML template') -p.add_option('-b', '--base', dest='base', default='', - help='Base URL for contest') -p.add_option('-p', '--puzzles', dest='puzzles', default='puzzles', - help='Directory containing puzzles') -p.add_option('-w', '--htmldir', dest='htmldir', default='puzzler', - help='Directory to write HTML puzzle tree') -p.add_option('-k', '--keyfile', dest='keyfile', default='puzzler.keys', - help='Where to write keys') - -opts, args = p.parse_args() - -keys = [] - -tmpl_f = open(opts.template, encoding='utf-8') -template = string.Template(tmpl_f.read()) -tmpl_f.close() - -js = ''' - -''' - -for cat in os.listdir(opts.puzzles): - dirname = os.path.join(opts.puzzles, cat) - for points in os.listdir(dirname): - pointsdir = os.path.join(dirname, points) - if not os.path.isdir(pointsdir): - continue - - outdir = os.path.join(opts.htmldir, cat, points) - try: - os.makedirs(outdir) - except OSError: - pass - - readme = '' - files = [] - for fn in os.listdir(pointsdir): - path = os.path.join(pointsdir, fn) - if fn == 'key': - for key in open(path, encoding='utf-8'): - key = key.rstrip() - keys.append((cat, points, key)) - elif fn == 'hint': - pass - elif fn == 'index.exe': - p = os.popen(path) - m = rfc822.Message(p) - for key in m.getallmatchingheaders('Key'): - print key - keys.append((cat, points, key)) - readme = m.fp.read() - if m.get('Content-Type', 'text/markdown') == 'text/markdown': - readme = markdown.markdown(readme) - elif fn == 'index.html': - readme = open(path, encoding='utf-8').read() - elif fn == 'index.mdwn': - readme = open(path, encoding='utf-8').read() - readme = markdown.markdown(readme) - elif fn.endswith('~'): - pass - else: - files.append((fn, path)) - - title = '%s for %s points' % (cat, points) - - body = [] - if readme: - body.append('
None (someone is slacking)
') - serve('Open in %s' % escape(cat), out) - -def win(cat, team, points): - out = StringIO() - points = int(points) - f = open(datafile, 'a', encoding='utf-8') - pointscli.award(cat, team, points) - fcntl.lockf(f, fcntl.LOCK_EX) - f.write('%s\t%s\t%d\n' % (quote(cat), quote(team), points)) - out.write('%d points for %s.
' % (points, cgi.escape(team))) - out.write('Back to %s.
' % (html.base, cat, cat)) - serve('Winner!', out) - -def check_key(cat, points, candidate): - for line in open(keysfile, encoding='utf-8'): - thiscat, thispoints, key = line.split('\t', 2) - if (cat, points) == (thiscat, thispoints): - if key.rstrip() == candidate: - return True - return False - -def main(): - cat_dir = safe_join(puzzles_dir, cat) - points_dir = safe_join(puzzles_dir, cat, points) - - if not cat_dir: - # Show categories - show_cats() - elif not points_dir: - # Show available puzzles in category - show_puzzles(cat, cat_dir) - else: - if not teams.chkpasswd(team, passwd): - serve('Wrong password') - elif not check_key(cat, points, key): - serve('Wrong key') - elif int(points) in points_by_team.get((team, cat), set()): - serve('Greedy greedy') - else: - win(cat, team, points) - -if __name__ == '__main__': - import optparse - import sys, codecs - - sys.stdout = codecs.getwriter('utf-8')(sys.stdout) - - main() - - -# Local Variables: -# mode: python -# End: diff --git a/www/register.cgi b/www/register.cgi deleted file mode 100755 index bb0021c..0000000 --- a/www/register.cgi +++ /dev/null @@ -1,106 +0,0 @@ -#! /usr/bin/lua - -function decode(str) - local hexdec = function(h) - return string.char(tonumber(h, 16)) - end - str = string.gsub(str, "+", " ") - return string.gsub(str, "%%(%x%x)", hexdec) -end - -function decode_query(query) - local ret = {} - - for key, val in string.gfind(query, "([^&=]+)=([^&=]+)") do - ret[string.lower(decode(key))] = decode(val) - end - - return ret -end - -function escape(str) - str = string.gsub(str, "&", "&") - str = string.gsub(str, "<", "<") - str = string.gsub(str, ">", ">") - return str -end - -function djbhash(s) - local hash = 5380 - for i=0,string.len(s) do - local c = string.byte(string.sub(s, i, i+1)) - hash = math.mod(((hash * 32) + hash + c), 2147483647) - end - return string.format("%08x", hash) -end - -function head(title) - print("Content-type: text/html") - print("") - print("") - print("") - print(" ") - print("I only speak POST. Sorry.
") -end - - -inlen = tonumber(os.getenv("CONTENT_LENGTH")) -if (inlen > 200) then - head("Bad team name") - print("That's a bit on the long side, don't you think?
") - foot() -end -formdata = io.read(inlen) -f = decode_query(formdata) - -team = f["t"] -if (not team) or (team == "dirtbags") then - head("Bad team name") - print("Go back and try again.
") - foot() -end -hash = djbhash(team) - -if io.open(hash) then - head("Team name taken") - print("Either someone's already using that team name,") - print("or you found a hash collision. Either way, you're") - print("going to have to pick something else.
") - foot() -end - -f = io.open(hash, "w"):write(team) - -head("Team registered") -print("Team name: ") -print(escape(team)) -print("
") -print("Team token: ") -print(hash) -print("
") -print("Save your team token somewhere!") -print("You will need it to claim points.
") -foot() \ No newline at end of file diff --git a/www/scoring.html b/www/scoring.html new file mode 100644 index 0000000..b78c82f --- /dev/null +++ b/www/scoring.html @@ -0,0 +1,94 @@ + + + ++ 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 main ways to make points: puzzles + and tokens. Your contest may have other ways to make + points: these will either be automatic, or explained elsewhere. +
+ + ++ Many of the categories are in the form of + multiple puzzles: for each puzzle presented, a + case-sensitive 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. +
+ + ++ Tokens are strings redeemable once for a single point each. A + token for the "example" category might look like this: +
+ +example:xenon-codex+ +
+ Tokens are typically associated with "live" categories, such as a + network-based service or a treasure hunt. Tokens can be submitted + with the form on the welcome page, or you + can write your own script to automate token submission. +
+ ++ Some tokens change periodically, typically once a minute. If you + find a token, it's worth looking in the same place again later to + see if the token changes. +
+ ++ The scoreboard shows total score on + the left, and scores within each category. If you have a smaller + or more novice team, you may wish to ignore total rankings and + strive to do well within only a few categories. +
+ ++ If your browser supports the HTML5 canvas (Firefox, Safari, + Chrome, Opera, iPhone, Android) and JavaScript, the scoreboard + will also have a graph of scores over time. Additionally, + JavaScript-enabled browsers will highlight all point blocks + belonging to a team, and a team's line on the graph, when the + mouse cursor is over a point block. +
+ + ++ Many Capture The Flag contests attempt to reward teams who answer + quickly with more points, by adding a "quick answer" bonus or + decaying point values over time. Our contest doesn't work this + way. +
++ We believe that given enough things to work on, quick-moving teams + will emerge with more points by answering more questions; while + providing novice teams a realistic picture of how they're doing. + In addition, when the game infrastructure goes down—which seems to + happen a lot in anybody's CTF—there's no losing points while the + organizers struggle to get things back up. +
+ + diff --git a/www/tanks/errors.cgi b/www/tanks/errors.cgi deleted file mode 100755 index f99d874..0000000 --- a/www/tanks/errors.cgi +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/python - -import cgi -import cgitb; cgitb.enable() -import sys -import os - -from urllib import quote - -from ctf import teams, html, paths - -basedir = os.path.join(paths.VAR, 'tanks') - -links = ''' -Your latest errors:
') - errors = open(path).readlines() - if errors: - body.append('There were no errors.
') - else: - body.append('No error file found.
') -else: - body.append('Authentication failed.') - -body.append(''' -''') - -html.serve('Tanks Errors', '\n'.join(body), links=links) - diff --git a/www/tanks/results.cgi b/www/tanks/results.cgi deleted file mode 100755 index e6632f1..0000000 --- a/www/tanks/results.cgi +++ /dev/null @@ -1,33 +0,0 @@ -#! /usr/bin/python - -import os -from ctf import html, paths -from cgi import escape - -basedir = os.path.join(paths.VAR, 'tanks') - -links = ''' -') -body.append(escape(open(os.path.join(basedir, 'winner')).read())) -body.append('
') -body.append('Authentication failed.
' -elif not code: - body = 'No program given.
' -else: - path = os.path.join(basedir, 'ai/players', quote(team, safe='')) - file = open(path, 'w') - file.write(code) - file.close() - - body = ("Submission successful.
") - -html.serve('Tanks Submission', body, links=links) diff --git a/www/tanks/tanks.js b/www/tanks/tanks.js deleted file mode 100644 index f416423..0000000 --- a/www/tanks/tanks.js +++ /dev/null @@ -1,213 +0,0 @@ -function dbg(o) { - e = document.getElementById("debug"); - e.innerHTML = o; -} - -function torgba(color, alpha) { - var r = parseInt(color.substring(1,3), 16); - var g = parseInt(color.substring(3,5), 16); - var b = parseInt(color.substring(5,7), 16); - - return "rgba(" + r + "," + g + "," + b + "," + alpha + ")"; -} - -function Tank(ctx, width, height, color, sensors) { - var craterStroke = torgba(color, 0.5); - var craterFill = torgba(color, 0.2); - var sensorStroke = torgba(color, 0.4); - var maxlen = 0; - - this.x = 0; - this.y = 0; - this.rotation = 0; - this.turret = 0; - - // Do all the yucky math up front - this.sensors = new Array(); - for (i in sensors) { - s = sensors[i]; - // r, angle, width, turret - this.sensors[i] = new Array(); - this.sensors[i][0] = s[0]; - this.sensors[i][1] = s[1] - (s[2]/2); - this.sensors[i][2] = s[1] + (s[2]/2); - this.sensors[i][3] = s[3]?1:0; - if (s[0] > maxlen) { - maxlen = s[0]; - } - } - - // Set up our state, for later interleaved draw requests - this.set_state = function(x, y, rotation, turret, flags, sensor_state) { - this.x = x; - this.y = y; - this.rotation = rotation; - this.turret = turret; - this.fire = flags & 1; - this.led = flags & 2; - this.sensor_state = sensor_state; - } - - this.draw_crater = function() { - var points = 7; - var angle = Math.PI / points; - - ctx.save(); - ctx.translate(this.x, this.y); - ctx.rotate(this.rotation); - - ctx.lineWidth = 2; - ctx.strokeStyle = craterStroke; - ctx.fillStyle = craterFill; - ctx.beginPath(); - ctx.moveTo(12, 0); - for (i = 0; i < points; i += 1) { - ctx.rotate(angle); - ctx.lineTo(6, 0); - ctx.rotate(angle); - ctx.lineTo(12, 0); - } - ctx.closePath() - ctx.stroke(); - ctx.fill(); - - ctx.restore(); - } - - this.draw_sensors = function() { - ctx.save(); - ctx.translate(this.x, this.y); - ctx.rotate(this.rotation); - - ctx.lineWidth = 1; - for (i in this.sensors) { - var s = this.sensors[i]; - var adj = this.turret * s[3]; - - if (this.sensor_state & (1 << i)) { - // Sensor is triggered - ctx.strokeStyle = "#000"; - } else { - ctx.strokeStyle = sensorStroke; - } - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.arc(0, 0, s[0], s[1] + adj, s[2] + adj, false); - ctx.closePath(); - ctx.stroke(); - } - - ctx.restore(); - } - - this.draw_tank = function() { - ctx.save(); - ctx.translate(this.x, this.y); - ctx.rotate(this.rotation); - - ctx.fillStyle = color; - ctx.fillRect(-5, -4, 10, 8); - ctx.fillStyle = "#777"; - ctx.fillRect(-7, -9, 15, 5); - ctx.fillRect(-7, 4, 15, 5); - ctx.rotate(this.turret); - if (this.fire) { - ctx.fillStyle = color; - ctx.fillRect(0, -1, 45, 2); - } else { - if (this.led) { - ctx.fillStyle = "#f00"; - } else { - ctx.fillStyle = "#000"; - } - ctx.fillRect(0, -1, 10, 2); - } - - ctx.restore(); - } - - this.draw_wrap_sensors = function() { - var orig_x = this.x; - var orig_y = this.y; - for (x = this.x - width; x < width + maxlen; x += width) { - for (y = this.y - height; y < height + maxlen; y += height) { - if ((-maxlen < x) && (x < width + maxlen) && - (-maxlen < y) && (y < height + maxlen)) { - this.x = x; - this.y = y; - this.draw_sensors(); - } - } - } - this.x = orig_x; - this.y = orig_y; - } -} - -function start(game) { - var canvas = document.getElementById('battlefield'); - var ctx = canvas.getContext('2d'); - var loop_id; - - canvas.width = game[0]; - canvas.height = game[1]; - // game[2] is tank descriptions - var turns = game[3]; - - // Set up tanks - var tanks = new Array(); - for (i in game[2]) { - var desc = game[2][i]; - tanks[i] = new Tank(ctx, game[0], game[1], desc[0], desc[1]); - } - - var frame = 0; - var lastframe = 0; - var fps = document.getElementById('fps'); - - function update_fps() { - fps.innerHTML = (frame - lastframe); - lastframe = frame; - } - - function update() { - var idx = frame % (turns.length + 20); - var turn; - - frame += 1; - if (idx >= turns.length) { - return; - } - - canvas.width = canvas.width; - turn = turns[idx]; - - // Draw craters first - for (i in turn) { - t = turn[i]; - if (! t) { - tanks[i].draw_crater(); - } - } - // Then sensors - for (i in turn) { - t = turn[i]; - if (t) { - // Surely there's a better way to do this. - tanks[i].set_state(t[0], t[1], t[2], t[3], t[4], t[5]); - tanks[i].draw_wrap_sensors(); - } - } - // Then tanks - for (i in turn) { - t = turn[i]; - if (t) { - tanks[i].draw_tank() - } - } - } - - loop_id = setInterval(update, 66); - setInterval(update_fps, 1000); -} -