Initial effort to decouple from buildroot

This commit is contained in:
Neale Pickett 2010-03-02 20:45:21 -07:00
commit 00694e8472
399 changed files with 15132 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*~
*.pyc

53
Makefile Normal file
View File

@ -0,0 +1,53 @@
BASE = /opt/ctf
VAR = $(BASE)/var
WWW = $(BASE)/www
LIB = $(BASE)/lib
BIN = $(BASE)/bin
SBIN = $(BASE)/sbin
BASE_URL = /ctf/
install:
id ctf || useradd --system --shell /bin/false --home $(VAR) \
--comment "Capture The Flag" ctf
install --directory $(LIB) $(BIN) $(SBIN) $(LIB)/disabled
install --directory --owner=ctf $(VAR)
install --directory --owner=ctf $(WWW)
install --directory --owner=ctf $(WWW)/puzzler
install --directory --owner=ctf $(VAR)/points
install --directory --owner=ctf $(VAR)/points/tmp
install --directory --owner=ctf $(VAR)/points/cur
install --directory --owner=ctf $(VAR)/flags
# Tanks has a lot of files.
install --directory --owner=ctf $(VAR)/tanks
install --directory --owner=ctf $(VAR)/tanks/results
install --directory --owner=ctf $(VAR)/tanks/errors
install --directory --owner=ctf $(VAR)/tanks/ai
install --directory --owner=ctf $(VAR)/tanks/ai/players
install --directory --owner=ctf $(VAR)/tanks/ai/house
echo 'VAR = "$(VAR)"' > ctf/paths.py
echo 'WWW = "$(WWW)"' >> ctf/paths.py
echo 'LIB = "$(LIB)"' >> ctf/paths.py
echo 'BIN = "$(BIN)"' >> ctf/paths.py
echo 'SBIN = "$(SBIN)"' >> ctf/paths.py
echo 'BASE_URL = "$(BASE_URL)"' >> ctf/paths.py
python setup.py install
install bin/pointscli $(BIN)
install bin/in.pointsd bin/in.flagd \
bin/scoreboard bin/run-tanks \
bin/run-ctf $(SBIN)
cp -r lib/* $(LIB)
cp -r www/* $(WWW)
rm -f $(WWW)/tanks/results
ln -s $(VAR)/tanks/results $(WWW)/tanks/results
cp template.html $(LIB)
./mkpuzzles.py --base=$(BASE_URL) --puzzles=puzzles \
--htmldir=$(WWW)/puzzler --keyfile=$(LIB)/puzzler.keys
uninstall:
rm -rf $(VAR) $(WWW) $(LIB) $(BIN) $(SBIN)
rmdir $(BASE) || true

265
bin/badmathbot Executable file
View File

@ -0,0 +1,265 @@
#! /usr/bin/python
import badmath
import time
import os
import traceback
import pickle
import irc
from ctf import teams
from ctf.flagger import Flagger
class Gyopi(irc.Bot):
STATE_FN = 'badmath.state'
SALT = 'this is questionable.'
MAX_ATTEMPT_RATE = 3
NOBODY = '\002[nobody]\002'
def __init__(self, host, channels, dataPath, flagger):
irc.Bot.__init__(self, host, ['gyopi', 'gyopi_', '_gyopi', '[gyopi]'], 'Gyopi', channels)
self._dataPath = dataPath
self._flag = flagger
try:
self._loadState()
except:
self._lvl = 0
self._flag.set_flag(teams.house)
self._tokens = []
self._lastAttempt = {}
self._affiliations = {}
self._newPuzzle()
def err(self, exception):
"""Save the traceback for later inspection"""
irc.Bot.err(self, exception)
t,v,tb = exception
info = []
while 1:
info.append('%s:%d(%s)' %
(os.path.basename(tb.tb_frame.f_code.co_filename),
tb.tb_lineno,
tb.tb_frame.f_code.co_name))
tb = tb.tb_next
if not tb:
break
del tb # just to be safe
infostr = '[' + '] ['.join(info) + ']'
self.last_tb = '%s %s %s' % (t, v, infostr)
print(self.last_tb)
def cmd_JOIN(self, sender, forum, addl):
"""On join, announce who has the flag."""
if sender.name() in self.nicks:
self._tellFlag(forum)
self._tellPuzzle(forum)
self.write(['TOPIC', '#badmath'], 'type !help')
def _newPuzzle(self):
"""Create a new puzzle."""
self._key, self._puzzle, self._banned = badmath.mkPuzzle(self._lvl)
def _loadState(self):
"""Load the last state from the stateFile."""
statePath = os.path.join(self._dataPath, self.STATE_FN)
stateFile = open( statePath, 'rb' )
state = pickle.load(stateFile)
self._lvl = state['lvl']
self._flag.set_flag( state['flag'] )
self._lastAttempt = state['lastAttempt']
self._affiliations = state['affiliations']
self._puzzle = state['puzzle']
self._key = state['key']
self._banned = state['banned']
self._tokens = state.get('tokens', [])
def _saveState(self):
"""Write the current state to file."""
state = {'lvl': self._lvl,
'flag': self._flag.flag,
'lastAttempt': self._lastAttempt,
'affiliations': self._affiliations,
'puzzle': self._puzzle,
'key': self._key,
'banned': self._banned,
'tokens': self._tokens}
# Do the write as an atomic move operation
statePath = os.path.join(self._dataPath, self.STATE_FN)
stateFile = open(statePath + '.tmp', 'wb')
pickle.dump(state, stateFile)
stateFile.close()
os.rename( statePath + '.tmp', statePath)
def _tellFlag(self, forum):
"""Announce who owns the flag."""
forum.msg('%s has the flag.' % (self._flag.flag))
def _tellPuzzle(self, forum):
"""Announce the current puzzle."""
forum.msg('Difficulty level is %d' % self._lvl)
forum.msg('The problem is: %s' % ' '.join( map(str, self._puzzle)))
def _getStations(self):
stations = {}
file = open(os.path.join(STORAGE, 'stations.txt'))
lines = file.readlines()
for line in lines:
try:
name, file = line.split(':')
except:
continue
stations[name] = file
return stations
def _giveToken(self, user, forum):
"""Hand a Jukebox token to the user."""
token = self._jukebox.mkToken(user)
forum.msg('You get a jukebox token: %s' % token)
forum.msg('Use this with the !set command to change the music.')
forum.msg('This token is specific to your user name, and is only '
'useable once.')
def _useToken(self, user, forum, token, station):
"""Use the given token, and change the current station to station."""
try:
station = int(station)
stations = self._getStations()
assert station in stations
except:
forum.msg('%s: Invalid Station (%s)' % station)
return
if token in self._tokens[user]:
self._tokens[user].remove(token)
def cmd_PRIVMSG(self, sender, forum, addl):
text = addl[0]
who = sender.name()
if text.startswith('!'):
parts = text[1:].split(' ', 1)
cmd = parts[0]
if len(parts) > 1:
args = parts[1]
else:
args = None
if cmd.startswith('r'):
# Register
if args:
self._affiliations[who] = args
team = self._affiliations.get(who, self.NOBODY)
forum.msg('%s is playing for %s' % (who, team))
elif cmd.startswith('w'):
forum.msg('Teams:')
for player in self._affiliations:
forum.msg('%s: %s' % (player, self._affiliations[player]))
elif cmd.startswith('embrace'):
# Embrace
forum.ctcp('ACTION', 'is devoid of emotion.')
elif cmd.startswith('f'):
# Flag
self._tellFlag(forum)
elif cmd.startswith('h'):
# Help
forum.msg('''Goal: Help me with my math homework, FROM ANOTHER DIMENSION! Order of operations is always left to right in that dimension, but the operators are alien.''')
forum.msg('Order of operations is always left to right.')
#forum.msg('Goal: The current winner gets to control the contest music.')
forum.msg('Commands: !help, !flag, !register [TEAM], !solve SOLUTION,!? EQUATION, !ops, !problem, !who')
elif cmd.startswith('prob'):
self._tellPuzzle(forum)
elif cmd.startswith('solve') and args:
# Solve
team = self._affiliations.get(who)
lastAttempt = time.time() - self._lastAttempt.get(team, 0)
#UN-COMMENT AFTER NMT CTF
# self._lastAttempt[team] = time.time()
answer = badmath.solve(self._key, self._puzzle)
try:
attempt = int(''.join(args).strip())
except:
forum.msg("%s: Answers are always integers.")
if not team:
forum.msg('%s: register first (!register TEAM).' % who)
elif self._flag.flag == team:
forum.msg('%s: Greedy, greedy.' % who)
elif lastAttempt < self.MAX_ATTEMPT_RATE:
forum.msg('%s: Wait at least %d seconds between attempts' %
(team, self.MAX_ATTEMPT_RATE))
elif answer == attempt:
self._flag.set_flag( team )
self._lvl = self._lvl + 1
self._tellFlag(forum)
self._newPuzzle()
self._tellPuzzle(forum)
# self._giveToken(who, sender)
self._saveState()
else:
forum.msg('%s: That is not correct.' % who)
# Test a simple one op command.
elif cmd.startswith('?'):
if not args:
forum.msg('%s: Give me an easier problem, and I\'ll '
'give you the answer.' % who)
return
try:
tokens = badmath.parse(''.join(args))
except (ValueError), msg:
forum.msg('%s: %s' % (who, msg))
return
if len(tokens) > 3:
forum.msg('%s: You can only test one op at a time.' % who)
return
for num in self._banned:
if num in tokens:
forum.msg('%s: You can\'t test numbers in the '
'puzzle.' % who)
return
try:
result = badmath.solve(self._key, tokens)
forum.msg('%s: %s -> %d' % (who, ''.join(args), result))
except Exception, msg:
forum.msg("%s: That doesn't work at all: %s" % (who, msg))
elif cmd == 'birdzerk':
self._saveState()
elif cmd == 'traceback':
forum.msg(self.last_tb or 'No traceback')
if __name__ == '__main__':
import optparse
p = optparse.OptionParser()
p.add_option('-i', '--irc', dest='ircHost', default='localhost',
help='IRC Host to connect to.')
p.add_option('-f', '--flagd', dest='flagd', default='localhost',
help='Flag Server to connect to')
p.add_option('-p', '--password', dest='password',
default='badmath:::a41c6753210c0bdafd84b3b62d7d1666',
help='Flag server password')
p.add_option('-d', '--path', dest='path', default='/var/lib/ctf/badmath',
help='Path to where we can store state info.')
p.add_option('-c', '--channel', dest='channel', default='#badmath',
help='Which channel to join')
opts, args = p.parse_args()
channels = [opts.channel]
flagger = Flagger(opts.flagd, opts.password.encode('utf-8'))
gyopi = Gyopi((opts.ircHost, 6667), channels, opts.path, flagger)
irc.run_forever()

96
bin/in.flagd Executable file
View File

@ -0,0 +1,96 @@
#! /usr/bin/python
import sys
import optparse
import hmac
import time
import select
from ctf import teams, pointscli
import os
from urllib import quote
basedir = None
flagsdir = None
key = 'My First Shared Secret (tm)'
def hexdigest(s):
return hmac.new(key, s.encode('utf-8')).hexdigest()
def auth():
# Pretend to be in.tcpmuxd
while True:
line = sys.stdin.readline()
if not line:
return
line = line.strip().lower()
if line == 'tcpmux':
sys.stdout.write('+Okay, fine.\r\n')
sys.stdout.flush()
continue
elif line == 'help':
sys.stdout.write('tcpmux\r\n')
elif ':::' in line:
# Authentication
cat, passwd = line.split(':::')
if passwd == hexdigest(cat):
return cat
else:
sys.stdout.write('-Blow me.\r\n')
else:
sys.stdout.write('-Blow me.\r\n')
return
def award(cat, team):
qcat = quote(cat, '')
fn = os.path.join(flagsdir, qcat)
f = open(fn, 'w')
f.write(team)
f.close()
pointscli.award(cat, team, 1)
print('+%s' % team)
sys.stdout.flush()
def run():
cat = auth()
if not cat:
return
now = time.time()
next_award = now - (now % 60)
flag = teams.house
while True:
now = time.time()
while now >= next_award:
next_award += 60
award(cat, flag)
timeout = next_award - now
r, w, x = select.select([sys.stdin], [], [], timeout)
if r:
line = sys.stdin.readline()
if not line:
break
new_flag = line.strip() or teams.house
if new_flag != flag:
# Award a point if the flag is captured
flag = new_flag
award(cat, flag)
def main():
p = optparse.OptionParser(usage='%prog [options] FLAGSDIR')
p.add_option('-a', '--auth', dest='cat', default=None,
help='Generate authentication for the given category')
opts, args = p.parse_args()
if opts.cat:
print('%s:::%s' % (opts.cat, hexdigest(opts.cat.encode('utf-8'))))
elif len(args) != 1:
p.error('Wrong number of arguments')
else:
global flagsdir
flagsdir = args[0]
run()
if __name__ == '__main__':
main()

5
bin/in.heartbeatd Executable file
View File

@ -0,0 +1,5 @@
#! /bin/sh
ip=$(echo $UDPREMOTEADDR | cut -d: -f1)
touch $1/$ip
echo 'Hello.'

19
bin/in.pointsd Executable file
View File

@ -0,0 +1,19 @@
#! /bin/sh
##
## This is meant to be run from inotifyd like so:
##
## inotifyd in.pointsd $base/cur:y
##
## inotifyd runs in.pointsd serially, so all we have to do is just echo
## each file to the log, and then remove it. This allows a log message
## to be entered by writing a file into tmp, moving it to cur, and then
## moving along. Even if pointsd dies, everybody is still able to score
## points asynchronously without losing anything: it'll just get picked
## up when pointsd restarts.
##
# Args: flag dir file
fn=$2/$3
cat $fn >> $2/../log && rm $fn

136
bin/kevin Executable file
View File

@ -0,0 +1,136 @@
#! /usr/bin/python
import os
import optparse
import asynchat
import socket
import asyncore
from urllib import quote_plus as quote
import irc
from ctf.flagger import Flagger
nobody = '\002[nobody]\002'
class Kevin(irc.Bot):
def __init__(self, host, flagger, tokens, victims):
irc.Bot.__init__(self, host,
['kevin', 'kev', 'kevin_', 'kev_', 'kevinm', 'kevinm_'],
'Kevin',
['+kevin'])
self.flagger = flagger
self.tokens = tokens
self.victims = victims
self.affiliation = {}
def cmd_001(self, sender, forum, addl):
self.write(['OPER', 'bot', 'BottyMcBotpants'])
irc.Bot.cmd_001(self, sender, forum, addl)
def cmd_JOIN(self, sender, forum, addl):
if sender.name == self.nick:
self.write(['TOPIC', '#badmath'], 'type !help')
self.tell_flag(forum)
def cmd_381(self, sender, forum, addl):
# You are now an IRC Operator
if self.nick != 'kevin':
self.write(['KILL', 'kevin'], 'You are not kevin. I am kevin.')
self.write(['NICK', 'kevin'])
def err(self, exception):
"""Save the traceback for later inspection"""
irc.Bot.err(self, exception)
t,v,tb = exception
info = []
while 1:
info.append('%s:%d(%s)' % (os.path.basename(tb.tb_frame.f_code.co_filename),
tb.tb_lineno,
tb.tb_frame.f_code.co_name))
tb = tb.tb_next
if not tb:
break
del tb # just to be safe
infostr = '[' + '] ['.join(info) + ']'
self.last_tb = '%s %s %s' % (t, v, infostr)
print(self.last_tb)
def tell_flag(self, forum):
forum.msg('%s has the flag.' % (self.flagger.flag or nobody))
def cmd_PRIVMSG(self, sender, forum, addl):
text = addl[0]
if text.startswith('!'):
parts = text[1:].split(' ', 1)
cmd = parts[0].lower()
if len(parts) > 1:
args = parts[1]
else:
args = None
if cmd.startswith('r'):
# Register
who = sender.name()
if args:
self.affiliation[who] = args
team = self.affiliation.get(who, nobody)
forum.msg('%s is playing for %s' % (who, team))
elif cmd.startswith('e'):
# Embrace
forum.ctcp('ACTION', 'hugs %s' % sender.name())
elif cmd.startswith('f'):
# Flag
self.tell_flag(forum)
elif cmd.startswith('h'):
# Help
forum.msg('Goal: Obtain a token with social engineering.')
forum.msg('Commands: !help, !flag, !register [TEAM], !claim TOKEN, !victims, !embrace')
elif cmd.startswith('c') and args:
# Claim
sn = sender.name()
team = self.affiliation.get(sn)
token = quote(args, safe='')
fn = os.path.join(self.tokens, token)
if not team:
forum.msg('%s: register first (!register TEAM).' % sn)
elif self.flagger.flag == team:
forum.msg('%s: Greedy, greedy.' % sn)
elif not os.path.exists(fn):
forum.msg('%s: Token does not exist (possibly already claimed).' % sn)
else:
os.unlink(fn)
self.flagger.set_flag(team)
self.tell_flag(forum)
elif cmd.startswith('v'):
# Victims
# Open the file each time, so it can change
try:
for line in open(self.victims):
forum.msg(line.strip())
except IOError:
forum.msg('There are no victims!')
elif cmd == 'traceback':
forum.msg(self.last_tb or 'No traceback')
def main():
p = optparse.OptionParser()
p.add_option('-t', '--tokens', dest='tokens', default='./tokens',
help='Directory containing tokens')
p.add_option('-v', '--victims', dest='victims', default='victims.txt',
help='File containing victims information')
p.add_option('-i', '--ircd', dest='ircd', default='localhost',
help='IRC server to connect to')
p.add_option('-f', '--flagd', dest='flagd', default='localhost',
help='Flag server to connect to')
p.add_option('-p', '--password', dest='password',
default='kevin:::7db3e44d53d4a466f8facd7b7e9aa2b7',
help='Flag server password')
p.add_option('-c', '--channel', dest='channel',
help='Channel to join')
opts, args = p.parse_args()
f = Flagger(opts.flagd, opts.password.encode('utf-8'))
k = Kevin((opts.ircd, 6667), f, opts.tokens, opts.victims)
irc.run_forever()
if __name__ == '__main__':
main()

5
bin/pointscli Executable file
View File

@ -0,0 +1,5 @@
#! /usr/bin/python
from ctf import pointscli
pointscli.main()

285
bin/pollster Executable file
View File

@ -0,0 +1,285 @@
#!/usr/bin/python
import os
import re
import sys
import time
import socket
import traceback
import subprocess
import random
import httplib
import optparse
import cStringIO as io
from ctf import pointscli, html
ifconfig = '/sbin/ifconfig'
udhcpc = '/sbin/udhcpc'
class BoundHTTPConnection(httplib.HTTPConnection):
''' http.client.HTTPConnection doesn't support binding to a particular
address, which is something we need. '''
def __init__(self, bindip, host, port=None, strict=None, timeout=None):
httplib.HTTPConnection.__init__(self, host, port, strict)
self.bindip = bindip
self.timeout = timeout
def connect(self):
''' Connect to the host and port specified in __init__, but
also bind first. '''
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((self.bindip, 0))
self.sock.settimeout(self.timeout)
self.sock.connect((self.host, self.port))
def random_mac():
''' Set a random mac on the poll interface. '''
retcode = subprocess.call((ifconfig, opts.iface, 'down'))
mac = ':'.join([opts.mac_vendor] + ['%02x' % random.randint(0,255) for i in range(3)])
retcode = subprocess.call((ifconfig, opts.iface, 'hw', 'ether', mac, 'up'))
def dhcp_request():
''' Request a new IP on the poll interface. '''
retcode = subprocess.call((udhcpc, '-i', opts.iface, '-q'))
def get_ip():
''' Return the IP of the poll interface. '''
ip_match = re.compile(r'inet addr:([0-9.]+)')
p = subprocess.Popen((ifconfig, opts.iface), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
for line in out.splitlines():
m = ip_match.search(line)
if m is not None:
return m.group(1).decode('utf-8')
return '10.1.1.1'
def socket_poll(srcip, ip, port, msg, prot, max_recv=1):
''' Connect via socket to the specified <ip>:<port> using the
specified <prot>, send the specified <msg> and return the
response or None if something went wrong. <max_recvs> specifies
how many times to read from the socket (defaults to once). '''
# create a socket
try:
sock = socket.socket(socket.AF_INET, prot)
except Exception, e:
print('pollster: create socket failed (%s)' % e)
traceback.print_exc()
return None
sock.bind((srcip, 0))
sock.settimeout(opts.timeout)
# connect
try:
sock.connect((ip, port))
except socket.timeout, e:
print('pollster: attempt to connect to %s:%d timed out (%s)' % (ip, port, e))
traceback.print_exc()
return None
except Exception, e:
print('pollster: attempt to connect to %s:%d failed (%s)' % (ip, port, e))
traceback.print_exc()
return None
# send something
sock.send(msg)
# get a response
resp = []
try:
# read from the socket until <max_recv> responses or read,
# a timeout occurs, the socket closes, or some other exception
# is raised
for i in range(max_recv):
data = sock.recv(1024)
if len(data) == 0:
break
resp.append(data)
except socket.timeout, e:
print('pollster: timed out waiting for a response from %s:%d (%s)' % (ip, port, e))
traceback.print_exc()
except Exception, e:
print('pollster: receive from %s:%d failed (%s)' % (ip, port, e))
traceback.print_exc()
sock.close()
if len(resp) == 0:
return None
return ''.join(resp)
# PUT POLLS FUNCTIONS HERE
# Each function should take an IP address and return a team name or None
# if (a) the service is not up, (b) it doesn't return a valid team name.
def poll_fingerd(srcip, ip):
''' Poll the fingerd service. Returns None or a team name. '''
resp = socket_poll(srcip, ip, 79, 'flag\n', socket.SOCK_STREAM)
if resp is None:
return None
return resp.split('\n')[0]
def poll_noted(srcip, ip):
''' Poll the noted service. Returns None or a team name. '''
resp = socket_poll(srcip, ip, 4000, 'rflag\n', socket.SOCK_STREAM)
if resp is None:
return None
return resp.split('\n')[0]
def poll_catcgi(srcip, ip):
''' Poll the cat.cgi web service. Returns None or a team name. '''
try:
conn = BoundHTTPConnection(srcip, ip, timeout=opts.timeout)
conn.request('GET', '/var/www/flag')
except Exception, e:
traceback.print_exc()
return None
resp = conn.getresponse()
if resp.status != 200:
conn.close()
return None
data = resp.read()
conn.close()
return data.split('\n')[0]
def poll_tftpd(srcip, ip):
''' Poll the tftp service. Returns None or a team name. '''
resp = socket_poll(srcip, ip, 69, '\x00\x01' + 'flag' + '\x00' + 'octet' + '\x00', socket.SOCK_DGRAM)
if resp is None:
return None
if len(resp) <= 5:
return None
resp = resp.split('\n')[0]
# ack
_ = socket_poll(srcip, ip, 69, '\x00\x04\x00\x01' + '\x00' * 14, socket.SOCK_DGRAM, 0)
return resp[4:].split('\n')[0]
# PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED
POLLS = {
'fingerd' : poll_fingerd,
'noted' : poll_noted,
'catcgi' : poll_catcgi,
'tftpd' : poll_tftpd,
}
p = optparse.OptionParser()
p.add_option('-d', '--debug', action='store_true', dest='debug',
default=False,
help='Turn on debugging output')
p.add_option('-s', '--interval', type='float', dest='interval',
default=60,
help='Time between polls, in seconds (default: %default)')
p.add_option('-t', '--timeout', type='float', dest='timeout',
default=0.5,
help='Poll timeout, in seconds (default: %default)')
p.add_option('-b', '--heartbeat-dir', dest='heartbeat_dir',
default='/var/lib/ctf/heartbeat',
help='Where in.heartbeatd writes its files')
p.add_option('-r', '--results-page', dest='results',
default='/tmp/services.html',
help='Where to write results file')
p.add_option('-i', '--interface', dest='iface',
default='eth1',
help='Interface to bind to')
p.add_option('-m', '--mac-vendor', dest='mac_vendor',
default='00:01:c0',
help='MAC vendor to look for (default: %default)')
opts, args = p.parse_args()
socket.setdefaulttimeout(opts.timeout)
ip_re = re.compile('(\d{1,3}\.){3}\d{1,3}')
# loop forever
while True:
random_mac()
dhcp_request()
srcip = get_ip()
t_start = time.time()
# gather the list of IPs to poll
ips = os.listdir(opts.heartbeat_dir)
out = io.StringIO()
for ip in ips:
# check file name format is ip
if ip_re.match(ip) is None:
continue
# remove the file
fn = os.path.join(opts.heartbeat_dir, ip)
try:
os.remove(fn)
except Exception, e:
print('pollster: could not remove %s' % fn)
traceback.print_exc()
results = {}
if opts.debug:
print('ip: %s' % ip)
if out is not None:
out.write('<h2>%s</h2>\n' % ip)
out.write('<table class="pollster">\n<thead><tr><td>Service Name</td></td>')
out.write('<td>Flag Holder</td></tr></thead>\n')
# perform polls
for service,func in POLLS.items():
try:
team = func(srcip, ip).decode('utf-8')
if len(team) == 0:
team = 'dirtbags'
except:
team = 'dirtbags'
if opts.debug:
print('\t%s - %s' % (service, team))
if out is not None:
out.write('<tr><td>%s</td><td>%s</td>\n' % (service, team))
pointscli.award('svc.' + service, team, 1)
if out is not None:
out.write('</table>\n')
if opts.debug:
print('+-----------------------------------------+')
time_str = time.strftime('%a, %d %b %Y %H:%M:%S %Z')
out.write('''
<p>This page was generated on %s. That was <span id="diff">?</span> seconds ago.</p>
<script type="text/javascript">
var gen_time = new Date(%f);
var cur_time = new Date();
var diff = (cur_time.getTime() - gen_time.getTime())/1000;
document.getElementById("diff").innerHTML = diff;
</script>
''' % (time_str, time.time()*1000))
t_end = time.time()
exec_time = int(t_end - t_start)
sleep_time = opts.interval - exec_time
html.write(opts.results,'Team Service Availability', out.getvalue())
# sleep until its time to poll again
time.sleep(sleep_time)

33
bin/run-ctf Executable file
View File

@ -0,0 +1,33 @@
#! /bin/sh
POINTS=var/points/log
cd $(dirname $0)/..
while true; do
# Timestamp
start=$(date +%s)
next=$(expr $start + 60)
# If enabled, run tanks
if ! [ -f var/disabled/tanks ]; then
sbin/run-tanks -1 var/tanks
fi
# Collect any new points
for fn in var/points/cur/*; do
[ -f $fn ] || continue
cat $fn >> $POINTS || break
rm $fn
done
if [ -f $POINTS ]; then
sbin/scoreboard -t www/scoreboard.html -j www/myplot.js < $POINTS
fi
# Wait until the next minute
now=$(date +%s)
if [ $now -lt $next ]; then
sleep $(expr $next - $now)
fi
done

59
bin/run-tanks Executable file
View File

@ -0,0 +1,59 @@
#! /usr/bin/python
import optparse
import os
import shutil
import socket
import time
from ctf import pointscli, teams, paths
from tanks import Pflanzarr
MAX_HIST = 30
HIST_STEP = 100
running = True
def run_tanks(basedir, turns):
try:
p = Pflanzarr.Pflanzarr(basedir)
p.run(turns)
winner = p.winner
except Pflanzarr.NotEnoughPlayers:
winner = teams.house
pointscli.award('tanks', winner, 1)
winnerFile = open(os.path.join(basedir, 'winner'),'w')
winnerFile.write(winner or teams.house)
winnerFile.close()
# Fake being a flag, so the most recent winner shows up on the
# scoreboard.
try:
open(os.path.join(paths.VAR, 'flags', 'tanks'), 'w').write(winner or teams.house)
except IOError:
pass
def main():
parser = optparse.OptionParser('%prog [options] DATA_DIR')
parser.add_option('-1', '--once',
action='store_true', dest='once',
help='Run only once')
parser.add_option('-t', '--max-turns',
type='int', dest='turns', default=500,
help='Maximum number of turns per round')
parser.add_option('-s', '--sleep-time',
type='int', dest='sleep', default=60,
help='Wait SLEEP seconds between turns (default %default)')
opts, args = parser.parse_args()
if (len(args) != 1):
parser.error('Wrong number of arguments')
while running:
run_tanks(args[0], opts.turns)
if opts.once:
break
time.sleep(opts.sleep)
if __name__ == '__main__':
main()

174
bin/scoreboard Executable file
View File

@ -0,0 +1,174 @@
#! /usr/bin/python
import sys
import codecs
import time
import optparse
import string
import os
from urllib import unquote
from ctf import teams, html, paths
from codecs import open
from sets import Set as set
from cgi import escape
flags_dir = os.path.join(paths.VAR, 'flags')
sys.stdin = codecs.getreader('utf-8')(sys.stdin)
def incdict(dict, key, amt=1):
dict[key] = dict.get(key, 0) + amt
class Chart:
def __init__(self):
self.points_by_cat = {}
self.points_by_cat_team = {}
self.high_score = 0.001
self.teams = set()
self.cats = set()
self.log = []
def add_points(self, when, cat, team, points):
self.log.append((when, cat, team, points))
self.teams.add(team)
self.cats.add(cat)
incdict(self.points_by_cat, cat, points)
incdict(self.points_by_cat_team, (cat, team), points)
def team_points(self, team):
points = 0
for cat, tot in self.points_by_cat.items():
if not tot:
continue
team_points = self.team_points_in_cat(cat, team)
points += team_points / float(tot)
return points
def team_points_in_cat(self, cat, team):
return self.points_by_cat_team.get((cat, team), 0)
def write_js(self, f):
start = self.log[0][0]
end = self.log[-1][0]
# Calculate high score
high_score = reduce(max, [self.team_points(t) for t in self.teams])
width = end - start
height = high_score * 1.1
f.write('function draw(id) {\n')
f.write(' p = new Plot(id, %d, %.3f);\n' % (width, height))
for team in self.teams:
f.write(' p.line("#%s",[' % teams.color(team))
score = 0
for when, cat, t, points in self.log:
if t == team:
cat_points = self.points_by_cat[cat]
if not cat_points:
continue
pct = float(points) / cat_points
score += pct
f.write('[%d,%.2f],' % (when - start, score))
f.write(']); // %s\n' % team)
f.write('}')
def make_table(self):
body = []
body.append('<table class="scoreboard">')
body.append('<tr>')
body.append('<th>Overall</th>')
for cat in self.cats:
points = self.points_by_cat[cat]
if not points:
continue
body.append('<th>')
body.append(' %s (%d)' % (cat, points))
try:
fn = os.path.join(flags_dir, cat)
team = open(fn).read().strip() or teams.house
body.append(' <br/>')
body.append(' <span style="color: #%s" title="flag holder">' % teams.color(team))
body.append(' <!-- flag: %s --> %s\n' % (cat, escape(team[:15])))
body.append(' </span>')
except IOError:
pass
body.append('</th>')
body.append('</tr>')
body.append('<tr>')
body.append('<td><ol>')
totals = []
for team in self.teams:
total = self.team_points(team)
totals.append((total, team))
totals.sort()
totals.reverse()
for total, team in totals:
if total < 0.1:
break
body.append('<li><span style="color: #%s;">%s (%0.3f)</span></li>'
% (teams.color(team), escape(team[:15]), total))
body.append('</ol></td>')
for cat in self.cats:
total = self.points_by_cat[cat]
if not total:
continue
body.append('<td>')
scores = sorted([(self.team_points_in_cat(cat, team), team) for team in self.teams])
for score, team in scores:
if not score:
continue
color = teams.color(team)
body.append('<div style="height: %f%%; overflow: hidden; background: #%s; color: black;">' % (float(score * 100)/total, color))
body.append('<!-- category: %s --> %s: %d' % (cat, escape(team[:15]), score))
body.append('</div>')
body.append('</td>')
body.append('</tr>')
body.append('</table>')
return '\n'.join(body)
def main():
p = optparse.OptionParser(usage='%prog [options] < logfile')
p.add_option('-t', '--html', dest='html', default=None,
help='Write a web page to HTML')
p.add_option('-j', '--javascript', dest='js', default=None,
help='Write javascript params to JS')
opts, args = p.parse_args()
if args:
return p.print_help()
chart = Chart()
for line in sys.stdin:
line = line.strip()
try:
date, qcat, qteam, points = line.split('\t')
except ValueError:
print 'Possible line corruption: %s' % (repr(line)[:40])
cat = unquote(qcat)
team = unquote(qteam)
when = time.strptime(date, '%Y-%m-%dT%H:%M:%S')
chart.add_points(time.mktime(when),
cat,
team,
int(points))
if opts.html:
hdr = ('<script type="application/javascript" src="plot.js"></script>'
'<script type="application/javascript" src="myplot.js"></script>'
'<meta http-equiv="refresh" content="60" />')
body = chart.make_table()
body += '\n<canvas id="history"></canvas>'
html.write(opts.html,
'Scoreboard',
body,
hdr=hdr,
body_class='wide',
onload="draw('history')")
if opts.js:
f = open(opts.js, 'w', encoding='utf-8')
chart.write_js(f)
if __name__ == '__main__':
main()

View File

33
build/lib/ctf/flagger.py Normal file
View File

@ -0,0 +1,33 @@
#! /usr/bin/python
import asynchat
import asyncore
import socket
class Flagger(asynchat.async_chat):
"""Use to connect to flagd and submit the current flag holder."""
def __init__(self, addr, auth):
asynchat.async_chat.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((addr, 1))
self.push(auth + '\n')
self.flag = None
def handle_read(self):
# We don't care.
msg = self.recv(4096)
def handle_error(self):
# If we lose the connection to flagd, nobody can score any
# points. Terminate everything.
asyncore.close_all()
asynchat.async_chat.handle_error(self)
def set_flag(self, team):
if team:
eteam = team.encode('utf-8')
else:
eteam = ''
self.push(eteam + '\n')
self.flag = team

35
build/lib/ctf/html.py Normal file
View File

@ -0,0 +1,35 @@
#! /usr/bin/python
import os
import string
import sys
from codecs import open
from paths import *
template_fn = os.path.join(LIB, 'template.html')
template = string.Template(open(template_fn, encoding='utf-8').read())
base = BASE_URL
css = base + 'ctf.css'
def substitute(title, body, base=base, hdr='', body_class='', onload='', links=''):
return template.substitute(title=title,
hdr=hdr,
body_class=body_class,
base=base,
links=links,
onload=onload,
body=body)
def serve(title, body, **kwargs):
out = substitute(title, body, **kwargs)
print 'Content-type: text/html'
print 'Content-length: %d' % len(out)
print
sys.stdout.write(out)
sys.stdout.flush()
def write(filename, title, body, **kwargs):
f = open(filename, 'w', encoding='utf-8')
f.write(substitute(title, body, **kwargs))

6
build/lib/ctf/paths.py Normal file
View File

@ -0,0 +1,6 @@
VAR = "/opt/ctf/var"
WWW = "/opt/ctf/www"
LIB = "/opt/ctf/lib"
BIN = "/opt/ctf/bin"
SBIN = "/opt/ctf/sbin"
BASE_URL = "/ctf/"

View File

@ -0,0 +1,40 @@
#! /usr/bin/python
from urllib import quote
import teams
import time
import os
import paths
pointsdir = os.path.join(paths.VAR, 'points')
def award(cat, team, points):
if not team:
team = teams.house
now = time.strftime('%Y-%m-%dT%H:%M:%S')
pid = os.getpid()
qcat = quote(cat, '')
qteam = quote(team, '')
basename = '%s.%d.%s.%s' % (now, pid, qcat, qteam)
# FAT can't handle :
basename = basename.replace(':', '.')
tmpfn = os.path.join(pointsdir, 'tmp', basename)
curfn = os.path.join(pointsdir, 'cur', basename)
f = open(tmpfn, 'w')
f.write('%s\t%s\t%s\t%d\n' % (now, cat, team, points))
f.close()
os.rename(tmpfn, curfn)
def main():
import optparse
p = optparse.OptionParser('%prog CATEGORY TEAM POINTS')
opts, args = p.parse_args()
if len(args) != 3:
p.error('Wrong number of arguments')
cat, team, points = args
points = int(points)
award(cat, team, points)
if __name__ == '__main__':
main()

72
build/lib/ctf/teams.py Normal file
View File

@ -0,0 +1,72 @@
#! /usr/bin/python
import fcntl
import time
import os
from urllib import quote, unquote
import paths
house = 'dirtbags'
passwdfn = os.path.join(paths.VAR, 'passwd')
team_colors = ['F0888A', '88BDF0', '00782B', '999900', 'EF9C00',
'F4B5B7', 'E2EFFB', '89CA9D', 'FAF519', 'FFE7BB',
'BA88F0', '8DCFF4', 'BEDFC4', 'FFFAB2', 'D7D7D7',
'C5B9D7', '006189', '8DCB41', 'FFCC00', '898989']
teams = {}
built = 0
def build_teams():
global teams, built
if not os.path.exists(passwdfn):
return
if os.path.getmtime(passwdfn) <= built:
return
teams = {}
try:
f = open(passwdfn)
for line in f:
line = line.strip()
if not line:
continue
team, passwd, color = map(unquote, line.strip().split('\t'))
teams[team] = (passwd, color)
except IOError:
pass
built = time.time()
def validate(team):
build_teams()
def chkpasswd(team, passwd):
validate(team)
if teams.get(team, [None, None])[0] == passwd:
return True
else:
return False
def exists(team):
validate(team)
if team == house:
return True
return team in teams
def add(team, passwd):
build_teams()
color = team_colors[len(teams)%len(team_colors)]
assert team not in teams, "Team already exists."
f = open(passwdfn, 'a')
fcntl.lockf(f, fcntl.LOCK_EX)
f.seek(0, 2)
f.write('%s\t%s\t%s\n' % (quote(team, ''),
quote(passwd, ''),
quote(color, '')))
def color(team):
validate(team)
t = teams.get(team)
if not t:
return '888888'
return t[1]

View File

@ -0,0 +1,46 @@
import math
class Function(object):
"""Represents a single condition or action. This doc string is printed
as user documentation. You should override it to say something useful."""
def __call__(self, tank):
"""The __call__ method should be of this basic form. Actions
should return None, conditions should return True or False. Actions
should utilize the set* methods of tanks. Conditions can utilize the
tanks get* methods."""
pass
def _limitArgs(self, args, max):
"""Raises a ValueError if there are more than max args."""
if len(args) > max:
raise ValueError("Too many arguments: %s" % ','.join(args))
def _checkRange(self, value, name, min=0, max=100):
"""Check that the value is in the given range.
Raises an exception with useful info for invalid values. Name is used to
let the user know which value is wrong."""
try:
value = int(value)
except:
raise ValueError("Invalid %s value: %s" % (name, value))
assert value >= min and value <= max, "Invalid %s. %ss must be in"\
" the %s %d-%d" % \
(name, name.capitalize(), value, min, max)
return value
def _convertAngle(self, value, name):
"""Parse the given value as an angle in degrees, and return its value
in radians. Raise useful errors.
Name is used in the errors to describe the field."""
try:
angle = math.radians(value)
except:
raise ValueError("Invalid %s value: %s" % (name, value))
assert angle >= 0 and angle < 2*math.pi, "Invalid %s; "\
"It be in the range 0 and 359." % name
return angle

206
build/lib/tanks/GameMath.py Normal file
View File

@ -0,0 +1,206 @@
import math
def rotatePoint(point, angle):
"""Assuming 0,0 is the center, rotate the given point around it."""
x,y = point
r = math.sqrt(x**2 + y**2)
if r == 0:
return 0, 0
theta = math.acos(x/r)
if y < 0:
theta = -theta
theta = theta + angle
return int(round(r*math.cos(theta))), int(round(r*math.sin(theta)))
def rotatePoly(points, angle):
"""Rotate the given list of points around 0,0 by angle."""
return [ rotatePoint(point, angle) for point in points ]
def displace(point, disp, limits):
"""Displace point by disp, wrapping around limits."""
x = (point[0] + disp[0])
while x >= limits[0]:
x = x - limits[0]
while x < 0:
x = x + limits[0]
y = (point[1] + disp[1])
while y >= limits[1]:
y = y - limits[1]
while y < 0:
y = y + limits[1]
return x,y
def displacePoly(points, disp, limits, coordSequence=False):
"""Displace each point (x,y) in 'points' by 'disp' (x,y). The limits of
the drawing space are assumed to be at x=0, y=0 and x=limits[0],
y=limits[1]. If the poly overlaps the edge of the drawing space, the
poly is duplicated on each side.
@param coordSequence: If true, the coordinates are returned as a sequence -
x1, y1, x2, y2, ... This is need by some PIL drawing
commands.
@returns: A list of polys, displaced by disp
"""
xDup = 0; yDup = 0
maxX, maxY = limits
basePoints = []
for point in points:
x,y = int(point[0] + disp[0]), int(point[1] + disp[1])
# Check if duplication is needed on each axis
if x > maxX:
# If this is negative, then we need to duplicate in the negative
# direction.
xDup = -1
elif x < 0:
xDup = 1
if y > maxY:
yDup = -1
elif y < 0:
yDup = 1
basePoints.append( (x,y) )
polys = [basePoints]
if xDup:
polys.append([(x + maxX*xDup, y) for x,y in basePoints] )
if yDup:
polys.append([(x, maxY*yDup + y) for x,y in basePoints] )
if xDup and yDup:
polys.append([(x+maxX*xDup, maxY*yDup+y) for x,y in basePoints])
# Switch coordinates to sequence mode.
# (x1, y1, x2, y2) instead of ((x1, y1), (x2, y2))
if coordSequence:
seqPolys = []
for poly in polys:
points = []
for point in poly:
points.extend(point)
seqPolys.append(points)
polys = seqPolys
return polys
def polar2cart(r, theta):
"""Return the cartesian coordinates for r, theta."""
x = r*math.cos(theta)
y = r*math.sin(theta)
return x,y
def minShift(center, point, limits):
"""Get the minimum distances between the two points, given that the board
wraps at the givin limits."""
dx = point[0] - center[0]
if dx < -limits[0]/2.0:
dx = point[0] + limits[0] - center[0]
elif dx > limits[0]/2.0:
dx = point[0] - (center[0] + limits[0])
dy = point[1] - center[1]
if dy < - limits[1]/2.0:
dy = point[1] + limits[1] - center[1]
elif dy > limits[1]/2.0:
dy = point[1] - (limits[1] + center[1])
return dx, dy
def relativePolar(center, point, limits):
"""Returns the angle, from zero, to the given point assuming this
center is the origin. Take into account wrapping round the limits of the board.
@returns: r, theta
"""
dx, dy = minShift(center, point, limits)
r = math.sqrt(dx**2 + dy**2)
theta = math.acos(dx/r)
if dy < 0:
theta = 2*math.pi - theta
return r, theta
def reduceAngle(angle):
"""Reduce the angle such that it is in 0 <= angle < 2pi"""
while angle >= math.pi*2:
angle = angle - math.pi*2
while angle < 0:
angle = angle + math.pi*2
return angle
def angleDiff(angle1, angle2):
"""Returns the difference between the two angles. They are assumed
to be in radians, and must be in the range 0 <= angle < 2*pi.
@raises AssertionError: The angles given must be in the range 0 <= angle < 2pi
@returns: The minimum distance between the two angles; The distance
is negative if angle2 leads angle1 (clockwise)..
"""
for angle in angle1, angle2:
assert angle < 2*math.pi and angle >= 0, \
'angleDiff: bad angle %s' % angle
diff = angle2 - angle1
if diff > math.pi:
diff = diff - 2*math.pi
elif diff < -math.pi:
diff = diff + 2*math.pi
return diff
def getDist(point1, point2):
"""Returns the distance between point1 and point2."""
dx = point2[0] - point1[0]
dy = point2[1] - point1[1]
return math.sqrt(dx**2 + dy**2)
def segmentCircleCollision(segment, center, radius):
"""Return True if the given circle touches the given line segment.
@param segment: A list of two points [(x1,y1), (x2, y2)] that define
the line segment.
@param center: The center point of the circle.
@param radius: The radius of the circle.
@returns: True if the the circle touches the line segment, False otherwise.
"""
a = getDist(segment[0], center)
c = getDist(segment[1], center)
base = getDist(segment[0], segment[1])
# If we're close enough to the end points, then we're close
# enough to the segment.
if a < radius or c < radius:
return True
# First we find the are of the triangle formed by the line segment
# and point. I use Heron's formula for the area. Using this, we'll
# find the distance d from the point to the line. We'll later make
# sure that the collision is with the line segment, and not just the
# line.
s = (a + c + base)/2
A = math.sqrt(s*(s - a)*(s - c)*(s - base))
d = 2*A/base
# print s, a, c, A, d, radius
# If the distance from the point to the line is more than the
# target radius, this isn't a hit.
if d > radius:
return False
# If the distance from an endpoint to the intersection between
# our line segment and the line perpendicular to it that passes through
# the point is longer than the line segment, then this isn't a hit.
elif math.sqrt(a**2 - d**2) > base or \
math.sqrt(c**2 - d**2) > base:
return False
else:
# The triangle is acute, that means we're close enough.
return True

View File

@ -0,0 +1,399 @@
import fcntl
import math
import os
import random
import cgi
from sets import Set as set
from ctf import teams, html, paths
from cStringIO import StringIO
from urllib import unquote, quote
import Tank
class NotEnoughPlayers(Exception):
pass
class Pflanzarr:
SPACING = 150
def __init__(self, dir):
"""Initialize a new game of Pflanzarr.
@param dir: The data directory."""
# Setup the game environment
self._setupDirectories(dir)
# Figure out what game number this is.
self.gameNum = self._getGameNum()
self.gameFilename = os.path.join(self._resultsDir, '%04d.html' % self.gameNum)
tmpPlayers = os.listdir(self._playerDir)
players = []
for p in tmpPlayers:
p = unquote(p)
if (not (p.startswith('.')
or p.endswith('#')
or p.endswith('~'))
and teams.exists(p)):
players.append(p)
AIs = {}
for player in players:
AIs[player] = open(os.path.join(self._playerDir, player)).read()
defaultAIs = self._getDefaultAIs(dir)
if len(players) < 1:
raise NotEnoughPlayers()
# The one is added to ensure that there is at least one house
# bot.
cols = math.sqrt(len(players) + 1)
if int(cols) != cols:
cols = cols + 1
cols = int(cols)
cols = max(cols, 2)
rows = len(players)/cols
if len(players) % cols != 0:
rows = rows + 1
rows = max(rows, 2)
self._board = (cols*self.SPACING, rows*self.SPACING)
while len(players) < cols*rows:
players.append(None)
self._tanks = []
for i in range(cols):
for j in range(rows):
startX = i*self.SPACING + self.SPACING/2
startY = j*self.SPACING + self.SPACING/2
player = random.choice(players)
players.remove(player)
color = '#' + teams.color(player)
tank = Tank.Tank( player, (startX, startY), color,
self._board, testMode=True)
if player == None:
tank.program(random.choice(defaultAIs))
else:
tank.program(AIs[player])
self._tanks.append(tank)
# We only want to make these once, so we do it here.
self._tanksByX = list(self._tanks)
self._tanksByY = list(self._tanks)
self._deadTanks = set()
def run(self, maxTurns=None):
kills = {}
for tank in self._tanks:
kills[tank] = set()
# Open HTML output
hdr = StringIO()
hdr.write('<script type="application/javascript" src="../tanks.js"></script>\n'
'<script type="application/javascript">\n')
hdr.write('turns = [\n')
turn = 0
lastTurns = 3
while ((maxTurns is None) or turn < maxTurns) and lastTurns > 0:
if len(self._tanks) - len(self._deadTanks) < 2:
lastTurns = lastTurns - 1
near = self._getNear()
deadThisTurn = set()
liveTanks = set(self._tanks).difference(self._deadTanks)
for tank in liveTanks:
# Shoot now, if we said to shoot last turn
dead = tank.fire( near[tank] )
kills[tank] = kills[tank].union(dead)
self._killTanks(dead, 'Shot by %s' % cgi.escape(tank.name or teams.house))
for tank in liveTanks:
# We also check for collisions here, while we're at it.
dead = tank.sense( near[tank] )
kills[tank] = kills[tank].union(dead)
self._killTanks(dead, 'Collision')
hdr.write(' [\n')
# Draw the explosions
for tank in self._deadTanks:
tank.draw(hdr)
# Draw the live tanks.
for tank in self._tanksByX:
# Have the tank run its program, move, etc.
tank.draw(hdr)
hdr.write(' ],\n')
# Have the live tanks do their turns
for tank in self._tanksByX:
tank.execute()
turn = turn + 1
# Removes tanks from their own kill lists.
for tank in kills:
if tank in kills[tank]:
kills[tank].remove(tank)
for tank in self._tanks:
self._outputErrors(tank)
hdr.write('];\n')
hdr.write('</script>\n')
# Decide on the winner
winner = self._chooseWinner(kills)
self.winner = winner.name
# Now generate HTML body
body = StringIO()
body.write(' <canvas id="battlefield" width="%d" height="%d">\n' % self._board)
body.write(' Sorry, you need an HTML5-capable browser to see this.\n'
' </canvas>\n'
' <p>\n')
if self.gameNum > 0:
body.write(' <a href="%04d.html">&larr; Prev</a> |' %
(self.gameNum - 1))
body.write(' <a href="%04d.html">Next &rarr;</a> |' %
(self.gameNum + 1))
body.write(' <span id="fps">0</span> fps\n'
' </p>\n'
' <table class="results">\n'
' <tr>\n'
' <th>Team</th>\n'
' <th>Kills</th>\n'
' <th>Cause of Death</th>\n'
' </tr>\n')
tanks = self._tanks[:]
tanks.remove(winner)
tanks[0:0] = [winner]
for tank in tanks:
if tank is winner:
rowStyle = ('style="font-weight: bold; '
'color: #000; '
'background-color: %s;"' % tank.color)
else:
rowStyle = 'style="background-color:%s; color: #000;"' % tank.color
if tank.name:
name = cgi.escape(tank.name)
else:
name = teams.house
body.write('<tr %s><td>%s</td><td>%d</td><td>%s</td></tr>' %
(rowStyle,
name,
len(kills[tank]),
cgi.escape(tank.deathReason)))
body.write(' </table>\n')
# Write everything out
html.write(self.gameFilename,
'Tanks round %d' % self.gameNum,
body.getvalue(),
hdr=hdr.getvalue(),
onload='start(turns);')
def _killTanks(self, tanks, reason):
for tank in tanks:
if tank in self._tanksByX:
self._tanksByX.remove(tank)
if tank in self._tanksByY:
self._tanksByY.remove(tank)
tank.die(reason)
self._deadTanks = self._deadTanks.union(tanks)
def _chooseWinner(self, kills):
"""Choose a winner. In case of a tie, live tanks prevail, in case
of further ties, a winner is chosen at random. This outputs the winner
to the winners file and outputs a results table html file."""
tanks = list(self._tanks)
def winSort(t1, t2):
"""Sort by # of kill first, then by life status."""
result = cmp(len(kills[t1]), len(kills[t2]))
if result != 0:
return result
if t1.isDead and not t2.isDead:
return -1
elif not t1.isDead and t2.isDead:
return 1
else:
return 0
tanks.sort(winSort)
tanks.reverse()
# Get the list of potential winners
winners = []
for i in range(len(tanks)):
if len( kills[tanks[0]] ) == len( kills[tanks[i]] ) and \
tanks[0].isDead == tanks[i].isDead:
winners.append(tanks[i])
else:
break
winner = random.choice(winners)
return winner
def _outputErrors(self, tank):
"""Output errors for each team."""
if tank.name == None:
return
if tank._program.errors:
print tank.name, 'has errors'
fileName = os.path.join(self._errorDir, quote(tank.name, ''))
file = open(fileName, 'w')
for error in tank._program.errors:
file.write(error)
file.write('\n')
file.close()
def _getNear(self):
"""A dictionary of the set of tanks nearby each tank. Nearby is
defined as within the square centered the tank with side length equal
twice the sensor range. Only a few tanks within the set (those in the
corners of the square) should be outside the sensor range."""
self._tanksByX.sort(lambda t1, t2: cmp(t1.pos[0], t2.pos[0]))
self._tanksByY.sort(lambda t1, t2: cmp(t1.pos[1], t2.pos[1]))
nearX = {}
nearY = {}
for tank in self._tanksByX:
nearX[tank] = set()
nearY[tank] = set()
numTanks = len(self._tanksByX)
offset = 1
for index in range(numTanks):
cTank = self._tanksByX[index]
maxRange = cTank.SENSOR_RANGE + cTank.RADIUS + 1
near = set([cTank])
for i in [(j + index) % numTanks for j in range(1, offset)]:
near.add(self._tanksByX[i])
while offset < numTanks:
nTank = self._tanksByX[(index + offset) % numTanks]
if (index + offset >= numTanks and
self._board[0] + nTank.pos[0] - cTank.pos[0] < maxRange):
near.add(nTank)
offset = offset + 1
elif (index + offset < numTanks and
nTank.pos[0] - cTank.pos[0] < maxRange ):
near.add(nTank)
offset = offset + 1
else:
break
if offset > 1:
offset = offset - 1
for tank in near:
nearX[tank] = nearX[tank].union(near)
offset = 1
for index in range(numTanks):
cTank = self._tanksByY[index]
maxRange = cTank.SENSOR_RANGE + cTank.RADIUS + 1
near = set([cTank])
for i in [(j + index) % numTanks for j in range(1, offset)]:
near.add(self._tanksByY[i])
while offset < numTanks:
nTank = self._tanksByY[(index + offset) % numTanks]
if (index + offset < numTanks and
nTank.pos[1] - cTank.pos[1] < maxRange):
near.add(nTank)
offset = offset + 1
elif (index + offset >= numTanks and
self._board[1] + nTank.pos[1] - cTank.pos[1] < maxRange):
near.add(nTank)
offset = offset + 1
else:
break
if offset > 1:
offset = offset - 1
for tank in near:
nearY[tank] = nearY[tank].union(near)
near = {}
for tank in self._tanksByX:
near[tank] = nearX[tank].intersection(nearY[tank])
near[tank].remove(tank)
return near
def _setupDirectories(self, dir):
"""Setup all the directories needed by the game."""
if not os.path.exists(dir):
os.mkdir(dir)
self._dir = dir
# Don't run more than one game at the same time.
self._lockFile = open(os.path.join(dir, '.lock'), 'a')
try:
fcntl.flock(self._lockFile, fcntl.LOCK_EX|fcntl.LOCK_NB)
except:
sys.exit(1)
# Setup all the directories we'll need.
self._resultsDir = os.path.join(dir, 'results')
self._errorDir = os.path.join(dir, 'errors')
self._playerDir = os.path.join(dir, 'ai', 'players')
def _getDefaultAIs(self, basedir):
"""Load all the house bot AIs."""
defaultAIs = []
path = os.path.join(basedir, 'ai', 'house')
files = os.listdir(path)
for fn in files:
if fn.startswith('.'):
continue
fn = os.path.join(path, fn)
file = open(fn)
defaultAIs.append(file.read())
return defaultAIs
def _getGameNum(self):
"""Figure out what game number this is from the past games played."""
games = os.listdir(self._resultsDir)
games.sort()
if games:
fn = games[-1]
s, _ = os.path.splitext(fn)
return int(s) + 1
else:
return 0
if __name__ == '__main__':
import sys, traceback
try:
p = Pflanzarr(sys.argv[1])
p.run(int(sys.argv[3]))
except:
traceback.print_exc()
print "Usage: Pflanzarr.py dataDirectory #turns"

234
build/lib/tanks/Program.py Normal file
View File

@ -0,0 +1,234 @@
"""<H2>Introduction</H2>
You are the proud new operator of a M-375 Pflanzarr Tank. Your tank is
equipped with a powerful laser cannon, independently rotating turret
section, up to 10 enemy detection sensors, and a standard issue NATO hull.
Unfortunately, it lacks seats, and thus must rely own its own wits and your
skills at designing those wits to survive.
<H2>Programming Your Tank</H2>
Your tanks are programmed using the Super Useful Command and Kontrol language,
the very best in laser tank AI languages. It includes amazing features such
as comments (Started by a #, ended at EOL), logic, versatility, and
semi-colons (all lines must end in one). As with all new military systems
it utilizes only integers; we must never rest in our
diligence against the communist floating point conspiracy. Whitespace is
provided by trusted contractors, and should never interfere with operations.
<P>
Your program should be separated into Setup and AI commands. The definitions
section lets you designated the behaviors of its sensors and memory.
Each setup command must begin with a '>'. Placing setup commands after
the first AI command is a violation of protocol.
Here are some examples of correct setup commands:
<pre class="docs">>addsensor(80, 90, 33);
>addsensor(50, 0, 10, 1);
>addtimer(3);</pre>
The AI section will act as the brain of your tank. Each AI line is
separated into a group of conditions functions and a group of action
functions. If all the conditions are satisfactory (true), all of the actions
are given as orders. Conditions are separated by ampersands, actions separated
by periods. Here are some examples of AI commands:
<pre class="docs">
sensor(1) & sensor(2) & fireready() : fire();
sensor(0,0)&sin(5): move(40, 30) . turretcw(50);
sensor(4) & random(4,5) : led(1).settoggle(0,1);</pre>
Your tank will check its program each turn, and attempt to the best of its
abilities to carry out its orders (or die trying). Like any military mind,
your tank may receive a plethora of often conflicting orders and information.
This a SMART TANK, however. It knows that the proper thing to do with each
subsystem is to have that subsystem follow only the last order given each turn.
"""
import traceback
import conditions
import actions
import setup
class Statement(object):
"""Represents a single program statement. If all the condition Functions
evaluate to True, the actions are all executed in order."""
def __init__(self, lineNum, line, conditions, actions):
self.lineNum = lineNum
self.line = line
self._conditions = conditions
self._actions = actions
def __call__(self, tank):
success = True
for condition in self._conditions:
if not condition(tank):
success = False
break
if success:
for action in self._actions:
action(tank)
class Program(object):
"""This parses and represents a Tank program."""
CONDITION_SEP = '&'
ACTION_SEP = '.'
def __init__(self, text):
"""Initialize this program, parsing the given text."""
self.errors = []
self._program, self._setup = self._parse(text)
def setup(self, tank):
"""Execute all the setup actions."""
for action in self._setup:
try:
action(tank)
except Exception, msg:
self.errors.append("Bad setup action, line %d, msg: %s" % \
(action.lineNum, msg))
def __call__(self, tank):
"""Execute this program on the given tank."""
for statement in self._program:
try:
statement(tank)
except Exception, msg:
traceback.print_exc()
self.errors.append('Error executing program. \n'
'(%d) - %s\n'
'msg: %s\n' %
(statement.lineNum, statement.line, msg) )
def _parse(self, text):
"""Parse the text of the given program."""
program = []
setup = []
inSetup = True
lines = text.split(';')
lineNum = 0
for line in lines:
lineNum = lineNum + 1
originalLine = line
# Remove Comments
parts = line.split('\n')
for i in range(len(parts)):
comment = parts[i].find('#')
if comment != -1:
parts[i] = parts[i][:comment]
# Remove all whitespace
line = ''.join(parts)
line = line.replace('\r', '')
line = line.replace('\t', '')
line = line.replace(' ', '')
if line == '':
continue
if line.startswith('>'):
if inSetup:
if '>' in line[1:] or ':' in line:
self.errors.append('(%d) Missing semicolon: %s' %
(lineNum, line))
continue
try:
setupAction = self._parseSection(line[1:], 'setup')[0]
setupAction.lineNum = lineNum
setup.append(setupAction)
except Exception, msg:
self.errors.append('(%d) Error parsing setup line: %s'
'\nThe error was: %s' %
(lineNum, originalLine, msg))
continue
else:
self.errors.append('(%d) Setup lines aren\'t allowed '
'after the first command: %s' %
(lineNum, originalLine))
else:
# We've hit the first non-blank, non-comment, non-setup
# line
inSetup = False
semicolons = line.count(':')
if semicolons > 1:
self.errors.append('(%d) Missing semicolon: %s' %
(lineNum, line))
continue
elif semicolons == 1:
conditions, actions = line.split(':')
else:
self.errors.append('(%d) Invalid Line, no ":" seperator: %s'%
(lineNum, line) )
try:
conditions = self._parseSection(conditions, 'condition')
except Exception, msg:
self.errors.append('(%d) %s - "%s"' %
(lineNum, msg, line) )
continue
try:
actions = self._parseSection(actions, 'action')
except Exception, msg:
self.errors.append('(%d) %s - "%s"' %
(lineNum, msg, originalLine) )
continue
program.append(Statement(lineNum, line, conditions, actions))
return program, setup
def _parseSection(self, section, sectionType):
"""Parses either the action or condition section of each command.
@param section: The text of the section of the command to be parsed.
@param sectionType: The type of section to be parsed. Should be:
'condition', 'action', or 'setup'.
@raises ValueError: Raises ValueErrors when parsing errors occur.
@returns: Returns a list of parsed section components (Function objects).
"""
if sectionType == 'condition':
parts = section.split(self.CONDITION_SEP)
functions = conditions.conditions
if section == '':
return []
elif sectionType == 'action':
parts = section.split(self.ACTION_SEP)
functions = actions.actions
if section == '':
raise ValueError("The action section cannot be empty.")
elif sectionType == 'setup':
parts = [section]
functions = setup.setup
else:
raise ValueError('Invalid section Type - Contact Contest Admin')
parsed = []
for part in parts:
pos = part.find('(')
if pos == -1:
raise ValueError("Missing open paren in %s: %s" %
(sectionType, part) )
funcName = part[:pos]
if funcName not in functions:
raise ValueError("%s function %s is not accepted." %
(sectionType.capitalize(), funcName) )
if part[-1] != ')':
raise ValueError("Missing closing paren in %s: %s" %
(condition, sectionType) )
args = part[pos+1:-1]
if args != '':
args = args.split(',')
for i in range(len(args)):
args[i] = int(args[i])
else:
args = []
parsed.append(functions[funcName](*args))
return parsed

479
build/lib/tanks/Tank.py Normal file
View File

@ -0,0 +1,479 @@
import math
import random
from sets import Set as set
import GameMath as gm
import Program
class Tank(object):
# How often, in turns, that we can fire.
FIRE_RATE = 20
# How far the laser shoots from the center of the tank
FIRE_RANGE = 45.0
# The radius of the tank, from the center of the turret.
# This is what is used for collision and hit detection.
RADIUS = 7.5
# Max speed, in pixels
SPEED = 7.0
# Max acceleration, as a fraction of speed.
ACCEL = 35
# Sensor range, in pixels
SENSOR_RANGE = 90.0
# Max turret turn rate, in radians
TURRET_TURN_RATE = math.pi/10
# The max number of sensors/timers/toggles
SENSOR_LIMIT = 10
def __init__(self, name, pos, color, boardSize, angle=None, tAngle=None,
testMode=True):
"""Create a new tank.
@param name: The name name of the tank. Stored in self.name.
@param pos: The starting position of the tank (x,y)
@param color: The color of the tank.
@param boardSize: The size of the board. (maxX, maxY)
@param angle: The starting angle of the tank, defaults to random.
@param tAngle: The starting turretAngle of the tank, defaults to random.
@param testMode: When True, extra debugging information is displayed. Namely,
arcs for each sensor are drawn, which turn white when
activated.
"""
# Keep track of what turn number it is for this tank.
self._turn = 0
self.name = name
self._testMode = testMode
assert len(pos) == 2 and pos[0] > 0 and pos[1] > 0, \
'Bad starting position: %s' % str(pos)
self.pos = pos
# The last speed of each tread (left, right)
self._lastSpeed = 0.0, 0.0
# The next speed that the tank should try to attain.
self._nextMove = 0,0
# When set, the led is drawn on the tank.
self.led = False
assert len(boardSize) == 2 and boardSize[0] > 0 and boardSize[1] > 0
# The limits of the playfield (maxX, maxY)
self._limits = boardSize
# The current angle of the tank.
if angle is None:
self._angle = random.random()*2*math.pi
else:
self._angle = angle
# The current angle of the turret
if tAngle is None:
self._tAngle = random.random()*2*math.pi
else:
self._tAngle = tAngle
self.color = color
# You can't fire until fireReady is 0.
self._fireReady = self.FIRE_RATE
# Means the tank will fire at it's next opportunity.
self._fireNow = False
# True when the tank has fired this turn (for drawing purposes)
self._fired = False
# What the desired turret angle should be (from the front of the tank).
# None means the turret should stay stationary.
self._tGoal = None
# Holds the properties of each sensor
self._sensors = []
# Holds the state of each sensor
self._sensorState = []
# The tanks toggle memory
self.toggles = []
# The tanks timers
self._timers = []
# Is this tank dead?
self.isDead = False
# The frame of the death animation.
self._deadFrame = 10
# Death reason
self.deathReason = 'survived'
def __repr__(self):
return '<tank: %s, (%d, %d)>' % (self.name, self.pos[0], self.pos[1])
def get_turn(self):
return self._turn
turn = property(get_turn)
def fire(self, near):
"""Shoots, if ordered to and able. Returns the set of tanks
destroyed."""
killed = set()
if self._fireReady > 0:
# Ignore the shoot order
self._fireNow = False
if self._fireNow:
self._fireNow = False
self._fireReady = self.FIRE_RATE
self._fired = True
firePoint = gm.polar2cart(self.FIRE_RANGE,
self._angle + self._tAngle)
for tank in near:
enemyPos = gm.minShift(self.pos, tank.pos, self._limits)
if gm.segmentCircleCollision(((0,0), firePoint), enemyPos,
self.RADIUS):
killed.add(tank)
else:
self._fired = False
return killed
def addSensor(self, range, angle, width, attachedTurret=False):
"""Add a sensor to this tank.
@param angle: The angle, from the tanks front and going clockwise,
of the center of the sensor. (radians)
@param width: The width of the sensor (percent).
@param range: The range of the sensor (percent)
@param attachedTurret: If set, the sensor moves with the turret.
"""
assert range >=0 and range <= 1, "Invalid range value."
if len(self._sensors) >= self.SENSOR_LIMIT:
raise ValueError("You can only have 10 sensors.")
range = range * self.SENSOR_RANGE
if attachedTurret:
attachedTurret = True
else:
attachedTurret = False
self._sensors.append((range, angle, width, attachedTurret))
self._sensorState.append(False)
def getSensorState(self, key):
return self._sensorState[key]
def setMove(self, left, right):
"""Parse the speed values given, and set them for the next move."""
self._nextMove = left, right
def setTurretAngle(self, angle=None):
"""Set the desired angle of the turret. No angle means the turret
should remain stationary."""
if angle is None:
self._tGoal = None
else:
self._tGoal = gm.reduceAngle(angle)
def setFire(self):
"""Set the tank to fire, next turn."""
self._fireNow = True
def fireReady(self):
"""Returns True if the tank can fire now."""
return self._fireReady == 0
def addTimer(self, period):
"""Add a timer with timeout period 'period'."""
if len(self._timers) >= self.SENSOR_LIMIT:
raise ValueError('You can only have 10 timers')
self._timers.append(None)
self._timerPeriods.append(period)
def resetTimer(self, key):
"""Reset, and start the given timer, but only if it is inactive.
If it is active, raise a ValueError."""
if self._timer[key] is None:
self._timer[key] = self._timerPeriods[key]
else:
raise ValueError("You can't reset an active timer (#%d)" % key)
def clearTimer(self, key):
"""Clear the timer."""
self._timer[key] = None
def checkTimer(self, key):
"""Returns True if the timer has expired."""
return self._timer[key] == 0
def _manageTimers(self):
"""Decrement each active timer."""
for i in range(len(self._timers)):
if self._timers[i] is not None and \
self._timers[i] > 0:
self._timers[i] = self._timers[i] - 1
def program(self, text):
"""Set the program for this tank."""
self._program = Program.Program(text)
self._program.setup(self)
def execute(self):
"""Execute this tanks program."""
# Decrement the active timers
self._manageTimers()
self.led = False
self._program(self)
self._move(self._nextMove[0], self._nextMove[1])
self._moveTurret()
if self._fireReady > 0:
self._fireReady = self._fireReady - 1
self._turn = self._turn + 1
def sense(self, near):
"""Detect collisions and trigger sensors. Returns the set of
tanks collided with, plus this one. We do both these steps at once
mainly because all the data is available."""
near = list(near)
polar = []
for tank in near:
polar.append(gm.relativePolar(self.pos, tank.pos, self._limits))
for sensorId in range(len(self._sensors)):
# Reset the sensor
self._sensorState[sensorId] = False
dist, sensorAngle, width, tSens = self._sensors[sensorId]
# Adjust the sensor angles according to the tanks angles.
sensorAngle = sensorAngle + self._angle
# If the angle is tied to the turret, add that too.
if tSens:
sensorAngle = sensorAngle + self._tAngle
while sensorAngle >= 2*math.pi:
sensorAngle = sensorAngle - 2*math.pi
for i in range(len(near)):
r, theta = polar[i]
# Find the difference between the object angle and the sensor.
# The max this can be is pi, so adjust for that.
dAngle = gm.angleDiff(theta, sensorAngle)
rCoord = gm.polar2cart(dist, sensorAngle - width/2)
lCoord = gm.polar2cart(dist, sensorAngle + width/2)
rightLine = ((0,0), rCoord)
leftLine = ((0,0), lCoord)
tankRelPos = gm.minShift(self.pos, near[i].pos, self._limits)
if r < (dist + self.RADIUS):
if abs(dAngle) <= (width/2) or \
gm.segmentCircleCollision(rightLine, tankRelPos,
self.RADIUS) or \
gm.segmentCircleCollision(leftLine, tankRelPos,
self.RADIUS):
self._sensorState[sensorId] = True
break
# Check for collisions here, since we already have all the data.
collided = set()
for i in range(len(near)):
r, theta = polar[i]
if r < (self.RADIUS + near[i].RADIUS):
collided.add(near[i])
# Add this tank (a collision kills both, after all
if collided:
collided.add(self)
return collided
def die(self, reason):
self.isDead = True
self.deathReason = reason
def _moveTurret(self):
if self._tGoal is None or self._tAngle == self._tGoal:
return
diff = gm.angleDiff(self._tGoal, self._tAngle)
if abs(diff) < self.TURRET_TURN_RATE:
self._tAngle = self._tGoal
elif diff > 0:
self._tAngle = gm.reduceAngle(self._tAngle - self.TURRET_TURN_RATE)
else:
self._tAngle = gm.reduceAngle(self._tAngle + self.TURRET_TURN_RATE)
def _move(self, lSpeed, rSpeed):
assert abs(lSpeed) <= 100, "Bad speed value: %s" % lSpeed
assert abs(rSpeed) <= 100, "Bad speed value: %s" % rSpeed
# Handle acceleration
if self._lastSpeed[0] < lSpeed and \
self._lastSpeed[0] + self.ACCEL < lSpeed:
lSpeed = self._lastSpeed[0] + self.ACCEL
elif self._lastSpeed[0] > lSpeed and \
self._lastSpeed[0] - self.ACCEL > lSpeed:
lSpeed = self._lastSpeed[0] - self.ACCEL
if self._lastSpeed[1] < rSpeed and \
self._lastSpeed[1] + self.ACCEL < rSpeed:
rSpeed = self._lastSpeed[1] + self.ACCEL
elif self._lastSpeed[1] > rSpeed and \
self._lastSpeed[1] - self.ACCEL > rSpeed:
rSpeed = self._lastSpeed[1] - self.ACCEL
self._lastSpeed = lSpeed, rSpeed
# The simple case
if lSpeed == rSpeed:
fSpeed = self.SPEED*lSpeed/100
x = fSpeed*math.cos(self._angle)
y = fSpeed*math.sin(self._angle)
# Adjust our position
self._reposition((x,y), 0)
return
# The works as follows:
# The tank drives around in a circle of radius r, which is some
# offset on a line perpendicular to the tank. The distance it travels
# around the circle varies with the speed of each tread, and is
# such that each side of the tank moves an equal angle around the
# circle.
L = self.SPEED * lSpeed/100.0
R = self.SPEED * rSpeed/100.0
friction = .75 * abs(L-R)/(2.0*self.SPEED)
L = L * (1 - friction)
R = R * (1 - friction)
# Si is the speed of the tread on the inside of the turn,
# So is the speed on the outside of the turn.
# dir is to note the direction of rotation.
if abs(L) > abs(R):
Si = R; So = L
dir = 1
else:
Si = L; So = R
dir = -1
# The width of the tank...
w = self.RADIUS * 2
# This is the angle that will determine the circle the tank travels
# around.
# theta = math.atan((So - Sl)/w)
# This is the distance from the outer tread to the center of the
# circle formed by it's movement.
r = w*So/(So - Si)
# The fraction of the circle traveled is equal to the speed of
# the outer tread over the circumference of the circle.
# Ft = So/(2*pi*r)
# The angle traveled is equal to the Fraction traveled * 2 * pi
# This reduces to a simple: So/r
# We multiply it by dir to adjust for the direction of rotation
theta = So/r * dir
# These are the offsets from the center of the circle, given that
# the tank is turned in some direction. The tank is facing
# perpendicular to the circle
# So far everything has been relative to the outer tread. At this
# point, however, we need to move relative to the center of the
# tank. Hence the adjustment in r.
x = -math.cos( self._angle + math.pi/2*dir ) * (r - w/2.0)
y = -math.sin( self._angle + math.pi/2*dir ) * (r - w/2.0)
# Now we just rotate the tank's position around the center of the
# circle to get the change in coordinates.
mx, my = gm.rotatePoint((x,y), theta)
mx = mx - x
my = my - y
# Finally, we shift the tank relative to the playing field, and
# rotate it by theta.
self._reposition((mx, my), theta)
def _reposition(self, move, angleChange):
"""Move the tank by x,y = move, and change it's angle by angle.
I assume the tanks move slower than the boardSize."""
x = self.pos[0] + move[0]
y = self.pos[1] + move[1]
self._angle = self._angle + angleChange
if x < 0:
x = self._limits[0] + x
elif x > self._limits[0]:
x = x - self._limits[0]
if y < 0:
y = self._limits[1] + y
elif y > self._limits[1]:
y = y - self._limits[1]
self.pos = round(x), round(y)
while self._angle < 0:
self._angle = self._angle + math.pi * 2
while self._angle > math.pi * 2:
self._angle = self._angle - math.pi * 2
def draw(self, f):
"""Output this tank's state as JSON.
[color, x, y, angle, turret_angle, led, fired]
"""
f.write(' [')
f.write(str(int(self.isDead)));
f.write(',')
f.write(repr(self.color))
f.write(',')
f.write('%d' % self.pos[0])
f.write(',')
f.write('%d' % self.pos[1])
f.write(',')
f.write('%.2f' % self._angle)
f.write(',')
f.write('%.2f' % self._tAngle)
f.write(',')
f.write(str(int(self.led)))
f.write(',')
f.write('%d' % (self._fired and self.FIRE_RANGE) or 0)
if not self.isDead:
f.write(',[')
for i in range(len(self._sensors)):
dist, sensorAngle, width, tSens = self._sensors[i]
# If the angle is tied to the turret, add that.
if tSens:
sensorAngle = sensorAngle + self._tAngle
f.write('[')
f.write(str(int(dist)))
f.write(',')
f.write('%.2f' % (sensorAngle - width/2));
f.write(',')
f.write('%.2f' % (sensorAngle + width/2));
f.write(',')
f.write(str(int(self._sensorState[i])))
f.write('],')
f.write(']')
f.write('],\n')

View File

126
build/lib/tanks/actions.py Normal file
View File

@ -0,0 +1,126 @@
"""Define new action Functions here. They should inherit from the
Function.Function class. To make an action usable, add it to the
actions dictionary at the end of this file."""
import Function
class Move(Function.Function):
"""move(left tread speed, right tread speed)
Set the speeds for the tanks right and left treads. The speeds should
be a number (percent power) between -100 and 100."""
def __init__(self, left, right):
self._checkRange(left, 'left tread speed', min=-100)
self._checkRange(right, 'right tread speed', min=-100)
self._left = left
self._right = right
def __call__(self, tank):
tank.setMove(self._left, self._right)
class TurretCounterClockwise(Function.Function):
"""turretccw([percent speed])
Rotate the turret counter clockwise as a percentage of the max speed."""
def __init__(self, speed=100):
self._checkRange(speed, 'turret percent speed')
self._speed = speed/100.0
def __call__(self, tank):
tank.setTurretAngle(tank._tAngle - tank.TURRET_TURN_RATE*self._speed)
class TurretClockwise(Function.Function):
"""turretcw([percent speed])
Rotate the turret clockwise at a rate preportional to speed."""
def __init__(self, speed=100):
self._checkRange(speed, 'turret percent speed')
self._speed = speed/100.0
def __call__(self, tank):
tank.setTurretAngle(tank._tAngle + tank.TURRET_TURN_RATE*self._speed)
class TurretSet(Function.Function):
"""turretset([angle])
Set the turret to the given angle, in degrees, relative to the front of
the tank. Angles increase counterclockwise.
The angle can be left out; in that case, this locks the turret
to it's current position."""
def __init__(self, angle=None):
# Convert the angle to radians
if angle is not None:
angle = self._convertAngle(angle, 'turret angle')
self._angle = angle
def __call__(self, tank):
tank.setTurretAngle(self._angle)
class Fire(Function.Function):
"""fire()
Attempt to fire the tanks laser cannon."""
def __call__(self, tank):
tank.setFire()
class SetToggle(Function.Function):
"""settoggle(key, state)
Set toggle 'key' to 'state'.
"""
def __init__(self, key, state):
self._key = key
self._state = state
def __call__(self, tank):
tank.toggles[self._key] = self._state
class Toggle(Function.Function):
"""toggle(key)
Toggle the value of toggle 'key'.
"""
def __init__(self, key):
self._key = key
def __call__(self, tank):
try:
tank.toggles[self._key] = not tank.toggles[self._key]
except IndexError:
raise IndexError('Invalid toggle: %d' % self._key)
class LED(Function.Function):
"""led(state)
Set the tanks LED to state (true is on, false is off).
The led is a light that appears behind the tanks turret.
It remains on for a single turn."""
def __init__(self, state=1):
self._state = state
def __call__(self, tank):
tank.led = self._state
class StartTimer(Function.Function):
"""starttimer(#)
Start (and reset) the given timer, but only if it is inactive.
"""
def __init__(self, key):
self._key = key
def __call__(self, tank):
tank.resetTimer(key)
class ClearTimer(Function.Function):
"""cleartimer(#)
Clear the given timer such that it is no longer active (inactive timers
are always False)."""
def __init__(self, key):
self._key = key
def __call__(self, tank):
tank.clearTimer(self._key)
### When adding names to this dict, make sure they are lower case and alpha
### numeric.
actions = {'move': Move,
'turretccw': TurretCounterClockwise,
'turretcw': TurretClockwise,
'turretset': TurretSet,
'fire': Fire,
'settoggle': SetToggle,
'toggle': Toggle,
'led': LED,
'starttimer': StartTimer,
'cleartimer': ClearTimer}

View File

@ -0,0 +1,126 @@
"""Define new condition functions here. Add it to the conditions dictionary
at the end to make it usable by Program.Program. These should inherit from
Function.Function."""
import Function
import math
import random
class Sense(Function.Function):
"""sense(#, [invert])
Takes a Sensor number as an argument.
Returns True if the given sensor is currently activated, False otherwise.
If the option argument invert is set to true then logic is inverted,
and then sensor returns True when it is NOT activated, and False when
it is. Invert is false by default.
"""
def __init__(self, sensor, invert=0):
self._sensor = sensor
self._invert = invert
def __call__(self, tank):
state = tank.getSensorState(self._sensor)
if self._invert:
return not state
else:
return state
class Toggle(Function.Function):
"""toggle(#)
Returns True if the given toggle is set, False otherwise. """
def __init__(self, toggle):
self._toggle = toggle
def __call__(self, tank):
return tank.toggles[toggle]
class TimerCheck(Function.Function):
"""timer(#, [invert])
Checks the state of timer # 'key'. Returns True if time has run out.
If invert is given (and true), then True is returned if the timer has
yet to expire.
"""
def __init__(self, key, invert=0):
self._key = key
self._invert = invert
def __call__(self, tank):
state = tank.checkTimer(self._key)
if invert:
return not state
else:
return state
class Random(Function.Function):
"""random(n,m)
Takes two arguments, n and m. Generates a random number between 1
and m (inclusive) each time it's checked. If the random number is less
than or equal
to n, then the condition returns True. Returns False otherwise."""
def __init__(self, n, m):
self._n = n
self._m = m
def __call__(self, tank):
if random.randint(1,self._m) <= self._n:
return True
else:
return False
class Sin(Function.Function):
"""sin(T)
A sin wave of period T (in turns). Returns True when the wave is positive.
A wave with period 1 or 2 is always False (it's 0 each turn), only
at periods of 3 or more does this become useful."""
def __init__(self, T):
self._T = T
def __call__(self, tank):
turn = tank.turn
factor = math.pi/self._T
if math.sin(turn * factor) > 0:
return True
else:
return False
class Cos(Function.Function):
"""cos(T)
A cos wave with period T (in turns). Returns True when the wave is
positive. A wave of period 1 is always True. Period 2 is True every
other turn, etc."""
def __init__(self, T):
self._T = T
def __call__(self, tank):
turn = tank.turn
factor = math.pi/self._T
if math.cos(turn * factor) > 0:
return True
else:
return False
class FireReady(Function.Function):
"""fireready()
True when the tank can fire."""
def __call__(self, tank):
return tank.fireReady()
class FireNotReady(Function.Function):
"""firenotready()
True when the tank can not fire."""
def __call__(self, tank):
return not tank.fireReady()
### When adding names to this dict, make sure they are lower case and alpha
### numeric.
conditions = {'sense': Sense,
'random': Random,
'toggle': Toggle,
'sin': Sin,
'cos': Cos,
'fireready': FireReady,
'firenotready': FireNotReady,
'timer': TimerCheck }

26
build/lib/tanks/docs.py Normal file
View File

@ -0,0 +1,26 @@
import xml.sax.saxutils
def mkDocTable(objects):
objects.sort(lambda o1, o2: cmp(o1.__doc__, o2.__doc__))
for object in objects:
if object.__doc__ is None:
print '<table><tr><th>%s<tr><td colspan=2>Bad object</table>' % \
xml.sax.saxutils.escape(str(object))
continue
text = object.__doc__
lines = text.split('\n')
head = lines[0].strip()
head = xml.sax.saxutils.escape(head)
body = []
for line in lines[1:]:
line = line.strip() #xml.sax.saxutils.escape( line.strip() )
line = line.replace('.', '.<BR>')
body.append(line)
body = '\n'.join(body)
print '<DL><DT><DIV class="tab">%s</DIV></DT><DD>%s</DD></DL>' % (head, body)
#print '<tr><th>%s<th>Intentionally blank<th><tr><td colspan=3>%s' % (head, body)

72
build/lib/tanks/setup.py Normal file
View File

@ -0,0 +1,72 @@
"""Each of these classes provides a function for configuring a tank.
They should inherit from Function.Function.
To make one available to the tank programmer, add it to the dictionary at
the end of this file."""
import Function
class AddSensor(Function.Function):
"""addsensor(range, angle, width, [turretAttached])
Add a new sensor to the tank. Sensors are an arc (pie slice) centered on
the tank that detect other tanks within their sweep.
A sensor is 'on' if another tank is within this arc.
Sensors are numbered, starting at 0, in the order they are added.
<p>
range - The range of the sensor, as a percent of the tanks max range.
angle - The angle of the center of the sensor, in degrees.
width - The width of the sensor, in percent (100 is a full circle).
turretAttached - Normally, the angle is relative to the front of the
tank. When this is set, the angle is relative to the current turret
direction.
<p>
Sensors are drawn for each tank, but not in the way you might expect.
Instead of drawing a pie slice (the actual shap of the sensor), an arc with
the end points connected by a line is drawn. Sensors with 0 width don't show
up, but still work.
"""
def __init__(self, range, angle, width, turretAttached=False):
self._checkRange(range, 'sensor range')
self._range = range / 100.0
self._width = self._convertAngle(width, 'sensor width')
self._angle = self._convertAngle(angle, 'sensor angle')
self._turretAttached = turretAttached
def __call__(self, tank):
tank.addSensor(self._range, self._angle, self._width,
self._turretAttached)
class AddToggle(Function.Function):
"""addtoggle([state])
Add a toggle to the tank. The state of the toggle defaults to 0 (False).
These essentially act as a single bit of memory.
Use the toggle() condition to check its state and the settoggle, cleartoggle,
and toggle actions to change the state. Toggles are named numerically,
starting at 0.
"""
def __init__(self, state=0):
self._state = state
def __call__(self, tank):
if len(tank.toggles) >= tank.SENSOR_LIMIT:
raise ValueError('You can not have more than 10 toggles.')
tank.toggles.append(self._state)
class AddTimer(Function.Function):
"""addtimer(timeout)
Add a new timer (they're numbered in the order added, starting from 0),
with the given timeout. The timeout is in number of turns. The timer
is created in inactive mode. You'll need to do a starttimer() action
to reset and start the timer. When the timer expires, the timer()
condition will begin to return True."""
def __init__(self, timeout):
self._timeout = timeout
def __call__(self, tank):
tank.addTimer(timeout)
setup = {'addsensor': AddSensor,
'addtoggle': AddToggle,
'addtimer': AddTimer}

0
ctf/__init__.py Executable file
View File

33
ctf/flagger.py Executable file
View File

@ -0,0 +1,33 @@
#! /usr/bin/python
import asynchat
import asyncore
import socket
class Flagger(asynchat.async_chat):
"""Use to connect to flagd and submit the current flag holder."""
def __init__(self, addr, auth):
asynchat.async_chat.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((addr, 1))
self.push(auth + '\n')
self.flag = None
def handle_read(self):
# We don't care.
msg = self.recv(4096)
def handle_error(self):
# If we lose the connection to flagd, nobody can score any
# points. Terminate everything.
asyncore.close_all()
asynchat.async_chat.handle_error(self)
def set_flag(self, team):
if team:
eteam = team.encode('utf-8')
else:
eteam = ''
self.push(eteam + '\n')
self.flag = team

35
ctf/html.py Executable file
View File

@ -0,0 +1,35 @@
#! /usr/bin/python
import os
import string
import sys
from codecs import open
from paths import *
template_fn = os.path.join(LIB, 'template.html')
template = string.Template(open(template_fn, encoding='utf-8').read())
base = BASE_URL
css = base + 'ctf.css'
def substitute(title, body, base=base, hdr='', body_class='', onload='', links=''):
return template.substitute(title=title,
hdr=hdr,
body_class=body_class,
base=base,
links=links,
onload=onload,
body=body)
def serve(title, body, **kwargs):
out = substitute(title, body, **kwargs)
print 'Content-type: text/html'
print 'Content-length: %d' % len(out)
print
sys.stdout.write(out)
sys.stdout.flush()
def write(filename, title, body, **kwargs):
f = open(filename, 'w', encoding='utf-8')
f.write(substitute(title, body, **kwargs))

6
ctf/paths.py Normal file
View File

@ -0,0 +1,6 @@
VAR = "/opt/ctf/var"
WWW = "/opt/ctf/www"
LIB = "/opt/ctf/lib"
BIN = "/opt/ctf/bin"
SBIN = "/opt/ctf/sbin"
BASE_URL = "/ctf/"

40
ctf/pointscli.py Executable file
View File

@ -0,0 +1,40 @@
#! /usr/bin/python
from urllib import quote
import teams
import time
import os
import paths
pointsdir = os.path.join(paths.VAR, 'points')
def award(cat, team, points):
if not team:
team = teams.house
now = time.strftime('%Y-%m-%dT%H:%M:%S')
pid = os.getpid()
qcat = quote(cat, '')
qteam = quote(team, '')
basename = '%s.%d.%s.%s' % (now, pid, qcat, qteam)
# FAT can't handle :
basename = basename.replace(':', '.')
tmpfn = os.path.join(pointsdir, 'tmp', basename)
curfn = os.path.join(pointsdir, 'cur', basename)
f = open(tmpfn, 'w')
f.write('%s\t%s\t%s\t%d\n' % (now, cat, team, points))
f.close()
os.rename(tmpfn, curfn)
def main():
import optparse
p = optparse.OptionParser('%prog CATEGORY TEAM POINTS')
opts, args = p.parse_args()
if len(args) != 3:
p.error('Wrong number of arguments')
cat, team, points = args
points = int(points)
award(cat, team, points)
if __name__ == '__main__':
main()

72
ctf/teams.py Executable file
View File

@ -0,0 +1,72 @@
#! /usr/bin/python
import fcntl
import time
import os
from urllib import quote, unquote
import paths
house = 'dirtbags'
passwdfn = os.path.join(paths.VAR, 'passwd')
team_colors = ['F0888A', '88BDF0', '00782B', '999900', 'EF9C00',
'F4B5B7', 'E2EFFB', '89CA9D', 'FAF519', 'FFE7BB',
'BA88F0', '8DCFF4', 'BEDFC4', 'FFFAB2', 'D7D7D7',
'C5B9D7', '006189', '8DCB41', 'FFCC00', '898989']
teams = {}
built = 0
def build_teams():
global teams, built
if not os.path.exists(passwdfn):
return
if os.path.getmtime(passwdfn) <= built:
return
teams = {}
try:
f = open(passwdfn)
for line in f:
line = line.strip()
if not line:
continue
team, passwd, color = map(unquote, line.strip().split('\t'))
teams[team] = (passwd, color)
except IOError:
pass
built = time.time()
def validate(team):
build_teams()
def chkpasswd(team, passwd):
validate(team)
if teams.get(team, [None, None])[0] == passwd:
return True
else:
return False
def exists(team):
validate(team)
if team == house:
return True
return team in teams
def add(team, passwd):
build_teams()
color = team_colors[len(teams)%len(team_colors)]
assert team not in teams, "Team already exists."
f = open(passwdfn, 'a')
fcntl.lockf(f, fcntl.LOCK_EX)
f.seek(0, 2)
f.write('%s\t%s\t%s\n' % (quote(team, ''),
quote(passwd, ''),
quote(color, '')))
def color(team):
validate(team)
t = teams.get(team)
if not t:
return '888888'
return t[1]

5
lib/tanks/easy/berzerker Normal file
View File

@ -0,0 +1,5 @@
random(1,10): move(50, 100);
random(1,10): move(100, 50);
random(1,10): turretcw();
random(1,10): turretccw();
random(1,30): fire();

View File

@ -0,0 +1,8 @@
>addsensor(50, 0, 0, 1);
>addsensor(70, 0, 50); # 1-Anti-collision sensor
: move(100, 100) . turretset(180);
random(1, 8): move(70, 100);
random(1, 8): move(100, 70);
sense(0) : fire();
sense(1) : move(-0, 100);

52
lib/tanks/hard/chashtank Executable file
View File

@ -0,0 +1,52 @@
>addsensor(50, 0, 10, 1);
>addsensor(35, 0, 90, 0);
>addsensor(100, 30, 59, 0);
>addsensor(100, 330, 59, 0);
>addsensor(70, 180, 180);
>addsensor(100, 90, 59, 0);
>addsensor(100, 270, 59, 0);
>addsensor(100, 0, 5, 1);
>addsensor(55, 50, 89, 0);
>addsensor(55, 310, 89, 0);
# move back and forth
: move(90,90).turretset(0);
random(2,6) : move(95,75).turretset(0);
#random(1,6) : move(75,95).turretset(0);
# rear sensor
sense(4) : move(90, 90);
# far right front sensor
sense(5) : move(100,-100);
# far left front sensor
sense(6) : move(-100,100);
# right front sensor
sense(2) : move(80,-80);
# left front sensor
sense(3) : move(-80,80);
# immediate front sensor in firing range
sense(0) & firenotready() : move(-50, -50);
: turretset(0);
# near far right front sensor
sense(8) : move(60,-60);
sense(9) : move(-60, 60);
fireready() : led();
# front far sensor
sense(7) & fireready() : move(100,100);
# collison sensor
sense(1) : move(-100, -100);
: turretset(0);
sense(0) & fireready() : fire();

View File

@ -0,0 +1,57 @@
# 3
# 000 33
# 2
# 2
# 2
# 11111 4
# 4
# 4
# @@/
# @@@
# @@@
#
#
#
#
>addsensor(50, 0, 05, 1); # 0 Fire Sensor
>addsensor(30, 0, 50); # 1 Anti-collision sensor
>addsensor(50, 0, 10); # 2 Anti-collision sensor
>addsensor(100, 315, 100, 1); # 3 Turret ccw
>addsensor(100, 45, 100, 1); # 4 Turret cw
>addsensor(60, 180, 180, 0); # 5 Ass
##
## Add "ears" so the tank is easy to pick out.
##
>addsensor(20, 90, 30, 0);
>addsensor(20, 270, 30, 0);
# Can't fire
: led(0) . move(80, 80) . turretset(0);
random(1, 3): led(0) . move(60, 80) . turretset(0);
random(2, 3): led(0) . move(80, 60) . turretset(0);
sense(0) : led(0) . move(10, 20) . turretset(0);
sense(1) : led(0) . move(10, 10) . turretset(0);
sense(2) : led(0) . move(10, 20) . turretset(0);
sense(3) : led(0) . move(70, 50) . turretset(0);
sense(4) : led(0) . move(50, 70) . turretset(0);
sense(3) & sense(4): led(0) . move(-100, 20) . turretset(0);
sense(5) : led(0) . move(100, 50) . turretset(0);
# Can fire
fireready() : led(1) . move(70, 70) . turretset(0);
fireready() & random(2, 40): led(1) . move(40, 70) . turretset(0);
fireready() & random(1, 40): led(1) . move(70, 40) . turretset(0);
fireready() & sense(3) : led(1) . move(0, 60) . turretccw(50);
fireready() & sense(4) : led(1) . move(60, 0) . turretcw(50);
fireready() & sense(3) & sense(4): led(1) . move(100, 100) . turretset();
fireready() & sense(1) : led(1) . turretset(0) . move(10, 10);
fireready() & sense(2) : led(1) . turretset(0) . move(10, 10);
fireready() & sense(0) : led(1) . turretset() . fire();
fireready() & sense(5) : led(1) . move(100, 40);

22
lib/tanks/hard/foobar Normal file
View File

@ -0,0 +1,22 @@
>addsensor(55, 0, 5, 1);
>addsensor(40, 0, 30);
>addsensor(80, 30, 59, 0);
>addsensor(80, 330, 59, 0);
>addsensor(70, 180, 180);
>addsensor(80, 90, 59, 0);
>addsensor(80, 270, 59, 0);
# : move(70,80);
# random(3,6) : move(80,70);
: move(65,85);
random(2,6) : move(90,65);
sense(2) : move(80,10).turretcw(100);
sense(3) : move(10,80).turretccw(100);
sense(4) : move(90, 90);
sense(5) : move(90,10).turretcw(100);
sense(6) : move(10,90).turretccw(100);
sense(0) & fireready() : turretset().move(90,90).fire();
sense(1) : move(-100, -100);
: turretset(0);
fireready() : led();

31
lib/tanks/hard/pflarr Normal file
View File

@ -0,0 +1,31 @@
>addsensor(50, 0, 45, 1); # 0-Fire Sensor
>addsensor(30, 0, 180); # 1-Anti-collision sensor
>addsensor(100, 40, 60, 1); # 2 turret clockwise
>addsensor(100, 320, 60, 1); # 3 turret ccw
>addsensor(80, 180, 160); # 4 Coward
>addsensor(100, 0, 0, 1); # 5-Fire Sensor2
>addsensor(100, 0, 0); # 6-Chase Sensor
>addsensor(75, 75, 30); # 7-quick turn right
>addsensor(75, 285, 30); # 8-quick turn left
# Commands
: move(70, 75).
turretset(0);
random(1, 10): move(75, 75).
turretset(0);
sense(2) : turretcw(50).
move(85, 70);
sense(2) & sense(0): turretcw(25).
move(85, 70);
sense(3) : turretccw(50).
move(70, 85);
sense(3) & sense(0) : turretccw(25).
move(70, 85);
sense(5) & sense(7) : move(70, 30);
sense(5) & sense(8) : move(30, 70);
#sense(5) : turretset();
sense(0) & sense(5) : fire();
sense(6) & sense(5) & fireready(): move(100,100);
sense(4) : move(100,100);
sense(1) : move(-50, 25);
fireready() : led();

View File

@ -0,0 +1,8 @@
>addsensor(50, 0, 5, 1); # 0-Fire Sensor
>addsensor(30, 0, 50); # 1-Anti-collision sensor
# Commands
: move(90, 100).
turretset(0);
sense(0) : fire();
sense(1) : move(-100, 100)

View File

@ -0,0 +1,9 @@
>addsensor(50, 0, 10, 1); # 0-Fire Sensor
>addsensor(100, 90, 150, 1);
>addsensor(100, 270, 150, 1);
: turretcw(75);
sense(0): fire();
sense(1): turretcw();
sense(2): turretccw();

18
lib/tanks/medium/sweeper Normal file
View File

@ -0,0 +1,18 @@
# Just sit there and sweep the field until it finds something to shoot.
# Uses a long-range sensor on the left and right to hone in.
>addsensor(50, 0, 5, 1); # 0
>addsensor(100, 90, 150, 1); # 1
>addsensor(100, 270, 150, 1); # 2
>addsensor(100, 0, 359, 0); # 3
# Default movement if nothing is detected
: move(70, 70) . turretccw();
random(2, 3): move(40, 70) . turretccw();
random(1, 3): move(70, 40) . turretccw();
# We found something!!
sense(3): move(0, 0);
sense(1): turretcw();
sense(2): turretccw();
sense(0): fire();

140
mkpuzzles.py Executable file
View File

@ -0,0 +1,140 @@
#! /usr/bin/python
import os
import shutil
import optparse
import string
import markdown
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.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)

112
puzzler.keys Normal file
View File

@ -0,0 +1,112 @@
bletchley 50 extra special text
bletchley 1000 It is a lovely day outside
bletchley 150 jackalope wheeze
bletchley 350 PC LOAD LETTER
bletchley 300 jako561962
bletchley 200 unequivocal
bletchley 900 PEANUT BUTTER JELLY TIME
bletchley 100 antediluvian
bletchley 500 xez.3nt
bletchley 250 DB1663<3
net-re 4 PINHEAD CATASTROPHE
net-re 2 great job
net-re 25000 galloping gallimimus
net-re 1000 a difficult key!
net-re 7 60.0.13.65
net-re 4000 gaucho moped fleet
net-re 800 10.72.148.66
net-re 5000 miniature commodore exercise
net-re 300 pumpkins
net-re 5 fishsticks
net-re 30 RSTNFGEAID
net-re 3000 galactic octopus
net-re 200 particulate
net-re 6 whatever.example.net
net-re 2000 obtuse
net-re 3 2,4,6,8,A,B,C,D,F
net-re 400 lettuce
net-re 100 chumbucket
net-re 8 bacon
net-re 1200 hotshot tomato
net-re 20 squirrel
net-re 1 163
net-re 10 69.35
net-re 700 fixate rasterize
net-re 250 alice_test@hotmail.com
survey 1000000 quux blorb frotz
sequence 50 42
sequence 2 111 1000
sequence 35 13 21
sequence 300 ┤
sequence 25 36
sequence 600 61 64 9b
sequence 200 E G G
sequence 16 a
sequence 400 0101
sequence 100 45
sequence 8 100
sequence 19 17
sequence 1 6
sequence 700 01 00 00 ca 0a
sequence 450 05
hispaniola 50 LANL GUYS
hispaniola 5 3acd767f2717b84076cdcd18e882f01d
hispaniola 125 ‽
hispaniola 75 ⚑ ◢ ◕ ★ ♥ ◢ ♥ ⚑ ◕ ★ ♥ ◕ ★ ♥ ⚑ ★ ⚑ ◢ ♥ ◢ ◕ ◕ ◢ ★ ⚑
hispaniola 15 -462766
hispaniola 10 You're well on your way :)
skynet 302 SkynetSasserVersionWithPingFast
skynet 401 67678dj*&78
skynet 102 beagle_beagle
skynet 300 pecompact
skynet 202 tftp
skynet 203 FreeConsole
skynet 200 402fcc
skynet 301 4028de
skynet 400 services.exe
skynet 100 bbeagle.exe
skynet 500 c:\windows\system32:lzx32.sys
skynet 500
skynet 501 kdD
crypto 160 chronic failure
crypto 220 open meadows
crypto 230 quavering tendrils
crypto 130 probable cause
crypto 210 The Colour Out of Space
crypto 150 flaming mastiff
crypto 240 in the same vein
crypto 140 giant chickens
crypto 200 the squirrels crow at noon
crypto 110 the s is for sucks
crypto 120 Rat Fink
crypto 180 The key for this puzzle is this sentence
crypto 400 --------========Thanks for Pl@y|ng========--------
crypto 100 caesar
crypto 170 terrifying silence
crypto 1 dirtbags
forensics 50 C:\WINDOWS\system32\klog.sys
forensics 200 winsecur.dll
forensics 400 avatar.txt
forensics 100 dll injection
forensics 20 klog.txt
forensics 10 lsass.exe
forensics 250 Dirka Dirka
compaq 50 4C
compaq 150 This is our world now... the world of the electron and the switch, the beauty of the baud.
compaq 350 Actually, Werner, we're all tickled to here you say that. Frankly, watchin' Donny beat Nazis to death is is the closest we ever get to goin' to the movies.
compaq 600 Now think real hard. You been bird-doggin' this township awhile now. They wouldn't mind a corpse of you. Now, you can luxuriate in a nice jail cell, but if your hand touches metal, I swear by my pretty floral bonnet, I will end you.
compaq 200 Gawain Ballard Tunisia
compaq 400 lawful forths Amanda
compaq 100 root:x:0:0:root:/root:/bin/bash
compaq 500 codger launched jet
hackme 806 That's all, folks.
hackme 200 james
hackme 614 james
webapp 50 eVkIwHzOok
webapp 40 765JBo4B54
webapp 70 s4nNlaMScV
webapp 30 BRrHdtdADI
webapp 60 QJebByJaKX
webapp 80 dmW5f9P54e
webapp 20 uq4G4dXrpx
webapp 10 ktFfb8R1Bw

View File

@ -0,0 +1 @@
antediluvian

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

View File

@ -0,0 +1,108 @@
Safe to ex<b>e</b>cute.
<img width="20" height="20" src="data:image;base64,
cjd7PTw9PDw8DQ0NDQ0NDQ8PDA0MDAwMzE5KQnYNDQ1lYmJiYmJiDTk5GRke
HjY2Kg0UFBISEhImJiYNObm9tYEBBQ3tDQ0N7e3t7ejo6A0JCQkJCgoKCh4M
DAwYmZ2VgQAEBRYWFhYFBQUFAQ0NDQwMDAwNDQ0NDQ0NDQ2NiYGBjYmBAQUF
BYWBgQ0ICAgICBgYGBkNDQ2NiYmJCZ2ZBYURFR0dHBwcGAwMDAoKCgoKGhoN
Dw8PD5ufn58LmZ2VAZWRmVFRUQ3FxcXFw8PDw8cNDQ0JCQkJISAgDSWkoKiA
AQUNLQ0NDS0tLS0pKSkNCQkJCVi9ya2tDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQsN
DQ0JCQkJJkojb0AsSGUJYA57AyNQPxEjIyMnJycNHR0dHRwcHBxbQxYWFhYW
FhQUFA0PDw8PCgoKCgkNDQ0LCwsLDg4ODQ8PDw8MDAwMDA0NDQ0NDQ0MDAwN
DQ0NDQ0NDQ0JDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDSMNDQ0NDQ0Np6amDR8fHx9d
XV1dXQ0NDdDQ0NDCwsINPj4+PmLm4uruDQ0NHBwSEhMTEw0NDQ0NDQ0NDS0N
DQ0YGBgYGBgYDQ0NDQ0tLS0tLVIYbjFjBmEIew9oGlk1VCdUMUJCUg1qB2gG
WSpeP38LVAsLZw5sDyF+ET8JCXkMeAsLUhtUC3gMaAFvMHgLbgoKVQpmD21u
MUI2VyVRDmMCZAoKTQFICkkWJCMTExMTERETExINDQ0NDQ0NDAwNDSkpKSk5
OTk5OQ0NDR10HRAQEBINWVlZWVlZWVkFmJyUkpeXl/tuagUCAwMDc+bi6u0P
Dw9a0za1WVG5bGxsbIQwMDAw2IaHh4dOjY1yRyOYnJRrTiazt7+/DQ0N8te7
LioiSg0NDQ3kBPsE+wQoWM3JwamhoaGh5DTLNMv6F0nAIY5qmsqezKRw8/cF
be1uamIzZQ1ljoqCaqVapVquPp3IQaT3Hx8fHx9W1xRndXV1J6wv8Q7xDotL
Pz3CElUOxwSUBJTBSK2OYmrq11fCxs7OeXadgg95efo6Pq7SR0NLtGbHuy4J
AYqaH824U5WQjRgcFBXcH5ZgNYRh4g4Gpzejp6+ISDwlnZ2dnZ0YzbmpKsbK
ojKmogX6KqltffCGhk/OXs6bEvd0mJAT6RmadmoCYubi6uXHOMc48TJn7gta
DF/cMDzU1NTUDVbXFMbX19c/4fMM83793SLdIq+evkG+QciNfVSEPMoL8/HI
Dn1r4tpTpVpO/Hc6yuP0snOKiLF/9gx+42CkqPOt8jv4cfuuJ8KVw5B4eHgN
DVbXFJWEhIQJjq5RrlHcZ0e4R/LbI+IaGJt3e/Z9gmls/AMXoO5t8wx5jmZI
SEhIy8nFnsCfVpUFlcCEYTJg21vPy8NijRkdFf70eQ8PjObiHc1GRcY+wbT5
ofoz8KUsyZpyDQ0NDVbXFA8eHg1dtQP9Av2k/zbOzs7Nzc3NzMzODUQwEHkK
KktrB2IUcR1kRCBBOBhiF2MQeR14eHh4DQ0N8g3yDQ0NDQ3yDfINDQ0NDQ0N
DQ0MDAwMKCgoDQEBAQF5+//3+g0NDTG1sbm9vb0NRcTAyM3Nzc0ZjIiAhoaG
hvJzdwUPDw8PUVFRUVoNDQ0dHR0dCAgIDQ0NDQ0ODg4ObpiclJaWlpaGhoYN
GRkZGQgICAgfDQ0NZefj6/r6+g1t7+vj8fHx8fkNDQ0eHh4eFhYWDfMM85zc
XlpSrfINYmNjY2OTbJNiUNLW3t7e3t7eDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0N
DQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ2ZDQkFBQUFBQUFBQWjj4uDNbez
u7u7uw0NDQ0NgRURGRlKCUpwUHg/cSQNLR4wBCocPA4+DjsLPw87GzNhBGAt
ZQRwUGNNeVdhIBgxMTF2NXZMbCViLHlQcENtWXc7GykZKR8vGysfLQVXMlZ2
Pl8rCz4QJAo8ESkAAABKCUpwUHg/cSQNLR4wBCocPA4+DjsLPw87GzNhBGAt
ZQRwUGNNeVdhIBkwMDB3NHdNbSViLHlQcENtWXc7GykZKR8vGysfLQVXMlZ2
Pl8rCz4QJAo8ESgBAQFKCUpwUHg/cSQNLR4wBCocPA4+DjsLPw87GzNhBGAt
ZQRwUGNNeVdhIBkwMDB3NHdNbSViLHlQcENtWXc7GykZKR8vGysfLQVXMlZ2
Pl8rCz4QJAo8ESkAAAAjUClEMFEzMx1ueQt/Hnx8UiFJOnkLfx58fFI7VSFo
GmpqRCpFMVR6TA5Hah5/GBg2Xmwfd3dZPUQqWSBgYE4qUz1OOkhII0QqX3EH
YhBjCmIMDCJFK15wBmN/DGUKZDtJSWcVaAQqTjdZWXcFYGFPP1MnJwlgDmd5
eVcjRj5KSmQCZApjY00/UDRVIWxsQidPEHYEZQhoaEYlUT5MPz8RaR1yAHNz
XTdUJg0jRz5QMVw1VlYjRCtfX3EWeQ0jfRFlZUsvTjpbWyNBMkFBbwxjDmNo
BnJycnJycnJyDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0N
DRYWFg0MDAwMDg4ODhqMiICUlZWVhoaGDQ0NDQ0NDQ0NDA0NDQ0NDQ0uLi4N
CgoKCggICAggjIiAqKmpqYmJiQ0NDQ0NDQ0NDQkNDQ0NDQ0NPDw8DQgICAgK
CgoKQoyIgMjJycnl5eUNCQkJCQkJCQkNDQ0NCQkJCT4+Pg0GBgYGBAQEBHCM
iID09fX1lZWVDQgICAgJCQkJDQ0NDR0dHR0iIiINDg4ODgwMDAzYjIiAVFVV
VQsLCw0NDQ0NDQ0NDQwNDQ0NDQ0NSkpKDfIN8p2fn5+frY+Lg7Gzs7O/v78N
CQkJCQkJCQkLDQ0NDw8PD1tbWw3zDPOcnp6ent6Pi4PDwcHB4eHhDQgICAgJ
CQkJDQ0NDQ0NDQ1ubm4NBAQEBAYGBgZmj4uD4+Hh4enp6Q0JCQkJCQkJCQ0N
DQ0FBQUFaWlpDQQEBAQGBgYGbo+Lg+vp6en5+fkNCQkJCQICAgIGDQ0NBQUF
BXBwcA0MDAwMCgoKCnKPi4P7+fn57u7uDQ0NDQ0NDQ0NCQ0NDQ0NDQ19fX0N
DAwMDAoKCgqaj4uDExERESEhIQ0NDQ0NDQ0NDQkNDQ0JCQkJcnJyDQwMDAwK
CgoKyo+Lg0NBQUE9PDwNDQ0NDQ0NDQ0JDQ0NDQ0NDYyMjA0MDAwMCgoKCjaJ
jYW5vb29p6enDQ0NDQ0NDQ0NCQ0NDQ0NDQ2KiooNDAwMDA4ODg5WiY2F3dnZ
2fr6+g0NDQ0NDQ0NDQkNDQ0NDQ0NgoKCDQwMDAwODg4OcomNhfn9/f35+fkN
DQ0NDQ0NDQ0JDQ0NDQ0NDZSUlA0MDAwMDw8PD4+ZnZUVERERGRkZDQ0NDQ0N
DQ0NCQ0NDQ0NDQ2tra0NDAwMDA8PDw+HmZ2VHRkZGREREQ0NDQ0NDQ0NDQkN
DQ0NDQ0NqqqqDQwMDAwPDw8Pn5mdlQUBAQEFBQUNDQ0NDQ0NDQ0JDQ0NDQ0N
DaGhoQ0LCwsLCAgICJyZnZUBBQUFzc3NDQgICAgICAgIDA0NDQUFBQWwsLAN
DAwMDA8PDw9TmJyUyM3NzcnJyQ0NDQ0NDQ0NDQkNDQ0JCQkJs7OzDQwMDAwP
Dw8Pb5iclPTx8fHl5eUNDQ0NDQ0NDQ0JDQ0NCQkJCcrKyg0MDAwMDw8PD3uY
nJTg5eXl6enpDQ0NDQ0NDQ0NCQ0NDQ0NDQ3ExMQNBQUFBQYGBgaGmJyUFBER
ERUVFQ0NDQ0NDQ0NDQkNDQ0NDQ0Nw8PDDQwMDAwMDAwMDA0NDY2IiIiGh4cN
DQ0NDQ0NDQ0MDQ0NDQ0NDRwcHA0ODg4ODg4ODg4NDQ2DhYWFUlJSDQ0NDQ0N
DQ0NDA0NDQ0NDQ0MDAwNDw8PDw8PDw8PDQ0Nxc7Ozq6qqg0WFhYWOjo6Oj4N
DQ0dHR0dFBQUDQ4ODg4ODg4ODg0NDSU1NTUBAwMNDQ0NDQ0NDQ0MDQ0NDQ0N
DQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NGZicBQUFBQUGBgcHBw0NDSWkoKioqKgN
Dg4MDAwMDAxEjIiAgICAgIODgA0NDQ0Nefj89PQNDQ0ODgoKCgoKDdlYXFRU
VFRUVw0ICAgICAg6uLwFBQUFBQYGAAAADQ0NTc/Lw8PDww0ODgkJCQkJCWmP
i4ODg4ODgICIDQ0NDQ1l5+Pr6w0NDQ4OBwcHBwcNdffz+/v7+/v4DQcHBwcH
B5cVEQUFBQUFBgYNDQ0NDQ3NT0tDQ0NDDQ4OAgICAgICPomNhYWFhYWGhosN
DQ0NDVXR1d3dDQ0NDg4AAAAAAA1x9fH5+fn5+foNAgICAgICghYSBQUFBQUG
BhYWFg0NDYURFR0dHR0NDg4fHx8fHx+PmZ2VlZWVlZaWhA0NDQ0NmQ0JAQEN
DQ0ODh0dHR0dDVHEwMjIyMjIyw0ZGRkZGRl57OgFBQUFBQYGExMTDQ0Neezo
4ODg4A0ODhgYGBgYGJiYnJSUlJSUl5eADQ0NDQ0NDQ0NDQ0NDQ4OFhYWFhYN
DQ0NDQ0NDQ0ODRQUFBQUFBQUFA0NDQ0NDg4UFBQNDQ0NDQ0NDQ0NDQ4OFRUU
FBQU8I+Lg4ODg4OBgY0NHBwcHBwcHBwcDQ0NCQn4BxsbGw2NGR0VFRUVFRQN
HR03Nzc3vysvBQUFBQUEBBUVLQ0NDZ0JDQUFBQUNDAweHltbW1snmJyUlJSU
lJWVgw1ERERExFFVXVwNDQ0MDBsbTk5ODQWGgoqKioqKiA0BAWpqampW1dEF
BQUFBQcHCwsaDQ0NDQ0NDQ0NDQ0JCfgHcHBwcPSZnZWVlZWVlJSEDYmJiYkF
kZWdnQ0NDQwMHR2MjIwNcfXx+fn5+fn4DQICnZ2dnQ2ZnQUFBQUFBAQWFr0N
DQ0VkZWdnZ2dDQ8PAwPCwsLCwg0NDQ0NDQ0JCfjyOjo6Oq46PjY2DQ0NHBwP
D97e3g1V0dXd2dnZ2cgNAwPb29vbW8/LBQUFBQUVF+YZ8A0NDXXg5Ozs7OwN
HB4ICP7+/v4qjoqCwMDAwNLS3g0LCgoKCgoKCqAMDAweHh4eCAkJDXX38/v7
+/v76Q0HBxsaGhraWFwFBQUFBRcXGxs4DAwMjBgcFBQUFA0dH+4RJyYmJqaO
ioLQ0NDQwsLODUtKSkrKX1tTUw0NDR0d7BNBQEANZebi6vLy8vLgDQEBVldX
V1dXVw3Q0NDQwsLCwrYMDAyMGBwUFBQUDR0f7hGUlZWV4ZiclJSUlJS0tKIN
nZycnKAkICgoDQ0NHx8SEoSFhQ2NGR0VFRUVFQUP/gGrqqqqKr+7BQUFBQUV
FeQbqgwMDGz5/fX19fUNHBwJCc7Pz89LmJyUlJSUlISEdfI+Pz8/vysvJycN
DQ0dH+4Rzs/PDVHV0dnd3d3dzA0DA+3s7OyYDQkFBQUFBRUVAwP4DAwMDAwM
DAwMDA0tLS0tIiAgIKCZnZWVlZWVhYd28tfV1dXV1dXV1Q0NDS0tLS0tTi9h
DVI1WDdZBnUBbB5qagl7D3wIfWsNI0BAH0ADVxhfAEwFVgJdAgJdUhZCDV8A
TAVWAlINDVINRwRWCUVEF0McQ0MzHS0tbgFsHHAVYQRgTjw8YzxYN2gPYwxv
DmI9WS1CMEMcbBlhYQd1FHkcQ2kccRxlZTplJnJCEE8KRABfAABfUhZCDV8A
RQtPEFJSDVIURgdKD1BIBkIdQkIdQghLXwBFC08QT08QT2kGWT5SPV8+Ug1u
GnUHdCtKP0dHeRxvGzVWVglNFEMCTwZFRRp8DFNlEhJNEnQdcxpFbB5sDXQr
TiBERFINaRp1KkIjTSlhBARbBGgBYwBfbh1oN1E4Vj8/T3gMfz9/OHQ9fzxS
YE5+fiFIJk87DVIhVTRGMjJtMmsCbAVaO0k7WiNSIVU0RjIybTJeZAZlOlkq
XwBpB2QQEE8QcgFyLV55GGoeHnMSexUVUg1hCGoJViVRMH8LVDlYMV8fXxhB
CEoJVmRKenolUjtVPEgXdgR2F3QrTiBERCBBNVRSIVU0RjIybQtiYwoKVQp6
CG0EamQQTy5cLk82aQxjBwdYPVk4TC0tUhVZFlQVWQZJD0sYXQlWAkMBTQhS
Ug1oBmJiPWILYwp+IUAyQCFYB34KaxltbTJ7NGt+Cm4HaTZDMFUxDVINaQh8
HUIxRWweamo1fwlWBGFqA3AEYRNQPF0ufhtoaDdoGGoPZmMKfiFAMkAhWAd+
CmsZbW0ybQpnYgxTIFQ1RzNsMw0=" alt="Santa's helpers binary" />

View File

@ -0,0 +1 @@
It is a lovely day outside

Binary file not shown.

View File

@ -0,0 +1,2 @@
Recovery, while not strictly necessary, may be tremendously helpful.

View File

@ -0,0 +1 @@
jackalope wheeze

View File

@ -0,0 +1 @@
tkftsuiuqvaheohrnsnuoleyriod"eic"

View File

@ -0,0 +1 @@
unequivocal

View File

@ -0,0 +1 @@
27586126814341379597440261571645814840581961154587430529221052323

View File

@ -0,0 +1 @@
DB1663<3

View File

@ -0,0 +1,22 @@
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)

View File

@ -0,0 +1 @@
jako561962

Binary file not shown.

View File

@ -0,0 +1 @@
PC LOAD LETTER

Binary file not shown.

1
puzzles/bletchley/50/key Normal file
View File

@ -0,0 +1 @@
extra special text

Binary file not shown.

View File

@ -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

View File

@ -0,0 +1 @@
journals.uchicago

View File

@ -0,0 +1 @@
xez.3nt

Binary file not shown.

View File

@ -0,0 +1 @@
PEANUT BUTTER JELLY TIME

View File

View File

@ -0,0 +1,53 @@
You are doing a forensics evaluation of a linux box that you know has
been compromised. You find a binary on the system and assume it was
used by the attackers to hide data on box that that was exfiltrated.
You dissamble the file and find the x86 assembly shown below (from Ida)
- this function was used for obfuscation. You also find a file
obfuscated by this tool. Using the key you find in this encoder code
what is the unobfuscated first line of the file which starts with
8%%>p2pzpzp8%%>pe8%%>pe(#$e(+9"
HINT: The function was orginally defined as void convert_buf(unsigned
char * buf, int len).
.text:08048474 ; =============== S U B R O U T I N E =======================================
.text:08048474
.text:08048474 ; Attributes: bp-based frame
.text:08048474
.text:08048474 public convert_buf
.text:08048474 convert_buf proc near ; CODE XREF: main+B2p
.text:08048474
.text:08048474 cnt = dword ptr -4
.text:08048474 buf = dword ptr 8
.text:08048474 len = dword ptr 0Ch
.text:08048474
.text:08048474 push ebp
.text:08048475 mov ebp, esp
.text:08048477 sub esp, 10h
.text:0804847A mov [ebp+cnt], 0
.text:08048481 mov [ebp+cnt], 0
.text:08048488 jmp short loc_80484A4
.text:0804848A ; ---------------------------------------------------------------------------
.text:0804848A
.text:0804848A loc_804848A: ; CODE XREF: convert_buf+36j
.text:0804848A mov eax, [ebp+cnt]
.text:0804848D mov edx, eax
.text:0804848F add edx, [ebp+buf]
.text:08048492 mov eax, [ebp+cnt]
.text:08048495 add eax, [ebp+buf]
.text:08048498 movzx eax, byte ptr [eax]
.text:0804849B xor eax, 4Ah
.text:0804849E mov [edx], al
.text:080484A0 add [ebp+cnt], 1
.text:080484A4
.text:080484A4 loc_80484A4: ; CODE XREF: convert_buf+14j
.text:080484A4 mov eax, [ebp+cnt]
.text:080484A7 cmp eax, [ebp+len]
.text:080484AA jl short loc_804848A
.text:080484AC leave
.text:080484AD retn
.text:080484AD convert_buf endp
.text:080484AD
.text:080484AE

1
puzzles/compaq/100/key Normal file
View File

@ -0,0 +1 @@
root:x:0:0:root:/root:/bin/bash

Binary file not shown.

1
puzzles/compaq/150/key Normal file
View File

@ -0,0 +1 @@
This is our world now... the world of the electron and the switch, the beauty of the baud.

Binary file not shown.

1
puzzles/compaq/200/key Normal file
View File

@ -0,0 +1 @@
Gawain Ballard Tunisia

Binary file not shown.

1
puzzles/compaq/350/key Normal file
View File

@ -0,0 +1 @@
Actually, Werner, we're all tickled to here you say that. Frankly, watchin' Donny beat Nazis to death is is the closest we ever get to goin' to the movies.

Binary file not shown.

1
puzzles/compaq/400/key Normal file
View File

@ -0,0 +1 @@
lawful forths Amanda

View File

@ -0,0 +1,52 @@
You are doing a forensics evaluation of a linux box that you know has
been compromised. You find a binary on the system and assume it was
used by the attackers to hide data on box that they were going to
exfiltrate. You dissamble the file and find the following lines of x86
assembly - this function was used to encode a buffer in place to
obfuscate a file. What is the 1 byte key used to obfuscate the data (in
hex)?
HINT: The function was orginally defined as void convert_buf(unsigned
char * buf, int len). You can solve this puzzle by writing some code,
or by using some of the advanced functions of some of the hex editors
out there.
.text:08048474 ; =============== S U B R O U T I N E =======================================
.text:08048474
.text:08048474 ; Attributes: bp-based frame
.text:08048474
.text:08048474 public convert_buf
.text:08048474 convert_buf proc near ; CODE XREF: main+B2p
.text:08048474
.text:08048474 cnt = dword ptr -4
.text:08048474 buf = dword ptr 8
.text:08048474 len = dword ptr 0Ch
.text:08048474
.text:08048474 push ebp
.text:08048475 mov ebp, esp
.text:08048477 sub esp, 10h
.text:0804847A mov [ebp+cnt], 0
.text:08048481 mov [ebp+cnt], 0
.text:08048488 jmp short loc_80484A4
.text:0804848A ; ---------------------------------------------------------------------------
.text:0804848A
.text:0804848A loc_804848A: ; CODE XREF: convert_buf+36j
.text:0804848A mov eax, [ebp+cnt]
.text:0804848D mov edx, eax
.text:0804848F add edx, [ebp+buf]
.text:08048492 mov eax, [ebp+cnt]
.text:08048495 add eax, [ebp+buf]
.text:08048498 movzx eax, byte ptr [eax]
.text:0804849B xor eax, 4Ch
.text:0804849E mov [edx], al
.text:080484A0 add [ebp+cnt], 1
.text:080484A4
.text:080484A4 loc_80484A4: ; CODE XREF: convert_buf+14j
.text:080484A4 mov eax, [ebp+cnt]
.text:080484A7 cmp eax, [ebp+len]
.text:080484AA jl short loc_804848A
.text:080484AC leave
.text:080484AD retn
.text:080484AD convert_buf endp
.text:080484AD
.text:080484AE

1
puzzles/compaq/50/key Normal file
View File

@ -0,0 +1 @@
4C

Binary file not shown.

1
puzzles/compaq/500/key Normal file
View File

@ -0,0 +1 @@
codger launched jet

Binary file not shown.

1
puzzles/compaq/600/key Normal file
View File

@ -0,0 +1 @@
Now think real hard. You been bird-doggin' this township awhile now. They wouldn't mind a corpse of you. Now, you can luxuriate in a nice jail cell, but if your hand touches metal, I swear by my pretty floral bonnet, I will end you.

View File

View File

@ -0,0 +1,16 @@
<dl>
<dt>Alice
<dd>Welcome to Crypto. It works like this: I'll say something to Bob,
and he'll say something back. Our communication will be encrypted in some
manner, or at least obfuscated. Your job is to get the plaintext, and
find the puzzle key.
<dt>Bob
<dd>Sometimes the plaintext from one puzzle will give you a hint (or the
cryptogaphic key) for the next. When we give you such keys, we'll always
do so in a straightforward manner. The puzzle key for each puzzle
is always in what I say, and there shouldn't be any tricks involved in
figuring out what it is.
<dt>Alice<dd>Good Luck!
<dt>Bob<dd>You'll need it. By the way, the key is 'dirtbags'.
</dl>

1
puzzles/crypto/1/key Normal file
View File

@ -0,0 +1 @@
dirtbags

View File

@ -0,0 +1,2 @@
<dl><dt>Alice<dd> nyy unvy pnrfne.
<dt>Bob<dd> pnrfne vf gur xrl </dl>

1
puzzles/crypto/100/key Normal file
View File

@ -0,0 +1 @@
caesar

View File

@ -0,0 +1,24 @@
plaintext = [b'all hail caesar.', b'caesar is the key']
alpha = b'abcdefghijklmnopqrstuvwxyz'
def ceasar(text, r):
out = bytearray()
for t in text:
if t in alpha:
t = t - b'a'[0]
t = (t + r)%26
out.append(t + b'a'[0])
else:
out.append(t)
return bytes(out)
encode = lambda text : ceasar(text, 13)
decode = lambda text : ceasar(text, -13)
c = encode(plaintext[0])
print('<dl><dt>Alice<dd>', str(c, 'utf-8'))
assert decode(c) == plaintext[0]
c = encode(plaintext[1])
print('<dt>Bob<dd>', str(c, 'utf-8'), '</dl>')
assert decode(c) == plaintext[1]

View File

@ -0,0 +1,2 @@
<dl><dt>Alice<dd> Vkbd ntg duun puwtvbauwg dbnjwu, hlv bv'd vku dtnu htdbe jpbfebjwud td lduq bf d-hxyud, t vuekfbmlu lduq bf ntfg nxqupf epgjvxcptjkbe twcxpbvnd. Xi exlpdu, bfdvutq xi wuvvup dlhdvbvlvbxf, gxl'pu qxbfc hgvu dlhdvbvlvbxf.
<dt>Bob<dd> Vku fuyv vzx jlsswud tpu t hbv qbiiupufv; Ipumlufeg exlfvd (xi ektptevupd) zbww rldv puautw ptfqxn fxbdu. Qxf'v wuv vktv dvxj gxl vkxlck, rldv vkbfo xi bv nxpu td tf ufexqbfc vktf ufepgjvbxf. Xk, hg vku ztg, vku oug vkbd vbnu bd: 'vku d bd ixp dleod'. </dl>

1
puzzles/crypto/110/key Normal file
View File

@ -0,0 +1 @@
the s is for sucks

View File

@ -0,0 +1,49 @@
#!/usr/bin/python3
plaintext = [b"This may seem relatively simple, but it's the same basic "
b"principles as used in s-boxes, a technique used in many modern "
b"cryptographic algoritms. Of course, instead of letter substitution, "
b"you're doing byte substitution.",
b"The next two puzzles are a bit different; Frequency counts (of characters) "
b"will just reveal random noise. Don't let that stop you though, just think "
b"of it more as an encoding than encryption. "
b"Oh, by the way, the key this time is: 'the s is for sucks'."]
key = b"thequickbrownfxjmpdvlazygs"
def encode(text):
ukey = key.upper()
lkey = key.lower()
assert len(set(key)) == 26, 'invalid key'
assert key.isalpha(), 'non alpha character in key'
out = bytearray()
for t in text:
if t in lkey:
out.append(lkey[t - ord('a')])
elif t in ukey:
out.append(ukey[t - ord('A')])
else:
out.append(t)
return bytes(out)
def decode(text):
ukey = key.upper()
lkey = key.lower()
assert len(set(key)) == 26, 'invalid key'
assert key.isalpha(), 'non alpha character in key'
out = bytearray()
for t in text:
if t in lkey:
out.append(ord('a') + lkey.index(bytes([t])))
elif t in ukey:
out.append(ord('A') + ukey.index(bytes([t])))
else:
out.append(t)
return bytes(out)
c = encode(plaintext[0])
print('<dl><dt>Alice<dd>', str(c, 'utf-8'))
assert decode(c) == plaintext[0]
c = encode(plaintext[1])
print('<dt>Bob<dd>', str(c, 'utf-8'), '</dl>')
assert decode(c) == plaintext[1]

View File

@ -0,0 +1,3 @@
<p>The 5 byte groupings are just a standard way of displaying cypher text. It has no bearing on the solution to the puzzle. This format will be used to display the cyphertext from now on.</p>
<dl><dt>Alice<dd> YkJzY rEFAd iVsPW RXwxG PnRoX<BR>QcFZC YWDzg MzkzH SegHM rUqSu<BR>vmfRP KYcma GlBBT EtFRP MJYut<BR>vSPHE UblvT uNaRh hexTd JHnjg<BR>yHtIR gaPme DHMne CUsEk EVoSB<BR>JVZMk lcVJq cWgbd XwCAX ceGZQ<BR>JgDxh gYIaX MheoP cWLWe kmLBe<BR>tAPgG JWOEC mzTcY ZbJwu IgmnN<BR>ACgLP AYPNw wkBuW KSewJ oDNMM<BR>efDBk SoPCk CSCTX FsteA QjrPv<BR>nWTOD zHdhS auWhT koFCv UnFHe<BR>SGGWM OhrmU yjmLv zDFPP eQjox<BR>uXQdN AtKyu DNLNj EVZCX PtpDZ<BR>PlSOp soDgH rHrwm UeOIQ QdZJP<BR>RNMph sCgSA JbxYr OWBYe uSErY<BR>rCAmN CSJMY qmiIV wTSvv JDqQy<BR>UoroE HrwCo cfQwA kOsxU hoUIL<BR>aomUm ESRRn UMJVJ CwaVu NEoGi<BR>tpUZo pCilR puxUI CmSEe mtEqr<BR>vMvWP MFdSK EWEQr twWNU QBrwA<BR>hxAMP oqQFP ThZmm BcIBi QmdAj<BR>uaEPU eRICD KJurQ MYhZU vnXyG<BR>AxBsy BzwsO WnyXO SCbDn usYBx<BR>QDbra nYWxY PkYqw iPEQh KQMRE<BR>VEmKB YXCEf XlUoY SGznM vCQVK<BR>waRrX WeLbz VfumH DtiHl UqdYD<BR>mMBUG PNyIV ohezI tEuWG ukVpV<BR>NvVGz RSrBD RIUPb vHvvT RShwS<BR>ETNsI srrwB KuUjl IKuGm BzhlH<BR>XmYJF hCZFT EQDtq MZEiJ XvAjb<BR>pA
<dt>Bob<dd> SkPic KVbSt sDOuK iNgow JQUZU<BR>NtXwh ZWTMy PDORL MnflO phjJj<BR>wOYeT oSLxX OdmaD ifyYT bBJnq<BR>ANdPs RFkRK ALTKw rzZRp xZwrh<BR>IeFSH vyObC XqQea RfoJG MifYf<BR>dZZXC eCPCL UMnjE DnTLD lmSEl<BR>McIhw TCQVt XqhNd xGIGo mMsEQ<BR>mGdwT isjMX fxGQt aqKBz MVJTF<BR>PkbbQ hmhIk yZxPK bThti JdZMK<BR>kwYaA VIPJx WBHAK PynLx SrGIe<BR>dgAjS nFuge CAerC jwmOv YVALv<BR>UAHND PafSD dXoTt eNwkg DNvbD<BR>JDjiA cjTbn aaUvk DRcSW JvwKb<BR>AEfGe bSscp HDnsV GztnS zwpIM<BR>szCKv GkwOJ CLzYS LSBYr lBles<BR>LLupN twhcC khkWh MQISc HYNLK<BR>DdiOQ pUuUg vFxqy OJmaF KYzkM<BR>ifvBL lLOww bgWKs ZrbzJ HFGMb<BR>rxSdZ QEpjO yAXjP ytVcr kxFcq<BR>VyfkY VYfFf paTEy NlNGx SHiiB<BR>GXBaF qzsZv FEHPx VQMBS HiXGV<BR>skOSj hIfIX nZduH khyGT xyKdY<BR>ny </dl>

1
puzzles/crypto/120/key Normal file
View File

@ -0,0 +1 @@
Rat Fink

View File

@ -0,0 +1,45 @@
#!/usr/bin/python3
"""This is non-obvious, so let me elaborate. The message is translated to
binary with one character per binary bit. Lower case characters are 1's,
and upper case is 0. The letters are chosen at random. Tricky, eh?"""
import random
import crypto
lower = b'abcdefghijklmnopqrstuvwxyz'
upper = lower.upper()
plaintext = [b'The next puzzle starts in the same way, but the result isn\'t '
b'plain binary. Think OSI layer 1.',
b'Yep, we\'re still dealing with just encodings, not encryption.'
b'Rat Fink']
def encode(text):
out = bytearray()
mask = 0x80
for t in text:
for i in range(8):
if t & mask:
out.append(random.choice(lower))
else:
out.append(random.choice(upper))
t = t << 1
return bytes(out)
def decode(text):
out = bytearray()
i = 0
while i < len(text):
c = 0
mask = 0x80
for j in range(8):
if text[i] in lower:
c = c + mask
mask = mask >> 1
i = i + 1
out.append(c)
return bytes(out)
print('<p>The 5 byte groupings are just a standard way of displaying cypher text. It has no bearing on the solution to the puzzle. This format will be used to display the cyphertext from now on.</p>')
crypto.mkIndex(encode, decode, plaintext[0], plaintext[1], crypto.groups)

View File

@ -0,0 +1,2 @@
<dl><dt>Alice<dd> OsaZO qrVCc bBEwT uVaeD cUIgs<BR>XBoLu SiOcy GqSMq LuoKN xjYUn<BR>MepZO lOeYl PqOiU ycJqJ RxnOo<BR>DsXEj CehRl WXsXw mKCka ILpaZ<BR>qWkCf MEyTt CyJtf AsYaD TvyYB<BR>xKgGg ZjrNS qQdRm McJzG uiEuE<BR>DxsLt VvXkY IpcVy FGnfM iAdOL<BR>kTybM kCJaV rlDRw tQBpZ aoTNj<BR>RrSbB nXzSc fBcIE qcFOw AjeBO<BR>ncVnJ nLXyV nuToR YuWdt FFlQq<BR>SxWeW wKpqA bQBok KNkQg qIIdf<BR>JfQWc sQrFd JDzUo ErzCA wGoJt<BR>SzEyT zeIUw TkzQa DNzdN FsrIo<BR>FGllP kOrPn SFjwI uYcNJ yUldB<BR>SmUez OaYzO EpIkc ZGuGl dRvRC<BR>dcEYq FtoZD bjWpS yMXvR jvSvC<BR>PgAnx KOqUe GdUdM xNotY YuYxN<BR>nYmaD nZBgu MzCUo lMcVk ZjCWt<BR>jPaCF kRwgZ SnDuX rbZzK NcIvr<BR>FIwrR AtYaf MLsiX zWiVM rQlJh<BR>oGIxC sEjJc KfSsN dfBFr LfOiM<BR>eBkKe pUBsd CMzoY DltIW knBtT<BR>UbnJl DrPYs QlyJx VJvfO cBNlL<BR>zHqnP wZUob QQcWm tUVff PcUPb<BR>gUGwp PmYRe dCcDO oWfmF GhmXU<BR>iEjhR OoRcT rKhNu CovZo IeFVm<BR>kDIdO jDsjZ wVIye DFxOi UfVfw<BR>GgHCi MorGD hgDNp VrvPE lFwJy<BR>KdBrE qtMuX rPIqK zEuQh VivSb<BR>YrYKg NzgVN eKskT yJEuQ vbPZr<BR>cXLzm HlKsI YrhDl USeRf sEgDS<BR>kbBHr QdxXY tnIjT UffSu WgOlL<BR>KzuHh ZvPIw mEKte EUesW bIxUL<BR>qHxwX iNEyM llYGz GhGoL jLjGk<BR>hXpCr NNwoC XcYzS btNeL iUXyd<BR>GmYoS XrsSu FEvkD dGzDq RUkMq<BR>xPUpm JpJKi JxLfI lfEKz BpSvD<BR>yBeCo jBoPf LMjhP ZaIrZ dyQxJ<BR>YgcPP gYnMs DoqVm UBcEl dDXha<BR>GEarV zQvAd JJvXq vUMnH xbOUh<BR>UeJcF iHcCi vJpWa MGipL rRoTI<BR>mwUpJ UfdCA jEafV FlkJu VIypK<BR>cUDhI gXkbC yDAzf JuMRm CzRfR<BR>llBCk VuSiU iGvPj iXwKQ qSiMi<BR>ExjMP laRzU XlPmF dnNwW LzvGf<BR>JoTPe kIAiP sOalO pYeXY srYEx<BR>qNFlb FbUVn IjVzV qzTLl lUxIV<BR>iiBeP QfRfX juMfH UtrMn OWaXe<BR>NvkNi BlUyJ MeBjj OVeJt bBXaP<BR>gSeUl EiPsn HoSBe iCeUh HCfBg<BR>yEqTH gGmqX EcmPJ bdVxC ZsKtw<BR>PLmsZ YqnSm GYhSs kEAcX yUdLu<BR>lHNjY jJlCl BlAby KwVtD GieJW<BR>bJrWl vJeFK fyHaU oLnPD pBsiC<BR>JqSqH zXhDl CunRi GTxCi mTUaZ<BR>vGxkK bIFaK oaNXo iYNxu ZtLfH<BR>BfrDF oPjQv aRoHO dJkgD AzgGQ<BR>okHdT aNJdE jnGWw RyrBo CObdY<BR>vOKvg EDjbB bDNns DBsPk dRQzn<BR>LpCOp mQpEv EDoSx nPwHG lSxnS<BR>WgwBH cZufD EoIgG xOzCv LgmYa<BR>TvXJh OtaMg TYpzQ vJVei ZjVpU<BR>aLDrs UxZCi bPyFJ qfAIz hFdCG<BR>qArpV CyrVK uDmwA ToLfE pHvYi<BR>YmsMo WjXOh XzpXe QOhwW xEOcu<BR>KjDnR fLOhx KgWaC EqPob HPgHv<BR>oCbQc PWhyK MsWkZ iJwpO JdNpS<BR>vScNw YnzUj FEmjR sEeYq PBnsL<BR>sSFvK kkQxY UvVmV dgKNb CxEpV<BR>fXpZz kFiRM kqZMp tQcWE ijCxD<BR>WaNgv YDhtI QtvAx YjHxJ HnPtp<BR>CUrGn vTAgo IvQrI Is
<dt>Bob<dd> DcfTV cQrxZ bTBnq HCjwG kNIie<BR>CoAyJ gRUqp PrCoZ IvImh WXiUy<BR>jRkJf FZlSa vNTxY yoCxI ShiID<BR>dXntL XyjPz JkOSm ZomOu UXqWz<BR>aYUvJ yVhEd DqPmg YbBPc SdUrt<BR>OaPTt bAoKZ eoChN iQzRL imGmV<BR>JjMhz EJeUp WpvNe LCgQv qKQco<BR>FMtLs zDGfE lXuKi QqWaw UtMjM<BR>GrjGg JrVVy fYpLC dyWNa SkuGW<BR>qoPtP jISrp SVnVg PrsVx FCdtW<BR>GpOzI gVeCg cEAyL wXtGi QjBce<BR>PyQCs YoVhC hjJPf VklPI lPoUm<BR>KfJdN veXvG ReoWE wiJsM TblSc<BR>QLeKr bXAvv GVwvS rGlUm UAnQa<BR>dBDkO lcBzT qDvGj MfZRw JsfML<BR>eHcTi OpEtP uKbvS CbAhW pMmBv<BR>YvzJX ceEFy yTBcB oIzjG gRTmc<BR>FDcAa YoHlg AoHHo CtDuC wmQTm<BR>mGmKb CCxiY QeIkO yTknZ XcHmZ<BR>jGqOo VkpAs WwFGs RlbYw QXluN<BR>sUUuq KoTdF nFEsc WtPfW UesOP<BR>jbYNc rBjMZ ajHjV zSPyF gyIcV<BR>CzRju MPhGc XipXc HcXOh MfrEz<BR>IFbTp yVXsQ wKyVw LbQzo ZkVDq<BR>SqBbq JWlUu kHmGL leKXy VpqEK<BR>mrLoU rLgTY pqBDj EhuCe OLiPo<BR>DvWrl PMjvM uBbTQ rOulG AfWyt<BR>FjQcL GuSkj EHkXl iGoXI sKobC<BR>TbdDG uViyC JvbRj XyFTy VlJyw<BR>KGoPq MdOjV fVxnS xNpGR vVyIc<BR>VnLpb RaFrU PeIgv YRcCb nAmYG<BR>soXgJ lCzFK rsZpJ KuNpZ npPOo<BR>CmmYy DXbMp VtQzb NFyiK uCLfU<BR>lUxpK MnHbq GsDPq gYpUM fGtHm<BR>mGxFQ bBfiA NlhXI wDyrH XfZnM<BR>xUzCe SryJj UExJm NssDa PObqH<BR>dOEmV vYzHg aNMbw IvVgK PbjQX<BR>kcWKv yGtPs VFkSb fYhAY ssYuY<BR>AtBgs FKdbO </dl>

1
puzzles/crypto/130/key Normal file
View File

@ -0,0 +1 @@
probable cause

View File

@ -0,0 +1,56 @@
#!/usr/bin/python3
'''This is the same as the previous, but it uses non-return to zero to encode
the binary.'''
import random
import crypto
lower = b'abcdefghijklmnopqrstuvwxyz'
upper = lower.upper()
alice = b'The next one is in Morris Code. Unlike the previous two, '\
b'they will actually need to determine some sort of key.'
bob = b'Morris code with a key? That sounds bizarre. probable cause'
def encode(text):
out = bytearray()
mask = 0x80
state = 0
for t in text:
for i in range(8):
next = t & mask
if not state and not next:
out.append(random.choice(upper))
out.append(random.choice(lower))
elif not state and next:
out.append(random.choice(lower))
out.append(random.choice(upper))
elif state and not next:
out.append(random.choice(upper))
out.append(random.choice(lower))
elif state and next:
out.append(random.choice(lower))
out.append(random.choice(upper))
state = next
t = t << 1
return bytes(out)
def decode(text):
out = bytearray()
i = 0
while i < len(text):
c = 0
mask = 0x80
for j in range(8):
a = 0 if text[i] in lower else 1
b = 0 if text[i+1] in lower else 1
assert a != b, 'bad encoding'
if b:
c = c + mask
mask = mask >> 1
i = i + 2
out.append(c)
return bytes(out)
crypto.mkIndex(encode, decode, alice, bob, crypto.groups)

Some files were not shown because too many files have changed in this diff Show More