mirror of https://github.com/dirtbags/moth.git
Add some online docs, clean up some cruft
This commit is contained in:
parent
8d47593986
commit
14d7e3509d
|
@ -1,3 +0,0 @@
|
||||||
[submodule "puzzles"]
|
|
||||||
path = puzzles
|
|
||||||
url = cfl:/var/projects/ctf-puzzles
|
|
|
@ -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)
|
|
150
mkpuzzles.py
150
mkpuzzles.py
|
@ -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 = '''
|
|
||||||
<script type="text/javascript">
|
|
||||||
function readCookie(key) {
|
|
||||||
var s = key + '=';
|
|
||||||
var toks = document.cookie.split(';');
|
|
||||||
for (var i = 0; i < toks.length; i++) {
|
|
||||||
var tok = toks[i];
|
|
||||||
while (tok.charAt(0) == ' ') {
|
|
||||||
tok = tok.substring(1, tok.length);
|
|
||||||
}
|
|
||||||
if (tok.indexOf(s) == 0) {
|
|
||||||
return tok.substring(s.length, tok.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTeamInfo() {
|
|
||||||
team = readCookie('team');
|
|
||||||
passwd = readCookie('passwd');
|
|
||||||
if (team != null) {
|
|
||||||
document.getElementById("form").t.value = team;
|
|
||||||
}
|
|
||||||
if (passwd != null) {
|
|
||||||
document.getElementById("form").w.value = passwd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
'''
|
|
||||||
|
|
||||||
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('<div class="readme">%s</div>\n' % readme)
|
|
||||||
if files:
|
|
||||||
body.append('<ul>\n')
|
|
||||||
for fn, path in files:
|
|
||||||
if os.path.isdir(path):
|
|
||||||
shutil.rmtree(os.path.join(outdir, fn), ignore_errors=True)
|
|
||||||
shutil.copytree(path, os.path.join(outdir, fn))
|
|
||||||
else:
|
|
||||||
shutil.copy(path, outdir)
|
|
||||||
|
|
||||||
if not fn.startswith(','):
|
|
||||||
body.append('<li><a href="%s">%s</a></li>\n' % (fn, fn))
|
|
||||||
body.append('</ul>\n')
|
|
||||||
body.append('''
|
|
||||||
<form id="form" action="%(base)spuzzler.cgi" method="post">
|
|
||||||
<fieldset>
|
|
||||||
<legend>Your answer:</legend>
|
|
||||||
<input type="hidden" name="c" value="%(cat)s" />
|
|
||||||
<input type="hidden" name="p" value="%(points)s" />
|
|
||||||
Team: <input name="t" /><br />
|
|
||||||
Password: <input type="password" name="w" /><br />
|
|
||||||
Key: <input name="k" /><br />
|
|
||||||
<input type="submit" />
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
''' % {'base': opts.base,
|
|
||||||
'cat': cat,
|
|
||||||
'points': points})
|
|
||||||
|
|
||||||
page = template.substitute(hdr=js,
|
|
||||||
title=title,
|
|
||||||
base=opts.base,
|
|
||||||
links='',
|
|
||||||
body_class='',
|
|
||||||
onload = "getTeamInfo()",
|
|
||||||
body=''.join(body))
|
|
||||||
|
|
||||||
f = open(os.path.join(outdir, 'index.html'), 'w', encoding='utf-8')
|
|
||||||
f.write(page)
|
|
||||||
|
|
||||||
f = open(opts.keyfile, 'w', encoding='utf-8')
|
|
||||||
for key in keys:
|
|
||||||
f.write('%s\t%s\t%s\n' % key)
|
|
||||||
|
|
1
puzzles
1
puzzles
|
@ -1 +0,0 @@
|
||||||
Subproject commit 2c4f955d275b03891cd84f17a80dd397780272c6
|
|
11
setup.py
11
setup.py
|
@ -1,11 +0,0 @@
|
||||||
#! /usr/bin/python
|
|
||||||
|
|
||||||
from distutils.core import setup
|
|
||||||
|
|
||||||
setup(name='ctf',
|
|
||||||
version='1.0',
|
|
||||||
description='Capture The Flag contest',
|
|
||||||
author='Neale Pickett',
|
|
||||||
author_email='neale@lanl.gov',
|
|
||||||
url='http://dirtbags.net/ctf/',
|
|
||||||
packages=['ctf', 'tanks'])
|
|
|
@ -35,8 +35,8 @@ a img {
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: underline;
|
||||||
color: #b71;
|
color: #84b;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Welcome</title>
|
||||||
|
<link rel="stylesheet" href="ctf.css" type="text/css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome</h1>
|
||||||
|
|
||||||
|
<h2>Important Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="scoreboard.html">Scoreboard</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="puzzles.html">Puzzles</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="irc://10.0.0.1/ctf"
|
||||||
|
title="IRC on 10.0.0.1, channel #ctf">Contest chat</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Claim Token</h2>
|
||||||
|
<form action="claim.cgi" method="post">
|
||||||
|
<fieldset>
|
||||||
|
team: <input name="t" size="8"><br>
|
||||||
|
token: <input name="k" size="20">
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2>Rules</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
No ARP-level attacks: this includes IP spoofing.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
No DoS attacks.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Do not attack machines outside the contest network
|
||||||
|
(10.<i>x</i>.<i>x</i>.<i>x</i>).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Consider the contest network hostile. It is up to you to
|
||||||
|
safeguard, encrypt, or delete sensitive data on your computer.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
We reserve the right to kick you out of the contest for any
|
||||||
|
reason, so play nice.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
186
www/puzzler.cgi
186
www/puzzler.cgi
|
@ -1,186 +0,0 @@
|
||||||
#! /usr/bin/python
|
|
||||||
|
|
||||||
##
|
|
||||||
## This is pretty crufty :<
|
|
||||||
##
|
|
||||||
|
|
||||||
import cgitb; cgitb.enable()
|
|
||||||
import cgi
|
|
||||||
import os
|
|
||||||
import fcntl
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from cgi import escape
|
|
||||||
import Cookie as cookies
|
|
||||||
from urllib import quote, unquote
|
|
||||||
from codecs import open
|
|
||||||
from sets import Set as set
|
|
||||||
from cStringIO import StringIO
|
|
||||||
|
|
||||||
from ctf import pointscli, teams, html, paths
|
|
||||||
|
|
||||||
keysfile = os.path.join(paths.LIB, 'puzzler.keys')
|
|
||||||
datafile = os.path.join(paths.VAR, 'puzzler.dat')
|
|
||||||
puzzles_dir = os.path.join(paths.WWW, 'puzzler')
|
|
||||||
|
|
||||||
##
|
|
||||||
## This allows you to edit the URL and work on puzzles that haven't been
|
|
||||||
## unlocked yet. For now I think that's an okay vulnerability. It's a
|
|
||||||
## hacking contest, after all.
|
|
||||||
##
|
|
||||||
|
|
||||||
cat_re = re.compile(r'^[a-z]+$')
|
|
||||||
points_re = re.compile(r'^[0-9]+$')
|
|
||||||
|
|
||||||
points_by_cat = {}
|
|
||||||
points_by_team = {}
|
|
||||||
try:
|
|
||||||
for line in open(datafile, encoding='utf-8'):
|
|
||||||
cat, team, pts = [unquote(v) for v in line.strip().split('\t')]
|
|
||||||
pts = int(pts)
|
|
||||||
points_by_cat[cat] = max(points_by_cat.get(cat, 0), pts)
|
|
||||||
points_by_team.setdefault((team, cat), set()).add(pts)
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
c = cookies.SimpleCookie(os.environ.get('HTTP_COOKIE', ''))
|
|
||||||
try:
|
|
||||||
team = c['team'].value
|
|
||||||
passwd = c['passwd'].value
|
|
||||||
except KeyError:
|
|
||||||
team, passwd = None, None
|
|
||||||
|
|
||||||
f = cgi.FieldStorage()
|
|
||||||
cat = f.getfirst('c')
|
|
||||||
points = f.getfirst('p')
|
|
||||||
team = f.getfirst('t', team)
|
|
||||||
passwd = f.getfirst('w', passwd)
|
|
||||||
key = f.getfirst('k', '').decode('utf-8')
|
|
||||||
|
|
||||||
def serve(title, sf=None, **kwargs):
|
|
||||||
if team or passwd:
|
|
||||||
c = cookies.SimpleCookie()
|
|
||||||
if team:
|
|
||||||
c['team'] = team
|
|
||||||
if passwd:
|
|
||||||
c['passwd'] = passwd
|
|
||||||
print(c)
|
|
||||||
if not sf:
|
|
||||||
sf = StringIO()
|
|
||||||
return html.serve(title, sf.getvalue(), **kwargs)
|
|
||||||
|
|
||||||
def safe_join(*args):
|
|
||||||
safe = list(args[:1])
|
|
||||||
for a in args[1:]:
|
|
||||||
if not a:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
a = a.replace('..', '')
|
|
||||||
a = a.replace('/', '')
|
|
||||||
safe.append(a)
|
|
||||||
ret = '/'.join(safe)
|
|
||||||
if os.path.exists(ret):
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def dump_file(fn):
|
|
||||||
f = open(fn, 'rb')
|
|
||||||
while True:
|
|
||||||
d = f.read(4096)
|
|
||||||
if not d:
|
|
||||||
break
|
|
||||||
sys.stdout.buffer.write(d)
|
|
||||||
|
|
||||||
def disabled(cat):
|
|
||||||
return os.path.exists(os.path.join(paths.VAR, 'disabled', cat))
|
|
||||||
|
|
||||||
def show_cats():
|
|
||||||
out = StringIO()
|
|
||||||
out.write('<ul>')
|
|
||||||
puzzles = os.listdir(puzzles_dir)
|
|
||||||
puzzles.sort()
|
|
||||||
for p in puzzles:
|
|
||||||
if disabled(p):
|
|
||||||
continue
|
|
||||||
out.write('<li><a href="%spuzzler.cgi?c=%s">%s</a></li>' % (html.base, p, p))
|
|
||||||
out.write('</ul>')
|
|
||||||
serve('Categories', out)
|
|
||||||
|
|
||||||
|
|
||||||
def show_puzzles(cat, cat_dir):
|
|
||||||
out = StringIO()
|
|
||||||
opened = points_by_cat.get(cat, 0)
|
|
||||||
puzzles = ([int(v) for v in os.listdir(cat_dir)])
|
|
||||||
puzzles.sort()
|
|
||||||
if puzzles:
|
|
||||||
out.write('<ul>')
|
|
||||||
for p in puzzles:
|
|
||||||
cls = ''
|
|
||||||
try:
|
|
||||||
if p in points_by_team[(team, cat)]:
|
|
||||||
cls = 'solved'
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
out.write('<li><a href="%(base)spuzzler/%(cat)s/%(points)d" class="%(class)s">%(points)d</a></li>' %
|
|
||||||
{'base': html.base,
|
|
||||||
'cat': cat,
|
|
||||||
'points': p,
|
|
||||||
'class': cls})
|
|
||||||
if p > opened:
|
|
||||||
break
|
|
||||||
out.write('</ul>')
|
|
||||||
else:
|
|
||||||
out.write('<p>None (someone is slacking)</p>')
|
|
||||||
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('<p>%d points for %s.</p>' % (points, cgi.escape(team)))
|
|
||||||
out.write('<p>Back to <a href="%spuzzler.cgi?c=%s">%s</a>.</p>' % (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:
|
|
106
www/register.cgi
106
www/register.cgi
|
@ -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("<!DOCTYPE html>")
|
|
||||||
print("<html>")
|
|
||||||
print(" <head>")
|
|
||||||
print(" <title>")
|
|
||||||
print(title)
|
|
||||||
print(" </title")
|
|
||||||
print(' <link rel="stylesheet" href="ctf.css" type="text/css">')
|
|
||||||
print(" </head>")
|
|
||||||
print(" <body>")
|
|
||||||
print(" <h1>")
|
|
||||||
print(title)
|
|
||||||
print(" </h1>")
|
|
||||||
end
|
|
||||||
|
|
||||||
function foot()
|
|
||||||
print(" </body>")
|
|
||||||
print("</html>")
|
|
||||||
os.exit()
|
|
||||||
end
|
|
||||||
|
|
||||||
if (os.getenv("REQUEST_METHOD") ~= "POST") then
|
|
||||||
print("405 Method not allowed")
|
|
||||||
print("Allow: POST")
|
|
||||||
print("Content-type: text/html")
|
|
||||||
print()
|
|
||||||
print("<h1>Method not allowed</h1>")
|
|
||||||
print("<p>I only speak POST. Sorry.</p>")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
inlen = tonumber(os.getenv("CONTENT_LENGTH"))
|
|
||||||
if (inlen > 200) then
|
|
||||||
head("Bad team name")
|
|
||||||
print("<p>That's a bit on the long side, don't you think?</p>")
|
|
||||||
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("<p>Go back and try again.</p>")
|
|
||||||
foot()
|
|
||||||
end
|
|
||||||
hash = djbhash(team)
|
|
||||||
|
|
||||||
if io.open(hash) then
|
|
||||||
head("Team name taken")
|
|
||||||
print("<p>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.</p>")
|
|
||||||
foot()
|
|
||||||
end
|
|
||||||
|
|
||||||
f = io.open(hash, "w"):write(team)
|
|
||||||
|
|
||||||
head("Team registered")
|
|
||||||
print("<p>Team name: <samp>")
|
|
||||||
print(escape(team))
|
|
||||||
print("</samp></p>")
|
|
||||||
print("<p>Team token: <samp>")
|
|
||||||
print(hash)
|
|
||||||
print("</samp></p>")
|
|
||||||
print("<p><b>Save your team token somewhere</b>!")
|
|
||||||
print("You will need it to claim points.</p>")
|
|
||||||
foot()
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>About scoring</title>
|
||||||
|
<link rel="stylesheet" href="ctf.css" type="text/css">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>About scoring</h1>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
There are two main ways to make points: <em>puzzles</em>
|
||||||
|
and <em>tokens</em>. Your contest may have other ways to make
|
||||||
|
points: these will either be automatic, or explained elsewhere.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Puzzles</h2>
|
||||||
|
<p>
|
||||||
|
Many of the categories are in the form of
|
||||||
|
multiple <em>puzzles</em>: 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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Tokens</h2>
|
||||||
|
<p>
|
||||||
|
Tokens are strings redeemable once for a single point each. A
|
||||||
|
token for the "example" category might look like this:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>example:xenon-codex</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
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 <a href="index.html">welcome page</a>, or you
|
||||||
|
can write your own script to automate token submission.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Reading the scoreboard</h2>
|
||||||
|
<p>
|
||||||
|
The <a href="scoreboard.html">scoreboard</a> 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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>About time</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -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 = '''
|
|
||||||
<h3>Tanks</h3>
|
|
||||||
<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>
|
|
||||||
'''
|
|
||||||
|
|
||||||
body = []
|
|
||||||
fields = cgi.FieldStorage()
|
|
||||||
team = fields.getfirst('team', '').strip()
|
|
||||||
passwd = fields.getfirst('passwd', '').strip()
|
|
||||||
if not team:
|
|
||||||
pass
|
|
||||||
elif teams.chkpasswd(team, passwd):
|
|
||||||
path = os.path.join(basedir, 'errors', quote(team))
|
|
||||||
if os.path.isfile(path):
|
|
||||||
body.append('<p>Your latest errors:</p>')
|
|
||||||
errors = open(path).readlines()
|
|
||||||
if errors:
|
|
||||||
body.append('<ul class="errors">')
|
|
||||||
for e in errors:
|
|
||||||
body.append('<li>%s</li>' % cgi.escape(e))
|
|
||||||
body.append('</ul>')
|
|
||||||
else:
|
|
||||||
body.append('<p>There were no errors.</p>')
|
|
||||||
else:
|
|
||||||
body.append('<p>No error file found.</p>')
|
|
||||||
else:
|
|
||||||
body.append('Authentication failed.')
|
|
||||||
|
|
||||||
body.append('''
|
|
||||||
<form action="errors.cgi" method="get">
|
|
||||||
<fieldset>
|
|
||||||
<legend>Error report request:</legend>
|
|
||||||
Team: <input type="text" name="team"/><br/>
|
|
||||||
Password: <input type="password" name="passwd"/><br/>
|
|
||||||
<button type="get my errors">Submit</button>
|
|
||||||
</fieldset>
|
|
||||||
</form>''')
|
|
||||||
|
|
||||||
html.serve('Tanks Errors', '\n'.join(body), links=links)
|
|
||||||
|
|
|
@ -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 = '''
|
|
||||||
<h3>Tanks</h3>
|
|
||||||
<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>
|
|
||||||
'''
|
|
||||||
|
|
||||||
body = []
|
|
||||||
|
|
||||||
body.append('<h1>Last Winner:</h1>')
|
|
||||||
body.append('<p>')
|
|
||||||
body.append(escape(open(os.path.join(basedir, 'winner')).read()))
|
|
||||||
body.append('</p>')
|
|
||||||
body.append('<h1>Results so far:</h1>')
|
|
||||||
body.append('<ul>')
|
|
||||||
results = os.listdir(os.path.join(basedir, 'results'))
|
|
||||||
results.sort()
|
|
||||||
results.reverse()
|
|
||||||
for fn in results:
|
|
||||||
num, _ = os.path.splitext(fn)
|
|
||||||
body.append('<li><a href="results/%s">%s</a></li>' % (fn, num))
|
|
||||||
body.append('</ul>')
|
|
||||||
|
|
||||||
html.serve('Tanks Results', '\n'.join(body), links=links)
|
|
|
@ -1,39 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import cgi
|
|
||||||
import cgitb; cgitb.enable()
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from urllib import quote
|
|
||||||
|
|
||||||
from ctf import teams, html, paths
|
|
||||||
|
|
||||||
basedir = os.path.join(paths.VAR, 'tanks')
|
|
||||||
|
|
||||||
links = '''
|
|
||||||
<h3>Tanks</h3>
|
|
||||||
<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>
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
fields = cgi.FieldStorage()
|
|
||||||
team = fields.getfirst('team', '').strip()
|
|
||||||
passwd = fields.getfirst('passwd', '').strip()
|
|
||||||
code = fields.getfirst('code', '')
|
|
||||||
if not teams.chkpasswd(team, passwd):
|
|
||||||
body = '<p>Authentication failed.</p>'
|
|
||||||
elif not code:
|
|
||||||
body = '<p>No program given.</p>'
|
|
||||||
else:
|
|
||||||
path = os.path.join(basedir, 'ai/players', quote(team, safe=''))
|
|
||||||
file = open(path, 'w')
|
|
||||||
file.write(code)
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
body = ("<p>Submission successful.</p>")
|
|
||||||
|
|
||||||
html.serve('Tanks Submission', body, links=links)
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue