From 4dfdca03f558144ea9332b1e97354dfef1fe6849 Mon Sep 17 00:00:00 2001 From: Spoike Date: Wed, 25 Jun 2008 07:13:47 +0000 Subject: [PATCH] Fixed handling of header fields. This is a major security bug fix (omission bug). Added some additional measures to prevent client exploitation also (clients should do this themselves too). Used some snprintfs instead of sprintfs due to paranoia, but these will fix nothing as it currently stands. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@3016 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- fteqtv/httpsv.c | 1495 ++++++++++++++++++++++++----------------------- 1 file changed, 753 insertions(+), 742 deletions(-) diff --git a/fteqtv/httpsv.c b/fteqtv/httpsv.c index e19b4d7cc..93c997b18 100644 --- a/fteqtv/httpsv.c +++ b/fteqtv/httpsv.c @@ -1,618 +1,629 @@ -#include "qtv.h" - -//main reason to use connection close is because we're lazy and don't want to give sizes in advance (yes, we could use chunks..) - -//#define ALLOWDOWNLOADS - - - - -static const char qfont_table[256] = { - '\0', '#', '#', '#', '#', '.', '#', '#', - '#', 9, 10, '#', ' ', 13, '.', '.', - '[', ']', '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', '.', '<', '=', '>', - ' ', '!', '"', '#', '$', '%', '&', '\'', - '(', ')', '*', '+', ',', '-', '.', '/', - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', ':', ';', '<', '=', '>', '?', - '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', - 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', - 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', - 'x', 'y', 'z', '{', '|', '}', '~', '<', - - '<', '=', '>', '#', '#', '.', '#', '#', - '#', '#', ' ', '#', ' ', '>', '.', '.', - '[', ']', '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', '.', '<', '=', '>', - ' ', '!', '"', '#', '$', '%', '&', '\'', - '(', ')', '*', '+', ',', '-', '.', '/', - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', ':', ';', '<', '=', '>', '?', - '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', - 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', - 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', - 'x', 'y', 'z', '{', '|', '}', '~', '<' -}; - -static void HTMLprintf(char *outb, int outl, char *fmt, ...) -{ - va_list val; - char qfmt[8192*4]; - char *inb = qfmt; - - va_start(val, fmt); - vsnprintf(qfmt, sizeof(qfmt), fmt, val); - va_end(val); - qfmt[sizeof(qfmt)-1] = 0; - - outl--; - outl -= 5; - while (outl > 0 && *inb) - { - if (*inb == '<') - { - *outb++ = '&'; - *outb++ = 'l'; - *outb++ = 't'; - *outb++ = ';'; - outl -= 4; - } - else if (*inb == '>') - { - *outb++ = '&'; - *outb++ = 'g'; - *outb++ = 't'; - *outb++ = ';'; - outl -= 4; - } - else if (*inb == '\n') - { - *outb++ = '<'; - *outb++ = 'b'; - *outb++ = 'r'; - *outb++ = '/'; - *outb++ = '>'; - outl -= 5; - } - else if (*inb == '&') - { - *outb++ = '&'; - *outb++ = 'a'; - *outb++ = 'm'; - *outb++ = 'p'; - *outb++ = ';'; - outl -= 5; - } - else - { - *outb++ = qfont_table[*(unsigned char*)inb]; - outl -= 1; - } - inb++; - } - *outb++ = 0; -} - -static void HTTPSV_SendHTTPHeader(cluster_t *cluster, oproxy_t *dest, char *error_code, char *content_type, qboolean nocache) -{ - char *s; - char buffer[2048]; - - if (nocache) - { - s = "HTTP/1.1 %s OK\n" - "Content-Type: %s\n" - "Cache-Control: no-cache, must-revalidate\n" - "Expires: Mon, 26 Jul 1997 05:00:00 GMT\n" - "Connection: close\n" - "\n"; - } - else - { - s = "HTTP/1.1 %s OK\n" - "Content-Type: %s\n" - "Connection: close\n" - "\n"; - } - - snprintf(buffer, sizeof(buffer), s, error_code, content_type); - - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); -} - -static void HTTPSV_SendHTMLHeader(cluster_t *cluster, oproxy_t *dest, char *title) -{ - char *s; - char buffer[2048]; - - s = "\n" - "\n" - "\n" - " \n" - " %s\n" - " \n" - "\n" - "
"; - - snprintf(buffer, sizeof(buffer), s, title); - - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); -} - -static void HTTPSV_SendHTMLFooter(cluster_t *cluster, oproxy_t *dest) -{ - char *s; - char buffer[2048]; - - sprintf(buffer, "
QTV Version: %i www.fteqw.com
", cluster->buildnumber); - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); - - s = "\n" - "\n"; - Net_ProxySend(cluster, dest, s, strlen(s)); -} - -#define HTMLPRINT(str) Net_ProxySend(cluster, dest, str "\n", strlen(str "\n")) - -static void HTTPSV_GenerateNowPlaying(cluster_t *cluster, oproxy_t *dest) -{ - int player; - char *s; - char buffer[1024]; - char plname[64]; - sv_t *streams; - - HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/html", true); - HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Now Playing"); - - if (!strcmp(cluster->hostname, DEFAULT_HOSTNAME)) - snprintf(buffer, sizeof(buffer), "

QuakeTV: Now Playing

"); //don't show the hostname if its set to the default - else - snprintf(buffer, sizeof(buffer), "

QuakeTV: Now Playing on %s

", cluster->hostname); - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); - - HTMLPRINT("
"); - for (streams = cluster->servers; streams; streams = streams->next) - { - HTMLPRINT("
"); - HTMLprintf(buffer, sizeof(buffer), "%s (%s: %s)", streams->server, streams->gamedir, streams->mapname); - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); - sprintf(buffer, " [ Watch Now ]", streams->streamid); - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); - HTMLPRINT("
    "); - - for (player = 0; player < MAX_CLIENTS; player++) - { - if (*streams->players[player].userinfo) - { - Info_ValueForKey(streams->players[player].userinfo, "name", plname, sizeof(plname)); - - if (streams->players[player].frags < -90) - { - HTMLPRINT("
  • "); - } - else - { - HTMLPRINT("
  • "); - } - - HTMLprintf(buffer, sizeof(buffer), "%s", plname); - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); - HTMLPRINT("
  • "); - } - } - HTMLPRINT("
"); - } - HTMLPRINT("
"); - if (!cluster->servers) - { - s = "No streams are currently being played
"; - Net_ProxySend(cluster, dest, s, strlen(s)); - } - - HTTPSV_SendHTMLFooter(cluster, dest); -} - -static void HTTPSV_GenerateCSSFile(cluster_t *cluster, oproxy_t *dest) -{ - HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/css", false); - - HTMLPRINT("* { font-family: Verdana, Helvetica, sans-serif; }"); - HTMLPRINT("body { color: #000; background-color: #fff; padding: 0 40px; }"); - HTMLPRINT("a { color: #00f; }"); - HTMLPRINT("a.qtvfile { font-weight: bold; }"); - HTMLPRINT("a:visited { color: #00f; }"); - HTMLPRINT("a:hover { background-color: black; color: yellow; }"); - HTMLPRINT("li.spectator { color: #666; font-size: 0.9ex; }"); - HTMLPRINT("dl.nowplaying dd { margin: 0 0 2em 0; }"); - HTMLPRINT("dl.nowplaying dt { margin: 1em 0 0 0; font-size: 1.1em; font-weight: bold; }"); - HTMLPRINT("dl.nowplaying li { list-style: none; margin: 0 0 0 1em; padding: 0; }"); - HTMLPRINT("dl.nowplaying ul { margin: 0 0 0 1em; padding: 0; }"); - HTMLPRINT("#navigation { background-color: #eef; }"); - HTMLPRINT("#navigation li { display: inline; list-style: none; margin: 0 3em; }"); -} - -static qboolean HTTPSV_GetHeaderField(char *s, char *field, char *buffer, int buffersize) -{ - char *e; - char *colon; - int fieldnamelen = strlen(field); - - buffer[0] = 0; - - e = s; - while(*e) - { - if (*e == '\n') - { - *e = '\0'; - colon = strchr(s, ':'); - if (!colon) - { - if (!strncmp(field, s, fieldnamelen)) - { - if (s[fieldnamelen] <= ' ') - { - return true; - } - } - } - else - { - if (fieldnamelen == colon - s) - { - if (!strncmp(field, s, colon-s)) - { - colon++; - while (*colon == ' ') - colon++; - while (buffersize > 1) - { - if (*colon == '\r' || *colon == '\n') - break; - *buffer++ = *colon++; - } - *buffer = 0; - return true; - } - } - - } - s = e+1; - } - - e++; - } - return false; -} - -static void HTTPSV_GenerateQTVStub(cluster_t *cluster, oproxy_t *dest, char *streamtype, char *streamid) -{ - char *s; - char hostname[64]; - char buffer[1024]; - - - char fname[256]; - s = fname; - while (*streamid > ' ') - { - if (s > fname + sizeof(fname)-4) //4 cos I'm too lazy to work out what the actual number should be - break; - if (*streamid == '%') - { - *s = 0; - streamid++; - if (*streamid <= ' ') - break; - else if (*streamid >= 'a' && *streamid <= 'f') - *s += 10 + *streamid-'a'; - else if (*streamid >= 'A' && *streamid <= 'F') - *s += 10 + *streamid-'A'; - else if (*streamid >= '0' && *streamid <= '9') - *s += *streamid-'0'; - - *s <<= 4; - - streamid++; - if (*streamid <= ' ') - break; - else if (*streamid >= 'a' && *streamid <= 'f') - *s += 10 + *streamid-'a'; - else if (*streamid >= 'A' && *streamid <= 'F') - *s += 10 + *streamid-'A'; - else if (*streamid >= '0' && *streamid <= '9') - *s += *streamid-'0'; - - s++; - } - else - *s++ = *streamid++; - } - *s = 0; - streamid = fname; - - - if (!HTTPSV_GetHeaderField((char*)dest->inbuffer, "Host", hostname, sizeof(hostname))) - { - HTTPSV_SendHTTPHeader(cluster, dest, "400", "text/html", true); - HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Error"); - - s = "Your client did not send a Host field, which is required in HTTP/1.1\n
" - "Please try a different browser.\n" - "" - ""; - - Net_ProxySend(cluster, dest, s, strlen(s)); - return; - } - - HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/x-quaketvident", false); - - sprintf(buffer, "[QTV]\r\n" - "Stream: %s%s@%s\r\n" - "", - streamtype, streamid, hostname); - - - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); -} - -static char *HTTPSV_ParsePOST(char *post, char *buffer, int buffersize) -{ - while(*post && *post != '&') - { - if (--buffersize>0) - { - if (*post == '+') - *buffer++ = ' '; - else if (*post == '%') - { - *buffer = 0; - post++; - if (*post == '\0' || *post == '&') - break; - else if (*post >= 'a' && *post <= 'f') - *buffer += 10 + *post-'a'; - else if (*post >= 'A' && *post <= 'F') - *buffer += 10 + *post-'A'; - else if (*post >= '0' && *post <= '9') - *buffer += *post-'0'; - - *buffer <<= 4; - - post++; - if (*post == '\0' || *post == '&') - break; - else if (*post >= 'a' && *post <= 'f') - *buffer += 10 + *post-'a'; - else if (*post >= 'A' && *post <= 'F') - *buffer += 10 + *post-'A'; - else if (*post >= '0' && *post <= '9') - *buffer += *post-'0'; - - buffer++; - } - else - *buffer++ = *post; - } - post++; - } - *buffer = 0; - - return post; -} -static void HTTPSV_GenerateAdmin(cluster_t *cluster, oproxy_t *dest, int streamid, char *postbody) -{ - char pwd[64]; - char cmd[256]; - char result[8192]; - char *s; - char *o; - int passwordokay = false; - - if (!*cluster->adminpassword) - { - HTTPSV_SendHTTPHeader(cluster, dest, "403", "text/html", true); - HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Admin Error"); - - s = "The admin password is disabled. You may not log in remotely.\n"; - Net_ProxySend(cluster, dest, s, strlen(s)); - return; - } - - - pwd[0] = 0; - cmd[0] = 0; - if (postbody) - while (*postbody) - { - if (!strncmp(postbody, "pwd=", 4)) - { - postbody = HTTPSV_ParsePOST(postbody+4, pwd, sizeof(pwd)); - } - else if (!strncmp(postbody, "cmd=", 4)) - { - postbody = HTTPSV_ParsePOST(postbody+4, cmd, sizeof(cmd)); - } - else - { - while(*postbody && *postbody != '&') - { - postbody++; - } - if (*postbody == '&') - postbody++; - } - } - - if (!*pwd) - { - if (postbody) - o = "No Password"; - else - o = ""; - } - else if (!strcmp(pwd, cluster->adminpassword)) - { - passwordokay = true; - //small hack (as http connections are considered non-connected proxies) - cluster->numproxies--; - if (*cmd) - o = Rcon_Command(cluster, NULL, cmd, result, sizeof(result), false); - else - o = ""; - cluster->numproxies++; - } - else - { - o = "Bad Password"; - } - if (o != result) - { - strcpy(result, o); - o = result; - } - - HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/html", true); - HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Admin"); - - s = "

QuakeTV Admin: "; - Net_ProxySend(cluster, dest, s, strlen(s)); - s = cluster->hostname; - Net_ProxySend(cluster, dest, s, strlen(s)); - s = "

"; - Net_ProxySend(cluster, dest, s, strlen(s)); - - - s = "
" - "
" - "Password " - "
" - "Command " - "" - "
" - "
"; - Net_ProxySend(cluster, dest, s, strlen(s)); - - if (passwordokay) - HTMLPRINT(""); - else - HTMLPRINT(""); - - while(*o) - { - s = strchr(o, '\n'); - if (s) - *s = 0; - HTMLprintf(cmd, sizeof(cmd), "%s", o); - Net_ProxySend(cluster, dest, cmd, strlen(cmd)); - Net_ProxySend(cluster, dest, "
", 6); - if (!s) - break; - o = s+1; - } - - HTTPSV_SendHTMLFooter(cluster, dest); -} - -static void HTTPSV_GenerateDemoListing(cluster_t *cluster, oproxy_t *dest) -{ - int i; - char link[256]; - char *s; - - HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/html", true); - HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Demos"); - - s = "

QuakeTV: Demo Listing

"; - Net_ProxySend(cluster, dest, s, strlen(s)); - - Cluster_BuildAvailableDemoList(cluster); - for (i = 0; i < cluster->availdemoscount; i++) - { - snprintf(link, sizeof(link), "%s (%ikb)
", cluster->availdemos[i].name, cluster->availdemos[i].name, cluster->availdemos[i].size/1024); - Net_ProxySend(cluster, dest, link, strlen(link)); - } - - sprintf(link, "

Total: %i demos

", cluster->availdemoscount); - Net_ProxySend(cluster, dest, link, strlen(link)); - - HTTPSV_SendHTMLFooter(cluster, dest); -} - -static void HTTPSV_GenerateDownload(cluster_t *cluster, oproxy_t *dest, char *filename) -{ -#ifdef ALLOWDOWNLOADS +#include "qtv.h" + +//main reason to use connection close is because we're lazy and don't want to give sizes in advance (yes, we could use chunks..) + +//#define ALLOWDOWNLOADS + + + + +static const char qfont_table[256] = { + '\0', '#', '#', '#', '#', '.', '#', '#', + '#', 9, 10, '#', ' ', 13, '.', '.', + '[', ']', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '.', '<', '=', '>', + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', '<', + + '<', '=', '>', '#', '#', '.', '#', '#', + '#', '#', ' ', '#', ' ', '>', '.', '.', + '[', ']', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '.', '<', '=', '>', + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', '<' +}; + +static void HTMLprintf(char *outb, int outl, char *fmt, ...) +{ + va_list val; + char qfmt[8192*4]; + char *inb = qfmt; + + va_start(val, fmt); + vsnprintf(qfmt, sizeof(qfmt), fmt, val); + va_end(val); + qfmt[sizeof(qfmt)-1] = 0; + + outl--; + outl -= 5; + while (outl > 0 && *inb) + { + if (*inb == '<') + { + *outb++ = '&'; + *outb++ = 'l'; + *outb++ = 't'; + *outb++ = ';'; + outl -= 4; + } + else if (*inb == '>') + { + *outb++ = '&'; + *outb++ = 'g'; + *outb++ = 't'; + *outb++ = ';'; + outl -= 4; + } + else if (*inb == '\n') + { + *outb++ = '<'; + *outb++ = 'b'; + *outb++ = 'r'; + *outb++ = '/'; + *outb++ = '>'; + outl -= 5; + } + else if (*inb == '&') + { + *outb++ = '&'; + *outb++ = 'a'; + *outb++ = 'm'; + *outb++ = 'p'; + *outb++ = ';'; + outl -= 5; + } + else + { + *outb++ = qfont_table[*(unsigned char*)inb]; + outl -= 1; + } + inb++; + } + *outb++ = 0; +} + +static void HTTPSV_SendHTTPHeader(cluster_t *cluster, oproxy_t *dest, char *error_code, char *content_type, qboolean nocache) +{ + char *s; + char buffer[2048]; + + if (nocache) + { + s = "HTTP/1.1 %s OK\n" + "Content-Type: %s\n" + "Cache-Control: no-cache, must-revalidate\n" + "Expires: Mon, 26 Jul 1997 05:00:00 GMT\n" + "Connection: close\n" + "\n"; + } + else + { + s = "HTTP/1.1 %s OK\n" + "Content-Type: %s\n" + "Connection: close\n" + "\n"; + } + + snprintf(buffer, sizeof(buffer), s, error_code, content_type); + + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); +} + +static void HTTPSV_SendHTMLHeader(cluster_t *cluster, oproxy_t *dest, char *title) +{ + char *s; + char buffer[2048]; + + s = "\n" + "\n" + "\n" + " \n" + " %s\n" + " \n" + "\n" + "
"; + + snprintf(buffer, sizeof(buffer), s, title); + + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); +} + +static void HTTPSV_SendHTMLFooter(cluster_t *cluster, oproxy_t *dest) +{ + char *s; + char buffer[2048]; + + snprintf(buffer, sizeof(buffer), "
QTV Version: %i www.fteqw.com
", cluster->buildnumber); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + + s = "\n" + "\n"; + Net_ProxySend(cluster, dest, s, strlen(s)); +} + +#define HTMLPRINT(str) Net_ProxySend(cluster, dest, str "\n", strlen(str "\n")) + +static void HTTPSV_GenerateNowPlaying(cluster_t *cluster, oproxy_t *dest) +{ + int player; + char *s; + char buffer[1024]; + char plname[64]; + sv_t *streams; + + HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Now Playing"); + + if (!strcmp(cluster->hostname, DEFAULT_HOSTNAME)) + snprintf(buffer, sizeof(buffer), "

QuakeTV: Now Playing

"); //don't show the hostname if its set to the default + else + snprintf(buffer, sizeof(buffer), "

QuakeTV: Now Playing on %s

", cluster->hostname); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + + HTMLPRINT("
"); + for (streams = cluster->servers; streams; streams = streams->next) + { + HTMLPRINT("
"); + HTMLprintf(buffer, sizeof(buffer), "%s (%s: %s)", streams->server, streams->gamedir, streams->mapname); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + snprintf(buffer, sizeof(buffer), " [ Watch Now ]", streams->streamid); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + HTMLPRINT("
    "); + + for (player = 0; player < MAX_CLIENTS; player++) + { + if (*streams->players[player].userinfo) + { + Info_ValueForKey(streams->players[player].userinfo, "name", plname, sizeof(plname)); + + if (streams->players[player].frags < -90) + { + HTMLPRINT("
  • "); + } + else + { + HTMLPRINT("
  • "); + } + + HTMLprintf(buffer, sizeof(buffer), "%s", plname); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + HTMLPRINT("
  • "); + } + } + HTMLPRINT("
"); + } + HTMLPRINT("
"); + if (!cluster->servers) + { + s = "No streams are currently being played
"; + Net_ProxySend(cluster, dest, s, strlen(s)); + } + + HTTPSV_SendHTMLFooter(cluster, dest); +} + +static void HTTPSV_GenerateCSSFile(cluster_t *cluster, oproxy_t *dest) +{ + HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/css", false); + + HTMLPRINT("* { font-family: Verdana, Helvetica, sans-serif; }"); + HTMLPRINT("body { color: #000; background-color: #fff; padding: 0 40px; }"); + HTMLPRINT("a { color: #00f; }"); + HTMLPRINT("a.qtvfile { font-weight: bold; }"); + HTMLPRINT("a:visited { color: #00f; }"); + HTMLPRINT("a:hover { background-color: black; color: yellow; }"); + HTMLPRINT("li.spectator { color: #666; font-size: 0.9ex; }"); + HTMLPRINT("dl.nowplaying dd { margin: 0 0 2em 0; }"); + HTMLPRINT("dl.nowplaying dt { margin: 1em 0 0 0; font-size: 1.1em; font-weight: bold; }"); + HTMLPRINT("dl.nowplaying li { list-style: none; margin: 0 0 0 1em; padding: 0; }"); + HTMLPRINT("dl.nowplaying ul { margin: 0 0 0 1em; padding: 0; }"); + HTMLPRINT("#navigation { background-color: #eef; }"); + HTMLPRINT("#navigation li { display: inline; list-style: none; margin: 0 3em; }"); +} + +static qboolean HTTPSV_GetHeaderField(char *s, char *field, char *buffer, int buffersize) +{ + char *e; + char *colon; + int fieldnamelen = strlen(field); + + buffer[0] = 0; + + e = s; + while(*e) + { + if (*e == '\n') + { + *e = '\0'; + colon = strchr(s, ':'); + if (!colon) + { + if (!strncmp(field, s, fieldnamelen)) + { + if (s[fieldnamelen] <= ' ') + { + return true; + } + } + } + else + { + if (fieldnamelen == colon - s) + { + if (!strncmp(field, s, colon-s)) + { + colon++; + while (*colon == ' ') + colon++; + while (buffersize > 2) + { + if (*colon == '\r' || *colon == '\n') + break; + *buffer++ = *colon++; + buffersize--; + } + *buffer = 0; + return true; + } + } + + } + s = e+1; + } + + e++; + } + return false; +} + +static void HTTPSV_GenerateQTVStub(cluster_t *cluster, oproxy_t *dest, char *streamtype, char *streamid) +{ + char *s; + char hostname[64]; + char buffer[1024]; + + char fname[256]; - char link[512]; - char *s, *suppliedname; - int len; - - if (cluster->allowdownloads) -#endif - { - HTTPSV_SendHTTPHeader(cluster, dest, "403", "text/html", true); - HTTPSV_SendHTMLHeader(cluster, dest, "Permission denied"); - HTMLPRINT("

403: Forbidden

"); - HTMLPRINT("File downloads from this proxy are currently not permitted."); - HTTPSV_SendHTMLFooter(cluster, dest); - return; - } + s = fname; + while (*streamid > ' ') + { + if (s > fname + sizeof(fname)-4) //4 cos I'm too lazy to work out what the actual number should be + break; + if (*streamid == '%') + { + *s = 0; + streamid++; + if (*streamid <= ' ') + break; + else if (*streamid >= 'a' && *streamid <= 'f') + *s += 10 + *streamid-'a'; + else if (*streamid >= 'A' && *streamid <= 'F') + *s += 10 + *streamid-'A'; + else if (*streamid >= '0' && *streamid <= '9') + *s += *streamid-'0'; + + *s <<= 4; + + streamid++; + if (*streamid <= ' ') + break; + else if (*streamid >= 'a' && *streamid <= 'f') + *s += 10 + *streamid-'a'; + else if (*streamid >= 'A' && *streamid <= 'F') + *s += 10 + *streamid-'A'; + else if (*streamid >= '0' && *streamid <= '9') + *s += *streamid-'0'; + + //don't let hackers try adding extra commands to it. + if (*s == '$' || *s == ';' || *s == '\r' || *s == '\n') + continue; + + s++; + } + else if (*streamid == '$' || *streamid == ';' || *streamid == '\r' || *streamid == '\n') + { + //don't let hackers try adding extra commands to it. + streamid++; + } + else + *s++ = *streamid++; + } + *s = 0; + streamid = fname; + + + if (!HTTPSV_GetHeaderField((char*)dest->inbuffer, "Host", hostname, sizeof(hostname))) + { + HTTPSV_SendHTTPHeader(cluster, dest, "400", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Error"); + + s = "Your client did not send a Host field, which is required in HTTP/1.1\n
" + "Please try a different browser.\n" + "" + ""; + + Net_ProxySend(cluster, dest, s, strlen(s)); + return; + } + + HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/x-quaketvident", false); + + snprintf(buffer, sizeof(buffer), "[QTV]\r\n" + "Stream: %s%s@%s\r\n" + "", + //5, 256, 64. snprintf is not required, but paranoia is a wonderful thing. + streamtype, streamid, hostname); + + + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); +} + +static char *HTTPSV_ParsePOST(char *post, char *buffer, int buffersize) +{ + while(*post && *post != '&') + { + if (--buffersize>0) + { + if (*post == '+') + *buffer++ = ' '; + else if (*post == '%') + { + *buffer = 0; + post++; + if (*post == '\0' || *post == '&') + break; + else if (*post >= 'a' && *post <= 'f') + *buffer += 10 + *post-'a'; + else if (*post >= 'A' && *post <= 'F') + *buffer += 10 + *post-'A'; + else if (*post >= '0' && *post <= '9') + *buffer += *post-'0'; + + *buffer <<= 4; + + post++; + if (*post == '\0' || *post == '&') + break; + else if (*post >= 'a' && *post <= 'f') + *buffer += 10 + *post-'a'; + else if (*post >= 'A' && *post <= 'F') + *buffer += 10 + *post-'A'; + else if (*post >= '0' && *post <= '9') + *buffer += *post-'0'; + + buffer++; + } + else + *buffer++ = *post; + } + post++; + } + *buffer = 0; + + return post; +} +static void HTTPSV_GenerateAdmin(cluster_t *cluster, oproxy_t *dest, int streamid, char *postbody) +{ + char pwd[64]; + char cmd[256]; + char result[8192]; + char *s; + char *o; + int passwordokay = false; + + if (!*cluster->adminpassword) + { + HTTPSV_SendHTTPHeader(cluster, dest, "403", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Admin Error"); + + s = "The admin password is disabled. You may not log in remotely.\n"; + Net_ProxySend(cluster, dest, s, strlen(s)); + return; + } + + + pwd[0] = 0; + cmd[0] = 0; + if (postbody) + while (*postbody) + { + if (!strncmp(postbody, "pwd=", 4)) + { + postbody = HTTPSV_ParsePOST(postbody+4, pwd, sizeof(pwd)); + } + else if (!strncmp(postbody, "cmd=", 4)) + { + postbody = HTTPSV_ParsePOST(postbody+4, cmd, sizeof(cmd)); + } + else + { + while(*postbody && *postbody != '&') + { + postbody++; + } + if (*postbody == '&') + postbody++; + } + } + + if (!*pwd) + { + if (postbody) + o = "No Password"; + else + o = ""; + } + else if (!strcmp(pwd, cluster->adminpassword)) + { + passwordokay = true; + //small hack (as http connections are considered non-connected proxies) + cluster->numproxies--; + if (*cmd) + o = Rcon_Command(cluster, NULL, cmd, result, sizeof(result), false); + else + o = ""; + cluster->numproxies++; + } + else + { + o = "Bad Password"; + } + if (o != result) + { + strcpy(result, o); + o = result; + } + + HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Admin"); + + s = "

QuakeTV Admin: "; + Net_ProxySend(cluster, dest, s, strlen(s)); + s = cluster->hostname; + Net_ProxySend(cluster, dest, s, strlen(s)); + s = "

"; + Net_ProxySend(cluster, dest, s, strlen(s)); + + + s = "
" + "
" + "Password " + "
" + "Command " + "" + "
" + "
"; + Net_ProxySend(cluster, dest, s, strlen(s)); + + if (passwordokay) + HTMLPRINT(""); + else + HTMLPRINT(""); + + while(*o) + { + s = strchr(o, '\n'); + if (s) + *s = 0; + HTMLprintf(cmd, sizeof(cmd), "%s", o); + Net_ProxySend(cluster, dest, cmd, strlen(cmd)); + Net_ProxySend(cluster, dest, "
", 6); + if (!s) + break; + o = s+1; + } + + HTTPSV_SendHTMLFooter(cluster, dest); +} + +static void HTTPSV_GenerateDemoListing(cluster_t *cluster, oproxy_t *dest) +{ + int i; + char link[256]; + char *s; + + HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Demos"); + + s = "

QuakeTV: Demo Listing

"; + Net_ProxySend(cluster, dest, s, strlen(s)); + + Cluster_BuildAvailableDemoList(cluster); + for (i = 0; i < cluster->availdemoscount; i++) + { + snprintf(link, sizeof(link), "%s (%ikb)
", cluster->availdemos[i].name, cluster->availdemos[i].name, cluster->availdemos[i].size/1024); + Net_ProxySend(cluster, dest, link, strlen(link)); + } + + snprintf(link, sizeof(link), "

Total: %i demos

", cluster->availdemoscount); + Net_ProxySend(cluster, dest, link, strlen(link)); + + HTTPSV_SendHTMLFooter(cluster, dest); +} + +static void HTTPSV_GenerateDownload(cluster_t *cluster, oproxy_t *dest, char *filename) +{ #ifdef ALLOWDOWNLOADS - suppliedname = s = fname + strlcpy(fname, cluster->downloaddir, sizeof(fname)); - while (*filename > ' ') - { - if (s > fname + sizeof(fname)-4) //4 cos I'm too lazy to work out what the actual number should be - break; - if (*filename == '%') - { - *s = 0; - filename++; - if (*filename <= ' ') - break; - else if (*filename >= 'a' && *filename <= 'f') - *s += 10 + *filename-'a'; - else if (*filename >= 'A' && *filename <= 'F') - *s += 10 + *filename-'A'; - else if (*filename >= '0' && *filename <= '9') - *s += *filename-'0'; - - *s <<= 4; - - filename++; - if (*filename <= ' ') - break; - else if (*filename >= 'a' && *filename <= 'f') - *s += 10 + *filename-'a'; - else if (*filename >= 'A' && *filename <= 'F') - *s += 10 + *filename-'A'; - else if (*filename >= '0' && *filename <= '9') - *s += *filename-'0'; - - s++; - } - else - *s++ = *filename++; - } + char fname[256]; + char link[512]; + char *s, *suppliedname; + int len; + + if (cluster->allowdownloads) +#endif + { + HTTPSV_SendHTTPHeader(cluster, dest, "403", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "Permission denied"); + HTMLPRINT("

403: Forbidden

"); + HTMLPRINT("File downloads from this proxy are currently not permitted."); + HTTPSV_SendHTMLFooter(cluster, dest); + return; + } +#ifdef ALLOWDOWNLOADS + suppliedname = s = fname + strlcpy(fname, cluster->downloaddir, sizeof(fname)); + while (*filename > ' ') + { + if (s > fname + sizeof(fname)-4) //4 cos I'm too lazy to work out what the actual number should be + break; + if (*filename == '%') + { + *s = 0; + filename++; + if (*filename <= ' ') + break; + else if (*filename >= 'a' && *filename <= 'f') + *s += 10 + *filename-'a'; + else if (*filename >= 'A' && *filename <= 'F') + *s += 10 + *filename-'A'; + else if (*filename >= '0' && *filename <= '9') + *s += *filename-'0'; + + *s <<= 4; + + filename++; + if (*filename <= ' ') + break; + else if (*filename >= 'a' && *filename <= 'f') + *s += 10 + *filename-'a'; + else if (*filename >= 'A' && *filename <= 'F') + *s += 10 + *filename-'A'; + else if (*filename >= '0' && *filename <= '9') + *s += *filename-'0'; + + s++; + } + else + *s++ = *filename++; + } *s = 0; if (*suppliedname == '\\' || *suppliedname == '/' || strstr(suppliedname, "..") || suppliedname[1] == ':') @@ -644,138 +655,138 @@ static void HTTPSV_GenerateDownload(cluster_t *cluster, oproxy_t *dest, char *fi } } - - dest->srcfile = fopen(fname, "rb"); - - if (dest->srcfile) - { - HTTPSV_SendHTTPHeader(cluster, dest, "200", "application/x-forcedownload", false); - } - else - { - HTTPSV_SendHTTPHeader(cluster, dest, "404", "text/html", true); - HTTPSV_SendHTMLHeader(cluster, dest, "File not found"); + + dest->srcfile = fopen(fname, "rb"); + + if (dest->srcfile) + { + HTTPSV_SendHTTPHeader(cluster, dest, "200", "application/x-forcedownload", false); + } + else + { + HTTPSV_SendHTTPHeader(cluster, dest, "404", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "File not found"); HTMLPRINT("

404: File not found

"); HTMLPRINT("

"); HTMLprintf(link, sizeof(link), "The file '%s' could not be found on this server", fname); Net_ProxySend(cluster, dest, link, strlen(link)); HTMLPRINT("

"); - - HTTPSV_SendHTMLFooter(cluster, dest); - } -#endif -} - - - - - - - -void HTTPSV_PostMethod(cluster_t *cluster, oproxy_t *pend, char *postdata) -{ - char tempbuf[512]; - char *s; - int len; - - if (!HTTPSV_GetHeaderField((char*)pend->inbuffer, "Content-Length", tempbuf, sizeof(tempbuf))) - { - s = "HTTP/1.1 411 OK\n" - "Content-Type: text/html\n" - "Connection: close\n" - "\n" - "QuakeTVNo Content-Length was provided.\n"; - Net_ProxySend(cluster, pend, s, strlen(s)); - pend->flushing = true; - return; - } - len = atoi(tempbuf); - if (pend->inbuffersize + len >= sizeof(pend->inbuffer)-20) - { //too much data - pend->flushing = true; - return; - } - len = postdata - (char*)pend->inbuffer + len; - if (len > pend->inbuffersize) - return; //still need the body - -// if (len <= pend->inbuffersize) - { - if (!strncmp((char*)pend->inbuffer+5, "/admin", 6)) - { - HTTPSV_GenerateAdmin(cluster, pend, 0, postdata); - } - else - { - s = "HTTP/1.1 404 OK\n" - "Content-Type: text/html\n" - "Connection: close\n" - "\n" - "QuakeTVThat HTTP method is not supported for that URL.\n"; - Net_ProxySend(cluster, pend, s, strlen(s)); - - } - pend->flushing = true; - return; - } -} - -void HTTPSV_GetMethod(cluster_t *cluster, oproxy_t *pend) -{ - char *s; - if (!strncmp((char*)pend->inbuffer+4, "/nowplaying", 11)) - { - HTTPSV_GenerateNowPlaying(cluster, pend); - } - else if (!strncmp((char*)pend->inbuffer+4, "/watch.qtv?sid=", 15)) - { - HTTPSV_GenerateQTVStub(cluster, pend, "", (char*)pend->inbuffer+19); - } - else if (!strncmp((char*)pend->inbuffer+4, "/watch.qtv?demo=", 16)) - { - HTTPSV_GenerateQTVStub(cluster, pend, "file:", (char*)pend->inbuffer+20); - } -// else if (!strncmp((char*)pend->inbuffer+4, "/demo/", 6)) -// { //fixme: make this send the demo as an http download -// HTTPSV_GenerateQTVStub(cluster, pend, "file:", (char*)pend->inbuffer+10); -// } - else if (!strncmp((char*)pend->inbuffer+4, "/about", 6)) - { //redirect them to our funky website - s = "HTTP/1.0 302 Found\n" - "Location: http://www.fteqw.com/\n" - "\n"; - Net_ProxySend(cluster, pend, s, strlen(s)); - } - else if (!strncmp((char*)pend->inbuffer+4, "/admin", 6)) - { - HTTPSV_GenerateAdmin(cluster, pend, 0, NULL); - } - else if (!strncmp((char*)pend->inbuffer+4, "/ ", 2)) - { - s = "HTTP/1.0 302 Found\n" - "Location: /nowplaying/\n" - "\n"; - Net_ProxySend(cluster, pend, s, strlen(s)); - } - else if (!strncmp((char*)pend->inbuffer+4, "/demos", 6)) - { - HTTPSV_GenerateDemoListing(cluster, pend); - } - else if (!strncmp((char*)pend->inbuffer+4, "/file/", 6)) - { - HTTPSV_GenerateDownload(cluster, pend, (char*)pend->inbuffer+10); - } - else if (!strncmp((char*)pend->inbuffer+4, "/style.css", 10)) - { - HTTPSV_GenerateCSSFile(cluster, pend); - } - else - { -#define dest pend - HTTPSV_SendHTTPHeader(cluster, dest, "404", "text/html", true); - HTTPSV_SendHTMLHeader(cluster, dest, "Address not recognised"); - HTMLPRINT("

Address not recognised

"); - HTTPSV_SendHTMLFooter(cluster, dest); - } -} + + HTTPSV_SendHTMLFooter(cluster, dest); + } +#endif +} + + + + + + + +void HTTPSV_PostMethod(cluster_t *cluster, oproxy_t *pend, char *postdata) +{ + char tempbuf[512]; + char *s; + int len; + + if (!HTTPSV_GetHeaderField((char*)pend->inbuffer, "Content-Length", tempbuf, sizeof(tempbuf))) + { + s = "HTTP/1.1 411 OK\n" + "Content-Type: text/html\n" + "Connection: close\n" + "\n" + "QuakeTVNo Content-Length was provided.\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + pend->flushing = true; + return; + } + len = atoi(tempbuf); + if (pend->inbuffersize + len >= sizeof(pend->inbuffer)-20) + { //too much data + pend->flushing = true; + return; + } + len = postdata - (char*)pend->inbuffer + len; + if (len > pend->inbuffersize) + return; //still need the body + +// if (len <= pend->inbuffersize) + { + if (!strncmp((char*)pend->inbuffer+5, "/admin", 6)) + { + HTTPSV_GenerateAdmin(cluster, pend, 0, postdata); + } + else + { + s = "HTTP/1.1 404 OK\n" + "Content-Type: text/html\n" + "Connection: close\n" + "\n" + "QuakeTVThat HTTP method is not supported for that URL.\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + + } + pend->flushing = true; + return; + } +} + +void HTTPSV_GetMethod(cluster_t *cluster, oproxy_t *pend) +{ + char *s; + if (!strncmp((char*)pend->inbuffer+4, "/nowplaying", 11)) + { + HTTPSV_GenerateNowPlaying(cluster, pend); + } + else if (!strncmp((char*)pend->inbuffer+4, "/watch.qtv?sid=", 15)) + { + HTTPSV_GenerateQTVStub(cluster, pend, "", (char*)pend->inbuffer+19); + } + else if (!strncmp((char*)pend->inbuffer+4, "/watch.qtv?demo=", 16)) + { + HTTPSV_GenerateQTVStub(cluster, pend, "file:", (char*)pend->inbuffer+20); + } +// else if (!strncmp((char*)pend->inbuffer+4, "/demo/", 6)) +// { //fixme: make this send the demo as an http download +// HTTPSV_GenerateQTVStub(cluster, pend, "file:", (char*)pend->inbuffer+10); +// } + else if (!strncmp((char*)pend->inbuffer+4, "/about", 6)) + { //redirect them to our funky website + s = "HTTP/1.0 302 Found\n" + "Location: http://www.fteqw.com/\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } + else if (!strncmp((char*)pend->inbuffer+4, "/admin", 6)) + { + HTTPSV_GenerateAdmin(cluster, pend, 0, NULL); + } + else if (!strncmp((char*)pend->inbuffer+4, "/ ", 2)) + { + s = "HTTP/1.0 302 Found\n" + "Location: /nowplaying/\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } + else if (!strncmp((char*)pend->inbuffer+4, "/demos", 6)) + { + HTTPSV_GenerateDemoListing(cluster, pend); + } + else if (!strncmp((char*)pend->inbuffer+4, "/file/", 6)) + { + HTTPSV_GenerateDownload(cluster, pend, (char*)pend->inbuffer+10); + } + else if (!strncmp((char*)pend->inbuffer+4, "/style.css", 10)) + { + HTTPSV_GenerateCSSFile(cluster, pend); + } + else + { +#define dest pend + HTTPSV_SendHTTPHeader(cluster, dest, "404", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "Address not recognised"); + HTMLPRINT("

Address not recognised

"); + HTTPSV_SendHTMLFooter(cluster, dest); + } +}