diff --git a/new-contest b/new-contest
new file mode 100755
index 0000000..2325e44
--- /dev/null
+++ b/new-contest
@@ -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
diff --git a/pollster/in.heartbeatd b/pollster/in.heartbeatd
new file mode 100755
index 0000000..e991a6a
--- /dev/null
+++ b/pollster/in.heartbeatd
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+case "$REMOTEADDR" in
+ 10.0.0.[2-254])
+ touch /var/lib/pollster/$REMOTEADDR
+ ;;
+esac
+
diff --git a/pollster/pollster.py b/pollster/pollster.py
new file mode 100755
index 0000000..bde9fea
--- /dev/null
+++ b/pollster/pollster.py
@@ -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 : using the
+ specified , send the specified and return the
+ response or None if something went wrong. 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('\n\n')
+ out.write('Pollster Results\n')
+ out.write('\n')
+ out.write('\n
Polling Results
\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('
%s
\n' % ip)
+ out.write('
\n
Service Name
')
+ out.write('
Flag Holder
\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('
%s
%s
\n' % (service, team))
+
+ point_queue.put((service, team, 1))
+
+ if out is not None:
+ out.write('
\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('
Next poll in: %ds
\n' % sleep_time)
+ out.write('\n\n')
+ out.close()
+
+ # sleep until its time to poll again
+ time.sleep(sleep_time)
+
diff --git a/pollster/run.heartbeatd b/pollster/run.heartbeatd
new file mode 100755
index 0000000..8241141
--- /dev/null
+++ b/pollster/run.heartbeatd
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+exec udpsvd 0 9 /usr/sbin/in.heartbeatd
diff --git a/puzzles/bletchley/1000/index.html b/puzzles/bletchley/1000/index.html
new file mode 100644
index 0000000..7dd37e9
--- /dev/null
+++ b/puzzles/bletchley/1000/index.html
@@ -0,0 +1,108 @@
+Safe to execute.
+
+
diff --git a/puzzles/bletchley/1000/key b/puzzles/bletchley/1000/key
new file mode 100644
index 0000000..3be61b9
--- /dev/null
+++ b/puzzles/bletchley/1000/key
@@ -0,0 +1 @@
+It is a lovely day outside
diff --git a/puzzles/compaq/100/index.html b/puzzles/compaq/100/index.html
new file mode 100644
index 0000000..672516c
--- /dev/null
+++ b/puzzles/compaq/100/index.html
@@ -0,0 +1,2 @@
+Recovery, while not strictly necessary, may be tremendously helpful.
+
diff --git a/puzzles/compaq/150/b2f3f6b43ecadc7ae0b5f0edde694c78 b/puzzles/compaq/150/b2f3f6b43ecadc7ae0b5f0edde694c78
new file mode 100755
index 0000000..c20b65b
Binary files /dev/null and b/puzzles/compaq/150/b2f3f6b43ecadc7ae0b5f0edde694c78 differ
diff --git a/puzzles/compaq/150/key b/puzzles/compaq/150/key
new file mode 100644
index 0000000..164f87f
--- /dev/null
+++ b/puzzles/compaq/150/key
@@ -0,0 +1 @@
+This is our world now... the world of the electron and the switch, the beauty of the baud.
diff --git a/puzzles/compaq/350/e76cb42be0c0f12f97b2071aba8b74f2 b/puzzles/compaq/350/e76cb42be0c0f12f97b2071aba8b74f2
new file mode 100755
index 0000000..10b82c9
Binary files /dev/null and b/puzzles/compaq/350/e76cb42be0c0f12f97b2071aba8b74f2 differ
diff --git a/puzzles/compaq/350/key b/puzzles/compaq/350/key
new file mode 100644
index 0000000..c4fa843
--- /dev/null
+++ b/puzzles/compaq/350/key
@@ -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.
diff --git a/puzzles/compaq/50/adddbafb502355634d9ef10e1848cf52 b/puzzles/compaq/50/adddbafb502355634d9ef10e1848cf52
new file mode 100644
index 0000000..97e2900
Binary files /dev/null and b/puzzles/compaq/50/adddbafb502355634d9ef10e1848cf52 differ
diff --git a/puzzles/compaq/50/key b/puzzles/compaq/50/key
new file mode 100644
index 0000000..0db4aae
--- /dev/null
+++ b/puzzles/compaq/50/key
@@ -0,0 +1 @@
+extra special text
diff --git a/puzzles/compaq/600/daa36d50d4c807634dfd13a8239046de b/puzzles/compaq/600/daa36d50d4c807634dfd13a8239046de
new file mode 100755
index 0000000..41ba532
Binary files /dev/null and b/puzzles/compaq/600/daa36d50d4c807634dfd13a8239046de differ
diff --git a/puzzles/compaq/600/key b/puzzles/compaq/600/key
new file mode 100644
index 0000000..99d2435
--- /dev/null
+++ b/puzzles/compaq/600/key
@@ -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.
diff --git a/puzzles/survey/1000000/submit.cgi b/puzzles/survey/1000000/,submit.cgi
similarity index 57%
rename from puzzles/survey/1000000/submit.cgi
rename to puzzles/survey/1000000/,submit.cgi
index bf1bbc4..39a9a08 100755
--- a/puzzles/survey/1000000/submit.cgi
+++ b/puzzles/survey/1000000/,submit.cgi
@@ -1,6 +1,8 @@
#! /usr/bin/env python3
import cgi
+import time
+import os
f = cgi.FieldStorage()
if f.getfirst('submit'):
@@ -8,6 +10,13 @@ if f.getfirst('submit'):
print()
print('Thanks for filling in the survey.')
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(' quux blorb frotz')
else:
diff --git a/puzzles/survey/1000000/,survey.html b/puzzles/survey/1000000/,survey.html
new file mode 100644
index 0000000..0ed037b
--- /dev/null
+++ b/puzzles/survey/1000000/,survey.html
@@ -0,0 +1,44 @@
+
+
+
+
+ Survey
+
+
+
+
+
+
+
diff --git a/puzzles/survey/1000000/index.html b/puzzles/survey/1000000/index.html
index 2b39091..aa2727d 100644
--- a/puzzles/survey/1000000/index.html
+++ b/puzzles/survey/1000000/index.html
@@ -4,38 +4,7 @@
recieve a key redeemable for ONE MILLION POINTS.
-
+
diff --git a/pwnables/Makefile b/pwnables/Makefile
index 154b3a8..5327560 100644
--- a/pwnables/Makefile
+++ b/pwnables/Makefile
@@ -5,9 +5,9 @@ TARGET = $(CURDIR)/target
FAKE = fakeroot -s $(CURDIR)/fake -i $(CURDIR)/fake
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=*~ .' > $@
target:
@@ -17,13 +17,5 @@ target:
$(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:
- rm -rf target
+ rm -rf target pwnables.tce
diff --git a/pwnables/fingerd/Makefile b/pwnables/fingerd/Makefile
deleted file mode 100644
index 74a0f1b..0000000
--- a/pwnables/fingerd/Makefile
+++ /dev/null
@@ -1 +0,0 @@
-all: in.fingerd
diff --git a/pwnables/fingerd/in.fingerd.c b/pwnables/fingerd/in.fingerd.c
deleted file mode 100644
index cb5d9e7..0000000
--- a/pwnables/fingerd/in.fingerd.c
+++ /dev/null
@@ -1,38 +0,0 @@
-#include
-
-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;
-}
diff --git a/pwnables/fingerd/run b/pwnables/fingerd/run
deleted file mode 100755
index 4088271..0000000
--- a/pwnables/fingerd/run
+++ /dev/null
@@ -1,3 +0,0 @@
-#! /bin/sh
-
-exec tcpsvd 0 79 /usr/sbin/in.fingerd
diff --git a/pwnables/skel/home/flag/.plan b/pwnables/skel/home/flag/.plan
new file mode 100644
index 0000000..a69e835
--- /dev/null
+++ b/pwnables/skel/home/flag/.plan
@@ -0,0 +1 @@
+dirtbags
diff --git a/pwnables/cat.cgi b/pwnables/skel/usr/lib/www/cat.cgi
similarity index 100%
rename from pwnables/cat.cgi
rename to pwnables/skel/usr/lib/www/cat.cgi
diff --git a/pwnables/skel/usr/lib/www/flag b/pwnables/skel/usr/lib/www/flag
new file mode 120000
index 0000000..f4bdb9f
--- /dev/null
+++ b/pwnables/skel/usr/lib/www/flag
@@ -0,0 +1 @@
+/var/lib/cat/flag
\ No newline at end of file
diff --git a/pwnables/skel/var/lib/cat/flag b/pwnables/skel/var/lib/cat/flag
new file mode 100644
index 0000000..a69e835
--- /dev/null
+++ b/pwnables/skel/var/lib/cat/flag
@@ -0,0 +1 @@
+dirtbags
diff --git a/pwnables/skel/var/lib/notes/flag b/pwnables/skel/var/lib/notes/flag
new file mode 100644
index 0000000..a69e835
--- /dev/null
+++ b/pwnables/skel/var/lib/notes/flag
@@ -0,0 +1 @@
+dirtbags
diff --git a/pwnables/skel/var/lib/tftp/flag b/pwnables/skel/var/lib/tftp/flag
new file mode 100644
index 0000000..a69e835
--- /dev/null
+++ b/pwnables/skel/var/lib/tftp/flag
@@ -0,0 +1 @@
+dirtbags
diff --git a/pwnables/skel/var/service/heartbeat/run b/pwnables/skel/var/service/heartbeat/run
new file mode 100755
index 0000000..f7704d8
--- /dev/null
+++ b/pwnables/skel/var/service/heartbeat/run
@@ -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
diff --git a/pwnables/tftpd/run b/pwnables/tftpd/run
deleted file mode 100755
index bcba031..0000000
--- a/pwnables/tftpd/run
+++ /dev/null
@@ -1,3 +0,0 @@
-#! /bin/sh
-
-exec udpsvd 0 69 tftpd /var/lib/tftp
diff --git a/run.log.ctfd b/run.log.ctfd
new file mode 100755
index 0000000..820d402
--- /dev/null
+++ b/run.log.ctfd
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+exec logger -t ctfd
diff --git a/tanks/Makefile b/tanks/Makefile
index cb27c38..4c83034 100644
--- a/tanks/Makefile
+++ b/tanks/Makefile
@@ -3,12 +3,16 @@ INSTALL = $(FAKE) install -o 100
all: tanks.tce
+push: tanks.tce
+ netcat -l -q 0 -p 3333 < tanks.tce
+
tanks.tce: target
$(FAKE) sh -c 'cd target && tar -czf - .' > $@
target:
$(INSTALL) -d target/var/lib/tanks/
$(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/medium
$(INSTALL) -d target/var/lib/tanks/ai/hard
@@ -17,16 +21,17 @@ target:
$(INSTALL) AI/medium/* target/var/lib/tanks/ai/medium/
$(INSTALL) AI/hard/* target/var/lib/tanks/ai/hard/
- $(INSTALL) -d target/var/lib/www/tanks/
- $(INSTALL) www/* target/var/lib/www/tanks/
- $(FAKE) ln -s target/var/lib/tanks/ target/var/lib/www/data
+ $(INSTALL) -d target/usr/lib/www/tanks/
+ $(INSTALL) www/* target/usr/lib/www/tanks/
+
+ ln -s /var/lib/tanks/results target/usr/lib/www/tanks/results
$(INSTALL) -d 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) run target/var/service/tanks/run
-
+ $(INSTALL) run run_tanks.py target/var/service/tanks/
+
$(INSTALL) -d target/var/service/tanks/log/
$(INSTALL) log.run target/var/service/tanks/log/run
diff --git a/tanks/lib/GameMath.py b/tanks/lib/GameMath.py
index ff47880..481bf81 100644
--- a/tanks/lib/GameMath.py
+++ b/tanks/lib/GameMath.py
@@ -48,7 +48,7 @@ def displacePoly(points, disp, limits, coordSequence=False):
maxX, maxY = limits
basePoints = []
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
if x > maxX:
diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py
index 4f31ca1..ac600a2 100644
--- a/tanks/lib/Pflanzarr.py
+++ b/tanks/lib/Pflanzarr.py
@@ -9,19 +9,12 @@ from urllib import unquote, quote
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
class Pflanzarr:
+ TEAMS_FILE = '/var/lib/ctf/passwd'
+
FRAME_DELAY = 15
SPACING = 150
@@ -42,12 +35,14 @@ class Pflanzarr:
if not os.path.exists(self._gameDir):
os.mkdir(self._gameDir)
+ colors = self._getColors()
+
tmpPlayers = os.listdir(self._playerDir)
players = []
for p in tmpPlayers:
p = unquote(p)
if not (p.startswith('.') or p.endswith('#') or p.endswith('~'))\
- and p in teams.teams:
+ and p in colors:
players.append(p)
AIs = {}
@@ -73,7 +68,7 @@ class Pflanzarr:
self._board = (cols*self.SPACING, rows*self.SPACING)
while len(players) < cols*rows:
- players.append('#default')
+ players.append(None)
self._tanks = []
for i in range(cols):
@@ -82,13 +77,13 @@ class Pflanzarr:
startY = j*self.SPACING + self.SPACING/2
player = random.choice(players)
players.remove(player)
- if player == '#default':
+ if player == None:
color = '#a0a0a0'
else:
- color = '#%s' % teams.teams[player][1]
+ color = colors[player]
tank = Tank.Tank( player, (startX, startY), color,
self._board, testMode=True)
- if player == '#default':
+ if player == None:
tank.program(random.choice(defaultAIs))
else:
tank.program(AIs[player])
@@ -156,10 +151,12 @@ class Pflanzarr:
if tank in kills[tank]:
kills[tank].remove(tank)
- self._saveResults(kills)
for tank in self._tanks:
self._outputErrors(tank)
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):
for tank in tanks:
@@ -204,22 +201,32 @@ class Pflanzarr:
break
winner = random.choice(winners)
- html = ['',
+ html = ['',
+ 'Game %d results',
+ '',
+ '',
+ '',
'
Team
Kills
Cause of Death']
for tank in tanks:
if tank is winner:
- rowStyle = 'style="color:red;"'
+ rowStyle = 'style="font-weight:bold; '\
+ 'background-color:%s"' % tank.color
else:
- rowStyle = ''
+ rowStyle = 'style="background-color:%s"' % tank.color
+ if tank.name:
+ name = xml.sax.saxutils.escape(tank.name)
+ else:
+ name = '#default'
html.append('