Merge branch 'master' of cfl.lanl.gov:/var/projects/gctf

This commit is contained in:
Alexander Brugh 2009-10-08 11:28:12 -06:00
commit b0815fdb9f
60 changed files with 820 additions and 239 deletions

2
.gitignore vendored
View File

@ -2,7 +2,9 @@
*.pyc *.pyc
*.dat *.dat
*.swp *.swp
*.tce
passwd passwd
fake
target/ target/
puzzler/ puzzler/
ctf.tce ctf.tce

View File

@ -17,6 +17,9 @@ all: ctf.tce
target: $(PYC) target: $(PYC)
$(INSTALL) -d --mode=0755 --owner=100 $(DESTDIR)/var/lib/ctf $(INSTALL) -d --mode=0755 --owner=100 $(DESTDIR)/var/lib/ctf
$(INSTALL) -d --mode=0755 --owner=100 $(DESTDIR)/var/lib/ctf/survey
$(INSTALL) -d $(DESTDIR)/var/lib/ctf/disabled $(INSTALL) -d $(DESTDIR)/var/lib/ctf/disabled
touch $(DESTDIR)/var/lib/ctf/disabled/survey touch $(DESTDIR)/var/lib/ctf/disabled/survey
@ -25,6 +28,7 @@ target: $(PYC)
$(INSTALL) -d $(DESTDIR)/usr/sbin $(INSTALL) -d $(DESTDIR)/usr/sbin
$(INSTALL) ctfd.py $(DESTDIR)/usr/sbin $(INSTALL) ctfd.py $(DESTDIR)/usr/sbin
$(INSTALL) new-contest $(DESTDIR)/usr/sbin
$(INSTALL) -d $(WWWDIR) $(INSTALL) -d $(WWWDIR)
$(INSTALL) index.html intro.html ctf.css grunge.png $(WWWDIR) $(INSTALL) index.html intro.html ctf.css grunge.png $(WWWDIR)

View File

@ -40,6 +40,7 @@ class Gyopi(irc.Bot):
self._lvl = 0 self._lvl = 0
self._flag.set_flag( self.FLAG_DEFAULT ) self._flag.set_flag( self.FLAG_DEFAULT )
self._tokens = []
self._lastAttempt = {} self._lastAttempt = {}
self._affiliations = {} self._affiliations = {}
self._newPuzzle() self._newPuzzle()
@ -62,7 +63,7 @@ class Gyopi(irc.Bot):
self.last_tb = '%s %s %s' % (t, v, infostr) self.last_tb = '%s %s %s' % (t, v, infostr)
print(self.last_tb) print(self.last_tb)
def cmd_join(self, sender, forum, addl): def cmd_JOIN(self, sender, forum, addl):
"""On join, announce who has the flag.""" """On join, announce who has the flag."""
if sender.name() in self.nicks: if sender.name() in self.nicks:
self._tellFlag(forum) self._tellFlag(forum)
@ -150,11 +151,11 @@ class Gyopi(irc.Bot):
self._tokens[user].remove(token) self._tokens[user].remove(token)
def cmd_privmsg(self, sender, forum, addl): def cmd_PRIVMSG(self, sender, forum, addl):
text = addl[0] text = addl[0]
who = sender.name() who = sender.name()
if text.startswith('!'): if text.startswith('!'):
parts = text[1:].lower().split(' ', 1) parts = text[1:].split(' ', 1)
cmd = parts[0] cmd = parts[0]
if len(parts) > 1: if len(parts) > 1:
args = parts[1] args = parts[1]
@ -179,6 +180,7 @@ class Gyopi(irc.Bot):
elif cmd.startswith('h'): elif cmd.startswith('h'):
# Help # Help
forum.msg('Goal: Help me with my math homework, FROM ANOTHER DIMENSION!') forum.msg('Goal: Help me with my math homework, FROM ANOTHER DIMENSION!')
forum.msg('Order of operations is always left to right.')
#forum.msg('Goal: The current winner gets to control the contest music.') #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') forum.msg('Commands: !help, !flag, !register [TEAM], !solve SOLUTION,!? EQUATION, !ops, !problem, !who')
elif cmd.startswith('prob'): elif cmd.startswith('prob'):
@ -208,11 +210,15 @@ class Gyopi(irc.Bot):
# self._giveToken(who, sender) # self._giveToken(who, sender)
self._saveState() self._saveState()
else: else:
forum.msg('%s: %s != %s' % (who, attempt, answer))
forum.msg('%s: That is not correct.' % who) forum.msg('%s: That is not correct.' % who)
# Test a simple one op command. # Test a simple one op command.
elif cmd.startswith('?'): elif cmd.startswith('?'):
if not args:
forum.msg('%s: Give me an easier problem, and I\'ll '
'give you the answer.' % who)
return
try: try:
tokens = badmath.parse(''.join(args)) tokens = badmath.parse(''.join(args))
except (ValueError) as msg: except (ValueError) as msg:
@ -253,7 +259,7 @@ if __name__ == '__main__':
help='Flag server password') help='Flag server password')
p.add_option('-d', '--path', dest='path', default='/var/lib/badmath', p.add_option('-d', '--path', dest='path', default='/var/lib/badmath',
help='Path to where we can store state info.') help='Path to where we can store state info.')
p.add_option('-c', '--channel', dest='channel', default='+badmath', p.add_option('-c', '--channel', dest='channel', default='#badmath',
help='Which channel to join') help='Which channel to join')
opts, args = p.parse_args() opts, args = p.parse_args()

Binary file not shown.

View File

@ -10,5 +10,6 @@ dev=16,ino=87557238,mode=40755,uid=0,gid=0,nlink=2,rdev=0
dev=16,ino=87557239,mode=40755,uid=0,gid=0,nlink=2,rdev=0 dev=16,ino=87557239,mode=40755,uid=0,gid=0,nlink=2,rdev=0
dev=16,ino=87557240,mode=40755,uid=0,gid=0,nlink=2,rdev=0 dev=16,ino=87557240,mode=40755,uid=0,gid=0,nlink=2,rdev=0
dev=16,ino=87573208,mode=100755,uid=0,gid=0,nlink=1,rdev=0 dev=16,ino=87573208,mode=100755,uid=0,gid=0,nlink=1,rdev=0
dev=16,ino=87573213,mode=100755,uid=0,gid=0,nlink=1,rdev=0
dev=16,ino=87573285,mode=100755,uid=0,gid=0,nlink=1,rdev=0
dev=16,ino=87573433,mode=100755,uid=0,gid=0,nlink=1,rdev=0 dev=16,ino=87573433,mode=100755,uid=0,gid=0,nlink=1,rdev=0
dev=16,ino=87573456,mode=100755,uid=0,gid=0,nlink=1,rdev=0

View File

@ -5,4 +5,4 @@
DATA_PATH=/var/lib/badmath DATA_PATH=/var/lib/badmath
mkdir -p $DATA_PATH mkdir -p $DATA_PATH
exec envuidgid ctf python3.0 usr/lib/ctf/badmath/Gyopi.py --data=$DATA_PATH exec envuidgid ctf python3 /usr/lib/ctf/badmath/Gyopi.py --path=$DATA_PATH

16
ctf.css
View File

@ -77,3 +77,19 @@ p {
margin-bottom: 20px; margin-bottom: 20px;
color: #f4f4f4; color: #f4f4f4;
} }
.solved {
text-decoration: line-through;
}
table.pollster {
margin-left: 5em;
}
table.pollster td {
padding: 2px 1em 2px 5px;
}
table.pollster thead {
font-weight: bold;
}

View File

@ -37,6 +37,10 @@ else:
'house_team': 'dirtbags', 'house_team': 'dirtbags',
'passwd': '/var/lib/ctf/passwd', 'passwd': '/var/lib/ctf/passwd',
'team_colors': team_colors, 'team_colors': team_colors,
'poll_interval': 60,
'poll_timeout': 0.5,
'heartbeat_dir': '/var/lib/pollster',
'poll_dir': '/var/lib/www',
}, },
'puzzler': 'puzzler':
{'dir': '/usr/lib/www/puzzler', {'dir': '/usr/lib/www/puzzler',
@ -66,3 +70,23 @@ def datafile(filename):
def url(path): def url(path):
return base_url + path return base_url + path
def start_html(title):
if os.environ.get('GATEWAY_INTERFACE'):
print('Content-type: text/html')
print()
print('''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>%s</title>
<link rel="stylesheet" href="%s" type="text/css" />
</head>
<body>
<h1>%s</h1>
''' % (title, css, title))
def end_html():
print('</body></html>')

View File

@ -142,6 +142,8 @@ class FlagServer(asynchat.async_chat):
self.inbuf.append(data) self.inbuf.append(data)
def set_flag(self, team): def set_flag(self, team):
if not self.cat:
return
self.flag = team self.flag = team
self.submitter.set_flag(self.cat, team) self.submitter.set_flag(self.cat, team)
f = open(os.path.join(flags_dir, self.cat), 'w') f = open(os.path.join(flags_dir, self.cat), 'w')

View File

@ -27,7 +27,7 @@ cat_re = re.compile(r'^[a-z]+$')
points_re = re.compile(r'^[0-9]+$') points_re = re.compile(r'^[0-9]+$')
def dbg(*vals): def dbg(*vals):
print('<--: \nContent-type: text/html\n\n--><pre>') print('<!--: \nContent-type: text/html\n\n--><pre>')
print(*vals) print(*vals)
print('</pre>') print('</pre>')
@ -59,8 +59,6 @@ passwd = f.getfirst('w', passwd)
key = f.getfirst('k') key = f.getfirst('k')
def start_html(title): def start_html(title):
if os.environ.get('GATEWAY_INTERFACE'):
print('Content-type: text/html')
if team or passwd: if team or passwd:
c = http.cookies.SimpleCookie() c = http.cookies.SimpleCookie()
if team: if team:
@ -68,23 +66,9 @@ def start_html(title):
if passwd: if passwd:
c['passwd'] = passwd c['passwd'] = passwd
print(c) print(c)
print() config.start_html(title)
print('''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>%s</title>
<link rel="stylesheet" href="%s" type="text/css" />
</head>
<body>
<h1>%s</h1>
''' % (title, config.css, title))
def end_html():
print('</body></html>')
end_html = config.end_html
def safe_join(*args): def safe_join(*args):
safe = list(args[:1]) safe = list(args[:1])
@ -125,7 +109,17 @@ def show_puzzles(cat, cat_dir):
if puzzles: if puzzles:
print('<ul>') print('<ul>')
for p in puzzles: for p in puzzles:
print('<li><a href="%s/%s/%d">%d</a></li>' % (base_url, cat, p, p)) cls = ''
try:
if p in points_by_team[(team, cat)]:
cls = 'solved'
except KeyError:
pass
print('<li><a href="%(base)s/%(cat)s/%(points)d" class="%(class)s">%(points)d</a></li>' %
{'base': base_url,
'cat': cat,
'points': p,
'class': cls})
if p > opened: if p > opened:
break break
print('</ul>') print('</ul>')

View File

@ -7,6 +7,22 @@ import string
from . import teams from . import teams
from . import config from . import config
def head(title):
return '''<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Team Registration</title>
<link rel="stylesheet" href="%s" type="text/css" />
</head>
<body>
<h1>%s</h1>
''' % (config.css, title)
def foot():
return '''</body></html>'''
def main(): def main():
print('Content-type: text/html') print('Content-type: text/html')
print() print()
@ -17,17 +33,8 @@ def main():
pw = f.getfirst('pw') pw = f.getfirst('pw')
confirm_pw = f.getfirst('confirm_pw') confirm_pw = f.getfirst('confirm_pw')
html = string.Template('''<?xml version="1.0" encoding="UTF-8" ?> html = string.Template(config.start_html('Team Registration') +
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ('''
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Team Registration</title>
<link rel="stylesheet" href="%s" type="text/css" />
</head>
<body>
<h1>Team Registration</h1>
<p> <p>
Pick a short team name: you'll be typing it a lot. Pick a short team name: you'll be typing it a lot.
</p> </p>
@ -50,10 +57,8 @@ def main():
<input type="submit" value="Register" /> <input type="submit" value="Register" />
</fieldset> </fieldset>
</form> </form>''' % config.url('register.cgi')) +
</body> config.end_html())
</html>
''' % (config.css, config.url('register.cgi')))
if not (team and pw and confirm_pw): # If we're starting from the beginning? if not (team and pw and confirm_pw): # If we're starting from the beginning?
html = html.substitute(team_error='', html = html.substitute(team_error='',
@ -66,7 +71,9 @@ def main():
pw_match_error='Passwords do not match') pw_match_error='Passwords do not match')
else: else:
teams.add(team, pw) teams.add(team, pw)
html = 'Team registered.' html = (config.start_html('Team registered') +
('<p>Congratulations, <samp>%s</samp> is now registered. Go <a href="%s">back to the front page</a> and start playing!</p>' % (team, config.url(''))) +
config.end_html())
print(html) print(html)

View File

@ -28,9 +28,7 @@ def build_teams():
f = open(passwdfn) f = open(passwdfn)
for line in f: for line in f:
line = line.strip() line = line.strip()
team, passwd = [unquote(v) for v in line.strip().split('\t')] team, passwd, color = map(unquote, line.strip().split('\t'))
color = team_colors.pop(0)
team_colors.append(color)
teams[team] = (passwd, color) teams[team] = (passwd, color)
except IOError: except IOError:
pass pass
@ -53,10 +51,15 @@ def exists(team):
return team in teams return team in teams
def add(team, passwd): 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') f = open(passwdfn, 'a')
fcntl.lockf(f, fcntl.LOCK_EX) fcntl.lockf(f, fcntl.LOCK_EX)
f.seek(0, 2) f.seek(0, 2)
f.write('%s\t%s\n' % (quote(team), quote(passwd))) f.write('%s\t%s\t%s\n' % (quote(team), quote(passwd), quote(color)))
def color(team): def color(team):
t = teams.get(team) t = teams.get(team)
@ -66,6 +69,3 @@ def color(team):
if not t: if not t:
return '888888' return '888888'
return t[1] return t[1]

View File

@ -4,6 +4,7 @@ import asyncore
import os import os
import sys import sys
import optparse import optparse
import signal
from ctf import pointsd from ctf import pointsd
from ctf import flagd from ctf import flagd
from ctf import histogram from ctf import histogram
@ -38,6 +39,7 @@ def main():
pointsrv = pointsd.start() pointsrv = pointsd.start()
flagsrv = flagd.start() flagsrv = flagd.start()
signal.signal(signal.SIGCHLD, sigchld)
s = pointsrv.store s = pointsrv.store
slen = 0 slen = 0
while True: while True:

View File

@ -10,7 +10,7 @@
<h1>Welcome</h1> <h1>Welcome</h1>
<ol> <ol>
<li><a href="intro.html">Read the introduction</a> to this event</li> <li><a href="intro.html">Read the introduction and rules</a></li>
<li><a href="register.cgi">Register</a> your team</li> <li><a href="register.cgi">Register</a> your team</li>
<li><a href="scoreboard.cgi">View the score board</a></li> <li><a href="scoreboard.cgi">View the score board</a></li>
</ol> </ol>

18
new-contest Executable file
View File

@ -0,0 +1,18 @@
#! /bin/sh -e
ctime () {
stat -c %z $1 | awk '{ print $1; }'
}
rotate () {
mv $1 $1.$(ctime $1)
}
rotate /var/lib/ctf/puzzler.dat
rotate /var/lib/ctf/scores.dat
rotate /var/lib/ctf/passwd
rm -f /var/lib/ctf/flags/* || true
echo "Things you may want to tweak:"
find /var/lib/ctf/disabled
find /var/lib/kevin/tokens

8
pollster/in.heartbeatd Executable file
View File

@ -0,0 +1,8 @@
#! /bin/sh
case "$REMOTEADDR" in
10.0.0.[2-254])
touch /var/lib/pollster/$REMOTEADDR
;;
esac

249
pollster/pollster.py Executable file
View File

@ -0,0 +1,249 @@
#!/usr/bin/env python3
import os
import re
import sys
import time
import socket
import traceback
import threading
import queue
from ctf import config
from ctf import pointscli
DEBUG = False
POLL_INTERVAL = config.get('poll_interval')
IP_DIR = config.get('heartbeat_dir')
REPORT_PATH = config.get('poll_dir')
SOCK_TIMEOUT = config.get('poll_timeout')
class PointSubmitter(threading.Thread):
''' Pulls point allocations from the queue and submits them. '''
def __init__(self, point_queue):
threading.Thread.__init__(self)
self.point_queue = point_queue
self.sock = pointscli.makesock('localhost')
def run(self):
# loop forever
while(True):
cat, team, score = self.point_queue.get()
if None in [cat, team, score]:
continue
try:
pointscli.submit(cat, team, score, sock=self.sock)
except ValueError:
print('pollster: error submitting score (%s, %s, %d)' % (cat, team, score))
traceback.print_exc()
def socket_poll(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 (default to once). '''
# create a socket
try:
sock = socket.socket(socket.AF_INET, prot)
except Exception as e:
print('pollster: create socket failed (%s)' % e)
traceback.print_exc()
return None
sock.settimeout(SOCK_TIMEOUT)
# connect
try:
sock.connect((ip, port))
except socket.timeout as e:
print('pollster: attempt to connect to %s:%d timed out (%s)' % (ip, port, e))
traceback.print_exc()
return None
except Exception as 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:
# first read
data = sock.recv(1024)
resp += data.decode('utf-8')
max_recv -= 1
# remaining reads as necessary until timeout or socket closes
while(len(data) > 0 and max_recv > 0):
data = sock.recv(1024)
resp += data.decode('utf-8')
max_recv -= 1
sock.close()
except socket.timeout as e:
print('pollster: timed out waiting for a response from %s:%d (%s)' % (ip, port, e))
traceback.print_exc()
except Exception as e:
print('pollster: receive from %s:%d failed (%s)' % (ip, port, e))
traceback.print_exc()
if len(resp) == 0:
return None
return 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(ip):
''' Poll the fingerd service. Returns None or a team name. '''
resp = socket_poll(ip, 79, b'flag\n', socket.SOCK_STREAM)
if resp is None:
return None
return resp.strip('\r\n')
def poll_noted(ip):
''' Poll the noted service. Returns None or a team name. '''
resp = socket_poll(ip, 4000, b'rflag\n', socket.SOCK_STREAM)
if resp is None:
return None
return resp.strip('\r\n')
def poll_catcgi(ip):
''' Poll the cat.cgi web service. Returns None or a team name. '''
request = bytes('GET /cat.cgi/flag HTTP/1.1\r\nHost: %s\r\n\r\n' % ip, 'ascii')
resp = socket_poll(ip, 80, request, socket.SOCK_STREAM, 3)
if resp is None:
return None
content = resp.split('\r\n\r\n')
if len(content) < 3:
return None
content = content[1].split('\r\n')
try:
content_len = int(content[0])
except Exception as e:
return None
if content_len <= 0:
return None
return content[1].strip('\r\n')
def poll_tftpd(ip):
''' Poll the tftp service. Returns None or a team name. '''
resp = socket_poll(ip, 69, b'\x00\x01' + b'flag' + b'\x00' + b'octet' + b'\x00', socket.SOCK_DGRAM)
if resp is None:
return None
if len(resp) <= 5:
return None
resp = resp.split('\n')[0]
return resp[4:].strip('\r\n')
# PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED
POLLS = {
'fingerd' : poll_fingerd,
'noted' : poll_noted,
'catcgi' : poll_catcgi,
'tftpd' : poll_tftpd,
}
ip_re = re.compile('(\d{1,3}\.){3}\d{1,3}')
# start point submitter thread
point_queue = queue.Queue()
t = PointSubmitter(point_queue)
t.start()
# loop forever
while True:
t_start = time.time()
# gather the list of IPs to poll
try:
ips = os.listdir(IP_DIR)
except Exception as e:
print('pollster: could not list dir %s (%s)' % (IP_DIR, e))
traceback.print_exc()
try:
os.remove(REPORT_PATH)
except Exception as e:
pass
try:
out = open(REPORT_PATH, 'w')
except Exception as e:
out = None
pass
if out is not None:
out.write('<html>\n<head>\n')
out.write('<title>Pollster Results</title>\n')
out.write('<link rel="stylesheet" href="ctf.css" type="text/css" media="all" />\n')
out.write('</head><body>\n<h1>Polling Results</h1>\n')
for ip in ips:
# check file name format is ip
if ip_re.match(ip) is None:
continue
# remove the file
try:
os.remove(os.path.join(IP_DIR, ip))
except Exception as e:
print('pollster: could not remove %s' % os.path.join(IP_DIR, ip))
traceback.print_exc()
results = {}
if DEBUG is True:
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():
team = func(ip)
if team is None:
team = 'dirtbags'
if DEBUG is True:
print('\t%s - %s' % (service, team))
if out is not None:
out.write('<tr><td>%s</td><td>%s</td>\n' % (service, team))
point_queue.put((service, team, 1))
if out is not None:
out.write('</table>\n')
if DEBUG is True:
print('+-----------------------------------------+')
t_end = time.time()
exec_time = int(t_end - t_start)
sleep_time = POLL_INTERVAL - exec_time
if out is not None:
out.write('<p><b>Next poll in: %ds</b></p>\n' % sleep_time)
out.write('</body>\n</html>\n')
out.close()
# sleep until its time to poll again
time.sleep(sleep_time)

3
pollster/run.heartbeatd Executable file
View File

@ -0,0 +1,3 @@
#! /bin/sh
exec udpsvd 0 9 /usr/sbin/in.heartbeatd

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

View File

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

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/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/50/key Normal file
View File

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

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

@ -1,6 +1,8 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
import cgi import cgi
import time
import os
f = cgi.FieldStorage() f = cgi.FieldStorage()
if f.getfirst('submit'): if f.getfirst('submit'):
@ -8,6 +10,13 @@ if f.getfirst('submit'):
print() print()
print('Thanks for filling in the survey.') print('Thanks for filling in the survey.')
print() print()
try:
fn = '/var/lib/ctf/survey/%s.%d.%d.txt' % (time.strftime('%Y-%m-%d'), time.time(), os.getpid())
o = open(fn, 'w')
for k in f.keys():
o.write('%s: %r\n' % (k, f.getlist(k)))
except IOError:
pass
print('The key is:') print('The key is:')
print(' quux blorb frotz') print(' quux blorb frotz')
else: else:

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Survey</title>
<link rel="stylesheet" href="%s" type="text/css" />
</head>
<body>
<form method="post" action=",submit.cgi">
<ul>
<li>
Did you have any trouble figuring out how to play?
<select name="getting-started">
<option>A lot of trouble</option>
<option selected="selected">Not much trouble</option>
<option>No trouble</option>
</select>
</li>
<li>
How difficult were the puzzles?
<select name="puzzle-strength">
<option>Too hard</option>
<option selected="selected">About right</option>
<option>Too easy</option>
</select>
</li>
</ul>
<p>
Please use the provided space for any additional comments.
</p>
<textarea name="comments" style="height: 5em; width: 40em;"></textarea>
<p>
Thanks for your feedback! We hope you had fun and learned
something!
</p>
<input type="submit" name="submit" value="Submit survey" />
</form>
</body>
</html>

View File

@ -4,38 +4,7 @@
recieve a key redeemable for <b>ONE MILLION POINTS</b>. recieve a key redeemable for <b>ONE MILLION POINTS</b>.
</p> </p>
<fieldset> <object data=",survey.html" type="text/html"
<legend>Survey</legend> style="width: 100%; height: 40em;">
<a href=",survey.html">Survey</a>
<form method="post" action="submit.cgi"> </object>
<ul>
<li>
Did you have any trouble figuring out how to play?
<select name="getting-started">
<option>A lot of trouble</option>
<option selected="selected">Not much trouble</option>
<option>No trouble</option>
</select>
</li>
<li>
How difficult were the puzzles?
<select name="puzzle-strength">
<option>Too hard</option>
<option selected="selected">About right</option>
<option>Too easy</option>
</select>
</li>
</ul>
<p>
Please use the provided space for any additional comments.
</p>
<textarea name="comments" style="height: 5em; width: 40em;"></textarea>
<p>
Thanks for your feedback! We hope you had fun and learned
something!
</p>
<input type="submit" name="submit" value="Submit survey" />
</form>
</fieldset>

View File

@ -5,9 +5,9 @@ TARGET = $(CURDIR)/target
FAKE = fakeroot -s $(CURDIR)/fake -i $(CURDIR)/fake FAKE = fakeroot -s $(CURDIR)/fake -i $(CURDIR)/fake
INSTALL = $(FAKE) install INSTALL = $(FAKE) install
all: 99-pwnables.tce all: pwnables.tce
99-pwnables.tce: target pwnables.tce: target
$(FAKE) sh -c 'cd target && tar -czf - --exclude=placeholder --exclude=*~ .' > $@ $(FAKE) sh -c 'cd target && tar -czf - --exclude=placeholder --exclude=*~ .' > $@
target: target:
@ -17,13 +17,5 @@ target:
$(MAKE) -C daemons TARGET=$(TARGET) install $(MAKE) -C daemons TARGET=$(TARGET) install
$(INSTALL) -d $(TARGET)/usr/lib/www
$(INSTALL) $(CGI) $(TARGET)/usr/lib/www
$(INSTALL) -D flag $(TARGET)/var/lib/tftp/flag
$(INSTALL) -D flag $(TARGET)/var/lib/notes/flag
$(INSTALL) -D flag $(TARGET)/home/flag/.plan
clean: clean:
rm -rf target rm -rf target pwnables.tce

View File

@ -1 +0,0 @@
all: in.fingerd

View File

@ -1,38 +0,0 @@
#include <stdio.h>
int
main(int argc, char *argv)
{
char user[256];
char path[512];
char *data;
FILE *f;
size_t count;
int i;
if (NULL == gets(user)) {
return 0;
}
for (data = user; *data; data += 1) {
if ('\r' == *data) {
*data = 0;
}
}
if (0 == user[0]) {
printf("Nobody's home.\n");
return 0;
}
sprintf(path, "/home/%s/.plan", user);
f = fopen(path, "r");
if (NULL == f) {
printf("No such user.\n");
return 0;
}
data = path;
while (count = fread(data, sizeof(*data), 1, f)) {
fwrite(data, count, 1, stdout);
}
return 0;
}

View File

@ -1,3 +0,0 @@
#! /bin/sh
exec tcpsvd 0 79 /usr/sbin/in.fingerd

View File

@ -0,0 +1 @@
dirtbags

View File

@ -0,0 +1 @@
/var/lib/cat/flag

View File

@ -0,0 +1 @@
dirtbags

View File

@ -0,0 +1 @@
dirtbags

View File

@ -0,0 +1 @@
dirtbags

View File

@ -0,0 +1,9 @@
#! /bin/sh
# Busybox netcat doesn't support UDP unless you compile in desktop mode.
# No problem, traceroute can send a UDP packet too.
while true; do
# Apparently traceroute adds 1 to the base port (-p)
traceroute -m 2 -q 1 -p 8 10.0.0.1 2>/dev/null >/dev/null
sleep 10
done

View File

@ -1,3 +0,0 @@
#! /bin/sh
exec udpsvd 0 69 tftpd /var/lib/tftp

3
run.log.ctfd Executable file
View File

@ -0,0 +1,3 @@
#! /bin/sh
exec logger -t ctfd

View File

@ -3,12 +3,16 @@ INSTALL = $(FAKE) install -o 100
all: tanks.tce all: tanks.tce
push: tanks.tce
netcat -l -q 0 -p 3333 < tanks.tce
tanks.tce: target tanks.tce: target
$(FAKE) sh -c 'cd target && tar -czf - .' > $@ $(FAKE) sh -c 'cd target && tar -czf - .' > $@
target: target:
$(INSTALL) -d target/var/lib/tanks/ $(INSTALL) -d target/var/lib/tanks/
$(INSTALL) -d target/var/lib/tanks/results/ $(INSTALL) -d target/var/lib/tanks/results/
$(INSTALL) -d target/var/lib/tanks/errors/
$(INSTALL) -d target/var/lib/tanks/ai/easy $(INSTALL) -d target/var/lib/tanks/ai/easy
$(INSTALL) -d target/var/lib/tanks/ai/medium $(INSTALL) -d target/var/lib/tanks/ai/medium
$(INSTALL) -d target/var/lib/tanks/ai/hard $(INSTALL) -d target/var/lib/tanks/ai/hard
@ -17,15 +21,16 @@ target:
$(INSTALL) AI/medium/* target/var/lib/tanks/ai/medium/ $(INSTALL) AI/medium/* target/var/lib/tanks/ai/medium/
$(INSTALL) AI/hard/* target/var/lib/tanks/ai/hard/ $(INSTALL) AI/hard/* target/var/lib/tanks/ai/hard/
$(INSTALL) -d target/var/lib/www/tanks/ $(INSTALL) -d target/usr/lib/www/tanks/
$(INSTALL) www/* target/var/lib/www/tanks/ $(INSTALL) www/* target/usr/lib/www/tanks/
$(FAKE) ln -s target/var/lib/tanks/ target/var/lib/www/data
ln -s /var/lib/tanks/results target/usr/lib/www/tanks/results
$(INSTALL) -d target/usr/lib/python2.6/site-packages/tanks/ $(INSTALL) -d target/usr/lib/python2.6/site-packages/tanks/
$(INSTALL) lib/* target/usr/lib/python2.6/site-packages/tanks/ $(INSTALL) lib/* target/usr/lib/python2.6/site-packages/tanks/
$(INSTALL) -d target/var/service/tanks $(INSTALL) -d target/var/service/tanks
$(INSTALL) run target/var/service/tanks/run $(INSTALL) run run_tanks.py target/var/service/tanks/
$(INSTALL) -d target/var/service/tanks/log/ $(INSTALL) -d target/var/service/tanks/log/
$(INSTALL) log.run target/var/service/tanks/log/run $(INSTALL) log.run target/var/service/tanks/log/run

View File

@ -48,7 +48,7 @@ def displacePoly(points, disp, limits, coordSequence=False):
maxX, maxY = limits maxX, maxY = limits
basePoints = [] basePoints = []
for point in points: for point in points:
x,y = point[0] + disp[0], point[1] + disp[1] x,y = int(point[0] + disp[0]), int(point[1] + disp[1])
# Check if duplication is needed on each axis # Check if duplication is needed on each axis
if x > maxX: if x > maxX:

View File

@ -9,19 +9,12 @@ from urllib import unquote, quote
from PIL import Image, ImageColor, ImageDraw from PIL import Image, ImageColor, ImageDraw
try:
from ctf import teams
except:
import sys
path = '/home/pflarr/repos/gctf/'
sys.path.append(path)
from ctf import teams
teams.build_teams()
import Tank import Tank
class Pflanzarr: class Pflanzarr:
TEAMS_FILE = '/var/lib/ctf/passwd'
FRAME_DELAY = 15 FRAME_DELAY = 15
SPACING = 150 SPACING = 150
@ -42,12 +35,14 @@ class Pflanzarr:
if not os.path.exists(self._gameDir): if not os.path.exists(self._gameDir):
os.mkdir(self._gameDir) os.mkdir(self._gameDir)
colors = self._getColors()
tmpPlayers = os.listdir(self._playerDir) tmpPlayers = os.listdir(self._playerDir)
players = [] players = []
for p in tmpPlayers: for p in tmpPlayers:
p = unquote(p) p = unquote(p)
if not (p.startswith('.') or p.endswith('#') or p.endswith('~'))\ if not (p.startswith('.') or p.endswith('#') or p.endswith('~'))\
and p in teams.teams: and p in colors:
players.append(p) players.append(p)
AIs = {} AIs = {}
@ -73,7 +68,7 @@ class Pflanzarr:
self._board = (cols*self.SPACING, rows*self.SPACING) self._board = (cols*self.SPACING, rows*self.SPACING)
while len(players) < cols*rows: while len(players) < cols*rows:
players.append('#default') players.append(None)
self._tanks = [] self._tanks = []
for i in range(cols): for i in range(cols):
@ -82,13 +77,13 @@ class Pflanzarr:
startY = j*self.SPACING + self.SPACING/2 startY = j*self.SPACING + self.SPACING/2
player = random.choice(players) player = random.choice(players)
players.remove(player) players.remove(player)
if player == '#default': if player == None:
color = '#a0a0a0' color = '#a0a0a0'
else: else:
color = '#%s' % teams.teams[player][1] color = colors[player]
tank = Tank.Tank( player, (startX, startY), color, tank = Tank.Tank( player, (startX, startY), color,
self._board, testMode=True) self._board, testMode=True)
if player == '#default': if player == None:
tank.program(random.choice(defaultAIs)) tank.program(random.choice(defaultAIs))
else: else:
tank.program(AIs[player]) tank.program(AIs[player])
@ -156,10 +151,12 @@ class Pflanzarr:
if tank in kills[tank]: if tank in kills[tank]:
kills[tank].remove(tank) kills[tank].remove(tank)
self._saveResults(kills)
for tank in self._tanks: for tank in self._tanks:
self._outputErrors(tank) self._outputErrors(tank)
self._makeMovie() self._makeMovie()
# This needs to go after _makeMovie; the web scripts look for these
# files to see if the game has completed.
self._saveResults(kills)
def _killTanks(self, tanks, reason): def _killTanks(self, tanks, reason):
for tank in tanks: for tank in tanks:
@ -204,22 +201,32 @@ class Pflanzarr:
break break
winner = random.choice(winners) winner = random.choice(winners)
html = ['<html><body>', html = ['<html>',
'<head><title>Game %d results</title>',
'<link href="/ctf.css" rel="stylesheet" type="text/css">',
'</head>',
'<body>',
'<table><tr><th>Team<th>Kills<th>Cause of Death'] '<table><tr><th>Team<th>Kills<th>Cause of Death']
for tank in tanks: for tank in tanks:
if tank is winner: if tank is winner:
rowStyle = 'style="color:red;"' rowStyle = 'style="font-weight:bold; '\
'background-color:%s"' % tank.color
else: else:
rowStyle = '' rowStyle = 'style="background-color:%s"' % tank.color
if tank.name:
name = xml.sax.saxutils.escape(tank.name)
else:
name = '#default'
html.append('<tr %s><td>%s<td>%d<td>%s' % html.append('<tr %s><td>%s<td>%d<td>%s' %
(rowStyle, (rowStyle,
xml.sax.saxutils.escape(tank.name), name,
len(kills[tank]), len(kills[tank]),
xml.sax.saxutils.escape(tank.deathReason))) xml.sax.saxutils.escape(tank.deathReason)))
html.append('</table><body></html>') html.append('</table><body></html>')
if winner.name != '#default': # Write a blank file if the winner is a default tank..
if winner.name != None:
winnerFile.write(tanks[0].name) winnerFile.write(tanks[0].name)
winnerFile.close() winnerFile.close()
@ -243,14 +250,14 @@ class Pflanzarr:
clearFrames = ['rm', '-rf', '%s' % self._imageDir] clearFrames = ['rm', '-rf', '%s' % self._imageDir]
print 'Making Movie' print 'Making Movie'
subprocess.call(movieCmd) # subprocess.call(movieCmd)
# subprocess.call(movieCmd, stderr=open('/dev/null', 'w'), subprocess.call(movieCmd, stderr=open('/dev/null', 'w'),
# stdout=open('/dev/null', 'w')) stdout=open('/dev/null', 'w'))
subprocess.call(clearFrames) subprocess.call(clearFrames)
def _outputErrors(self, tank): def _outputErrors(self, tank):
"""Output errors for each team.""" """Output errors for each team."""
if tank.name == '#default': if tank.name == None:
return return
if tank._program.errors: if tank._program.errors:
@ -379,6 +386,26 @@ class Pflanzarr:
return defaultAIs return defaultAIs
def _getColors(self):
"""Get the team colors from the passwd file. The passwd file location
is set by self.TEAMS_FILE. Returns a dictionary of players->color"""
errorColor = '#ffffff'
try:
file = open(self.TEAMS_FILE)
except:
return {}.fromkeys(players, errorColor)
colors = {}
for line in file:
try:
team, passwd, color = map(unquote, line.split('\t'))
colors[team] = '#%s' % color
except:
colors[team] = errorColor
return colors
def _getGameNum(self): def _getGameNum(self):
"""Figure out what game number this is from the past games played.""" """Figure out what game number this is from the past games played."""

View File

@ -75,7 +75,7 @@ class Tank(object):
else: else:
self._tAngle = tAngle self._tAngle = tAngle
self._color = color self.color = color
# You can't fire until fireReady is 0. # You can't fire until fireReady is 0.
self._fireReady = self.FIRE_RATE self._fireReady = self.FIRE_RATE
@ -466,7 +466,7 @@ class Tank(object):
# The base body rectangle. # The base body rectangle.
for poly in gm.displacePoly(hood, self.pos, self._limits): for poly in gm.displacePoly(hood, self.pos, self._limits):
d.polygon( poly, fill=self._color ) d.polygon( poly, fill=self.color )
# The treads # The treads
for poly in gm.displacePoly(tread1, self.pos, self._limits) + \ for poly in gm.displacePoly(tread1, self.pos, self._limits) + \
@ -475,7 +475,7 @@ class Tank(object):
# The turret circle # The turret circle
for poly in gm.displacePoly(self.body, self.pos, self._limits): for poly in gm.displacePoly(self.body, self.pos, self._limits):
d.ellipse( poly, fill=self._color, outline='black') d.ellipse( poly, fill=self.color, outline='black')
self._drawLaser(d) self._drawLaser(d)
@ -491,7 +491,7 @@ class Tank(object):
if self._fired: if self._fired:
laser = gm.rotatePoly( self.laser, self._angle + self._tAngle ) laser = gm.rotatePoly( self.laser, self._angle + self._tAngle )
for poly in gm.displacePoly(laser, self.pos, self._limits): for poly in gm.displacePoly(laser, self.pos, self._limits):
drawing.polygon(poly, fill=self._color) drawing.polygon(poly, fill=self.color)
self._fired = False self._fired = False
@ -522,7 +522,7 @@ class Tank(object):
if self._sensorState[i]: if self._sensorState[i]:
color = '#000000' color = '#000000'
else: else:
color = self._color color = self.color
r, angle, width, tAttached = self._sensors[i] r, angle, width, tAttached = self._sensors[i]
r = int(r) r = int(r)

View File

@ -2,5 +2,5 @@
[ -f /var/lib/ctf/disabled/tanks ] && exit 0 [ -f /var/lib/ctf/disabled/tanks ] && exit 0
envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 & exec envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 2>&1
envuidgid ctf report_score.py #envuidgid ctf report_score.py 2>&1

95
tanks/run_tanks.py Normal file → Executable file
View File

@ -1,20 +1,93 @@
#! /usr/bin/python
import asynchat
import asyncore
import optparse
import os
import shutil
import socket
import time import time
from tanks import Pflanzarr from tanks import Pflanzarr
import sys
T = 60*5 T = 60*5
MAX_HIST = 30
HIST_STEP = 100
key = 'tanks:::2bac5e912ff2e1ad559b177eb5aeecca'
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, 6668))
self.push(auth + '\n')
self.flag = None
def handle_read(self):
msg = self.recv(4096)
raise ValueError("Flagger died: %r" % msg)
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
else:
eteam = ''
self.push(eteam + '\n')
self.flag = team
def run_tanks(args, turns, flagger):
p = Pflanzarr.Pflanzarr(args[0], args[1])
p.run(turns)
path = os.path.join(args[0], 'results')
files = os.listdir(path)
gameNums = []
for file in files:
try:
gameNums.append( int(file) )
except:
continue
gameNums.sort(reverse=True)
highest = gameNums[0]
for num in gameNums:
if highest - MAX_HIST > num and not (num % HIST_STEP == 0):
shutil.rmtree(os.path.join(path, str(num)))
try: try:
while 1: winner = open('/var/lib/tanks/winner').read().strip()
start = time.time()
p = Pflanzarr(sys.argv[1], sys.argv[2])
p.run(int(sys.argv[3]))
diff = time.time() - start
if diff - T > 0:
time.sleep( diff - T )
except: except:
print 'Usage: python2.6 run_tanks.py data_dir easy|medium|hard max_turns' winner = None
flagger.set_flag(winner)
def main():
parser = optparse.OptionParser('DATA_DIR easy|medium|hard MAX_TURNS')
opts, args = parser.parse_args()
if (len(args) != 3) or (args[1] not in ('easy', 'medium', 'hard')):
parser.error('Wrong number of arguments')
try:
turns = int(args[2])
except:
parser.error('Invalid number of turns')
flagger = Flagger('localhost', key)
lastrun = 0
while True:
asyncore.loop(60, count=1)
now = time.time()
if now - lastrun >= 60:
run_tanks(args, turns, flagger)
lastrun = now
if __name__ == '__main__':
main()

3
tanks/t.py Normal file
View File

@ -0,0 +1,3 @@
import sys
print >> sys.stderr, 'hello'

View File

@ -7,7 +7,7 @@ import os
import sys import sys
try: try:
from Tanks import Program, setup, conditions, actions, docs from tanks import Program, setup, conditions, actions, docs
except: except:
path = os.getcwd().split('/') path = os.getcwd().split('/')
path.pop() path.pop()

View File

@ -1,9 +1,10 @@
#!/usr/bin/python #!/usr/bin/python3
print """Content-Type: text/html\n\n""" print("""Content-Type: text/html\n\n""")
print """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN">\n\n""" print("""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN">\n\n""")
import cgi import cgi
import cgitb; cgitb.enable() import cgitb; cgitb.enable()
import sys
import os import os
import Config import Config
@ -16,18 +17,17 @@ except:
try: try:
from ctf import teams from ctf import teams
except: except:
import sys
path = '/home/pflarr/repos/gctf/' path = '/home/pflarr/repos/gctf/'
sys.path.append(path) sys.path.append(path)
from ctf import teams from ctf import teams
teams.build_teams() teams.build_teams()
head = open('head.html').read() % "Error Report" head = open('head.html').read() % "Error Report"
print head print(head)
print open('links.html').read() print(open('links.html').read())
def done(): def done():
print '</body></html>' print('</body></html>')
sys.exit(0) sys.exit(0)
fields = cgi.FieldStorage() fields = cgi.FieldStorage()
@ -38,25 +38,25 @@ if team and passwd and \
path = os.path.join(Config.DATA_PATH, 'errors', quote(team)) path = os.path.join(Config.DATA_PATH, 'errors', quote(team))
if os.path.isfile(path): if os.path.isfile(path):
errors = open(path).readlines() errors = open(path).readlines()
print '<p>Your latest errors:' print('<p>Your latest errors:')
print '<div class=errors>' print('<div class=errors>')
if errors: if errors:
print '<BR>\n'.join(errors) print('<BR>\n'.join(errors))
else: else:
print 'There were no errors.' print('There were no errors.')
print '</div>' print('</div>')
else: else:
print '<p>No error file found.' print('<p>No error file found.')
done() done()
if team and team not in teams.teams: if team and team not in teams.teams:
print '<p>Invalid team.' print('<p>Invalid team.')
if team and team in teams.teams and passwd != teams.teams[team][0]: if team and team in teams.teams and passwd != teams.teams[team][0]:
print '<p>Invalid password.' print('<p>Invalid password.')
print ''' print('''
<form action="errors.cgi" method="get"> <form action="errors.cgi" method="get">
<fieldset> <fieldset>
<legend>Error report request:</legend> <legend>Error report request:</legend>
@ -64,6 +64,6 @@ print '''
Password: <input type="text" name="passwd"><BR> Password: <input type="text" name="passwd"><BR>
<button type="get my errors">Submit</button> <button type="get my errors">Submit</button>
</fieldset> </fieldset>
</form>''' </form>''')
done() done()

View File

@ -28,22 +28,28 @@ except:
if not games: if not games:
print "<p>No games have occurred yet." print "<p>No games have occurred yet."
gameNums = [] gameNums = []
for game in games: for game in games:
try: try:
num = int(game) gameNums.append( int(game) )
path = os.path.join( 'results', game, 'results.html')
if os.path.exists( path ):
gameNums.append( int(num) )
else:
continue
except: except:
continue continue
gameNums.sort(reverse=True) gameNums.sort(reverse=True)
# Don't include games that haven't completed
i = 0
num = str(gameNums[i])
for i in range(len(gameNums)):
path = os.path.join( 'results', str(gameNums[i]), 'results.html') )
if os.path.exists( path ):
break
gameNums = gameNums[i:]
for num in gameNums: for num in gameNums:
print '<p>%d - ' % num, print '<p>%d - ' % num,
print '<a href="results/%d/game.avi">v</a>' % num, print '<a href="results/%d/game.avi">v</a>' % num,
print '<a href="results/%d/results.html">r</a>' % num print '<a href="results/%d/results.html">r</a>' % num
print '</body></html>'

View File

@ -1,8 +1,11 @@
#!/usr/bin/python #!/usr/bin/python3
print("Content-Type: text/html\n\n")
print("""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN">\n\n""")
import cgi import cgi
import cgitb; cgitb.enable() import cgitb; cgitb.enable()
import os import os
import sys
import Config import Config
@ -14,21 +17,18 @@ except:
try: try:
from ctf import teams from ctf import teams
except: except:
import sys
path = '/home/pflarr/repos/gctf/' path = '/home/pflarr/repos/gctf/'
sys.path.append(path) sys.path.append(path)
from ctf import teams from ctf import teams
teams.build_teams() teams.build_teams()
print """Content-Type: text/html\n\n"""
print """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN">\n\n"""
head = open('head.html').read() % "Submission Results" head = open('head.html').read() % "Submission Results"
print head print(head)
print "<H1>Results</H1>" print("<H1>Results</H1>")
print open('links.html').read() print(open('links.html').read())
def done(): def done():
print '</body></html>' print('</body></html>')
sys.exit(0) sys.exit(0)
fields = cgi.FieldStorage() fields = cgi.FieldStorage()
@ -36,21 +36,23 @@ team = fields.getfirst('team', '').strip()
passwd = fields.getfirst('passwd', '').strip() passwd = fields.getfirst('passwd', '').strip()
code = fields.getfirst('code', '') code = fields.getfirst('code', '')
if not team: if not team:
print '<p>No team specified'; done() print('<p>No team specified'); done()
elif not passwd: elif not passwd:
print '<p>No password given'; done() print('<p>No password given'); done()
elif not code: elif not code:
print '<p>No program given.'; done() print('<p>No program given.'); done()
if team not in teams.teams: if team not in teams.teams:
print '<p>Team is not registered.'; done() print('<p>Team is not registered.'); done()
if passwd != teams.teams[team][0]: if passwd != teams.teams[team][0]:
print '<p>Invalid password.'; done() print('<p>Invalid password.'); done()
path = os.path.join(Config.DATA_PATH, 'ai/players', encode(team) ) path = os.path.join(Config.DATA_PATH, 'ai/players', quote(team) )
file = open(path, 'w') file = open(path, 'w')
file.write(code) file.write(code)
file.close() file.close()
print("<P>Submission Successful")
done() done()

20
tanksFlagger/Makefile Normal file
View File

@ -0,0 +1,20 @@
FAKE = fakeroot -s fake -i fake
INSTALL = $(FAKE) install -o 100
all: tanksFlagger.tce
push: tanksFlagger.tce
netcat -l -q 0 -p 3333 < tanksFlagger.tce
tanksFlagger.tce: target
$(FAKE) sh -c 'cd target && tar -czf - .' > $@
target:
$(INSTALL) -d target/var/service/tanksFlagger
$(INSTALL) run report_score.py target/var/service/tanksFlagger/
$(INSTALL) -d target/var/service/tanksFlagger/log/
$(INSTALL) log.run target/var/service/tanksFlagger/log/run
clean:
rm -rf target tanksFlagger.tce fake

3
tanksFlagger/log.run Executable file
View File

@ -0,0 +1,3 @@
#! /bin/sh
exec logger -t tanksFlagger

5
tanksFlagger/run Executable file
View File

@ -0,0 +1,5 @@
#! /bin/sh
[ -f /var/lib/ctf/disabled/tanks ] && exit 0
exec envuidgid ctf python3 report_score.py 2>&1