diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15953f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*~ +*.pyc +*.dat +passwd +target/ +puzzler/ +ctf.tce diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8ce3a5a --- /dev/null +++ b/Makefile @@ -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 $*' diff --git a/config.py b/config.py new file mode 100755 index 0000000..e7bbeda --- /dev/null +++ b/config.py @@ -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 diff --git a/games/bletchey/arecibo.py b/games/bletchey/arecibo.py deleted file mode 100755 index 447f620..0000000 --- a/games/bletchey/arecibo.py +++ /dev/null @@ -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) diff --git a/games/bletchey/scytale.py b/games/bletchey/scytale.py deleted file mode 100755 index ece0efd..0000000 --- a/games/bletchey/scytale.py +++ /dev/null @@ -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)) diff --git a/histogram.py b/histogram.py index 3d5bc9c..434ac68 100755 --- a/histogram.py +++ b/histogram.py @@ -4,13 +4,16 @@ import points import time import os import tempfile +import config + +pngout = config.datafile('histogram.png') def main(s=None): scores = {} now = 0 if not s: - s = points.Storage('scores.dat') + s = points.Storage() plotparts = [] teams = s.teams() @@ -57,11 +60,12 @@ set xtics nomirror set ytics nomirror set nokey set terminal png transparent size 640,200 x000000 xffffff -set output "histogram.png" -plot %(plot)s\n''' % {'plot': ','.join(plotparts)}) +set output "%(pngout)s" +plot %(plot)s\n''' % {'plot': ','.join(plotparts), + 'pngout': pngout}) instructions.flush() - gp = os.system('gnuplot %s' % instructions.name) + gp = os.system('gnuplot %s 2>/dev/null' % instructions.name) if __name__ == '__main__': main() diff --git a/mkpuzzles.py b/mkpuzzles.py new file mode 100755 index 0000000..fab4e98 --- /dev/null +++ b/mkpuzzles.py @@ -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(''' + + + + %(title)s + + + +

%(title)s

+''' % {'title': title, + 'css': config.css}) + if readme: + f.write('
%s
\n' % readme) + if files: + f.write('\n') + f.write(''' +
+ + + Team:
+ Password:
+ Key:
+ +
+ + +''' % {'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) + diff --git a/points.py b/points.py index a88e7cd..b818691 100755 --- a/points.py +++ b/points.py @@ -5,6 +5,8 @@ import hmac import struct import io import teams +import config +import os ## ## Authentication @@ -88,7 +90,9 @@ def incdict(dict, key, amt=1): dict[key] = dict.get(key, 0) + amt 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_cat = {} self.points_by_cat_team = {} diff --git a/puzzler.cgi b/puzzler.cgi index 290d8d8..3fde24a 100755 --- a/puzzler.cgi +++ b/puzzler.cgi @@ -1,206 +1,7 @@ #! /usr/bin/env python3 import cgitb; cgitb.enable() -import cgi -import os -import fcntl -import re import sys -import pointscli -import teams -import http.cookies -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(''' - - - - %s - - - -

%s

-''' % (title, title)) - -def end_html(): - print('') - - -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('') - 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('') - else: - print('

None (someone is slacking)

') - 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('
') - dump_file(fn) - print('
') - print('') - print('
') - print('' % cat) - print('' % points) - print('Team:
' % (team or '')) - print('Password:
' % (passwd or '')) - print('Key:
') - print('') - print('
') - 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('

%d points for %s.

' % (points, team)) - print('

Back to %s.

' % (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: +sys.path.insert(0, '/usr/lib/ctf') +import puzzler +puzzler.main() diff --git a/puzzler.py b/puzzler.py new file mode 100755 index 0000000..027ac8d --- /dev/null +++ b/puzzler.py @@ -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-->
')
+    print(*vals)
+    print('
') + + +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(''' + + + + %s + + + +

%s

+''' % (title, config.css, title)) + +def end_html(): + print('') + + +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('') + 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('') + else: + print('

None (someone is slacking)

') + 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('

%d points for %s.

' % (points, team)) + print('

Back to %s.

' % (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: diff --git a/puzzles/bletchey/100/key b/puzzles/bletchley/100/key similarity index 100% rename from puzzles/bletchey/100/key rename to puzzles/bletchley/100/key diff --git a/puzzles/bletchey/100/key.png b/puzzles/bletchley/100/key.png similarity index 100% rename from puzzles/bletchey/100/key.png rename to puzzles/bletchley/100/key.png diff --git a/puzzles/bletchey/200/index.html b/puzzles/bletchley/200/index.html similarity index 100% rename from puzzles/bletchey/200/index.html rename to puzzles/bletchley/200/index.html diff --git a/puzzles/bletchey/200/key b/puzzles/bletchley/200/key similarity index 100% rename from puzzles/bletchey/200/key rename to puzzles/bletchley/200/key diff --git a/puzzles/bletchey/250/index.html b/puzzles/bletchley/250/index.html similarity index 100% rename from puzzles/bletchey/250/index.html rename to puzzles/bletchley/250/index.html diff --git a/puzzles/bletchey/250/key b/puzzles/bletchley/250/key similarity index 100% rename from puzzles/bletchey/250/key rename to puzzles/bletchley/250/key diff --git a/puzzles/bletchey/300/index.html b/puzzles/bletchley/300/index.html similarity index 100% rename from puzzles/bletchey/300/index.html rename to puzzles/bletchley/300/index.html diff --git a/puzzles/bletchey/300/key b/puzzles/bletchley/300/key similarity index 100% rename from puzzles/bletchey/300/key rename to puzzles/bletchley/300/key diff --git a/puzzles/bletchey/500/200601262232.ogg b/puzzles/bletchley/500/200601262232.ogg similarity index 100% rename from puzzles/bletchey/500/200601262232.ogg rename to puzzles/bletchley/500/200601262232.ogg diff --git a/puzzles/bletchey/500/cipher.txt b/puzzles/bletchley/500/cipher.txt similarity index 100% rename from puzzles/bletchey/500/cipher.txt rename to puzzles/bletchley/500/cipher.txt diff --git a/puzzles/bletchey/500/index.html b/puzzles/bletchley/500/index.html similarity index 100% rename from puzzles/bletchey/500/index.html rename to puzzles/bletchley/500/index.html diff --git a/puzzles/bletchey/500/key b/puzzles/bletchley/500/key similarity index 100% rename from puzzles/bletchey/500/key rename to puzzles/bletchley/500/key diff --git a/register.cgi b/register.cgi index 3dfb0c5..e6db08f 100755 --- a/register.cgi +++ b/register.cgi @@ -1,62 +1,6 @@ #! /usr/bin/env python3 -import cgitb; cgitb.enable() -import cgi -import teams -import fcntl -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(''' - - - - Team Registration - - - -

Team Registration

- -
-
- - - $team_error
- - -
- - - - $pw_match_error
- - -
-
- - -''') - -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) +import sys +sys.path.insert(0, '/usr/lib/ctf') +import register +register.main() diff --git a/register.py b/register.py new file mode 100755 index 0000000..84ac82a --- /dev/null +++ b/register.py @@ -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(''' + + + + Team Registration + + + +

Team Registration

+ +
+
+ + + $team_error
+ + + +
+ + + + $pw_match_error
+ + +
+
+ + + ''' % (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() diff --git a/run.uberserv b/run.uberserv new file mode 100755 index 0000000..94bd8bc --- /dev/null +++ b/run.uberserv @@ -0,0 +1,3 @@ +#! /bin/sh + +/usr/lib/ctf/uberserv.py | logger -t ctf diff --git a/scoreboard.cgi b/scoreboard.cgi index 7bb66a3..4049b89 100755 --- a/scoreboard.cgi +++ b/scoreboard.cgi @@ -1,51 +1,6 @@ -#!/usr/bin/env python3 +#! /usr/bin/env python3 -import cgitb; cgitb.enable() -import points - -s = points.Storage('scores.dat') - -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(''' - - - - CTF Scoreboard - - - -

Scoreboard

-''') -print('') -print('') -for cat, points in categories: - print('' % (cat, points)) -print('') - -print('') -for cat, total in categories: - print('') -print('') -print('''
%s (%d)
') - scores = sorted([(s.team_points_in_cat(cat, team), team) for team in teams]) - for points, team in scores: - color = teamcolors[team] - print('
' % (float(points * 100)/total, color)) - print(' %s: %d' % (cat, team, points)) - print('
') - print('
- - - -''') - -# Local Variables: -# mode: python -# End: +import sys +sys.path.insert(0, '/usr/lib/ctf') +import scoreboard +scoreboard.main() diff --git a/scoreboard.py b/scoreboard.py new file mode 100755 index 0000000..e6936d4 --- /dev/null +++ b/scoreboard.py @@ -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(''' + + + + CTF Scoreboard + + + +

Scoreboard

+ ''' % config.base_url) + print('') + print('') + for cat, score in categories: + print('' % (cat, score)) + print('') + + print('') + for cat, total in categories: + print('') + print('') + print('''
%s (%d)
') + scores = sorted([(s.team_points_in_cat(cat, team), team) for team in teams]) + for score, team in scores: + color = teamcolors[team] + print('
' % (float(score * 100)/total, color)) + print(' %s: %d' % (cat, team, score)) + print('
') + print('
+ + + + ''') + +if __name__ == '__main__': + main() + +# Local Variables: +# mode: python +# End: diff --git a/teams.py b/teams.py index be6dbb1..a7be5de 100755 --- a/teams.py +++ b/teams.py @@ -9,13 +9,14 @@ house = 'dirtbags' passwdfn = '/var/lib/ctf/passwd' -teams = None +teams = {} built = 0 def build_teams(): global teams, built - modt = os.path.getmtime(passwdfn) - if modt <= built: + if not os.path.exists(passwdfn): + return + if os.path.getmtime(passwdfn) <= built: return teams = {} @@ -46,7 +47,7 @@ def exists(team): return team in teams def add(team, passwd): - f = open('passwd', 'a') + f = open(passwdfn, 'a') fcntl.lockf(f, fcntl.LOCK_EX) f.seek(0, 2) f.write('%s\t%s\n' % (quote(team), quote(passwd))) diff --git a/ubersrv.py b/uberserv.py similarity index 78% rename from ubersrv.py rename to uberserv.py index 78ccc4b..3c04100 100755 --- a/ubersrv.py +++ b/uberserv.py @@ -2,7 +2,6 @@ import asyncore import pointsd -import roshambo import game import flagd import histogram @@ -10,7 +9,11 @@ import histogram def main(): pointsrv = pointsd.start() flagsrv = flagd.start() - roshambosrv = roshambo.start() + + if config.enabled('roshambo'): + import roshambo + roshambosrv = roshambo.start() + s = pointsrv.store slen = 0 while True: