Changes to work on qnap

This commit is contained in:
Neale Pickett 2009-09-29 15:36:25 -06:00
parent ba19575d62
commit 46e7971151
29 changed files with 543 additions and 368 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*~
*.pyc
*.dat
passwd
target/
puzzler/
ctf.tce

44
Makefile Normal file
View File

@ -0,0 +1,44 @@
DESTDIR = target
CTFDIR = $(DESTDIR)/usr/lib/ctf
WWWDIR = $(DESTDIR)/usr/lib/www
FAKE = fakeroot -s fake -i fake
INSTALL = $(FAKE) install
PYC = config.pyc points.pyc game.pyc teams.pyc
PYC += register.pyc scoreboard.pyc puzzler.pyc
PYC += flagd.pyc pointsd.pyc pointscli.pyc
PYC += roshambo.pyc histogram.pyc
all: ctf.tce
target: $(PYC)
$(INSTALL) -d --mode=0755 --owner=100 $(DESTDIR)/var/lib/ctf
$(INSTALL) -d $(DESTDIR)/var/lib/ctf/disabled
$(INSTALL) -d $(CTFDIR)
$(INSTALL) $(PYC) $(CTFDIR)
$(INSTALL) uberserv.py $(CTFDIR)
$(INSTALL) -d $(WWWDIR)
$(INSTALL) index.html ctf.css $(WWWDIR)
$(FAKE) ln -s /var/lib/ctf/histogram.png $(WWWDIR)
$(INSTALL) register.cgi scoreboard.cgi puzzler.cgi $(WWWDIR)
$(INSTALL) -d $(DESTDIR)/var/service/ctf
$(INSTALL) run.uberserv $(DESTDIR)/var/service/ctf/run
rm -rf $(WWWDIR)/puzzler
$(INSTALL) -d $(WWWDIR)/puzzler
./mkpuzzles.py --htmldir=$(WWWDIR)/puzzler --keyfile=$(CTFDIR)/puzzler.keys
ctf.tce: target
$(FAKE) sh -c 'cd target && tar -czf - --exclude=placeholder --exclude=*~ .' > $@
clean:
rm -rf target
rm -f fake ctf.tce $(PYC)
%.pyc: %.py
python3 -c 'import $*'

57
config.py Executable file
View File

@ -0,0 +1,57 @@
#! /usr/bin/env python3
import os
if 'home' in os.environ.get('SCRIPT_FILENAME', ''):
# We're a CGI running out of someone's home directory
config = {'global':
{'data_dir': '.',
'base_url': '.',
'css_url': 'ctf.css',
'diasbled_dir': 'disabled'
},
'puzzler':
{'dir': 'puzzles',
'ignore_dir': 'puzzler.ignore',
'cgi_url': 'puzzler.cgi',
'base_url': 'puzzler',
'keys_file': 'puzzler.keys',
},
}
else:
# An actual installation
config = {'global':
{'data_dir': '/var/lib/ctf',
'base_url': '/',
'css_url': '/ctf.css',
'disabled_dir': '/var/lib/ctf/disabled',
},
'puzzler':
{'dir': '/usr/lib/www/puzzler',
'ignore_dir': '/var/lib/ctf/puzzler.ignore',
'cgi_url': '/puzzler.cgi',
'base_url': '/puzzler',
'keys_file': '/usr/lib/ctf/puzzler.keys',
},
}
def get(section, key):
return config[section][key]
disabled_dir = get('global', 'disabled_dir')
data_dir = get('global', 'data_dir')
base_url = get('global', 'base_url')
css = get('global', 'css_url')
def disabled(cat):
path = os.path.join(disabled_dir, cat)
return os.path.exists(path)
def enabled(cat):
return not disabled(cat)
def datafile(filename):
return os.path.join(data_dir, filename)
def url(path):
return base_url + path

View File

@ -1,15 +0,0 @@
#! /usr/bin/env python3
# 0 1 2 3 4
# 1234567890123456789012345678901234567890123
# ||| | | | | | | | | | | | |
msg = (' #### #### # ### ### ### # ### '
' # # # # ## # # # # # '
' # # #### # #### #### # # # '
' # # # # # # # # # # # # '
' #### #### ### ### ### ### # ### ')
msg = msg.replace('#', '0')
msg = msg.replace(' ', '1')
num = int(msg, 2)
print(num)

View File

@ -1,30 +0,0 @@
#! /usr/bin/env python3
import sys
import random
primes = [2, 3, 5, 7, 11, 13, 17, 19]
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
data = sys.stdin.read().strip()
jumble = ''.join(data.split())
lj = len(jumble)
below = (0, 0)
above = (lj, 2)
for i in primes:
for j in primes:
m = i * j
if (m < lj) and (m > below[0] * below[1]):
below = (i, j)
elif (m >= lj) and (m < (above[0] * above[1])):
above = (i, j)
for i in range(lj, (above[0] * above[1])):
jumble += random.choice(letters)
out = []
for i in range(above[0]):
for j in range(above[1]):
out.append(jumble[j*above[0] + i])
print(''.join(out))

View File

@ -4,13 +4,16 @@ import points
import time import time
import os import os
import tempfile import tempfile
import config
pngout = config.datafile('histogram.png')
def main(s=None): def main(s=None):
scores = {} scores = {}
now = 0 now = 0
if not s: if not s:
s = points.Storage('scores.dat') s = points.Storage()
plotparts = [] plotparts = []
teams = s.teams() teams = s.teams()
@ -57,11 +60,12 @@ set xtics nomirror
set ytics nomirror set ytics nomirror
set nokey set nokey
set terminal png transparent size 640,200 x000000 xffffff set terminal png transparent size 640,200 x000000 xffffff
set output "histogram.png" set output "%(pngout)s"
plot %(plot)s\n''' % {'plot': ','.join(plotparts)}) plot %(plot)s\n''' % {'plot': ','.join(plotparts),
'pngout': pngout})
instructions.flush() instructions.flush()
gp = os.system('gnuplot %s' % instructions.name) gp = os.system('gnuplot %s 2>/dev/null' % instructions.name)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

80
mkpuzzles.py Executable file
View File

@ -0,0 +1,80 @@
#! /usr/bin/env python3
import os
import shutil
import optparse
import config
p = optparse.OptionParser()
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 = []
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)
outdir = os.path.join(opts.htmldir, cat, points)
os.makedirs(outdir)
readme = ''
files = []
for fn in os.listdir(pointsdir):
path = os.path.join(pointsdir, fn)
if fn == 'key':
key = open(path, encoding='utf-8').readline().strip()
keys.append((cat, points, key))
elif fn == 'index.html':
readme = open(path, encoding='utf-8').read()
else:
files.append((fn, path))
title = '%s for %s points' % (cat, points)
f = open(os.path.join(outdir, 'index.html'), 'w', encoding='utf-8')
f.write('''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>%(title)s</title>
<link rel="stylesheet" href="%(css)s" type="text/css" />
</head>
<body>
<h1>%(title)s</h1>
''' % {'title': title,
'css': config.css})
if readme:
f.write('<div class="readme">%s</div>\n' % readme)
if files:
f.write('<ul>\n')
for fn, path in files:
shutil.copy(path, outdir)
f.write('<li><a href="%s">%s</a></li>\n' % (fn, fn))
f.write('</ul>\n')
f.write('''
<form action="%(cgi)s" method="post">
<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" />
</form>
</body>
</html>
''' % {'cgi': config.get('puzzler', 'cgi_url'),
'cat': cat,
'points': points})
f = open(opts.keyfile, 'w', encoding='utf-8')
for key in keys:
f.write('%s\t%s\t%s\n' % key)

View File

@ -5,6 +5,8 @@ import hmac
import struct import struct
import io import io
import teams import teams
import config
import os
## ##
## Authentication ## Authentication
@ -88,7 +90,9 @@ def incdict(dict, key, amt=1):
dict[key] = dict.get(key, 0) + amt dict[key] = dict.get(key, 0) + amt
class Storage: class Storage:
def __init__(self, fn='/var/lib/ctf/points.dat'): def __init__(self, fn=None):
if not fn:
fn = config.datafile('scores.dat')
self.points_by_team = {} self.points_by_team = {}
self.points_by_cat = {} self.points_by_cat = {}
self.points_by_cat_team = {} self.points_by_cat_team = {}

View File

@ -1,206 +1,7 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
import cgitb; cgitb.enable() import cgitb; cgitb.enable()
import cgi
import os
import fcntl
import re
import sys import sys
import pointscli sys.path.insert(0, '/usr/lib/ctf')
import teams import puzzler
import http.cookies puzzler.main()
from urllib.parse import quote, unquote
##
## 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]+$')
def dbg(*vals):
print('Content-type: text/plain\n\n')
print(*vals)
points_by_cat = {}
points_by_team = {}
try:
for line in open('/var/lib/ctf/puzzler.dat'):
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 = http.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')
verboten = ['key', 'index.html']
def start_html(title):
print('Content-type: text/html')
if team or passwd:
c = http.cookies.SimpleCookie()
if team:
c['team'] = team
if passwd:
c['passwd'] = passwd
print(c)
print()
print('''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>%s</title>
<link rel="stylesheet" href="ctf.css" type="text/css" />
</head>
<body>
<h1>%s</h1>
''' % (title, title))
def end_html():
print('</body></html>')
def safe_join(*args):
safe = []
for a in args:
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 show_cats():
start_html('Categories')
print('<ul>')
for p in sorted(os.listdir('puzzles')):
print('<li><a href="puzzler.cgi?c=%s">%s</a></li>' % (p, p))
print('</ul>')
end_html()
def show_puzzles(cat, cat_dir):
start_html('Open in %s' % cat)
opened = points_by_cat.get(cat, 0)
puzzles = sorted([int(v) for v in os.listdir(cat_dir)])
if puzzles:
print('<ul>')
for p in puzzles:
print('<li><a href="puzzler.cgi?c=%s&p=%d">%d</a></li>' % (cat, p, p))
if p > opened:
break
print('</ul>')
else:
print('<p>None (someone is slacking)</p>')
end_html()
def show_puzzle(cat, points, points_dir, team='', passwd=''):
# Show puzzle in cat for points
start_html('%s for %s points' % (cat, points))
fn = os.path.join(points_dir, 'index.html')
if os.path.exists(fn):
print('<div class="readme">')
dump_file(fn)
print('</div>')
print('<ul>')
for fn in sorted(os.listdir(points_dir)):
if fn.endswith('~') or fn.startswith('.') or fn in verboten:
continue
print('<li><a href="puzzler.cgi?c=%s&p=%s&f=%s">%s</a></li>' % (cat, points, fn, fn))
print('</ul>')
print('<form action="puzzler.cgi" method="post">')
print('<input type="hidden" name="c" value="%s" />' % cat)
print('<input type="hidden" name="p" value="%s" />' % points)
print('Team: <input name="t" value="%s" /><br />' % (team or ''))
print('Password: <input type="password" name="w" value="%s" /><br />' % (passwd or ''))
print('Key: <input name="k" /><br />')
print('<input type="submit" />')
print('</form>')
end_html()
def win(cat, team, points):
start_html('Winner!')
points = int(points)
f = open('puzzler.dat', 'a')
pointscli.submit(cat, team, points)
fcntl.lockf(f, fcntl.LOCK_EX)
f.write('%s\t%s\t%d\n' % (quote(cat), quote(team), points))
print('<p>%d points for %s.</p>' % (points, team))
print('<p>Back to <a href="puzzler.cgi?c=%s">%s</a>.</p>' % (cat, cat))
end_html()
def main():
cat_dir = safe_join('puzzles', cat)
points_dir = safe_join('puzzles', cat, points)
if not cat_dir:
# Show categories
show_cats()
elif not points_dir:
# Show available puzzles in category
show_puzzles(cat, cat_dir)
elif not (team and passwd and key):
fn = f.getfirst('f')
if fn in verboten:
fn = None
fn = safe_join('puzzles', cat, points, fn)
if fn:
# Provide a file from this directory
print('Content-type: application/octet-stream')
print()
dump_file(fn)
else:
show_puzzle(cat, points, points_dir, team, passwd)
else:
try:
thekey = open('%s/key' % points_dir, encoding='utf-8').read().strip()
except IOError:
# If there's no key, this can never be solved.
thekey = False
if not teams.chkpasswd(team, passwd):
start_html('Wrong password')
end_html()
elif key != thekey:
show_puzzle(cat, points, points_dir, team, passwd)
elif int(points) in points_by_team.get((team, cat), set()):
start_html('Greedy greedy')
end_html()
else:
win(cat, team, points)
main()
# Local Variables:
# mode: python
# End:

193
puzzler.py Executable file
View File

@ -0,0 +1,193 @@
#! /usr/bin/env python3
import cgi
import os
import fcntl
import re
import sys
import http.cookies
from urllib.parse import quote, unquote
import config
import pointscli
import teams
datafile = config.datafile('puzzler.dat')
keysfile = config.get('puzzler', 'keys_file')
puzzles_dir = config.get('puzzler', 'dir')
cgi_url = config.get('puzzler', 'cgi_url')
base_url = config.get('puzzler', 'base_url')
##
## 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]+$')
def dbg(*vals):
print('<--: \nContent-type: text/html\n\n--><pre>')
print(*vals)
print('</pre>')
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 = http.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')
def start_html(title):
if os.environ.get('GATEWAY_INTERFACE'):
print('Content-type: text/html')
if team or passwd:
c = http.cookies.SimpleCookie()
if team:
c['team'] = team
if passwd:
c['passwd'] = passwd
print(c)
print()
print('''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>%s</title>
<link rel="stylesheet" href="%s" type="text/css" />
</head>
<body>
<h1>%s</h1>
''' % (title, config.css, title))
def end_html():
print('</body></html>')
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 show_cats():
start_html('Categories')
print('<ul>')
for p in sorted(os.listdir(puzzles_dir)):
if config.disabled(p):
continue
print('<li><a href="%s?c=%s">%s</a></li>' % (cgi_url, p, p))
print('</ul>')
end_html()
def show_puzzles(cat, cat_dir):
start_html('Open in %s' % cat)
opened = points_by_cat.get(cat, 0)
puzzles = sorted([int(v) for v in os.listdir(cat_dir)])
if puzzles:
print('<ul>')
for p in puzzles:
print('<li><a href="%s/%s/%d">%d</a></li>' % (base_url, cat, p, p))
if p > opened:
break
print('</ul>')
else:
print('<p>None (someone is slacking)</p>')
end_html()
def win(cat, team, points):
start_html('Winner!')
points = int(points)
f = open(datafile, 'a', encoding='utf-8')
pointscli.submit(cat, team, points)
fcntl.lockf(f, fcntl.LOCK_EX)
f.write('%s\t%s\t%d\n' % (quote(cat), quote(team), points))
print('<p>%d points for %s.</p>' % (points, team))
print('<p>Back to <a href="%s?c=%s">%s</a>.</p>' % (cgi_url, cat, cat))
end_html()
def get_key(cat, points):
for line in open(keysfile, encoding='utf-8'):
thiscat, thispoints, ret = line.split('\t', 2)
if (cat, points) == (thiscat, thispoints):
return ret.strip()
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:
thekey = get_key(cat, points)
if not teams.chkpasswd(team, passwd):
start_html('Wrong password')
end_html()
elif key != thekey:
start_html('Wrong key')
end_html()
elif int(points) in points_by_team.get((team, cat), set()):
start_html('Greedy greedy')
end_html()
else:
win(cat, team, points)
if __name__ == '__main__':
import optparse
parser = optparse.OptionParser('%prog CATEGORY POINTS')
opts, args = parser.parse_args()
if len(args) == 2:
cat, points = args
show_puzzle(cat, points)
else:
parser.print_usage()
# Local Variables:
# mode: python
# End:

View File

Before

Width:  |  Height:  |  Size: 87 B

After

Width:  |  Height:  |  Size: 87 B

View File

@ -1,62 +1,6 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
import cgitb; cgitb.enable() import sys
import cgi sys.path.insert(0, '/usr/lib/ctf')
import teams import register
import fcntl register.main()
import string
print('Content-type: text/html')
print()
f = cgi.FieldStorage()
team = f.getfirst('team', '')
pw = f.getfirst('pw')
confirm_pw = f.getfirst('confirm_pw')
html = string.Template('''<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Team Registration</title>
<link rel="stylesheet" href="ctf.css" type="text/css" />
</head>
<body>
<h1>Team Registration</h1>
<form method="post" action="register.cgi">
<fieldset>
<label>Desired Team Team:</label>
<input type="text" name="team" />
<span class="error">$team_error</span><br />
<label>Password:</label>
<input type="password" name="pw" /> <br />
<label>Confirm Password:</label>
<input type="password" name="confirm_pw" />
<span class="error">$pw_match_error</span><br />
<input type="submit" value="Register" />
</fieldset>
</form>
</body>
</html>
''')
if not (team and pw and confirm_pw): #If we're starting from the beginning?
html = html.substitute(team_error='',
pw_match_error='')
elif teams.exists(team):
html = html.substitute(team_error='Team team already taken',
pw_match_error='')
elif pw != confirm_pw:
html = html.substitute(team_error='',
pw_match_error='Passwords do not match')
else:
teams.add(team, pw)
html = 'Team registered.'
print(html)

68
register.py Executable file
View File

@ -0,0 +1,68 @@
#! /usr/bin/env python3
import cgitb; cgitb.enable()
import cgi
import teams
import fcntl
import string
import config
def main():
print('Content-type: text/html')
print()
f = cgi.FieldStorage()
team = f.getfirst('team', '')
pw = f.getfirst('pw')
confirm_pw = f.getfirst('confirm_pw')
html = string.Template('''<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Team Registration</title>
<link rel="stylesheet" href="%s" type="text/css" />
</head>
<body>
<h1>Team Registration</h1>
<form method="post" action="%s">
<fieldset>
<label>Desired Team Team:</label>
<input type="text" name="team" />
<span class="error">$team_error</span><br />
<label>Password:</label>
<input type="password" name="pw" />
<br />
<label>Confirm Password:</label>
<input type="password" name="confirm_pw" />
<span class="error">$pw_match_error</span><br />
<input type="submit" value="Register" />
</fieldset>
</form>
</body>
</html>
''' % (config.css, config.url('register.cgi')))
if not (team and pw and confirm_pw): # If we're starting from the beginning?
html = html.substitute(team_error='',
pw_match_error='')
elif teams.exists(team):
html = html.substitute(team_error='Team team already taken',
pw_match_error='')
elif pw != confirm_pw:
html = html.substitute(team_error='',
pw_match_error='Passwords do not match')
else:
teams.add(team, pw)
html = 'Team registered.'
print(html)
if __name__ == '__main__':
main()

3
run.uberserv Executable file
View File

@ -0,0 +1,3 @@
#! /bin/sh
/usr/lib/ctf/uberserv.py | logger -t ctf

View File

@ -1,51 +1,6 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
import cgitb; cgitb.enable() import sys
import points sys.path.insert(0, '/usr/lib/ctf')
import scoreboard
s = points.Storage('scores.dat') scoreboard.main()
teams = s.teams()
categories = [(cat, s.cat_points(cat)) for cat in s.categories()]
teamcolors = points.colors(teams)
print('Content-type: text/html')
print()
print('''<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>CTF Scoreboard</title>
<link rel="stylesheet" href="ctf.css" type="text/css" />
</head>
<body>
<h1>Scoreboard</h1>
''')
print('<table>')
print('<tr>')
for cat, points in categories:
print('<th>%s (%d)</th>' % (cat, points))
print('</tr>')
print('<tr>')
for cat, total in categories:
print('<td style="height: 400px;">')
scores = sorted([(s.team_points_in_cat(cat, team), team) for team in teams])
for points, team in scores:
color = teamcolors[team]
print('<div style="height: %f%%; overflow: hidden; background: #%s; color: black;">' % (float(points * 100)/total, color))
print('<!-- category: %s --> %s: %d' % (cat, team, points))
print('</div>')
print('</td>')
print('</tr>')
print('''</table>
<img src="histogram.png" alt=""/>
</body>
</html>''')
# Local Variables:
# mode: python
# End:

56
scoreboard.py Executable file
View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
import cgitb; cgitb.enable()
import config
import points
def main():
s = points.Storage()
teams = s.teams()
categories = [(cat, s.cat_points(cat)) for cat in s.categories()]
teamcolors = points.colors(teams)
print('Content-type: text/html')
print()
print('''<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>CTF Scoreboard</title>
<link rel="stylesheet" href="%sctf.css" type="text/css" />
</head>
<body>
<h1>Scoreboard</h1>
''' % config.base_url)
print('<table>')
print('<tr>')
for cat, score in categories:
print('<th>%s (%d)</th>' % (cat, score))
print('</tr>')
print('<tr>')
for cat, total in categories:
print('<td style="height: 400px;">')
scores = sorted([(s.team_points_in_cat(cat, team), team) for team in teams])
for score, team in scores:
color = teamcolors[team]
print('<div style="height: %f%%; overflow: hidden; background: #%s; color: black;">' % (float(score * 100)/total, color))
print('<!-- category: %s --> %s: %d' % (cat, team, score))
print('</div>')
print('</td>')
print('</tr>')
print('''</table>
<img src="histogram.png" alt=""/>
</body>
</html>''')
if __name__ == '__main__':
main()
# Local Variables:
# mode: python
# End:

View File

@ -9,13 +9,14 @@ house = 'dirtbags'
passwdfn = '/var/lib/ctf/passwd' passwdfn = '/var/lib/ctf/passwd'
teams = None teams = {}
built = 0 built = 0
def build_teams(): def build_teams():
global teams, built global teams, built
modt = os.path.getmtime(passwdfn) if not os.path.exists(passwdfn):
if modt <= built: return
if os.path.getmtime(passwdfn) <= built:
return return
teams = {} teams = {}
@ -46,7 +47,7 @@ def exists(team):
return team in teams return team in teams
def add(team, passwd): def add(team, passwd):
f = open('passwd', 'a') f = open(passwdfn, 'a')
fcntl.lockf(f, fcntl.LOCK_EX) fcntl.lockf(f, fcntl.LOCK_EX)
f.seek(0, 2) f.seek(0, 2)
f.write('%s\t%s\n' % (quote(team), quote(passwd))) f.write('%s\t%s\n' % (quote(team), quote(passwd)))

View File

@ -2,7 +2,6 @@
import asyncore import asyncore
import pointsd import pointsd
import roshambo
import game import game
import flagd import flagd
import histogram import histogram
@ -10,7 +9,11 @@ import histogram
def main(): def main():
pointsrv = pointsd.start() pointsrv = pointsd.start()
flagsrv = flagd.start() flagsrv = flagd.start()
if config.enabled('roshambo'):
import roshambo
roshambosrv = roshambo.start() roshambosrv = roshambo.start()
s = pointsrv.store s = pointsrv.store
slen = 0 slen = 0
while True: while True: