mirror of https://github.com/dirtbags/moth.git
Somewhat working game
This commit adds: * Registration * Puzzler cgi script * Several puzzles * Style sheet for HTML
This commit is contained in:
parent
d638a3be0c
commit
5f39aff5de
|
@ -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;
|
||||
}
|
3
flagd.py
3
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
|
||||
|
|
33
game.py
33
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)
|
||||
|
|
|
@ -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))
|
|
@ -0,0 +1,22 @@
|
|||
<?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>Capture The Flag</title>
|
||||
<link rel="stylesheet" href="ctf.css" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Capture The Flag</h1>
|
||||
|
||||
<ol>
|
||||
<li><a href="register.cgi">Register</a> your team</li>
|
||||
<li><a href="scoreboard.cgi">Scoreboard</a></li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
Some challenges are <a href="puzzler.cgi">puzzles</a>. Some are
|
||||
sitting on the network; you must find these yourself!
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -4,9 +4,7 @@ import socket
|
|||
import hmac
|
||||
import struct
|
||||
import io
|
||||
|
||||
## Name of the house team
|
||||
house = 'dirtbags'
|
||||
import teams
|
||||
|
||||
##
|
||||
## Authentication
|
||||
|
|
24
pointscli.py
24
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
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
<?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>')
|
||||
opened = max(opened, puzzles[0])
|
||||
for p in puzzles:
|
||||
if p <= opened:
|
||||
print('<li><a href="puzzler.cgi?c=%s&p=%d">%d</a></li>' % (cat, p, p))
|
||||
print('</ul>')
|
||||
else:
|
||||
print('<p>None (someone is slacking)</p>')
|
||||
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('<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" /><br />')
|
||||
print('Password: <input type="password" name="w" /><br />')
|
||||
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)
|
||||
pointscli.submit(cat, team, points)
|
||||
end_html()
|
||||
f = open('puzzler.dat', 'a')
|
||||
fctnl.lockf(f, LOCK_EX)
|
||||
f.write('%s\t%s\t%d\n' % (cat, team, points))
|
||||
|
||||
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)
|
||||
else:
|
||||
thekey = open('%s/key' % points_dir).read().strip()
|
||||
if not teams.chkpasswd(team, passwd):
|
||||
start_html('Wrong password')
|
||||
end_html()
|
||||
elif key != thekey:
|
||||
show_puzzle(cat, points, points_dir)
|
||||
elif points_by_team.get((team, cat)):
|
||||
start_html('Greedy greedy')
|
||||
end_html()
|
||||
else:
|
||||
win(cat, team, points)
|
||||
|
||||
main()
|
|
@ -0,0 +1 @@
|
|||
antediluvian
|
Binary file not shown.
After Width: | Height: | Size: 87 B |
|
@ -0,0 +1 @@
|
|||
tkftsuiuqvaheohrnsnuoleyriod"eic"
|
|
@ -0,0 +1 @@
|
|||
unequivocal
|
|
@ -0,0 +1 @@
|
|||
27586126814341379597440261571645814840581961154587430529221052323
|
|
@ -0,0 +1 @@
|
|||
DB1663<3
|
|
@ -0,0 +1,13 @@
|
|||
<p>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ą.</p>
|
||||
<pre>(61, 4)
|
||||
(47, 8)
|
||||
(19, 4)
|
||||
(37, 1)
|
||||
(51, 3)
|
||||
(67, 5)
|
||||
(9, 2)
|
||||
(26, 1)
|
||||
(2, 2)
|
||||
(26, 3)
|
||||
(50, 2)</pre>
|
||||
|
|
@ -0,0 +1 @@
|
|||
jako561962
|
Binary file not shown.
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
journals.uchicago
|
|
@ -0,0 +1 @@
|
|||
xez.3nt
|
|
@ -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('''<?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)
|
|
@ -17,9 +17,10 @@ print('''<?xml version="1.0" encoding="UTF-8" ?>
|
|||
"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>yo mom</title>
|
||||
<title>CTF Scoreboard</title>
|
||||
<link rel="stylesheet" href="ctf.css" type="text/css" />
|
||||
</head>
|
||||
<body style="background: black; color: white;">
|
||||
<body>
|
||||
<h1>Scoreboard</h1>
|
||||
''')
|
||||
print('<table>')
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
#! /usr/bin/env python3
|
||||
|
||||
import fcntl
|
||||
|
||||
house = 'dirtbags'
|
||||
|
||||
teams = None
|
||||
|
||||
def build_teams():
|
||||
global teams
|
||||
|
||||
teams = {}
|
||||
try:
|
||||
f = open('passwd')
|
||||
for line in f:
|
||||
team, passwd = line.strip().split('\t')
|
||||
teams[team] = passwd
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
def chkpasswd(team, passwd):
|
||||
if teams is None:
|
||||
build_teams()
|
||||
if teams.get(team) == passwd:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def exists(team):
|
||||
if teams is None:
|
||||
build_teams()
|
||||
if team == house:
|
||||
return True
|
||||
return team in teams
|
||||
|
||||
def add(team, passwd):
|
||||
f = open('passwd', 'a')
|
||||
fcntl.lockf(f, fcntl.LOCK_EX)
|
||||
f.seek(0, 2)
|
||||
f.write('%s\t%s\n' % (team, passwd))
|
Loading…
Reference in New Issue