move mcp to new points architecture

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

View File

@ -1,7 +1,5 @@
#! /bin/sh -e #! /bin/sh -e
PATH=/bin:/opt/ctfbase/bin
if [ $# -ne 1 ]; then if [ $# -ne 1 ]; then
echo "Usage: $0 TEAM" 1>&2 echo "Usage: $0 TEAM" 1>&2
exit 64 exit 64
@ -20,8 +18,8 @@ www=${CTF_BASE:-/var/www}
mkdir -p $base/teams/names mkdir -p $base/teams/names
mkdir -p $base/teams/colors mkdir -p $base/teams/colors
[ -f $base/teams/nonce ] || dd if=/dev/urandom count=1 | md5sum | cut -b 1-8 > $base/teams/nonce [ -f $base/teams/salt ] || dd if=/dev/urandom count=1 2>/dev/null | md5sum | cut -b 1-8 > $base/teams/salt
nonce=$(cat $base/teams/nonce) salt=$(cat $base/teams/salt)
# Assign a color. I spent weeks selecting a color pallette that # Assign a color. I spent weeks selecting a color pallette that
# wouldn't be hell on people with protanopia. Please don't change these # 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 # 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 # 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}/. # 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 "$1" > $base/teams/names/$hash
echo "$color" > $base/teams/colors/$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 $NEWDIR
install -o ctf -m 0755 -d /var/lib/ctf/points.tmp 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 # Create some files
CLAIM=/var/lib/ctf/claim.db CLAIM=/var/lib/ctf/claim.db
touch $CLAIM 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? */ /* 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 */ /* Award points */
{ {
char *p = token; char *p = token;
@ -62,6 +53,7 @@ main(int argc, char *argv[])
char category[40]; char category[40];
char points_s[40]; char points_s[40];
int points; int points;
int ret;
/* Pull category name out of the token */ /* Pull category name out of the token */
for (q = category; *p && (*p != ':'); p += 1) { for (q = category; *p && (*p != ':'); p += 1) {
@ -70,6 +62,11 @@ main(int argc, char *argv[])
*q = '\0'; *q = '\0';
if (p) p += 1; 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) */ /* Pull point value out of the token (if it has one) */
for (q = points_s; *p && (*p != ':'); p += 1) { for (q = points_s; *p && (*p != ':'); p += 1) {
*(q++) = *p; *(q++) = *p;
@ -78,11 +75,9 @@ main(int argc, char *argv[])
points = atoi(points_s); points = atoi(points_s);
if (0 == points) points = 1; if (0 == points) points = 1;
{ ret = award_points(team, category, points, token);
char line[200]; if (ret < 0) {
cgi_fail(ret);
my_snprintf(line, sizeof(line), "%s %s", team, token);
award_and_log_uniquely(team, category, points, state_path("claim.db"), line);
} }
} }

View File

@ -228,6 +228,21 @@ cgi_result(int code, char *desc, char *fmt, ...)
exit(0); 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 void
cgi_page(char *title, char *fmt, ...) cgi_page(char *title, char *fmt, ...)
{ {
@ -252,44 +267,55 @@ cgi_error(char *text)
* Common routines * Common routines
*/ */
/* cut -d$ANCHOR -f2- | grep -Fx "$NEEDLE" */
#define EOL(c) ((EOF == (c)) || ('\n' == (c)))
int int
fgrepx(char const *needle, char const *filename) anchored_search(char const *filename, char const *needle, char const anchor)
{ {
FILE *f; FILE *f = fopen(filename, "r");
int found = 0; size_t nlen = strlen(needle);
char const *p = needle; char line[1024];
int ret = 0;
f = fopen(filename, "r"); while (f) {
if (f) { char *p;
while (1) {
int c = fgetc(f);
/* This list of cases would have looked so much nicer in OCaml. I if (NULL == fgets(line, sizeof line, f)) {
apologize. */ break;
if (EOL(c) && ('\0' == *p)) { }
found = 1;
break; /* Find anchor */
} else if (EOF == c) { /* End of file */ if (anchor) {
break; p = strchr(line, anchor);
} else if (('\0' == p) || (*p != (char)c)) { if (! p) {
p = needle; continue;
/* Discard the rest of the line */ }
do { p += 1;
c = fgetc(f); } else {
} while (! EOL(c)); p = line;
} else if (EOL(c)) { }
p = needle;
} else { /* It matched */ /* Don't bother with strcmp if string lengths differ.
p += 1; 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;
}
} }
fclose(f);
}
return found; if (f) {
fclose(f);
}
return ret;
} }
void void
@ -343,9 +369,8 @@ mkpath(char const *base, char const *fmt, va_list ap)
char relpath[PATH_MAX]; char relpath[PATH_MAX];
static char path[PATH_MAX]; static char path[PATH_MAX];
char const *var; 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'; relpath[sizeof(relpath) - 1] = '\0';
var = getenv("CTF_BASE"); var = getenv("CTF_BASE");
@ -409,26 +434,36 @@ team_exists(char const *teamhash)
return 1; return 1;
} }
/* Return values:
-1: general error
-2: no such team
-3: points already awarded
*/
int int
award_points(char const *teamhash, award_points(char const *teamhash,
char const *category, char const *category,
long points) const long points,
char const *uid)
{ {
char line[100]; char line[100];
int linelen; int linelen;
char *filename; char *filename;
int fd; FILE *f;
time_t now = time(NULL); time_t now = time(NULL);
if (! team_exists(teamhash)) { if (! team_exists(teamhash)) {
return -2; return ERR_NOTEAM;
} }
linelen = snprintf(line, sizeof(line), linelen = snprintf(line, sizeof(line),
"%lu %s %s %ld\n", "%s %s %ld %s",
(unsigned long)now, teamhash, category, points); teamhash, category, points, uid);
if (sizeof(line) <= linelen) { 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. /* 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 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 that appends, edit the file at leisure, and then start the appender
back up, all without affecting things trying to score: they're back up, all without affecting things trying to score: they're
still able to record their score and move on. You don't even still able to record their score and move on.
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.
*/ */
filename = state_path("points.tmp/%d.%d.%s.%s.%ld", filename = state_path("points.tmp/%lu.%d.%s.%s.%ld",
now, getpid(), (unsigned long)now, getpid(),
teamhash, category, points); teamhash, category, points);
f = fopen(filename, "w");
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666); if (! f) {
if (-1 == fd) { return ERR_GENERAL;
return -1;
} }
if (-1 == write(fd, line, linelen)) { if (EOF == fprintf(f, "%lu %s\n", (unsigned long)now, line)) {
close(fd); return ERR_GENERAL;
return -1;
} }
close(fd); fclose(f);
/* Rename into points.new */ /* Rename into points.new */
{ {
char ofn[PATH_MAX]; char ofn[PATH_MAX];
strncpy(ofn, filename, sizeof(ofn)); strncpy(ofn, filename, sizeof(ofn));
filename = state_path("points.new/%d.%d.%s.%s.%ld", filename = state_path("points.new/%lu.%d.%s.%s.%ld",
now, getpid(), (unsigned long)now, getpid(),
teamhash, category, points); teamhash, category, points);
rename(ofn, filename); rename(ofn, filename);
} }
@ -487,49 +509,3 @@ award_points(char const *teamhash,
return 0; 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 TOKEN_MAX 80
#define itokenlen 5 #define itokenlen 5
#define ERR_GENERAL -1
#define ERR_NOTEAM -2
#define ERR_CLAIMED -3
int cgi_init(char *global_argv[]); 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();
void cgi_result(int code, char *desc, char *fmt, ...); void cgi_result(int code, char *desc, char *fmt, ...);
void cgi_fail(int err);
void cgi_page(char *title, char *fmt, ...); void cgi_page(char *title, char *fmt, ...);
void cgi_error(char *text); 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); void urandom(char *buf, size_t buflen);
int my_snprintf(char *buf, size_t buflen, char *fmt, ...); int my_snprintf(char *buf, size_t buflen, char *fmt, ...);
char *state_path(char const *fmt, ...); char *state_path(char const *fmt, ...);
char *package_path(char const *fmt, ...); char *package_path(char const *fmt, ...);
int team_exists(char const *teamhash); int team_exists(char const *teamhash);
int award_points(char const *teamhacsh, int award_points(char const *teamhash,
char const *category, char const *category,
long point); long point,
void award_and_log_uniquely(char const *team, char const *uid);
char const *category,
long points,
char const *logfile,
char const *line);
#endif #endif

View File

@ -1,6 +1,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sysexits.h> #include <sysexits.h>
#include <time.h>
#include "common.h" #include "common.h"
int int
@ -8,9 +9,10 @@ main(int argc, char *argv[])
{ {
int points; int points;
int ret; int ret;
char comment[512];
if (argc != 4) { if (argc != 5) {
fprintf(stderr, "Usage: pointscli TEAM CATEGORY POINTS\n"); fprintf(stderr, "Usage: pointscli TEAM CATEGORY POINTS 'COMMENT'\n");
return EX_USAGE; return EX_USAGE;
} }
@ -20,17 +22,22 @@ main(int argc, char *argv[])
return EX_USAGE; 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) { switch (ret) {
case -3: case ERR_GENERAL:
fprintf(stderr, "Runtime error\n"); perror("General error");
return EX_SOFTWARE; return EX_UNAVAILABLE;
case -2: case ERR_NOTEAM:
fprintf(stderr, "No such team\n"); fprintf(stderr, "No such team\n");
return EX_NOUSER; return EX_NOUSER;
case -1: case ERR_CLAIMED:
perror("Couldn't award points"); fprintf(stderr, "Duplicate entry\n");
return EX_UNAVAILABLE; return EX_DATAERR;
default:
fprintf(stderr, "Error %d\n", ret);
return EX_SOFTWARE;
} }
return 0; 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) */ /* Validate category name (prevent directory traversal) */
{ {
char *p; char *p;
@ -62,19 +57,16 @@ main(int argc, char *argv[])
my_snprintf(needle, sizeof(needle), "%ld %s", points, answer); my_snprintf(needle, sizeof(needle), "%ld %s", points, answer);
if (! fgrepx(needle, if (! anchored_search(package_path("%s/answers.txt", category), needle, 0)) {
package_path("%s/answers.txt", category))) {
cgi_page("Wrong answer", ""); cgi_page("Wrong answer", "");
} }
} }
{ {
char line[200]; int ret = award_points(team, category, points, "P");
if (ret < 0) {
my_snprintf(line, sizeof(line), cgi_fail(ret);
"%s %s %ld", team, category, points); }
award_and_log_uniquely(team, category, points,
state_path("puzzles.db"), line);
} }
cgi_page("Points awarded", cgi_page("Points awarded",