#include "quakedef.h" #ifdef WEBSERVER #include "iweb.h" #include "netinc.h" #ifdef WEBSVONLY //we need some functions from quake char *NET_SockadrToString(char *s, int slen, struct sockaddr_qstorage *addr) { switch(((struct sockaddr*)addr)->sa_family) { case AF_INET: Q_snprintfz(s, slen, "%s:%u", inet_ntoa(((struct sockaddr_in*)addr)->sin_addr), ntohs(((struct sockaddr_in*)addr)->sin_port)); break; case AF_INET6: if (!memcmp(((struct sockaddr_in6*)addr)->sin6_addr.s6_bytes, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12)) { //ipv4-mapped Q_snprintfz(s, slen, "[::ffff:%u.%u.%u.%u]:%u", ((struct sockaddr_in6*)addr)->sin6_addr.s6_bytes[12], ((struct sockaddr_in6*)addr)->sin6_addr.s6_bytes[13], ((struct sockaddr_in6*)addr)->sin6_addr.s6_bytes[14], ((struct sockaddr_in6*)addr)->sin6_addr.s6_bytes[15], ntohs(((struct sockaddr_in6*)addr)->sin6_port)); } else { Q_snprintfz(s, slen, "[%x:%x:%x:%x:%x:%x:%x:%x]:%u", ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[0]), ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[1]), ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[2]), ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[3]), ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[4]), ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[5]), ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[6]), ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[7]), ntohs(((struct sockaddr_in6*)addr)->sin6_port)); } break; default: *s = 0; break; } return s; } qboolean SV_AllowDownload (const char *name) { if (strstr(name, "..")) return false; if (strchr(name, ':')) return false; if (*name == '/' || *name == '\\') return false; return true; } char com_token[sizeof(com_token)]; com_tokentype_t com_tokentype; int com_argc; const char **com_argv; vfsfile_t *IWebGenerateFile(const char *name, const char *content, int contentlength) { return NULL; } vfsfile_t *VFSSTDIO_Open(const char *osname, const char *mode, qboolean *needsflush); vfsfile_t *FS_OpenVFS(const char *filename, const char *mode, enum fs_relative relativeto) { return VFSSTDIO_Open(filename, mode, NULL); } void Q_strncpyz(char *d, const char *s, int n) { int i; n--; if (n < 0) return; //this could be an error for (i=0; *s; i++) { if (i == n) break; *d++ = *s++; } *d='\0'; } void VARGS Q_snprintfz (char *dest, size_t size, const char *fmt, ...) { va_list args; va_start (args, fmt); #ifdef _WIN32 #undef _vsnprintf _vsnprintf (dest, size-1, fmt, args); #else vsnprintf (dest, size-1, fmt, args); #endif va_end (args); //make sure its terminated. dest[size-1] = 0; } /*char *va(char *format, ...) { #define VA_BUFFERS 2 //power of two va_list argptr; static char string[VA_BUFFERS][1024]; static int bufnum; bufnum++; bufnum &= (VA_BUFFERS-1); va_start (argptr, format); _vsnprintf (string[bufnum],sizeof(string[bufnum])-1, format,argptr); va_end (argptr); return string[bufnum]; }*/ #undef _vsnprintf void Sys_Error(const char *format, ...) { va_list argptr; char string[1024]; va_start (argptr, format); #ifdef _WIN32 _vsnprintf (string,sizeof(string)-1, format,argptr); string[sizeof(string)-1] = 0; #else vsnprintf (string,sizeof(string), format,argptr); #endif va_end (argptr); printf("%s", string); getchar(); exit(1000); } int COM_CheckParm(const char *parm) { return 0; } char *authedusername; char *autheduserpassword; int lport_min, lport_max; int anonaccess = IWEBACC_READ; iwboolean verbose; int main(int argc, char **argv) { int httpport = 80; int ftpport = 21; int arg = 1; #ifdef _WIN32 WSADATA pointlesscrap; WSAStartup(2, &pointlesscrap); #endif while (arg < argc) { char *a = argv[arg]; if (!a) continue; if (*a != '-') break; //other stuff while (*a == '-') a++; arg++; if (!strcmp(a, "help")) { printf("%s -http 80 -ftp 21 -user steve -pass swordfish -ports 5000 6000\n", argv[0]); printf("runs a simple http server\n"); printf(" -http specifies the port to listen on for http\n"); printf(" -ftp specifies the port to listen on for ftp\n"); printf(" -ports specifies a port range for incoming ftp connections, to work around firewall rules\n"); printf(" -user specifies the username that has full access. if not supplied noone can write.\n"); printf(" -pass specifies the password to go with that username\n"); printf(" -noanon will refuse to serve files to anyone but the authed user\n"); return; } else if (!strcmp(a, "port") || !strcmp(a, "p")) { httpport = atoi(argv[arg++]); ftpport = 0; } else if (!strcmp(a, "http") || !strcmp(a, "h")) httpport = atoi(argv[arg++]); else if (!strcmp(a, "ftp") || !strcmp(a, "f")) ftpport = atoi(argv[arg++]); else if (!strcmp(a, "noanon")) anonaccess = 0; else if (!strcmp(a, "ports")) { lport_min = atoi(argv[arg++]); lport_max = atoi(argv[arg++]); if (lport_max < lport_min) lport_max = lport_min; } else if (!strcmp(a, "verbose") || !strcmp(a, "v")) verbose = true; else if (!strcmp(a, "user")) authedusername = argv[arg++]; else if (!strcmp(a, "pass")) autheduserpassword = argv[arg++]; else printf("Unknown argument: %s\n", a); } if (arg < argc && atoi(argv[arg])) { httpport = atoi(argv[arg++]); ftpport = 0; } if (arg < argc) authedusername = argv[arg++]; if (arg < argc) autheduserpassword = argv[arg++]; printf("http port %i\n", httpport); printf("ftp port %i\n", ftpport); if (authedusername || autheduserpassword) printf("Username = \"%s\"\nPassword = \"%s\"\n", authedusername, autheduserpassword); else printf("Server is read only\n"); while(1) { if (ftpport) FTP_ServerRun(1, ftpport); if (httpport) HTTP_ServerPoll(1, httpport); #ifdef _WIN32 Sleep(1); #else usleep(10000); #endif } } int IWebGetSafeListeningPort(void) { static int sequence; return lport_min + (sequence++ % (lport_max+1-lport_min)); } void VARGS IWebDPrintf(char *fmt, ...) { va_list args; if (!verbose) return; va_start (args, fmt); vprintf (fmt, args); va_end (args); } #ifdef _WIN32 #ifdef _MSC_VER #define ULL(x) x##ui64 #else #define ULL(x) x##ull #endif static time_t Sys_FileTimeToTime(FILETIME ft) { ULARGE_INTEGER ull; ull.LowPart = ft.dwLowDateTime; ull.HighPart = ft.dwHighDateTime; return ull.QuadPart / ULL(10000000) - ULL(11644473600); } void COM_EnumerateFiles (const char *match, int (*func)(const char *, qofs_t, time_t mtime, void *, searchpathfuncs_t *f), void *parm) { HANDLE r; WIN32_FIND_DATAA fd; char apath[MAX_OSPATH]; char file[MAX_OSPATH]; char *s; int go; strcpy(apath, match); // sprintf(apath, "%s%s", gpath, match); for (s = apath+strlen(apath)-1; s>= apath; s--) { if (*s == '/') break; } s++; *s = '\0'; strcpy(file, match); r = FindFirstFileA(file, &fd); if (r==(HANDLE)-1) return; go = true; do { if (*fd.cFileName == '.'); else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //is a directory { sprintf(file, "%s%s/", apath, fd.cFileName); go = func(file, fd.nFileSizeLow, Sys_FileTimeToTime(fd.ftLastWriteTime), parm, NULL); } else { sprintf(file, "%s%s", apath, fd.cFileName); go = func(file, fd.nFileSizeLow, Sys_FileTimeToTime(fd.ftLastWriteTime), parm, NULL); } } while(FindNextFileA(r, &fd) && go); FindClose(r); } #endif char *COM_ParseType (const char *data, char *out, int outlen, com_tokentype_t *toktype) { int c; int len; len = 0; out[0] = 0; if (!data) return NULL; // skip whitespace skipwhite: while ( (c = *data) <= ' ') { if (c == 0) return NULL; // end of file; data++; } // skip // comments if (c=='/') { if (data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } } // handle quoted strings specially if (c == '\"') { com_tokentype = TTP_STRING; data++; while (1) { if (len >= outlen-1) return (char*)data; c = *data++; if (c=='\"' || !c) { out[len] = 0; return (char*)data; } out[len] = c; len++; } } com_tokentype = TTP_UNKNOWN; // parse a regular word do { if (len >= outlen-1) return (char*)data; out[len] = c; data++; len++; c = *data; } while (c>32); out[len] = 0; return (char*)data; } /*#undef COM_ParseToken char *COM_ParseToken (const char *data, const char *punctuation) { int c; int len; len = 0; com_token[0] = 0; if (!data) return NULL; // skip whitespace skipwhite: while ( (c = *data) <= ' ') { if (c == 0) return NULL; // end of file; data++; } // skip // comments if (c=='/') { if (data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } else if (data[1] == '*') { data+=2; while (*data && (*data != '*' || data[1] != '/')) data++; data+=2; goto skipwhite; } } // handle quoted strings specially if (c == '\"') { com_tokentype = TTP_STRING; data++; while (1) { c = *data++; if (c=='\"' || !c) { com_token[len] = 0; return (char*)data; } com_token[len] = c; len++; } } com_tokentype = TTP_UNKNOWN; // parse single characters if (c==',' || c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':' || c==';' || c == '=' || c == '!' || c == '>' || c == '<' || c == '&' || c == '|' || c == '+') { com_token[len] = c; len++; com_token[len] = 0; return (char*)data+1; } // parse a regular word do { com_token[len] = c; data++; len++; c = *data; if (c==',' || c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':' || c==';' || c == '=' || c == '!' || c == '>' || c == '<' || c == '&' || c == '|' || c == '+') break; } while (c>32); com_token[len] = 0; return (char*)data; }*/ /* IWEBFILE *IWebFOpenRead(char *name) //fread(name, "rb"); { FILE *f; char name2[512]; if (strstr(name, "..")) return NULL; sprintf(name2, "%s/%s", com_gamedir, name); f = fopen(name2, "rb"); if (f) { IWEBFILE *ret = IWebMalloc(sizeof(IWEBFILE)); if (!ret) { fclose(f); return NULL; } ret->f = f; ret->start = 0; fseek(f, 0, SEEK_END); ret->end = ftell(f);//ret->start+ret->length; fseek(f, 0, SEEK_SET); ret->length = ret->end - ret->start; return ret; } return NULL; } */ #else #ifdef WEBSERVER cvar_t ftpserver = CVAR("sv_ftp", "0"); cvar_t ftpserver_port = CVAR("sv_ftp_port", "21"); cvar_t httpserver = CVAR("sv_http", "0"); cvar_t httpserver_port = CVAR("sv_http_port", "80"); cvar_t sv_readlevel = CVAR("sv_readlevel", "0"); //default to allow anyone cvar_t sv_writelevel = CVAR("sv_writelevel", "35"); //allowed to write to uploads/uname cvar_t sv_fulllevel = CVAR("sv_fulllevel", "51"); //allowed to write anywhere, replace any file... cvar_t sv_ftp_port_range = CVARD("sv_ftp_port_range", "0", "Specifies the port range for the server to create listening sockets for 'active' ftp connections, to work around NAT/firewall issues.\nMost FTP clients should use passive connections, but there's still some holdouts like windows."); int IWebGetSafeListeningPort(void) { char *e; int base, range; static int sequence; if (!*sv_ftp_port_range.string) return 0; //lets the OS pick. base = strtol(sv_ftp_port_range.string, &e, 0); while(*e == ' ') e++; if (*e == '-') e++; while(*e == ' ') e++; range = strtol(e, NULL, 0); if (range < base) range = base; return base + (sequence++ % (range+1-base)); } #endif //this file contains functions called from each side. void VARGS IWebWarnPrintf(char *fmt, ...) { va_list argptr; char msg[4096]; va_start (argptr,fmt); vsnprintf (msg,sizeof(msg)-10, fmt,argptr); //catch any nasty bugs... (this is hopefully impossible) va_end (argptr); Con_Printf(CON_WARNING "%s", msg); } void IWebInit(void) { #ifdef WEBSERVER Cvar_Register(&sv_fulllevel, "Internet Server Access"); Cvar_Register(&sv_writelevel, "Internet Server Access"); Cvar_Register(&sv_readlevel, "Internet Server Access"); Cvar_Register(&ftpserver, "Internet Server Access"); Cvar_Register(&ftpserver_port, "Internet Server Access"); Cvar_Register(&sv_ftp_port_range, "Internet Server Access"); Cvar_Register(&httpserver, "Internet Server Access"); Cvar_Register(&httpserver_port, "Internet Server Access"); //don't allow these to be changed easily //this basically blocks these from rcon / stuffcmd ftpserver.restriction = RESTRICT_MAX; httpserver.restriction = RESTRICT_MAX; sv_fulllevel.restriction = RESTRICT_MAX; sv_writelevel.restriction = RESTRICT_MAX; sv_readlevel.restriction = RESTRICT_MAX; #endif } void IWebRun(void) { #ifdef WEBSERVER extern qboolean httpserverfailed, ftpserverfailed; FTP_ServerRun(ftpserver.ival!= 0, ftpserver_port.ival); HTTP_ServerPoll(httpserver.ival!=0, httpserver_port.ival); if (ftpserverfailed) { Con_Printf("FTP Server failed to load, setting %s to 0\n", ftpserver.name); Cvar_SetValue(&ftpserver, 0); ftpserverfailed = false; } if (httpserverfailed) { Con_Printf("HTTP Server failed to load, setting %s to 0\n", httpserver.name); Cvar_SetValue(&httpserver, 0); httpserverfailed = false; } #endif } void IWebShutdown(void) { } #endif #ifdef WEBSVONLY void *Sys_CreateThread(char *name, int (*func)(void *), void *args, int priority, int stacksize) { return NULL; } void Sys_WaitOnThread(void *thread) { } qboolean FS_Remove(const char *fname, enum fs_relative relativeto) { return false; } qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out, int outlen) { Q_strncpyz(out, fname, outlen); if (*out == '/' || strstr(out, "..")) { *out = 0; return false; } return strlen(fname) == strlen(out); } void FS_FlushFSHashWritten(const char *fname) {} void FS_FlushFSHashRemoved(const char *fname) {} qboolean FS_Rename(const char *oldf, const char *newf, enum fs_relative relativeto) { return rename(oldf, newf) != -1; } #ifdef _WIN32 #include void FS_CreatePath(const char *pname, enum fs_relative relativeto) { _mkdir(pname); } qboolean Sys_rmdir (const char *path) { return _rmdir(path) != -1; } #else #include void FS_CreatePath(const char *pname, enum fs_relative relativeto) { mkdir(pname); } qboolean Sys_rmdir (const char *path) { return rmdir(path) != -1; } #endif #endif int IWebAuthorize(const char *name, const char *password) { #ifdef WEBSVONLY if (authedusername) { if (!strcmp(name, authedusername)) { if (!strcmp(password, autheduserpassword)) return IWEBACC_FULL; } } //if they tried giving some other username, don't give them any access (prevents them from reading actual user files). if (*name && stricmp(name, "anonymous")) return 0; return anonaccess; #else #ifndef CLIENTONLY int id = Rank_GetPlayerID(NULL, name, atoi(password), false, true); rankinfo_t info; if (!id) { if (!sv_readlevel.value && (!*name || !stricmp(name, "anonymous"))) return IWEBACC_READ; //read only anywhere return 0; } Rank_GetPlayerInfo(id, &info); if (info.s.trustlevel >= sv_fulllevel.value) return IWEBACC_READ | IWEBACC_WRITE | IWEBACC_FULL; //allowed to read and write anywhere to the quake filesystem if (info.s.trustlevel >= sv_writelevel.value) return IWEBACC_READ | IWEBACC_WRITE; //allowed to read anywhere write to specific places if (info.s.trustlevel >= sv_readlevel.value) return IWEBACC_READ; //read only anywhere #endif return 0; #endif } iwboolean IWebAllowUpLoad(const char *fname, const char *uname) //called for partial write access { if (strstr(fname, "..")) return false; if (!strncmp(fname, "uploads/", 8)) { if (!strncmp(fname+8, uname, strlen(uname))) if (fname[8+strlen(uname)] == '/') return true; } return false; } char *Q_strcpyline(char *out, const char *in, int maxlen) { char *w = out; while (*in && maxlen > 0) { if (*in == '\r' || *in == '\n') break; *w = *in; in++; w++; maxlen--; } *w = '\0'; return out; } #endif