Many changes, unit test

* Everything in /srv/ctf now, set $CTF_BASE to override that
* CGI now accepts parms in argv
* Fix bug with uninitialized CGI vars
This commit is contained in:
Neale Pickett 2010-09-13 12:23:39 -06:00
parent 507dd93d88
commit 8d47593986
10 changed files with 295 additions and 100 deletions

View File

@ -1,11 +1,11 @@
TARGETS = in.tokend claim.cgi puzzler.cgi pointscli puzzles TARGETS = in.tokend pointscli claim.cgi puzzler.cgi puzzles.cgi
all: $(TARGETS) all: $(TARGETS)
in.tokend: in.tokend.o xxtea.o in.tokend: in.tokend.o xxtea.o common.o
puzzles: puzzles.o common.o
pointscli: pointscli.o common.o pointscli: pointscli.o common.o
puzzles.cgi: puzzles.cgi.o common.o
claim.cgi: claim.cgi.o common.o claim.cgi: claim.cgi.o common.o
puzzler.cgi: puzzler.cgi.o common.o puzzler.cgi: puzzler.cgi.o common.o

View File

@ -1,19 +1,16 @@
#include <stdlib.h> #include <stdlib.h>
#include "common.h" #include "common.h"
char const *tokenlog = "/var/lib/ctf/tokend/tokens.log";
char const *claimlog = "/var/lib/ctf/tokend/claim.log";
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
char team[9]; char team[9];
char token[100]; char token[100];
/* XXX: This code needs to be tested */ team[0] = 0;
return 1; token[0] = 0;
if (-1 == cgi_init()) { if (-1 == cgi_init(argv)) {
return 0; return 0;
} }
@ -53,7 +50,7 @@ main(int argc, char *argv[])
/* Does the token exist? */ /* Does the token exist? */
if (! fgrepx(token, tokenlog)) { if (! fgrepx(token, srv_path("tokens.db"))) {
cgi_page("Token does not exist", ""); cgi_page("Token does not exist", "");
} }
@ -69,7 +66,8 @@ main(int argc, char *argv[])
category[i] = '\0'; category[i] = '\0';
award_and_log_uniquely(team, category, 1, award_and_log_uniquely(team, category, 1,
claimlog, "%s %s", team, token); "tokens.db",
"%s %s", team, token);
} }

View File

@ -6,39 +6,56 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
#include <values.h>
#include <time.h> #include <time.h>
#include "common.h" #include "common.h"
/* /*
* CGI * CGI
*/ */
static size_t inlen = 0;
static int is_cgi = 0; static int is_cgi = 0;
static char **argv = NULL;
int static int
cgi_init() read_char_argv()
{ {
char *rm = getenv("REQUEST_METHOD"); static int arg = 0;
static char *p;
if (! (rm && (0 == strcmp(rm, "POST")))) { if (NULL == argv) {
printf("405 Method not allowed\r\n" return EOF;
"Allow: POST\r\n"
"Content-type: text/html\r\n"
"\r\n"
"<h1>Method not allowed</h1>\n"
"<p>I only speak POST. Sorry.</p>\n");
return -1;
} }
inlen = atoi(getenv("CONTENT_LENGTH")); if (0 == arg) {
is_cgi = 1; arg = 1;
p = argv[1];
}
return 0; if (! p) {
return EOF;
} else if (! *p) {
arg += 1;
p = argv[arg];
return '&';
}
return *(p++);
} }
static int static int
read_char() read_char_stdin()
{ {
static int inlen = -1;
if (-1 == inlen) {
char *p = getenv("CONTENT_LENGTH");
if (p) {
inlen = atoi(p);
} else {
inlen = 0;
}
}
if (inlen) { if (inlen) {
inlen -= 1; inlen -= 1;
return getchar(); return getchar();
@ -46,6 +63,53 @@ read_char()
return EOF; return EOF;
} }
static int
read_char_query_string()
{
static char *p = (char *)-1;
if ((char *)-1 == p) {
p = getenv("QUERY_STRING");
}
if (! p) {
return EOF;
} else if (! *p) {
return EOF;
} else {
return *(p++);
}
}
static int (* read_char)() = read_char_argv;
int
cgi_init(char *global_argv[])
{
char *rm = getenv("REQUEST_METHOD");
if (! rm) {
read_char = read_char_argv;
argv = global_argv;
} else if (0 == strcmp(rm, "POST")) {
read_char = read_char_stdin;
is_cgi = 1;
} else if (0 == strcmp(rm, "GET")) {
read_char = read_char_query_string;
is_cgi = 1;
} else {
printf(("405 Method not allowed\r\n"
"Allow: GET, POST\r\n"
"Content-type: text/plain\r\n"
"\r\n"
"%s is not allowed.\n"),
rm);
return -1;
}
return 0;
}
static char static char
tonum(int c) tonum(int c)
{ {
@ -207,22 +271,48 @@ my_snprintf(char *buf, size_t buflen, char *fmt, ...)
va_start(ap, fmt); va_start(ap, fmt);
len = vsnprintf(buf, buflen - 1, fmt, ap); len = vsnprintf(buf, buflen - 1, fmt, ap);
va_end(ap); va_end(ap);
if (len >= 0) { buf[buflen] = '\0';
buf[len] = '\0'; if (len >= buflen) {
return len; return buflen - 1;
} else { } else {
return -1; return len;
} }
} }
char *
srv_path(char const *fmt, ...)
{
char relpath[PATH_MAX];
static char path[PATH_MAX];
char *srv;
int len;
va_list ap;
va_start(ap, fmt);
len = vsnprintf(relpath, sizeof(relpath) - 1, fmt, ap);
va_end(ap);
relpath[sizeof(relpath) - 1] = '\0';
srv = getenv("CTF_BASE");
if (! srv) {
srv = "/srv/ctf";
}
my_snprintf(path, sizeof(path), "%s/%s", srv, relpath);
return path;
}
int int
team_exists(char const *teamhash) team_exists(char const *teamhash)
{ {
struct stat buf; struct stat buf;
char filename[100];
int ret; int ret;
int i; int i;
if ((! teamhash) || (! *teamhash)) {
return 0;
}
/* Check for invalid characters. */ /* Check for invalid characters. */
for (i = 0; teamhash[i]; i += 1) { for (i = 0; teamhash[i]; i += 1) {
if (! isalnum(teamhash[i])) { if (! isalnum(teamhash[i])) {
@ -230,15 +320,8 @@ team_exists(char const *teamhash)
} }
} }
/* Build filename. */
ret = snprintf(filename, sizeof(filename),
"%s/%s", teamdir, teamhash);
if (sizeof(filename) <= ret) {
return 0;
}
/* lstat seems to be the preferred way to check for existence. */ /* lstat seems to be the preferred way to check for existence. */
ret = lstat(filename, &buf); ret = lstat(srv_path("teams/names/%s", teamhash), &buf);
if (-1 == ret) { if (-1 == ret) {
return 0; return 0;
} }
@ -253,8 +336,7 @@ award_points(char const *teamhash,
{ {
char line[100]; char line[100];
int linelen; int linelen;
char filename[100]; char *filename;
int filenamelen;
int fd; int fd;
int ret; int ret;
time_t now = time(NULL); time_t now = time(NULL);
@ -296,13 +378,9 @@ award_points(char const *teamhash,
token log. token log.
*/ */
filenamelen = snprintf(filename, sizeof(filename), filename = srv_path("points.new/%d.%d.%s.%s.%ld",
"%s/%d.%d.%s.%s.%ld", now, getpid(),
pointsdir, now, getpid(),
teamhash, category, points); teamhash, category, points);
if (sizeof(filename) <= filenamelen) {
return -1;
}
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666); fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (-1 == fd) { if (-1 == fd) {
@ -322,9 +400,10 @@ void
award_and_log_uniquely(char const *team, award_and_log_uniquely(char const *team,
char const *category, char const *category,
long points, long points,
char const *logfile, char const *dbfile,
char const *fmt, ...) char const *fmt, ...)
{ {
char *dbpath = srv_path(dbfile);
char line[200]; char line[200];
int len; int len;
int ret; int ret;
@ -338,13 +417,13 @@ award_and_log_uniquely(char const *team,
if (sizeof(line) <= len) { if (sizeof(line) <= len) {
cgi_error("Log line too long"); cgi_error("Log line too long");
} }
if (fgrepx(line, logfile)) { if (fgrepx(line, dbpath)) {
cgi_page("Already claimed", cgi_page("Already claimed",
"<p>Your team has already claimed these points.</p>"); "<p>Your team has already claimed these points.</p>");
} }
/* Open and lock logfile */ /* Open and lock logfile */
fd = open(logfile, O_WRONLY | O_CREAT, 0666); fd = open(dbpath, O_WRONLY | O_CREAT, 0666);
if (-1 == fd) { if (-1 == fd) {
cgi_error("Unable to open log"); cgi_error("Unable to open log");
} }

View File

@ -6,7 +6,7 @@
#define TEAM_MAX 40 #define TEAM_MAX 40
#define CAT_MAX 40 #define CAT_MAX 40
int cgi_init(); int cgi_init(char *global_argv[]);
size_t cgi_item(char *str, size_t maxlen); size_t cgi_item(char *str, size_t maxlen);
void cgi_head(char *title); void cgi_head(char *title);
void cgi_foot(); void cgi_foot();
@ -14,12 +14,10 @@ void cgi_page(char *title, char *fmt, ...);
void cgi_error(char *fmt, ...); void cgi_error(char *fmt, ...);
#define teamdir "/var/lib/ctf/teams/names"
#define pointsdir "/var/lib/ctf/points/new"
int fgrepx(char const *needle, char const *filename); int fgrepx(char const *needle, char const *filename);
char *srv_path(char const *fmt, ...);
int team_exists(char const *teamhash); int team_exists(char const *teamhash);
int award_points(char const *teamhash, int award_points(char const *teamhacsh,
char const *category, char const *category,
long point); long point);
void award_and_log_uniquely(char const *team, void award_and_log_uniquely(char const *team,

View File

@ -8,13 +8,11 @@
#include <stdio.h> #include <stdio.h>
#include <stddef.h> #include <stddef.h>
#include <ctype.h> #include <ctype.h>
#include "common.h"
#include "xxtea.h" #include "xxtea.h"
#define itokenlen 3 #define itokenlen 3
char const *keydir = "/var/lib/ctf/tokend/keys";
char const *tokenlog = "/var/lib/ctf/tokend/tokens.log";
char const consonants[] = "bcdfghklmnprstvz"; char const consonants[] = "bcdfghklmnprstvz";
char const vowels[] = "aeiouy"; char const vowels[] = "aeiouy";
@ -91,16 +89,10 @@ main(int argc, char *argv[])
/* Read in that service's key. */ /* Read in that service's key. */
{ {
char path[100];
int fd; int fd;
size_t len; size_t len;
int ret;
ret = snprintf(path, sizeof(path), fd = open(srv_path("token.keys/%s", service), O_RDONLY);
"%s/%s.key", keydir, service);
if (ret < sizeof(path)) {
fd = open(path, O_RDONLY);
}
if (-1 == fd) { if (-1 == fd) {
write(1, "!nosvc", 6); write(1, "!nosvc", 6);
return 0; return 0;
@ -139,7 +131,7 @@ main(int argc, char *argv[])
int ret; int ret;
do { do {
fd = open(tokenlog, O_WRONLY | O_CREAT, 0644); fd = open(srv_path("tokens.db"), O_WRONLY | O_CREAT, 0644);
if (-1 == fd) break; if (-1 == fd) break;
ret = lockf(fd, F_LOCK, 0); ret = lockf(fd, F_LOCK, 0);

View File

@ -1,18 +1,21 @@
#include <stdlib.h> #include <stdlib.h>
#include "common.h" #include "common.h"
char const *logfile = "/var/lib/ctf/puzzler.log";
int int
main(int argc, char *argv) main(int argc, char *argv[])
{ {
char team[TEAM_MAX]; char team[TEAM_MAX];
char category[CAT_MAX]; char category[CAT_MAX];
char points_str[5]; char points_str[5];
char answer[500]; char answer[500];
long points; long points = 0;
if (-1 == cgi_init()) { team[0] = 0;
category[0] = 0;
answer[0] = 0;
if (-1 == cgi_init(argv)) {
return 0; return 0;
} }
@ -58,26 +61,24 @@ main(int argc, char *argv)
/* Check answer (also assures category exists) */ /* Check answer (also assures category exists) */
{ {
char filename[100];
char needle[400]; char needle[400];
my_snprintf(filename, sizeof(filename),
"/srv/%s/answers.txt", category);
my_snprintf(needle, sizeof(needle), my_snprintf(needle, sizeof(needle),
"%ld %s", points, answer); "%ld %s", points, answer);
if (! fgrepx(needle, filename)) { if (! fgrepx(needle,
srv_path("packages/%s/answers.txt", category))) {
cgi_page("Wrong answer", ""); cgi_page("Wrong answer", "");
} }
} }
award_and_log_uniquely(team, category, points, award_and_log_uniquely(team, category, points,
logfile, "%s %s %ld", team, category, points); "puzzler.db",
"%s %s %ld", team, category, points);
cgi_page("Points awarded", cgi_page("Points awarded",
("<p>%d points for %s.</p>\n" ("<p>%d points for %s.</p>"
"<p>Patience please: it may be up to 1 minute before " "<!-- awarded %d -->"),
"the next puzzle opens in this category.</p>"), points, team, points);
points, team);
return 0; return 0;
} }

View File

@ -27,7 +27,7 @@ int ncats = 0;
void void
read_points_by_cat() read_points_by_cat()
{ {
FILE *f = fopen("/var/lib/ctf/puzzler.log", "r"); FILE *f = fopen(srv_path("puzzler.db"), "r");
char cat[CAT_MAX]; char cat[CAT_MAX];
long points; long points;
int i; int i;
@ -59,10 +59,13 @@ main(int argc, char *argv[])
int i; int i;
DIR *srv; DIR *srv;
if (-1 == cgi_init(argv)) {
return 0;
}
read_points_by_cat(); read_points_by_cat();
/* Open /srv/ */ srv = opendir(srv_path("packages"));
srv = opendir("/srv");
if (NULL == srv) { if (NULL == srv) {
cgi_error("Cannot opendir(\"/srv\")"); cgi_error("Cannot opendir(\"/srv\")");
} }
@ -75,7 +78,6 @@ main(int argc, char *argv[])
struct dirent *e = readdir(srv); struct dirent *e = readdir(srv);
char *cat = e->d_name; char *cat = e->d_name;
DIR *puzzles; DIR *puzzles;
char path[PATH_MAX];
long catpoints[PUZZLES_MAX]; long catpoints[PUZZLES_MAX];
size_t ncatpoints = 0; size_t ncatpoints = 0;
@ -84,9 +86,8 @@ main(int argc, char *argv[])
/* We have to lstat anyway to see if it's a directory; may as /* We have to lstat anyway to see if it's a directory; may as
well just barge ahead and watch for errors. */ well just barge ahead and watch for errors. */
/* Open /srv/$cat/puzzles/ */ /* Open /srv/ctf/$cat/puzzles/ */
my_snprintf(path, sizeof(path), "/srv/%s/puzzles", cat); puzzles = opendir(srv_path("packages/%s/puzzles", cat));
puzzles = opendir(path);
if (NULL == puzzles) { if (NULL == puzzles) {
continue; continue;
} }

View File

@ -8,9 +8,11 @@ fi
# Don't overwrite files # Don't overwrite files
set -C set -C
base=${CTF_BASE:-/srv/ctf}
# Assign a color. I spent two days selecting this color pallette for # Assign a color. I spent two days selecting this color pallette for
# people with protanopia. Please don't change these colors. # people with protanopia. Please don't change these colors.
nteams=$(ls /var/lib/ctf/teams/names/ | wc -l) nteams=$(ls $base/teams/names/ | wc -l)
case $(expr $nteams % 15) in case $(expr $nteams % 15) in
0) color=8c7a69 ;; 0) color=8c7a69 ;;
1) color=7f7062 ;; 1) color=7f7062 ;;
@ -39,7 +41,7 @@ esac
# me since all team hashes are in the set /[0-9a-f]{8}/. # me since all team hashes are in the set /[0-9a-f]{8}/.
hash=$(echo "$1" | md5sum | cut -b 1-8) hash=$(echo "$1" | md5sum | cut -b 1-8)
echo "$1" > /var/lib/ctf/teams/names/$hash echo "$1" > $base/teams/names/$hash
echo "$color" > /var/lib/ctf/teams/colors/$hash echo "$color" > $base/teams/colors/$hash
echo "Registered with hash $hash" echo "Registered with hash: $hash"

View File

@ -63,6 +63,11 @@ function output( t, c) {
} }
BEGIN { BEGIN {
base = ENVIRON["CTF_BASE"]
if (! base) {
base = "/srv/ctf"
}
# Only display two decimal places # Only display two decimal places
CONVFMT = "%.2f" CONVFMT = "%.2f"
@ -95,11 +100,11 @@ BEGIN {
# Get team colors and names # Get team colors and names
for (team in teams) { for (team in teams) {
fn = "/var/lib/ctf/teams/colors/" team fn = base "/teams/colors/" team
getline colors_by_team[team] < fn getline colors_by_team[team] < fn
close(fn) close(fn)
fn = "/var/lib/ctf/teams/names/" team fn = base "/teams/names/" team
getline names_by_team[team] < fn getline names_by_team[team] < fn
close(fn) close(fn)
} }

119
src/test.sh Executable file
View File

@ -0,0 +1,119 @@
#! /bin/sh -e
die () {
echo $*
exit 1
}
CTF_BASE=/tmp/ctf-test.$$ export CTF_BASE
trap "rm -rf $CTF_BASE" 0
mkdir $CTF_BASE
# Some skeletal structure
mkdir -p $CTF_BASE/points.new
# Set up some packages
for cat in cat1 cat2 cat3; do
mkdir -p $CTF_BASE/packages/$cat
cat >$CTF_BASE/packages/$cat/answers.txt <<EOF
10 ${cat}answer10
20 ${cat}answer20
30 ${cat}answer30
EOF
for i in 10 20 30; do
mkdir -p $CTF_BASE/packages/$cat/puzzles/$i
done
done
# Set up some teams
mkdir -p $CTF_BASE/teams/names
mkdir -p $CTF_BASE/teams/colors
for team in team1 team2 team3; do
hash=$(./register $team | awk '{print $NF;}')
done
##
## Puzzler tests
##
if ./puzzles.cgi | grep 20; then
die "20 points puzzles shouldn't show up here"
fi
if ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer20 | grep -q 'awarded'; then
die "Awarded points with wrong answer"
fi
if ./puzzler.cgi t=$hash c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then
die "Awarded points with wrong category"
fi
if ./puzzler.cgi t=$hash c=cat1 p=20 a=cat1answer10 | grep -q 'awarded'; then
die "Awarded points with wrong point value"
fi
if ./puzzler.cgi t=merfmerfmerfmerf c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then
die "Awarded points with bad team"
fi
if ! ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then
die "Didn't award points for correct answer"
fi
if ! ./puzzles.cgi | grep -q 20; then
die "20 point answer didn't show up"
fi
if ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then
die "Awarded same points twice"
fi
##
## Scoreboard tests
##
if ! cat $CTF_BASE/points.new/* | ./scoreboard | grep -q 'total.*team3: 1'; then
die "Scoreboard total incorrect"
fi
if ! cat $CTF_BASE/points.new/* | ./scoreboard | grep -q 'cat1.*team3: 10'; then
die "Scoreboard cat1 points incorrect"
fi
##
## Token tests
##
mkdir -p $CTF_BASE/token.keys
echo -n '0123456789abcdef' > $CTF_BASE/token.keys/tokencat
# in.tokend uses a random number generator
echo -n 'tokencat' | ./in.tokend > /dev/null
if ! grep -q 'tokencat:x....-....x' $CTF_BASE/tokens.db; then
die "in.tokend didn't write to database"
fi
if ./claim.cgi t=lalalala k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
die "claim.cgi gave points to a bogus team"
fi
if ./claim.cgi t=$hash k=tokencat:xanax-xanax | grep -q success; then
die "claim.cgi gave points for a bogus token"
fi
if ! ./claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
die "claim.cgi didn't give me any points"
fi
if ./claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
die "claim.cgi gave me points twice for the same token"
fi
if ! [ -f $CTF_BASE/points.new/*.$hash.tokencat.1 ]; then
die "claim.cgi didn't actually record any points"
fi
echo "$0: All tests passed!"
echo "$0: Aren't you just the best programmer ever?"