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 {
|
||||
text-decoration: none;
|
||||
color: #b71;
|
||||
text-decoration: underline;
|
||||
color: #84b;
|
||||
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