diff --git a/ctf.css b/ctf.css new file mode 100644 index 0000000..1eb1fc1 --- /dev/null +++ b/ctf.css @@ -0,0 +1,21 @@ +body { + background: #000; + color: #0f0; +} +.readme { + background: #444; +} +a:link { + color: #ff0; +} +a:visited { + color: #880; +} +a:hover { + color: #000; + background: #ff0; +} +.error { + color: #000; + background: #f00; +} diff --git a/flagd.py b/flagd.py index 2e48be4..862afc7 100755 --- a/flagd.py +++ b/flagd.py @@ -9,6 +9,7 @@ import hmac import optparse import points import pointscli +import teams import traceback key = b'My First Shared Secret (tm)' @@ -67,7 +68,7 @@ class Submitter(asyncore.dispatcher): def set_flag(self, cat, team): now = int(time.time()) - team = team or points.house + team = team or teams.house if self.flags.get(cat) != team: self.flags[cat] = team diff --git a/game.py b/game.py index 82fe91c..45ae28b 100755 --- a/game.py +++ b/game.py @@ -6,6 +6,7 @@ import asynchat import socket import traceback import time +import teams from errno import EPIPE @@ -25,7 +26,6 @@ class Listener(asyncore.dispatcher): self.listen(4) self.player_factory = player_factory self.manager = manager - self.last_beat = 0 def handle_accept(self): conn, addr = self.accept() @@ -34,9 +34,7 @@ class Listener(asyncore.dispatcher): # has a reference to it for as long as it's open. def readable(self): - now = time.time() - if now > self.last_beat + pulse: - self.manager.heartbeat(now) + self.manager.heartbeat(time.time()) return True @@ -89,11 +87,25 @@ class Manager: self.lobby = set() self.contestants = [] self.last_beat = 0 + self.timers = set() def heartbeat(self, now): - # Called by listener to beat heart - for game in list(self.games): - game.heartbeat(now) + """Called by listener to beat heart.""" + + now = time.time() + if now > self.last_beat + pulse: + for game in list(self.games): + game.heartbeat(now) + for event in self.timers: + when, cb = event + if now >= when: + self.timers.remove(event) + cb() + + def add_timer(self, when, cb): + """Add a timed callback.""" + + self.timers.add((when, cb)) def enter_lobby(self, player): self.lobby.add(player) @@ -248,13 +260,14 @@ class Player(asynchat.async_chat): cmd, args = val[0].lower(), val[1:] if cmd == 'login': - if not self.name: - # XXX Check password + if self.name: + self.err('Already logged in.') + elif teams.chkpasswd(args[0], args[1]): self.name = args[0] self.write('Welcome to the fray, %s.' % self.name) self.manager.enter_lobby(self) else: - self.err('Already logged in.') + self.err('Invalid password.') elif cmd == '^': # Send to manager ret = self.manager.player_cmd(args) diff --git a/games/crypto/scytale.py b/games/crypto/scytale.py new file mode 100755 index 0000000..ece0efd --- /dev/null +++ b/games/crypto/scytale.py @@ -0,0 +1,30 @@ +#! /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/index.html b/index.html new file mode 100644 index 0000000..89fe545 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + +
++ Some challenges are puzzles. Some are + sitting on the network; you must find these yourself! +
+ + diff --git a/points.py b/points.py index 952ee00..10cadff 100755 --- a/points.py +++ b/points.py @@ -4,9 +4,7 @@ import socket import hmac import struct import io - -## Name of the house team -house = 'dirtbags' +import teams ## ## Authentication diff --git a/pointscli.py b/pointscli.py index c4bd659..c43a534 100755 --- a/pointscli.py +++ b/pointscli.py @@ -6,10 +6,17 @@ import points import socket import time -def submit(sock, cat, team, score): +def makesock(host): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect((host, 6667)) + return s + +def submit(cat, team, score, sock=None): + if not sock: + sock = makesock('cfl-sunray1') begin = time.time() mark = int(begin) - req = points.encode_request(mark, cat, team, score) + req = points.encode_request(1, mark, cat, team, score) while True: sock.send(req) r, w, x = select.select([sock], [], [], begin + 2 - time.time()) @@ -17,12 +24,12 @@ def submit(sock, cat, team, score): break b = sock.recv(500) try: - when, cat_, txt = points.decode_response(b) + id, txt = points.decode_response(b) except ValueError: # Ignore invalid packets continue - if (when != mark) or (cat_ != cat): - # Ignore wrong timestamp + if id != 1: + # Ignore wrong ID continue if txt == 'OK': return @@ -30,11 +37,6 @@ def submit(sock, cat, team, score): raise ValueError(txt) -def makesock(host): - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.connect((host, 6667)) - return s - def main(): p = optparse.OptionParser(usage='%prog CATEGORY TEAM SCORE') p.add_option('-s', '--host', dest='host', default='localhost', @@ -50,7 +52,7 @@ def main(): s = makesock(opts.host) try: - submit(s, cat, team, score) + submit(cat, team, score, sock=s) except ValueError as err: print(err) raise diff --git a/pointsd.py b/pointsd.py index c415dbc..3ec0e63 100755 --- a/pointsd.py +++ b/pointsd.py @@ -34,11 +34,11 @@ class MyHandler(asyncore.dispatcher): team = team or house # Replays can happen legitimately. - if not (id in self.acked): + if not ((peer, id) in self.acked): if not (now - 2 < when <= now): return self.respond(peer, id, 'Your clock is off') self.store.add((when, cat, team, score)) - self.acked.add(id) + self.acked.add((peer, id)) self.respond(peer, id, 'OK') diff --git a/puzzler.cgi b/puzzler.cgi new file mode 100755 index 0000000..84fe253 --- /dev/null +++ b/puzzler.cgi @@ -0,0 +1,176 @@ +#! /usr/bin/env python3 + +import cgitb; cgitb.enable() +import cgi +import os +import fcntl +import re +import sys +import pointscli +import teams + +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('puzzler.dat'): + line = line.strip() + cat, team, pts = line.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 + + +f = cgi.FieldStorage() + +cat = f.getfirst('c') +points = f.getfirst('p') +team = f.getfirst('t') +passwd = f.getfirst('w') +key = f.getfirst('k') + +verboten = ['key', 'index.html'] + +def start_html(title): + print('''Content-type: text/html + + + + + +None (someone is slacking)
') + end_html() + +def show_puzzle(cat, points, points_dir): + # Show puzzle in cat for points + start_html('%s for %s' % (cat, points)) + fn = os.path.join(points_dir, 'index.html') + if os.path.exists(fn): + print('Kolejne modele Panzerfausta, odpowiednio: 60, 100, 150, różnił kaliber głowicy i wielkość ładunku miotającego. Konstrukcja i mechanizm nie ulegał istotnym zmianom, z racji wzrastania zasięgu broni modyfikacjom ulegały nastawy celowników. Jedynie we wzorze 150 wprowadzono (a był to już początek 1945 roku) wielokrotne użycie wyrzutni rurowej. Osiągnięto to przez umieszczenie ładunku miotającego w głowicy oraz przez wzmocnienie rury. W wyniku problemu z transportem model ów nie wszedł do walki. Model 250 (o teoretycznym zasięgu 250 m) z racji zakończenia wojny nie opuścił desek kreślarskich nigdy nie wchodząc nawet w fazę prototypową.
+(61, 4) +(47, 8) +(19, 4) +(37, 1) +(51, 3) +(67, 5) +(9, 2) +(26, 1) +(2, 2) +(26, 3) +(50, 2)+ diff --git a/puzzles/bletchey/300/key b/puzzles/bletchey/300/key new file mode 100644 index 0000000..ea86794 --- /dev/null +++ b/puzzles/bletchey/300/key @@ -0,0 +1 @@ +jako561962 diff --git a/puzzles/bletchey/500/200601262232.ogg b/puzzles/bletchey/500/200601262232.ogg new file mode 100644 index 0000000..d00f825 Binary files /dev/null and b/puzzles/bletchey/500/200601262232.ogg differ diff --git a/puzzles/bletchey/500/cipher.txt b/puzzles/bletchey/500/cipher.txt new file mode 100644 index 0000000..cf43f95 --- /dev/null +++ b/puzzles/bletchey/500/cipher.txt @@ -0,0 +1 @@ +31 9 15 26 14 23 14 6 18 5 12 18 5 2 16 27 7 10 11 5 13 31 17 17 6 2 26 26 10 21 10 8 20 4 diff --git a/puzzles/bletchey/500/index.html b/puzzles/bletchey/500/index.html new file mode 100644 index 0000000..8c32ab6 --- /dev/null +++ b/puzzles/bletchey/500/index.html @@ -0,0 +1 @@ +journals.uchicago diff --git a/puzzles/bletchey/500/key b/puzzles/bletchey/500/key new file mode 100644 index 0000000..b197a4a --- /dev/null +++ b/puzzles/bletchey/500/key @@ -0,0 +1 @@ +xez.3nt diff --git a/register.cgi b/register.cgi new file mode 100755 index 0000000..3dfb0c5 --- /dev/null +++ b/register.cgi @@ -0,0 +1,62 @@ +#! /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(''' + + + +