2010-09-03 21:00:02 -06:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <unistd.h>
|
2010-09-11 22:57:46 -06:00
|
|
|
#include <stdarg.h>
|
2010-09-11 23:22:28 -06:00
|
|
|
#include <stdlib.h>
|
2010-09-03 21:00:02 -06:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <ctype.h>
|
2010-09-13 12:23:39 -06:00
|
|
|
#include <values.h>
|
2010-09-03 21:00:02 -06:00
|
|
|
#include <time.h>
|
|
|
|
#include "common.h"
|
|
|
|
|
2010-09-11 23:22:28 -06:00
|
|
|
/*
|
|
|
|
* CGI
|
|
|
|
*/
|
2010-09-13 12:23:39 -06:00
|
|
|
static int is_cgi = 0;
|
|
|
|
static char **argv = NULL;
|
2010-09-11 23:22:28 -06:00
|
|
|
|
2010-09-13 12:23:39 -06:00
|
|
|
static int
|
|
|
|
read_char_argv()
|
2010-09-11 23:22:28 -06:00
|
|
|
{
|
2010-09-13 12:23:39 -06:00
|
|
|
static int arg = 0;
|
|
|
|
static char *p;
|
2010-09-11 23:22:28 -06:00
|
|
|
|
2010-09-13 12:23:39 -06:00
|
|
|
if (NULL == argv) {
|
|
|
|
return EOF;
|
2010-09-11 23:22:28 -06:00
|
|
|
}
|
|
|
|
|
2010-09-13 12:23:39 -06:00
|
|
|
if (0 == arg) {
|
|
|
|
arg = 1;
|
|
|
|
p = argv[1];
|
|
|
|
}
|
2010-09-11 23:22:28 -06:00
|
|
|
|
2010-09-13 12:23:39 -06:00
|
|
|
if (! p) {
|
|
|
|
return EOF;
|
|
|
|
} else if (! *p) {
|
|
|
|
arg += 1;
|
|
|
|
p = argv[arg];
|
|
|
|
return '&';
|
|
|
|
}
|
|
|
|
|
|
|
|
return *(p++);
|
2010-09-11 23:22:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2010-09-13 12:23:39 -06:00
|
|
|
read_char_stdin()
|
2010-09-11 23:22:28 -06:00
|
|
|
{
|
2010-09-13 12:23:39 -06:00
|
|
|
static int inlen = -1;
|
|
|
|
|
|
|
|
if (-1 == inlen) {
|
|
|
|
char *p = getenv("CONTENT_LENGTH");
|
|
|
|
if (p) {
|
|
|
|
inlen = atoi(p);
|
|
|
|
} else {
|
|
|
|
inlen = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-11 23:22:28 -06:00
|
|
|
if (inlen) {
|
|
|
|
inlen -= 1;
|
|
|
|
return getchar();
|
|
|
|
}
|
|
|
|
return EOF;
|
|
|
|
}
|
|
|
|
|
2010-09-13 12:23:39 -06:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-09-11 23:22:28 -06:00
|
|
|
static char
|
|
|
|
tonum(int c)
|
|
|
|
{
|
|
|
|
if ((c >= '0') && (c <= '9')) {
|
|
|
|
return c - '0';
|
|
|
|
}
|
|
|
|
if ((c >= 'a') && (c <= 'f')) {
|
|
|
|
return 10 + c - 'a';
|
|
|
|
}
|
|
|
|
if ((c >= 'A') && (c <= 'F')) {
|
|
|
|
return 10 + c - 'A';
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char
|
|
|
|
read_hex()
|
|
|
|
{
|
|
|
|
int a = read_char();
|
|
|
|
int b = read_char();
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
size_t
|
|
|
|
cgi_item(char *str, size_t maxlen)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
size_t pos = 0;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
c = read_char();
|
|
|
|
switch (c) {
|
|
|
|
case EOF:
|
|
|
|
case '=':
|
|
|
|
case '&':
|
|
|
|
str[pos] = '\0';
|
|
|
|
return pos;
|
|
|
|
case '%':
|
|
|
|
c = read_hex();
|
|
|
|
break;
|
|
|
|
case '+':
|
|
|
|
c = ' ';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (pos < maxlen - 1) {
|
|
|
|
str[pos] = c;
|
|
|
|
pos += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2010-09-12 22:00:58 -06:00
|
|
|
cgi_head(char *title)
|
2010-09-11 23:22:28 -06:00
|
|
|
{
|
2010-09-12 22:15:06 -06:00
|
|
|
if (is_cgi) {
|
|
|
|
printf("Content-type: text/html\r\n\r\n");
|
|
|
|
}
|
|
|
|
printf(("<!DOCTYPE html>\n"
|
2010-09-11 23:22:28 -06:00
|
|
|
"<html>\n"
|
|
|
|
" <head>\n"
|
|
|
|
" <title>%s</title>\n"
|
|
|
|
" <link rel=\"stylesheet\" href=\"ctf.css\" type=\"text/css\">\n"
|
|
|
|
" </head>\n"
|
|
|
|
" <body>\n"
|
|
|
|
" <h1>%s</h1>\n"),
|
|
|
|
title, title);
|
2010-09-12 22:00:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
cgi_foot()
|
|
|
|
{
|
2010-09-11 23:22:28 -06:00
|
|
|
printf("\n"
|
|
|
|
" </body>\n"
|
|
|
|
"</html>\n");
|
2010-09-12 22:00:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
cgi_page(char *title, char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
cgi_head(title);
|
|
|
|
va_start(ap, fmt);
|
|
|
|
vprintf(fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
cgi_foot();
|
2010-09-11 23:22:28 -06:00
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
cgi_error(char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
printf("500 Internal Error\r\n"
|
|
|
|
"Content-type: text/plain\r\n"
|
|
|
|
"\r\n");
|
|
|
|
va_start(ap, fmt);
|
|
|
|
vprintf(fmt, ap);
|
|
|
|
va_end(ap);
|
2010-09-12 22:00:58 -06:00
|
|
|
printf("\n");
|
2010-09-11 23:22:28 -06:00
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Common routines
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2010-09-10 23:07:40 -06:00
|
|
|
#define EOL(c) ((EOF == (c)) || (0 == (c)) || ('\n' == (c)))
|
|
|
|
|
|
|
|
int
|
|
|
|
fgrepx(char const *needle, char const *filename)
|
|
|
|
{
|
|
|
|
FILE *f;
|
|
|
|
int found = 0;
|
|
|
|
char const *p = needle;
|
|
|
|
|
|
|
|
f = fopen(filename, "r");
|
|
|
|
if (f) {
|
|
|
|
while (1) {
|
|
|
|
int c = fgetc(f);
|
|
|
|
|
|
|
|
/* 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) {
|
|
|
|
break;
|
|
|
|
} else if ((0 == p) || (*p != c)) {
|
|
|
|
p = needle;
|
|
|
|
do {
|
|
|
|
c = fgetc(f);
|
|
|
|
} while (! EOL(c));
|
|
|
|
} else if ('\n' == c) {
|
|
|
|
p = needle;
|
|
|
|
} else {
|
|
|
|
p += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
2010-09-03 21:00:02 -06:00
|
|
|
int
|
2010-09-11 22:57:46 -06:00
|
|
|
my_snprintf(char *buf, size_t buflen, char *fmt, ...)
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
len = vsnprintf(buf, buflen - 1, fmt, ap);
|
|
|
|
va_end(ap);
|
2010-09-13 12:23:39 -06:00
|
|
|
buf[buflen] = '\0';
|
|
|
|
if (len >= buflen) {
|
|
|
|
return buflen - 1;
|
2010-09-11 22:57:46 -06:00
|
|
|
} else {
|
2010-09-13 12:23:39 -06:00
|
|
|
return len;
|
2010-09-11 22:57:46 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-13 12:23:39 -06:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-09-11 22:57:46 -06:00
|
|
|
int
|
|
|
|
team_exists(char const *teamhash)
|
2010-09-03 21:00:02 -06:00
|
|
|
{
|
|
|
|
struct stat buf;
|
|
|
|
int ret;
|
|
|
|
int i;
|
|
|
|
|
2010-09-13 12:23:39 -06:00
|
|
|
if ((! teamhash) || (! *teamhash)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-09-03 21:00:02 -06:00
|
|
|
/* Check for invalid characters. */
|
|
|
|
for (i = 0; teamhash[i]; i += 1) {
|
|
|
|
if (! isalnum(teamhash[i])) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* lstat seems to be the preferred way to check for existence. */
|
2010-09-13 12:23:39 -06:00
|
|
|
ret = lstat(srv_path("teams/names/%s", teamhash), &buf);
|
2010-09-03 21:00:02 -06:00
|
|
|
if (-1 == ret) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2010-09-11 22:57:46 -06:00
|
|
|
award_points(char const *teamhash,
|
|
|
|
char const *category,
|
2010-09-12 22:00:58 -06:00
|
|
|
long points)
|
2010-09-03 21:00:02 -06:00
|
|
|
{
|
2010-09-06 21:39:59 -06:00
|
|
|
char line[100];
|
|
|
|
int linelen;
|
2010-09-13 12:23:39 -06:00
|
|
|
char *filename;
|
2010-09-06 21:39:59 -06:00
|
|
|
int fd;
|
|
|
|
int ret;
|
2010-09-07 22:25:42 -06:00
|
|
|
time_t now = time(NULL);
|
2010-09-03 21:00:02 -06:00
|
|
|
|
|
|
|
if (! team_exists(teamhash)) {
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
|
|
|
linelen = snprintf(line, sizeof(line),
|
2010-09-12 22:00:58 -06:00
|
|
|
"%u %s %s %ld\n",
|
2010-09-07 22:25:42 -06:00
|
|
|
now, teamhash, category, points);
|
2010-09-11 22:57:46 -06:00
|
|
|
if (sizeof(line) <= linelen) {
|
2010-09-03 21:00:02 -06:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-09-07 22:25:42 -06:00
|
|
|
/* 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.
|
|
|
|
Editing the log file would require locking it, which would block
|
|
|
|
everything trying to score, effectively taking down the entire
|
2010-09-11 22:57:46 -06:00
|
|
|
contest. If you can't lock it first--and nothing in busybox lets
|
|
|
|
you do this--you have to bring down pretty much everything manually
|
2010-09-07 22:25:42 -06:00
|
|
|
anyway.
|
2010-09-03 21:00:02 -06:00
|
|
|
|
2010-09-07 22:25:42 -06:00
|
|
|
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. 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.
|
2010-09-11 22:57:46 -06:00
|
|
|
|
|
|
|
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.
|
2010-09-07 22:25:42 -06:00
|
|
|
*/
|
|
|
|
|
2010-09-13 12:23:39 -06:00
|
|
|
filename = srv_path("points.new/%d.%d.%s.%s.%ld",
|
|
|
|
now, getpid(),
|
|
|
|
teamhash, category, points);
|
2010-09-03 21:00:02 -06:00
|
|
|
|
2010-09-07 22:25:42 -06:00
|
|
|
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
|
|
|
|
if (-1 == fd) {
|
2010-09-03 21:00:02 -06:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-09-07 22:43:24 -06:00
|
|
|
if (-1 == write(fd, line, linelen)) {
|
|
|
|
close(fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-09-03 21:00:02 -06:00
|
|
|
close(fd);
|
|
|
|
return 0;
|
|
|
|
}
|
2010-09-11 22:57:46 -06:00
|
|
|
|
|
|
|
void
|
|
|
|
award_and_log_uniquely(char const *team,
|
|
|
|
char const *category,
|
2010-09-12 22:00:58 -06:00
|
|
|
long points,
|
2010-09-13 12:23:39 -06:00
|
|
|
char const *dbfile,
|
2010-09-11 22:57:46 -06:00
|
|
|
char const *fmt, ...)
|
|
|
|
{
|
2010-09-13 12:23:39 -06:00
|
|
|
char *dbpath = srv_path(dbfile);
|
|
|
|
char line[200];
|
|
|
|
int len;
|
|
|
|
int ret;
|
|
|
|
int fd;
|
|
|
|
va_list ap;
|
2010-09-11 22:57:46 -06:00
|
|
|
|
|
|
|
/* Make sure they haven't already claimed these points */
|
|
|
|
va_start(ap, fmt);
|
2010-09-11 23:22:28 -06:00
|
|
|
len = vsnprintf(line, sizeof(line), fmt, ap);
|
2010-09-11 22:57:46 -06:00
|
|
|
va_end(ap);
|
|
|
|
if (sizeof(line) <= len) {
|
|
|
|
cgi_error("Log line too long");
|
|
|
|
}
|
2010-09-13 12:23:39 -06:00
|
|
|
if (fgrepx(line, dbpath)) {
|
2010-09-11 22:57:46 -06:00
|
|
|
cgi_page("Already claimed",
|
|
|
|
"<p>Your team has already claimed these points.</p>");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Open and lock logfile */
|
2010-09-13 12:23:39 -06:00
|
|
|
fd = open(dbpath, O_WRONLY | O_CREAT, 0666);
|
2010-09-11 22:57:46 -06:00
|
|
|
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 */
|
|
|
|
/* We can turn that trailing NUL into a newline now since write
|
|
|
|
doesn't use C strings */
|
|
|
|
line[len] = '\n';
|
|
|
|
lseek(fd, 0, SEEK_END);
|
|
|
|
if (-1 == write(fd, line, len+1)) {
|
2010-09-11 23:22:28 -06:00
|
|
|
cgi_error("Unable to append log");
|
2010-09-11 22:57:46 -06:00
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
}
|