mirror of https://github.com/dirtbags/moth.git
move mcp to new points architecture
This commit is contained in:
parent
34d4dae4a4
commit
d95aecd25d
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
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 */
|
||||
p += 1;
|
||||
}
|
||||
if (NULL == fgets(line, sizeof line, f)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
return found;
|
||||
if (f) {
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue