Mostly cosmetics

This commit is contained in:
Neale Pickett 2009-10-08 16:42:26 -06:00
parent d9d7ddfe53
commit ec202f23f5
11 changed files with 179 additions and 155 deletions

69
ctf.css
View File

@ -30,6 +30,49 @@ h1:first-child:before {
content: "Capture The Flag: "; content: "Capture The Flag: ";
} }
/*** left side bar ***/
#navigation {
position: fixed;
background: #222;
opacity: 0.9;
top: 80px;
left: 0px;
padding: 0;
}
#navigation ul {
list-style: none;
padding: 0;
margin: 0;
}
#navigation li a {
display: block;
height: 25px;
width: 90px;
padding: 5px;
margin: 5px;
background: inherit;
border-right: 4px solid #444;
color: #999;
text-transform: lowercase;
font-size: 0.9em;
}
#navigation li a:hover {
color: #f4f4f4;
background: #333;
border-right: 4px solid #2a2;
}
#navigation li .active {
color: #999;
background: #333;
border-right: 4px solid #444;
}
/**** body ****/ /**** body ****/
a img { a img {
@ -64,6 +107,18 @@ th, td {
vertical-align: top; vertical-align: top;
} }
p {
line-height: 1.4em;
margin-bottom: 20px;
color: #f4f4f4;
}
/**** special cases ****/
.wide {
max-width: inherit;
}
.scoreboard { .scoreboard {
background: #222; background: #222;
} }
@ -72,24 +127,20 @@ th, td {
height: 400px; height: 400px;
} }
p {
line-height: 1.4em;
margin-bottom: 20px;
color: #f4f4f4;
}
.solved { .solved {
text-decoration: line-through; text-decoration: line-through;
} }
table.pollster { table.pollster {
margin-left: 5em; margin-left: 5em;
} }
table.pollster td { table.pollster td {
padding: 2px 1em 2px 5px; padding: 2px 1em 2px 5px;
} }
table.pollster thead { table.pollster thead {
font-weight: bold; font-weight: bold;
} }

View File

@ -28,27 +28,33 @@ if 'home' in os.environ.get('SCRIPT_FILENAME', ''):
} }
else: else:
# An actual installation # An actual installation
config = {'global': config = {
{'data_dir': '/var/lib/ctf', 'global':
'base_url': '/', {
'css_url': '/ctf.css', 'data_dir': '/var/lib/ctf',
'disabled_dir': '/var/lib/ctf/disabled', 'base_url': '/',
'flags_dir': '/var/lib/ctf/flags', 'css_url': '/ctf.css',
'house_team': 'dirtbags', 'disabled_dir': '/var/lib/ctf/disabled',
'passwd': '/var/lib/ctf/passwd', 'flags_dir': '/var/lib/ctf/flags',
'team_colors': team_colors, 'house_team': 'dirtbags',
'poll_interval': 60, 'passwd': '/var/lib/ctf/passwd',
'poll_timeout': 0.5, 'team_colors': team_colors,
'heartbeat_dir': '/var/lib/pollster', },
'poll_dir': '/var/lib/www', 'pollster':
}, {
'puzzler': 'poll_interval': 60,
{'dir': '/usr/lib/www/puzzler', 'poll_timeout': 0.5,
'cgi_url': '/puzzler.cgi', 'heartbeat_dir': '/var/lib/pollster',
'base_url': '/puzzler', 'results': '/var/lib/pollster/status.html',
'keys_file': '/usr/lib/ctf/puzzler.keys', },
}, 'puzzler':
} {
'dir': '/usr/lib/www/puzzler',
'cgi_url': '/puzzler.cgi',
'base_url': '/puzzler',
'keys_file': '/usr/lib/ctf/puzzler.keys',
},
}
def get(section, key): def get(section, key):
return config[section][key] return config[section][key]
@ -71,22 +77,37 @@ def datafile(filename):
def url(path): def url(path):
return base_url + path return base_url + path
def start_html(title): def start_html(title, hdr='', cls=''):
ret = []
if os.environ.get('GATEWAY_INTERFACE'): if os.environ.get('GATEWAY_INTERFACE'):
print('Content-type: text/html') ret.append('Content-type: text/html')
print() ret.append('')
print('''<?xml version="1.0" encoding="UTF-8"?> ret.append('''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC <!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN" "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<title>%s</title> <title>%(title)s</title>
<link rel="stylesheet" href="%s" type="text/css" /> <link rel="stylesheet" href="%(css)s" type="text/css" />
%(hdr)s
</head> </head>
<body> <body class="%(class)s">
<h1>%s</h1> <h1>%(title)s</h1>
''' % (title, css, title)) <div id="navigation">
<ul>
<li><a href="%(base)s">Home</a></li>
<li><a href="%(base)sintro.html">Intro/Rules</a></li>
<li><a href="%(base)spuzzler.cgi">Puzzles</a></li>
<li><a href="%(base)sscoresboard.cgi">Scoreboard</a></li>
</ul>
</div>
''' % {'title': title,
'css': css,
'hdr': hdr,
'base': base_url,
'class': cls})
return '\n'.join(ret)
def end_html(): def end_html():
print('</body></html>') return '</body></html>'

View File

@ -66,9 +66,10 @@ def start_html(title):
if passwd: if passwd:
c['passwd'] = passwd c['passwd'] = passwd
print(c) print(c)
config.start_html(title) print(config.start_html(title))
end_html = config.end_html def end_html():
print(config.end_html())
def safe_join(*args): def safe_join(*args):
safe = list(args[:1]) safe = list(args[:1])

View File

@ -7,26 +7,7 @@ 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()
f = cgi.FieldStorage() f = cgi.FieldStorage()
team = f.getfirst('team', '') team = f.getfirst('team', '')

View File

@ -15,20 +15,8 @@ def main():
categories = [(cat, s.cat_points(cat)) for cat in s.categories()] categories = [(cat, s.cat_points(cat)) for cat in s.categories()]
print('Content-type: text/html')
print('Refresh: 10') print('Refresh: 10')
print() print(config.start_html('Scoreboard', cls='wide'))
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" xml:lang="en" lang="en">
<head>
<title>Scoreboard</title>
<link rel="stylesheet" href="%sctf.css" type="text/css" />
</head>
<body style="max-width: inherit;">
<h1>Scoreboard</h1>
''' % config.base_url)
print('<table class="scoreboard">') print('<table class="scoreboard">')
print('<tr>') print('<tr>')
print('<th>Overall</th>') print('<th>Overall</th>')
@ -71,9 +59,8 @@ def main():
<p class="histogram"> <p class="histogram">
<img src="histogram.png" alt="scores over time" /> <img src="histogram.png" alt="scores over time" />
</p> </p>
''')
</body> print(config.end_html())
</html>''')
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -24,9 +24,6 @@ def reap():
except OSError: except OSError:
pass pass
def sigchld(signum, frame):
do_reap = True
def main(): def main():
p = optparse.OptionParser() p = optparse.OptionParser()
p.add_option('-p', '--genpass', dest='cat', default=None, p.add_option('-p', '--genpass', dest='cat', default=None,
@ -39,13 +36,11 @@ 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:
if do_reap:
reap()
asyncore.loop(timeout=30, use_poll=True, count=1) asyncore.loop(timeout=30, use_poll=True, count=1)
reap()
if len(s) > slen: if len(s) > slen:
slen = len(s) slen = len(s)
chart(s) chart(s)

View File

@ -17,6 +17,37 @@ opts, args = p.parse_args()
keys = [] keys = []
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;
}
}
window.onload = getTeamInfo;
</script>
'''
for cat in os.listdir(opts.puzzles): for cat in os.listdir(opts.puzzles):
dirname = os.path.join(opts.puzzles, cat) dirname = os.path.join(opts.puzzles, cat)
for points in os.listdir(dirname): for points in os.listdir(dirname):
@ -43,47 +74,7 @@ for cat in os.listdir(opts.puzzles):
title = '%s for %s points' % (cat, points) title = '%s for %s points' % (cat, points)
f = open(os.path.join(outdir, 'index.html'), 'w', encoding='utf-8') f = open(os.path.join(outdir, 'index.html'), 'w', encoding='utf-8')
f.write('''<?xml version="1.0" encoding="UTF-8"?> f.write(config.start_html(title, js))
<!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>%(title)s</title>
<link rel="stylesheet" href="%(css)s" type="text/css" />
<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;
}
}
window.onload = getTeamInfo;
</script>
</head>
<body>
<h1>%(title)s</h1>
''' % {'title': title,
'css': config.css})
if readme: if readme:
f.write('<div class="readme">%s</div>\n' % readme) f.write('<div class="readme">%s</div>\n' % readme)
if files: if files:
@ -105,11 +96,10 @@ for cat in os.listdir(opts.puzzles):
<input type="submit" /> <input type="submit" />
</fieldset> </fieldset>
</form> </form>
</body>
</html>
''' % {'cgi': config.get('puzzler', 'cgi_url'), ''' % {'cgi': config.get('puzzler', 'cgi_url'),
'cat': cat, 'cat': cat,
'points': points}) 'points': points})
f.write(config.end_html())
f = open(opts.keyfile, 'w', encoding='utf-8') f = open(opts.keyfile, 'w', encoding='utf-8')
for key in keys: for key in keys:

View File

@ -1,8 +1,9 @@
#! /bin/sh #! /bin/sh
case "$REMOTEADDR" in ip=$(echo $UDPREMOTEADDR | cut -d: -f1)
10.0.0.[2-254]) case "$ip" in
touch /var/lib/pollster/$REMOTEADDR 10.0.0.*)
touch /var/lib/pollster/$ip
;; ;;
esac esac
echo 'Hello.'

View File

@ -13,10 +13,10 @@ from ctf import config
from ctf import pointscli from ctf import pointscli
DEBUG = False DEBUG = False
POLL_INTERVAL = config.get('poll_interval') POLL_INTERVAL = config.get('pollster', 'poll_interval')
IP_DIR = config.get('heartbeat_dir') IP_DIR = config.get('pollster', 'heartbeat_dir')
REPORT_PATH = config.get('poll_dir') REPORT_PATH = config.get('pollster', 'results')
SOCK_TIMEOUT = config.get('poll_timeout') SOCK_TIMEOUT = config.get('pollster', 'poll_timeout')
class PointSubmitter(threading.Thread): class PointSubmitter(threading.Thread):
''' Pulls point allocations from the queue and submits them. ''' ''' Pulls point allocations from the queue and submits them. '''
@ -24,7 +24,7 @@ class PointSubmitter(threading.Thread):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.point_queue = point_queue self.point_queue = point_queue
self.sock = pointscli.makesock('localhost') self.sock = pointscli.makesock('localhost')
def run(self): def run(self):
# loop forever # loop forever
while(True): while(True):
@ -40,7 +40,7 @@ class PointSubmitter(threading.Thread):
def socket_poll(ip, port, msg, prot, max_recv=1): def socket_poll(ip, port, msg, prot, max_recv=1):
''' Connect via socket to the specified <ip>:<port> using the ''' Connect via socket to the specified <ip>:<port> using the
specified <prot>, send the specified <msg> and return the specified <prot>, send the specified <msg> and return the
response or None if something went wrong. <max_recvs> specifies response or None if something went wrong. <max_recvs> specifies
how many times to read from the socket (default to once). ''' how many times to read from the socket (default to once). '''
@ -51,7 +51,7 @@ def socket_poll(ip, port, msg, prot, max_recv=1):
print('pollster: create socket failed (%s)' % e) print('pollster: create socket failed (%s)' % e)
traceback.print_exc() traceback.print_exc()
return None return None
sock.settimeout(SOCK_TIMEOUT) sock.settimeout(SOCK_TIMEOUT)
# connect # connect
@ -90,7 +90,7 @@ def socket_poll(ip, port, msg, prot, max_recv=1):
except Exception as e: except Exception as e:
print('pollster: receive from %s:%d failed (%s)' % (ip, port, e)) print('pollster: receive from %s:%d failed (%s)' % (ip, port, e))
traceback.print_exc() traceback.print_exc()
if len(resp) == 0: if len(resp) == 0:
return None return None
@ -131,7 +131,7 @@ def poll_catcgi(ip):
content_len = int(content[0]) content_len = int(content[0])
except Exception as e: except Exception as e:
return None return None
if content_len <= 0: if content_len <= 0:
return None return None
return content[1].strip('\r\n') return content[1].strip('\r\n')
@ -141,13 +141,13 @@ def poll_tftpd(ip):
resp = socket_poll(ip, 69, b'\x00\x01' + b'flag' + b'\x00' + b'octet' + b'\x00', socket.SOCK_DGRAM) resp = socket_poll(ip, 69, b'\x00\x01' + b'flag' + b'\x00' + b'octet' + b'\x00', socket.SOCK_DGRAM)
if resp is None: if resp is None:
return None return None
if len(resp) <= 5: if len(resp) <= 5:
return None return None
resp = resp.split('\n')[0] resp = resp.split('\n')[0]
return resp[4:].strip('\r\n') return resp[4:].strip('\r\n')
# PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED # PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED
POLLS = { POLLS = {
'fingerd' : poll_fingerd, 'fingerd' : poll_fingerd,
@ -174,7 +174,7 @@ while True:
except Exception as e: except Exception as e:
print('pollster: could not list dir %s (%s)' % (IP_DIR, e)) print('pollster: could not list dir %s (%s)' % (IP_DIR, e))
traceback.print_exc() traceback.print_exc()
try: try:
os.remove(REPORT_PATH) os.remove(REPORT_PATH)
except Exception as e: except Exception as e:
@ -184,16 +184,9 @@ while True:
out = open(REPORT_PATH, 'w') out = open(REPORT_PATH, 'w')
except Exception as e: except Exception as e:
out = None 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')
out.write(config.start_html('Team Service Availability'))
for ip in ips: for ip in ips:
# check file name format is ip # check file name format is ip
if ip_re.match(ip) is None: if ip_re.match(ip) is None:
continue continue
@ -231,7 +224,7 @@ while True:
if out is not None: if out is not None:
out.write('</table>\n') out.write('</table>\n')
if DEBUG is True: if DEBUG is True:
print('+-----------------------------------------+') print('+-----------------------------------------+')
@ -240,8 +233,7 @@ while True:
sleep_time = POLL_INTERVAL - exec_time sleep_time = POLL_INTERVAL - exec_time
if out is not None: if out is not None:
out.write('<p><b>Next poll in: %ds</b></p>\n' % sleep_time) out.write(config.end_html())
out.write('</body>\n</html>\n')
out.close() out.close()
# sleep until its time to poll again # sleep until its time to poll again

View File

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

View File

@ -14,6 +14,8 @@ MAX_HIST = 30
HIST_STEP = 100 HIST_STEP = 100
key = 'tanks:::2bac5e912ff2e1ad559b177eb5aeecca' key = 'tanks:::2bac5e912ff2e1ad559b177eb5aeecca'
running = True
class Flagger(asynchat.async_chat): class Flagger(asynchat.async_chat):
"""Use to connect to flagd and submit the current flag holder.""" """Use to connect to flagd and submit the current flag holder."""
@ -31,6 +33,7 @@ class Flagger(asynchat.async_chat):
def handle_error(self): def handle_error(self):
# If we lose the connection to flagd, nobody can score any # If we lose the connection to flagd, nobody can score any
# points. Terminate everything. # points. Terminate everything.
running = False
asyncore.close_all() asyncore.close_all()
asynchat.async_chat.handle_error(self) asynchat.async_chat.handle_error(self)
@ -84,6 +87,8 @@ def main():
lastrun = 0 lastrun = 0
while True: while True:
asyncore.loop(60, count=1) asyncore.loop(60, count=1)
if not running:
break
now = time.time() now = time.time()
if now - lastrun >= 60: if now - lastrun >= 60:
run_tanks(args, turns, flagger) run_tanks(args, turns, flagger)