move mcp to new points architecture

This commit is contained in:
Neale Pickett 2012-05-30 18:04:24 -06:00
parent 34d4dae4a4
commit d95aecd25d
7 changed files with 129 additions and 168 deletions

View File

@ -1,7 +1,5 @@
#! /bin/sh -e
PATH=/bin:/opt/ctfbase/bin
if [ $# -ne 1 ]; then
echo "Usage: $0 TEAM" 1>&2
exit 64
@ -20,8 +18,8 @@ www=${CTF_BASE:-/var/www}
mkdir -p $base/teams/names
mkdir -p $base/teams/colors
[ -f $base/teams/nonce ] || dd if=/dev/urandom count=1 | md5sum | cut -b 1-8 > $base/teams/nonce
nonce=$(cat $base/teams/nonce)
[ -f $base/teams/salt ] || dd if=/dev/urandom count=1 2>/dev/null | md5sum | cut -b 1-8 > $base/teams/salt
salt=$(cat $base/teams/salt)
# Assign a color. I spent weeks selecting a color pallette that
# wouldn't be hell on people with protanopia. Please don't change these
@ -48,7 +46,7 @@ esac
# Compute hash of team name; they'll use this for everything in the
# contest instead of their team name, which makes stuff much easier on
# me since all team hashes are in the set /[0-9a-f]{8}/.
hash=$(printf "%s:%s" $nonce "$1" | md5sum | cut -b 1-8)
hash=$(printf "%s:%s" $salt "$1" | md5sum | cut -b 1-8)
echo "$1" > $base/teams/names/$hash
echo "$color" > $base/teams/colors/$hash

View File

@ -17,14 +17,6 @@ install -d /var/lib/ctf
install -o ctf -m 0755 -d $NEWDIR
install -o ctf -m 0755 -d /var/lib/ctf/points.tmp
# Make tokens database now, this is as good a time as any
TOKENS=/var/lib/ctf/tokens.db
for fn in $TOKENS /opt/*/tokens.txt; do
[ -f "$fn" ] || continue
cat $fn
done | sort | uniq > $TOKENS.new
mv $TOKENS.new $TOKENS
# Create some files
CLAIM=/var/lib/ctf/claim.db
touch $CLAIM

View File

@ -29,9 +29,6 @@ main(int argc, char *argv[])
}
}
if (! team_exists(team)) {
cgi_result(409, "No such team", "<p>There is no team with that hash.</p>");
}
/* Any weird characters in token name? */
{
@ -49,12 +46,6 @@ main(int argc, char *argv[])
}
}
/* Does the token exist? */
if (! fgrepx(token, state_path("tokens.db"))) {
cgi_result(409, "No such token", "<p>This token has not been issued.</p>");
}
/* Award points */
{
char *p = token;
@ -62,6 +53,7 @@ main(int argc, char *argv[])
char category[40];
char points_s[40];
int points;
int ret;
/* Pull category name out of the token */
for (q = category; *p && (*p != ':'); p += 1) {
@ -70,6 +62,11 @@ main(int argc, char *argv[])
*q = '\0';
if (p) p += 1;
/* Now we can check to see if it's valid */
if (! anchored_search(package_path("%s/tokens.txt", category), token, 0)) {
cgi_result(409, "No such token", "<p>This token has not been issued.</p>");
}
/* Pull point value out of the token (if it has one) */
for (q = points_s; *p && (*p != ':'); p += 1) {
*(q++) = *p;
@ -78,11 +75,9 @@ main(int argc, char *argv[])
points = atoi(points_s);
if (0 == points) points = 1;
{
char line[200];
my_snprintf(line, sizeof(line), "%s %s", team, token);
award_and_log_uniquely(team, category, points, state_path("claim.db"), line);
ret = award_points(team, category, points, token);
if (ret < 0) {
cgi_fail(ret);
}
}

View File

@ -228,6 +228,21 @@ cgi_result(int code, char *desc, char *fmt, ...)
exit(0);
}
void
cgi_fail(int err)
{
switch (err) {
case ERR_GENERAL:
cgi_result(500, "Points not awarded", "<p>The server is unable to award your points at this time.</p>");
case ERR_NOTEAM:
cgi_result(409, "No such team", "<p>There is no team with that hash.</p>");
case ERR_CLAIMED:
cgi_result(409, "Already claimed", "<p>Your team has already claimed these points.</p>");
default:
cgi_result(409, "Failure", "<p>Failure code: %d</p>", err);
}
}
void
cgi_page(char *title, char *fmt, ...)
{
@ -252,44 +267,55 @@ cgi_error(char *text)
* Common routines
*/
#define EOL(c) ((EOF == (c)) || ('\n' == (c)))
/* cut -d$ANCHOR -f2- | grep -Fx "$NEEDLE" */
int
fgrepx(char const *needle, char const *filename)
anchored_search(char const *filename, char const *needle, char const anchor)
{
FILE *f;
int found = 0;
char const *p = needle;
FILE *f = fopen(filename, "r");
size_t nlen = strlen(needle);
char line[1024];
int ret = 0;
f = fopen(filename, "r");
if (f) {
while (1) {
int c = fgetc(f);
while (f) {
char *p;
/* This list of cases would have looked so much nicer in OCaml. I
apologize. */
if (EOL(c) && ('\0' == *p)) {
found = 1;
if (NULL == fgets(line, sizeof line, f)) {
break;
} else if (EOF == c) { /* End of file */
break;
} else if (('\0' == p) || (*p != (char)c)) {
p = needle;
/* Discard the rest of the line */
do {
c = fgetc(f);
} while (! EOL(c));
} else if (EOL(c)) {
p = needle;
} else { /* It matched */
}
/* Find anchor */
if (anchor) {
p = strchr(line, anchor);
if (! p) {
continue;
}
p += 1;
} else {
p = line;
}
/* Don't bother with strcmp if string lengths differ.
If this string is shorter than the previous, it's okay. This is
just a performance hack.
*/
if ((p[nlen] != '\n') &&
(p[nlen] != '\0')) {
continue;
}
p[nlen] = 0;
/* Okay, now we can compare! */
if (0 == strcmp(p, needle)) {
ret = 1;
break;
}
}
if (f) {
fclose(f);
}
return found;
return ret;
}
void
@ -343,9 +369,8 @@ mkpath(char const *base, char const *fmt, va_list ap)
char relpath[PATH_MAX];
static char path[PATH_MAX];
char const *var;
int len;
len = vsnprintf(relpath, sizeof(relpath) - 1, fmt, ap);
vsnprintf(relpath, sizeof(relpath) - 1, fmt, ap);
relpath[sizeof(relpath) - 1] = '\0';
var = getenv("CTF_BASE");
@ -409,26 +434,36 @@ team_exists(char const *teamhash)
return 1;
}
/* Return values:
-1: general error
-2: no such team
-3: points already awarded
*/
int
award_points(char const *teamhash,
char const *category,
long points)
const long points,
char const *uid)
{
char line[100];
int linelen;
char *filename;
int fd;
FILE *f;
time_t now = time(NULL);
if (! team_exists(teamhash)) {
return -2;
return ERR_NOTEAM;
}
linelen = snprintf(line, sizeof(line),
"%lu %s %s %ld\n",
(unsigned long)now, teamhash, category, points);
"%s %s %ld %s",
teamhash, category, points, uid);
if (sizeof(line) <= linelen) {
return -1;
return ERR_GENERAL;
}
if (anchored_search(state_path("points.log"), line, ' ')) {
return ERR_CLAIMED;
}
/* At one time I had this writing to a single log file, using lockf.
@ -443,43 +478,30 @@ award_points(char const *teamhash,
those files to the main log file, it is possible to stop the thing
that appends, edit the file at leisure, and then start the appender
back up, all without affecting things trying to score: they're
still able to record their score and move on. You don't even
really need an appender, but it does make things look a little
nicer on the fs.
The fact that this makes the code simpler is just gravy.
Note that doing this means there's a little time between when a
score's written and when the scoreboard (which only reads the log
file) picks it up. It's not a big deal for the points log, but
this situation makes this technique unsuitable for writing log
files that prevent people from double-scoring, like the puzzler or
token log.
still able to record their score and move on.
*/
filename = state_path("points.tmp/%d.%d.%s.%s.%ld",
now, getpid(),
filename = state_path("points.tmp/%lu.%d.%s.%s.%ld",
(unsigned long)now, getpid(),
teamhash, category, points);
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (-1 == fd) {
return -1;
f = fopen(filename, "w");
if (! f) {
return ERR_GENERAL;
}
if (-1 == write(fd, line, linelen)) {
close(fd);
return -1;
if (EOF == fprintf(f, "%lu %s\n", (unsigned long)now, line)) {
return ERR_GENERAL;
}
close(fd);
fclose(f);
/* Rename into points.new */
{
char ofn[PATH_MAX];
strncpy(ofn, filename, sizeof(ofn));
filename = state_path("points.new/%d.%d.%s.%s.%ld",
now, getpid(),
filename = state_path("points.new/%lu.%d.%s.%s.%ld",
(unsigned long)now, getpid(),
teamhash, category, points);
rename(ofn, filename);
}
@ -487,49 +509,3 @@ award_points(char const *teamhash,
return 0;
}
/** Award points iff they haven't been logged.
If [line] is not in [dbfile], append it and give [points] to [team]
in [category].
*/
void
award_and_log_uniquely(char const *team,
char const *category,
long points,
char const *dbpath,
char const *line)
{
int fd;
/* Make sure they haven't already claimed these points */
if (fgrepx(line, dbpath)) {
cgi_result(409, "Already claimed",
"<p>Your team has already claimed these points.</p>");
}
/* Open and lock logfile */
fd = open(dbpath, O_WRONLY | O_CREAT, 0666);
if (-1 == fd) {
cgi_error("Unable to open log");
}
if (-1 == lockf(fd, F_LOCK, 0)) {
cgi_error("Unable to lock log");
}
/* Award points */
if (0 != award_points(team, category, points)) {
cgi_error("Unable to award points");
}
/* Log that we did so */
lseek(fd, 0, SEEK_END);
if (-1 == write(fd, line, strlen(line))) {
cgi_error("Unable to append log");
}
if (-1 == write(fd, "\n", 1)) {
cgi_error("Unable to append log");
}
close(fd);
}

View File

@ -9,28 +9,29 @@
#define TOKEN_MAX 80
#define itokenlen 5
#define ERR_GENERAL -1
#define ERR_NOTEAM -2
#define ERR_CLAIMED -3
int cgi_init(char *global_argv[]);
size_t cgi_item(char *str, size_t maxlen);
void cgi_head(char *title);
void cgi_foot();
void cgi_result(int code, char *desc, char *fmt, ...);
void cgi_fail(int err);
void cgi_page(char *title, char *fmt, ...);
void cgi_error(char *text);
int fgrepx(char const *needle, char const *filename);
int anchored_search(char const *filename, char const *needle, const char anchor);
void urandom(char *buf, size_t buflen);
int my_snprintf(char *buf, size_t buflen, char *fmt, ...);
char *state_path(char const *fmt, ...);
char *package_path(char const *fmt, ...);
int team_exists(char const *teamhash);
int award_points(char const *teamhacsh,
int award_points(char const *teamhash,
char const *category,
long point);
void award_and_log_uniquely(char const *team,
char const *category,
long points,
char const *logfile,
char const *line);
long point,
char const *uid);
#endif

View File

@ -1,6 +1,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <time.h>
#include "common.h"
int
@ -8,9 +9,10 @@ main(int argc, char *argv[])
{
int points;
int ret;
char comment[512];
if (argc != 4) {
fprintf(stderr, "Usage: pointscli TEAM CATEGORY POINTS\n");
if (argc != 5) {
fprintf(stderr, "Usage: pointscli TEAM CATEGORY POINTS 'COMMENT'\n");
return EX_USAGE;
}
@ -20,17 +22,22 @@ main(int argc, char *argv[])
return EX_USAGE;
}
ret = award_points(argv[1], argv[2], points);
snprintf(comment, sizeof comment, "--%s", argv[4]);
ret = award_points(argv[1], argv[2], points, comment);
switch (ret) {
case -3:
fprintf(stderr, "Runtime error\n");
return EX_SOFTWARE;
case -2:
case ERR_GENERAL:
perror("General error");
return EX_UNAVAILABLE;
case ERR_NOTEAM:
fprintf(stderr, "No such team\n");
return EX_NOUSER;
case -1:
perror("Couldn't award points");
return EX_UNAVAILABLE;
case ERR_CLAIMED:
fprintf(stderr, "Duplicate entry\n");
return EX_DATAERR;
default:
fprintf(stderr, "Error %d\n", ret);
return EX_SOFTWARE;
}
return 0;

View File

@ -40,11 +40,6 @@ main(int argc, char *argv[])
}
}
/* Check to see if team exists */
if (! team_exists(team)) {
cgi_page("No such team", "");
}
/* Validate category name (prevent directory traversal) */
{
char *p;
@ -62,19 +57,16 @@ main(int argc, char *argv[])
my_snprintf(needle, sizeof(needle), "%ld %s", points, answer);
if (! fgrepx(needle,
package_path("%s/answers.txt", category))) {
if (! anchored_search(package_path("%s/answers.txt", category), needle, 0)) {
cgi_page("Wrong answer", "");
}
}
{
char line[200];
my_snprintf(line, sizeof(line),
"%s %s %ld", team, category, points);
award_and_log_uniquely(team, category, points,
state_path("puzzles.db"), line);
int ret = award_points(team, category, points, "P");
if (ret < 0) {
cgi_fail(ret);
}
}
cgi_page("Points awarded",