installing okay now

This commit is contained in:
Neale Pickett 2015-04-10 16:37:21 -06:00
parent e5de92b049
commit dcc99528a8
10 changed files with 539 additions and 491 deletions

View File

@ -1,58 +0,0 @@
#! /bin/sh
echo "Figuring out web user..."
for www in www-data http _; do
id $www && break
done
if [ $www = _ ]; then
echo "Unable to determine httpd user on this system. Dying."
exit 1
fi
initialize () {
for i in points.new points.tmp teams; do
mkdir -p state/$i
setfacl -m ${www}:rwx state/$i
done
>> state/points.log
hd < /dev/urandom | awk '{print $3 $4 $5 $6;}' | head -n 100 > state/teams/assigned.txt
}
once () {
if [ -f disabled ]; then
return
fi
if ! [ -d state ]; then
initialize $1
fi
# Collect new points
find state/points.new -type f | while read fn; do
cat $fn >> state/points.log
rm $fn
done
# Generate new puzzles.html
if $KOTH_BASE/puzzles.cgi > www/puzzles.new; then
mv www/puzzles.new www/puzzles.html
fi
# Generate new points.json
if $KOTH_BASE/points > www/points.new; then
mv www/points.new www/points.json
fi
}
cd $(basename $0)
KOTH_BASE=$(pwd)
while true; do
for dn in $KOTH_BASE/*; do
cd $dn
once
done
sleep 5
done

37
bin/new Executable file
View File

@ -0,0 +1,37 @@
#! /bin/sh
newdir=$1
if [ -z "$newdir" ]; then
echo "Usage: $0 NEWDIR"
exit 1
fi
KOTH_BASE=$(cd $(dirname $0)/.. && pwd)
echo "Figuring out web user..."
for www in www-data http _; do
id $www && break
done
if [ $www = _ ]; then
echo "Unable to determine httpd user on this system. Dying."
exit 1
fi
mkdir -p $newdir
cd $newdir
for i in points.new points.tmp teams; do
mkdir -p state/$i
setfacl -m ${www}:rwx state/$i
done
>> state/points.log
if ! [ -f assigned.txt ]; then
hd < /dev/urandom | awk '{print $3 $4 $5 $6;}' | head -n 100 > assigned.txt
fi
mkdir -p www
cp -r $KOTH_BASE/html/* www/
cp $KOTH_BASE/bin/*.cgi www/

30
bin/once Executable file
View File

@ -0,0 +1,30 @@
#! /bin/sh
cd $(dirname $0)
# Do nothing if `disabled` is present
if [ -f disabled ]; then
exit
fi
# Reset to initial state if `reset` is present
if [ -f reset ]; then
rm -f state/teams/* state/points.new/* state/points.tmp/*
> state/points.log
fi
# Collect new points
find state/points.new -type f | while read fn; do
cat $fn >> state/points.log
rm $fn
done
# Generate new puzzles.html
if $KOTH_BASE/puzzles.cgi > www/puzzles.new; then
mv www/puzzles.new www/puzzles.html
fi
# Generate new points.json
if $KOTH_BASE/points > www/points.new; then
mv www/points.new www/points.json
fi

View File

@ -40,7 +40,7 @@ EOF
if [ -z "$hash" ] || [ -z "$team" ]; then if [ -z "$hash" ] || [ -z "$team" ]; then
echo "<p>Empty field, cannot complete request</p>" echo "<p>Empty field, cannot complete request</p>"
elif ! grep -q "^$hash$" state/teams/assigned.txt; then elif ! grep -q "^$hash$" assigned.txt; then
echo "<p>That hash has not been assigned.</p>" echo "<p>That hash has not been assigned.</p>"
elif [ -f state/teams/names/$hash ]; then elif [ -f state/teams/names/$hash ]; then
echo "<p>That hash has already been registered.</p>" echo "<p>That hash has already been registered.</p>"

View File

@ -8,7 +8,7 @@
<h1>Credits</h1> <h1>Credits</h1>
<section> <section>
<p>Dirtbags King of the Hill was created by:</p> <h2>Created By</h2>
<ul> <ul>
<li>Neale Pickett</li> <li>Neale Pickett</li>
@ -26,18 +26,23 @@
<li>William Phillips</li> <li>William Phillips</li>
<li>Should your name be here? Please remind me!</li> <li>Should your name be here? Please remind me!</li>
</ul> </ul>
</section>
<p>Parts of this contest were inspired by contests from:</p> <section>
<h2>Inspiration</h2>
<ul> <ul>
<li>DC949</li> <li>DC949</li>
<li>Tube Warriors</li> <li>Tube Warriors</li>
<li>Sandia National Laboratories</li> <li>Sandia National Laboratories</li>
</ul> </ul>
</section>
<section>
<h2>Thanks</h2>
<p> <p>
Lastly, this contest would not exist were it not for hundreds of This contest would not exist were it not for hundreds of
thousands of lines of code from free software authors around the thousands of lines of code from free software authors around the
world, including: world, including:
</p> </p>
<ul> <ul>
<li>Busybox</li> <li>Busybox</li>

View File

@ -6,7 +6,15 @@
<link rel="stylesheet" href="css/style.css" type="text/css"> <link rel="stylesheet" href="css/style.css" type="text/css">
</head> </head>
<body> <body>
<h1>Tracer FIRE 6</h1> <h1>Tracer FIRE</h1>
<nav>
<ul>
<li><a href="register.html">Register</a></li>
<li><a href="puzzles.html">Puzzles</a></li>
<li><a href="scoreboard.html">Scoreboard</a></li>
</ul>
</nav>
<section> <section>
<h2>Getting Started</h2> <h2>Getting Started</h2>
@ -42,7 +50,7 @@
<h2>Reading Material</h2> <h2>Reading Material</h2>
<p> <p>
Stuck? Need a break? In the bathroom? Stuck? Taking a break?
Here are some things to read. Here are some things to read.
</p> </p>
@ -57,12 +65,5 @@
</ul> </ul>
</section> </section>
<nav>
<ul>
<li><a href="register.html">Register</a></li>
<li><a href="puzzles.html">Puzzles</a></li>
<li><a href="scoreboard.html">Scoreboard</a></li>
</ul>
</nav>
</body> </body>
</html> </html>

View File

@ -1,25 +1,28 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Team Registration</title> <title>Team Registration</title>
<link rel="stylesheet" href="ctf.css" type="text/css"> <link rel="stylesheet" href="css/style.css" type="text/css">
</head> </head>
<body> <body>
<h1>Team Registration</h1> <h1>Team Registration</h1>
<p>
Before you can use a token, you must choose a team name. <section>
You can only do this once per token, <p>
so make sure it's the team name you actually want. Before you can use a token, you must choose a team name.
Staff are unable to make changes to team names. You can only do this once per token,
</p> so make sure it's the team name you actually want.
<form method="get" action="register.cgi"> Staff are unable to make changes to team names.
<label>Team Hash (Token):</label> </p>
<input type="text" name="h"> <form method="get" action="register.cgi">
<br> <label>Team Hash (Token):</label>
<label>Team Name:</label> <input type="text" name="h">
<input type="text" name="n"> <br>
<br> <label>Team Name:</label>
<input type="submit" value="Register"> <input type="text" name="n">
</form> <br>
</body> <input type="submit" value="Register">
</form>
</section>
</body>
</html> </html>

60
install
View File

@ -1,6 +1,11 @@
#! /bin/sh #! /bin/sh
DESTDIR=${1:-/opt/koth} DESTDIR=$1
if [ -z "$DESTDIR" ]; then
echo "Usage: $0 DESTDIR"
exit
fi
cd $(dirname $0) cd $(dirname $0)
@ -14,15 +19,6 @@ older () {
return 1 return 1
} }
html () {
target=$DESTDIR/${1%mdwn}html
if older $target $1 tmpl/*; then
echo "HTML $1"
mkdir -p $(dirname $target)
./tmpl/mdwntohtml < $1 > $target
fi
}
copy () { copy () {
target=$DESTDIR/$1 target=$DESTDIR/$1
if older $target $1; then if older $target $1; then
@ -41,6 +37,41 @@ cc () {
fi fi
} }
cgi () {
target=$DESTDIR/www/$(basename $1 .c)
if older $target $@; then
mkdir -p $(dirname $target)
src=$1; shift
echo "CC $src"
gcc -Wall -Werror -o $target $@ src/common.c $src
fi
}
setup() {
echo "SETUP"
for i in points.new points.tmp teams; do
dir=$DESTDIR/state/$i
mkdir -p $dir
setfacl -m ${www}:rwx $dir
done
>> $DESTDIR/state/points.log
if ! [ -f $DESTDIR/assigned.txt ]; then
hd </dev/urandom | awk '{print $3 $4 $5 $6;}' | head -n 100 > assigned.txt
fi
}
echo "Figuring out web user..."
for www in www-data http _; do
id $www && break
done
if [ $www = _ ]; then
echo "Unable to determine httpd user on this system. Dying."
exit 1
fi
mkdir -p $DESTDIR || exit 1
git ls-files | while read fn; do git ls-files | while read fn; do
case "$fn" in case "$fn" in
install|.*) install|.*)
@ -53,8 +84,13 @@ git ls-files | while read fn; do
bin/*) bin/*)
copy $fn copy $fn
;; ;;
src/*.cgi.c|src/pointscli.c) src/common.c)
cc src/common.c $fn ;;
src/pointscli.c)
cc $fn src/common.c
;;
src/*.cgi.c)
cgi $fn
;; ;;
src/*.c) src/*.c)
cc $fn cc $fn

View File

@ -12,9 +12,9 @@
#include "common.h" #include "common.h"
#ifdef NODUMP #ifdef NODUMP
# define DUMPf(fmt, args...) #define DUMPf(fmt, args...)
#else #else
# define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args) #define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args)
#endif #endif
#define DUMP() DUMPf("") #define DUMP() DUMPf("")
#define DUMP_d(v) DUMPf("%s = %d", #v, v) #define DUMP_d(v) DUMPf("%s = %d", #v, v)
@ -29,237 +29,230 @@
/* /*
* CGI * CGI
*/ */
static int is_cgi = 0; static int is_cgi = 0;
static char **argv = NULL; static char **argv = NULL;
static int static int
read_char_argv() read_char_argv()
{ {
static int arg = 0; static int arg = 0;
static char *p; static char *p;
if (NULL == argv) { if (NULL == argv) {
return EOF; return EOF;
} }
if (0 == arg) { if (0 == arg) {
arg = 1; arg = 1;
p = argv[1]; p = argv[1];
} }
if (! p) { if (!p) {
return EOF; return EOF;
} else if (! *p) { } else if (!*p) {
arg += 1; arg += 1;
p = argv[arg]; p = argv[arg];
return '&'; return '&';
} }
return *(p++); return *(p++);
} }
static int static int
read_char_stdin() read_char_stdin()
{ {
static int inlen = -1; static int inlen = -1;
if (-1 == inlen) { if (-1 == inlen) {
char *p = getenv("CONTENT_LENGTH"); char *p = getenv("CONTENT_LENGTH");
if (p) { if (p) {
inlen = atoi(p); inlen = atoi(p);
if (inlen > POST_MAX) { if (inlen > POST_MAX) {
inlen = POST_MAX; inlen = POST_MAX;
} }
if (inlen < 0) { if (inlen < 0) {
inlen = 0; inlen = 0;
} }
} else { } else {
inlen = 0; inlen = 0;
} }
} }
if (inlen) { if (inlen) {
inlen -= 1; inlen -= 1;
return getchar(); return getchar();
} }
return EOF; return EOF;
} }
static int static int
read_char_query_string() read_char_query_string()
{ {
static char *p = (char *)-1; static char *p = (char *) -1;
if ((char *)-1 == p) { if ((char *) -1 == p) {
p = getenv("QUERY_STRING"); p = getenv("QUERY_STRING");
} }
if (! p) { if (!p) {
return EOF; return EOF;
} else if (! *p) { } else if (!*p) {
return EOF; return EOF;
} else { } else {
return *(p++); return *(p++);
} }
} }
static int (* read_char)() = read_char_argv; static int (*read_char) () = read_char_argv;
int int
cgi_init(char *global_argv[]) cgi_init(char *global_argv[])
{ {
char *rm = getenv("REQUEST_METHOD"); char *rm = getenv("REQUEST_METHOD");
if (! rm) { if (!rm) {
read_char = read_char_argv; read_char = read_char_argv;
argv = global_argv; argv = global_argv;
} else if (0 == strcmp(rm, "POST")) { } else if (0 == strcmp(rm, "POST")) {
read_char = read_char_stdin; read_char = read_char_stdin;
is_cgi = 1; is_cgi = 1;
} else if (0 == strcmp(rm, "GET")) { } else if (0 == strcmp(rm, "GET")) {
read_char = read_char_query_string; read_char = read_char_query_string;
is_cgi = 1; is_cgi = 1;
} else { } else {
printf(("405 Method not allowed\r\n" printf(("405 Method not allowed\r\n"
"Allow: GET, POST\r\n" "Allow: GET, POST\r\n" "Content-type: text/plain\r\n" "\r\n" "%s is not allowed.\n"), rm);
"Content-type: text/plain\r\n" return -1;
"\r\n" }
"%s is not allowed.\n"),
rm);
return -1;
}
return 0; return 0;
} }
static char static char
tonum(int c) tonum(int c)
{ {
if ((c >= '0') && (c <= '9')) { if ((c >= '0') && (c <= '9')) {
return c - '0'; return c - '0';
} }
if ((c >= 'a') && (c <= 'f')) { if ((c >= 'a') && (c <= 'f')) {
return 10 + c - 'a'; return 10 + c - 'a';
} }
if ((c >= 'A') && (c <= 'F')) { if ((c >= 'A') && (c <= 'F')) {
return 10 + c - 'A'; return 10 + c - 'A';
} }
return 0; return 0;
} }
static char static char
read_hex() read_hex()
{ {
int a = read_char(); int a = read_char();
int b = read_char(); int b = read_char();
return tonum(a)*16 + tonum(b); return tonum(a) * 16 + tonum(b);
} }
/* Read a key or a value. Since & and = aren't supposed to appear /*
outside of boundaries, we can use the same function for both. * Read a key or a value. Since & and = aren't supposed to appear outside of boundaries, we can use the same function for both.
*/ */
size_t size_t
cgi_item(char *str, size_t maxlen) cgi_item(char *str, size_t maxlen)
{ {
int c; int c;
size_t pos = 0; size_t pos = 0;
while (1) { while (1) {
c = read_char(); c = read_char();
switch (c) { switch (c) {
case EOF: case EOF:
case '=': case '=':
case '&': case '&':
str[pos] = '\0'; str[pos] = '\0';
return pos; return pos;
case '%': case '%':
c = read_hex(); c = read_hex();
break; break;
case '+': case '+':
c = ' '; c = ' ';
break; break;
} }
if (pos < maxlen - 1) { if (pos < maxlen - 1) {
str[pos] = c; str[pos] = c;
pos += 1; pos += 1;
} }
} }
} }
void void
cgi_head(char *title) cgi_head(char *title)
{ {
if (is_cgi) { if (is_cgi) {
printf("Content-type: text/html\r\n\r\n"); printf("Content-type: text/html\r\n\r\n");
} }
printf(("<!DOCTYPE html>\n" printf(("<!DOCTYPE html>\n"
"<html>\n" "<html>\n"
" <head>\n" " <head>\n"
" <title>%s</title>\n" " <title>%s</title>\n"
" <link rel=\"stylesheet\" href=\"ctf.css\" type=\"text/css\">\n" " <link rel=\"stylesheet\" href=\"css/style.css\">\n"
" </head>\n" " </head>\n"
" <body>\n" " <body><h1>%s</h1><section>\n"), title, title);
" <h1>%s</h1>\n"),
title, title);
} }
void void
cgi_foot() cgi_foot()
{ {
printf("\n" printf("\n</section></body></html>\n");
" </body>\n"
"</html>\n");
} }
void void
cgi_result(int code, char *desc, char *fmt, ...) cgi_result(int code, char *desc, char *fmt, ...)
{ {
va_list ap; va_list ap;
if (is_cgi) { if (is_cgi) {
printf("Status: %d %s\r\n", code, desc); printf("Status: %d %s\r\n", code, desc);
} }
cgi_head(desc); cgi_head(desc);
va_start(ap, fmt); va_start(ap, fmt);
vprintf(fmt, ap); vprintf(fmt, ap);
va_end(ap); va_end(ap);
cgi_foot(); cgi_foot();
exit(0); exit(0);
} }
void void
cgi_fail(int err) cgi_fail(int err)
{ {
switch (err) { switch (err) {
case ERR_GENERAL: case ERR_GENERAL:
cgi_result(500, "Points not awarded", "<p>The server is unable to award your points at this time.</p>"); cgi_result(500, "Points not awarded", "<p>The server is unable to award your points at this time.</p>");
case ERR_NOTEAM: case ERR_NOTEAM:
cgi_result(409, "No such team", "<p>There is no team with that hash.</p>"); cgi_result(409, "No such team", "<p>There is no team with that hash.</p>");
case ERR_CLAIMED: case ERR_CLAIMED:
cgi_result(409, "Already claimed", "<p>That is the correct answer, but your team has already claimed these points.</p>"); cgi_result(409, "Already claimed",
default: "<p>That is the correct answer, but your team has already claimed these points.</p>");
cgi_result(409, "Failure", "<p>Failure code: %d</p>", err); 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, ...)
{ {
va_list ap; va_list ap;
cgi_head(title); cgi_head(title);
va_start(ap, fmt); va_start(ap, fmt);
vprintf(fmt, ap); vprintf(fmt, ap);
va_end(ap); va_end(ap);
cgi_foot(); cgi_foot();
exit(0); exit(0);
} }
void void
cgi_error(char *text) cgi_error(char *text)
{ {
cgi_result(500, "Internal error", "<p>%s</p>", text); cgi_result(500, "Internal error", "<p>%s</p>", text);
} }
@ -267,276 +260,278 @@ cgi_error(char *text)
* Common routines * Common routines
*/ */
/* cut -d$ANCHOR -f2- | grep -Fx "$NEEDLE" */ /*
* cut -d$ANCHOR -f2- | grep -Fx "$NEEDLE"
*/
int int
anchored_search(char const *filename, char const *needle, char const anchor) anchored_search(char const *filename, char const *needle, char const anchor)
{ {
FILE *f = fopen(filename, "r"); FILE *f = fopen(filename, "r");
size_t nlen = strlen(needle); size_t nlen = strlen(needle);
char line[1024]; char line[1024];
int ret = 0; int ret = 0;
while (f) { while (f) {
char *p; char *p;
if (NULL == fgets(line, sizeof line, f)) { if (NULL == fgets(line, sizeof line, f)) {
break; break;
} }
/* Find anchor */ /*
if (anchor) { * Find anchor
p = strchr(line, anchor); */
if (! p) { if (anchor) {
continue; p = strchr(line, anchor);
} if (!p) {
p += 1; continue;
} else { }
p = line; 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 * Don't bother with strcmp if string lengths differ. If this string is shorter than the previous, it's okay. This
just a performance hack. * is just a performance hack.
*/ */
if ((p[nlen] != '\n') && if ((p[nlen] != '\n') && (p[nlen] != '\0')) {
(p[nlen] != '\0')) { continue;
continue; }
} p[nlen] = 0;
p[nlen] = 0;
/* Okay, now we can compare! */ /*
if (0 == strcmp(p, needle)) { * Okay, now we can compare!
ret = 1; */
break; if (0 == strcmp(p, needle)) {
} ret = 1;
} break;
}
}
if (f) { if (f) {
fclose(f); fclose(f);
} }
return ret; return ret;
} }
void void
urandom(char *buf, size_t buflen) urandom(char *buf, size_t buflen)
{ {
static int fd = -2; static int fd = -2;
if (-2 == fd) { if (-2 == fd) {
srandom(time(NULL) * getpid()); srandom(time(NULL) * getpid());
fd = open("/dev/urandom", O_RDONLY); fd = open("/dev/urandom", O_RDONLY);
} }
if (-1 != fd) { if (-1 != fd) {
int len; int len;
len = read(fd, buf, buflen); len = read(fd, buf, buflen);
if (len == buflen) { if (len == buflen) {
return; return;
} }
} }
/* Fall back to libc's crappy thing */ /*
{ * Fall back to libc's crappy thing
int i; */
{
int i;
for (i = 0; i < buflen; i += 1) { for (i = 0; i < buflen; i += 1) {
buf[i] = (char)random(); buf[i] = (char) random();
} }
} }
} }
int int
my_snprintf(char *buf, size_t buflen, char *fmt, ...) my_snprintf(char *buf, size_t buflen, char *fmt, ...)
{ {
int len; int len;
va_list ap; va_list ap;
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);
buf[buflen - 1] = '\0'; buf[buflen - 1] = '\0';
if (len >= buflen) { if (len >= buflen) {
return buflen - 1; return buflen - 1;
} else { } else {
return len; return len;
} }
} }
void void
ctf_chdir() ctf_chdir()
{ {
static int initialized = 0; static int initialized = 0;
int i; int i;
if (initialized) { if (initialized) {
return; return;
} }
initialized = 1; initialized = 1;
/* chdir to $CTF_BASE */ /*
{ * chdir to $CTF_BASE
char const *ctf_base = getenv("CTF_BASE"); */
{
char const *ctf_base = getenv("CTF_BASE");
if (ctf_base) { if (ctf_base) {
chdir(ctf_base); chdir(ctf_base);
} }
} }
/* Keep going up one directory until there's a packages directory */ /*
for (i = 0; i < 5; i += 1) { * Keep going up one directory until there's a packages directory
struct stat st; */
for (i = 0; i < 5; i += 1) {
struct stat st;
if ((0 == stat("packages", &st)) && if ((0 == stat("packages", &st)) && S_ISDIR(st.st_mode)) {
S_ISDIR(st.st_mode)) { return;
return; }
} chdir("..");
chdir(".."); }
} fprintf(stderr, "Can not determine CTF_BASE directory: exiting.\n");
fprintf(stderr, "Can not determine CTF_BASE directory: exiting.\n"); exit(66);
exit(66);
} }
static char * static char *
mkpath(char const *type, char const *fmt, va_list ap) mkpath(char const *type, char const *fmt, va_list ap)
{ {
char relpath[PATH_MAX]; char relpath[PATH_MAX];
static char path[PATH_MAX]; static char path[PATH_MAX];
ctf_chdir(); ctf_chdir();
vsnprintf(relpath, sizeof(relpath) - 1, fmt, ap); vsnprintf(relpath, sizeof(relpath) - 1, fmt, ap);
relpath[sizeof(relpath) - 1] = '\0'; relpath[sizeof(relpath) - 1] = '\0';
/* $CTF_BASE/type/relpath */ /*
my_snprintf(path, sizeof(path), "%s/%s", type, relpath); * $CTF_BASE/type/relpath
return path; */
my_snprintf(path, sizeof(path), "%s/%s", type, relpath);
return path;
} }
char * char *
state_path(char const *fmt, ...) state_path(char const *fmt, ...)
{ {
va_list ap; va_list ap;
char *ret; char *ret;
va_start(ap, fmt); va_start(ap, fmt);
ret = mkpath("state", fmt, ap); ret = mkpath("state", fmt, ap);
va_end(ap); va_end(ap);
return ret; return ret;
} }
char * char *
package_path(char const *fmt, ...) package_path(char const *fmt, ...)
{ {
va_list ap; va_list ap;
char *ret; char *ret;
va_start(ap, fmt); va_start(ap, fmt);
ret = mkpath("packages", fmt, ap); ret = mkpath("packages", fmt, ap);
va_end(ap); va_end(ap);
return ret; return ret;
} }
int int
team_exists(char const *teamhash) team_exists(char const *teamhash)
{ {
struct stat buf; struct stat buf;
int ret; int ret;
int i; int i;
if ((! teamhash) || (! *teamhash)) { if ((!teamhash) || (!*teamhash)) {
return 0; return 0;
} }
/* Check for invalid characters. */ /*
for (i = 0; teamhash[i]; i += 1) { * Check for invalid characters.
if (! isalnum(teamhash[i])) { */
return 0; for (i = 0; teamhash[i]; i += 1) {
} if (!isalnum(teamhash[i])) {
} return 0;
}
}
/* stat seems to be the preferred way to check for existence. */ /*
ret = stat(state_path("teams/names/%s", teamhash), &buf); * stat seems to be the preferred way to check for existence.
if (-1 == ret) { */
return 0; ret = stat(state_path("teams/names/%s", teamhash), &buf);
} if (-1 == ret) {
return 0;
}
return 1; return 1;
} }
/* Return values: /*
-1: general error * Return values: -1: general error -2: no such team -3: points already awarded
-2: no such team
-3: points already awarded
*/ */
int int
award_points(char const *teamhash, award_points(char const *teamhash, char const *category, const long points, char const *uid)
char const *category,
const long points,
char const *uid)
{ {
char line[100]; char line[100];
int linelen; int linelen;
char *filename; char *filename;
FILE *f; FILE *f;
time_t now = time(NULL); time_t now = time(NULL);
if (! team_exists(teamhash)) { if (!team_exists(teamhash)) {
return ERR_NOTEAM; return ERR_NOTEAM;
} }
linelen = snprintf(line, sizeof(line), linelen = snprintf(line, sizeof(line), "%s %s %ld %s", teamhash, category, points, uid);
"%s %s %ld %s", if (sizeof(line) <= linelen) {
teamhash, category, points, uid); return ERR_GENERAL;
if (sizeof(line) <= linelen) { }
return ERR_GENERAL;
}
if (anchored_search(state_path("points.log"), line, ' ')) { if (anchored_search(state_path("points.log"), line, ' ')) {
return ERR_CLAIMED; return ERR_CLAIMED;
} }
/* At one time I had this writing to a single log file, using lockf. /*
This works, as long as nobody ever tries to edit the log file. * At one time I had this writing to a single log file, using lockf. This works, as long as nobody ever tries to edit the
Editing the log file would require locking it, which would block * log file. Editing the log file would require locking it, which would block everything trying to score, effectively
everything trying to score, effectively taking down the entire * taking down the entire contest. If you can't lock it first--and nothing in busybox lets you do this--you have to bring
contest. If you can't lock it first--and nothing in busybox lets * down pretty much everything manually anyway.
you do this--you have to bring down pretty much everything manually *
anyway. * By putting new scores into new files and periodically appending 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.
*/
By putting new scores into new files and periodically appending filename = state_path("points.tmp/%lu.%d.%s.%s.%ld", (unsigned long) now, getpid(), teamhash, category, points);
those files to the main log file, it is possible to stop the thing f = fopen(filename, "w");
that appends, edit the file at leisure, and then start the appender if (!f) {
back up, all without affecting things trying to score: they're return ERR_GENERAL;
still able to record their score and move on. }
*/
filename = state_path("points.tmp/%lu.%d.%s.%s.%ld", if (EOF == fprintf(f, "%lu %s\n", (unsigned long) now, line)) {
(unsigned long)now, getpid(), return ERR_GENERAL;
teamhash, category, points); }
f = fopen(filename, "w");
if (! f) {
return ERR_GENERAL;
}
if (EOF == fprintf(f, "%lu %s\n", (unsigned long)now, line)) { fclose(f);
return ERR_GENERAL;
}
fclose(f); /*
* Rename into points.new
*/
{
char ofn[PATH_MAX];
/* Rename into points.new */ strncpy(ofn, filename, sizeof(ofn));
{ filename = state_path("points.new/%lu.%d.%s.%s.%ld", (unsigned long) now, getpid(), teamhash, category, points);
char ofn[PATH_MAX]; rename(ofn, filename);
}
strncpy(ofn, filename, sizeof(ofn)); return 0;
filename = state_path("points.new/%lu.%d.%s.%s.%ld",
(unsigned long)now, getpid(),
teamhash, category, points);
rename(ofn, filename);
}
return 0;
} }

View File

@ -7,42 +7,41 @@
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
int points; int points;
int ret; int ret;
char comment[512]; char comment[512];
if (argc != 5) { if (argc != 5) {
fprintf(stderr, fprintf(stderr, "Usage: pointscli TEAM CATEGORY POINTS 'COMMENT'\n");
"Usage: pointscli TEAM CATEGORY POINTS 'COMMENT'\n"); return EX_USAGE;
return EX_USAGE; }
} ctf_chdir();
ctf_chdir();
points = atoi(argv[3]); points = atoi(argv[3]);
if (0 == points) { if (0 == points) {
fprintf(stderr, "Error: award 0 points?\n"); fprintf(stderr, "Error: award 0 points?\n");
return EX_USAGE; return EX_USAGE;
} }
snprintf(comment, sizeof comment, "--%s", argv[4]); snprintf(comment, sizeof comment, "--%s", argv[4]);
ret = award_points(argv[1], argv[2], points, comment); ret = award_points(argv[1], argv[2], points, comment);
switch (ret) { switch (ret) {
case 0: case 0:
return 0; return 0;
case ERR_GENERAL: case ERR_GENERAL:
perror("General error"); perror("General error");
return EX_UNAVAILABLE; return EX_UNAVAILABLE;
case ERR_NOTEAM: case ERR_NOTEAM:
fprintf(stderr, "No such team\n"); fprintf(stderr, "No such team\n");
return EX_NOUSER; return EX_NOUSER;
case ERR_CLAIMED: case ERR_CLAIMED:
fprintf(stderr, "Duplicate entry\n"); fprintf(stderr, "Duplicate entry\n");
return EX_DATAERR; return EX_DATAERR;
default: default:
fprintf(stderr, "Error %d\n", ret); fprintf(stderr, "Error %d\n", ret);
return EX_SOFTWARE; return EX_SOFTWARE;
} }
return 0; return 0;
} }