diff --git a/engine/http/ftpclient.c b/engine/http/ftpclient.c new file mode 100644 index 00000000..e16fd865 --- /dev/null +++ b/engine/http/ftpclient.c @@ -0,0 +1,868 @@ +#include "bothdefs.h" + +#ifdef WEBCLIENT + +#include "iweb.h" + +#ifdef _WIN32 +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EMSGSIZE WSAEMSGSIZE +#define ECONNRESET WSAECONNRESET +#define ECONNABORTED WSAECONNABORTED +#define ECONNREFUSED WSAECONNREFUSED +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL + +#define qerrno WSAGetLastError() +#else +#define qerrno errno + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef sun +#include +#endif + +#ifdef NeXT +#include +#endif + +#define closesocket close +#define ioctlsocket ioctl +#endif + + +typedef struct FTPclientconn_s{ + char server[256]; + char name[64]; + char pwd[64]; + char path[256]; + char pathprefix[256]; //Urhum.. Without this we can browse various entire hard drives too easily. + char file[64]; + char localfile[MAX_QPATH]; + + int transfersize; + int transfered; + + int controlsock; + int datasock; //FTP only allows one transfer per connection. + + enum {ftp_control, ftp_listing, ftp_getting, ftp_putting} type; + int stage; + + IWEBFILE *f; + + struct FTPclientconn_s *next; +} FTPclientconn_t; + +FTPclientconn_t *FTPclientconn; + +FTPclientconn_t *FTP_CreateConnection(char *addy) +{ + unsigned long _true = true; + struct sockaddr_qstorage from; + FTPclientconn_t *con; + + + + con = IWebMalloc(sizeof(FTPclientconn_t)); + + + + if ((con->controlsock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + Sys_Error ("FTP_UDP_OpenSocket: socket: %s\n", strerror(qerrno)); + } + + + {//quake routines using dns and stuff (Really, I wanna keep quake and ftp fairly seperate) + netadr_t qaddy; + NET_StringToAdr (addy, &qaddy); + if (!qaddy.port) + qaddy.port = htons(21); + NetadrToSockadr(&qaddy, &from); + } + + //not yet blocking. + if (connect(con->controlsock, (struct sockaddr *)&from, sizeof(from)) == -1) + { + IWebWarnPrintf ("FTP_TCP_OpenSocket: connect: %i %s\n", qerrno, strerror(qerrno)); + closesocket(con->controlsock); + IWebFree(con); + return NULL; + } + + if (ioctlsocket (con->controlsock, FIONBIO, &_true) == -1) //now make it non blocking. + { + Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO: %s\n", strerror(qerrno)); + } + + Q_strncpyz(con->server, addy, sizeof(con->server)); + strcpy(con->name, "anonymous"); + + con->next = FTPclientconn; + FTPclientconn = con; + con->stage = 1; + con->type = ftp_control; + + strcpy(con->path, "/"); + con->datasock = INVALID_SOCKET; + con->transfersize = -1; + con->transfered = 0; + + return FTPclientconn; +} +//duplicate a connection to get multiple data channels with a server. +FTPclientconn_t *FTP_DuplicateConnection(FTPclientconn_t *old) +{ + FTPclientconn_t *new; + new = FTP_CreateConnection(old->server); + *new->server = '\0'; //mark it as non control + strcpy(new->name, old->name); + strcpy(new->pwd, old->pwd); + strcpy(new->path, old->path); + strcpy(new->pathprefix, old->pathprefix); + + return new; +} + +int FTP_CL_makelistensocket(void) +{ + char name[256]; + unsigned long _true = true; + int sock; + struct hostent *hent; + + struct sockaddr_in address; +// int fromlen; + + address.sin_family = AF_INET; + if (gethostname(name, sizeof(name)) == -1) + return INVALID_SOCKET; + hent = gethostbyname(name); + if (!hent) + return INVALID_SOCKET; + address.sin_addr.s_addr = *(int *)(hent->h_addr_list[0]); + address.sin_port = 0; + + + + if ((sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + Sys_Error ("FTP_TCP_OpenSocket: socket:", strerror(qerrno)); + } + + if (ioctlsocket (sock, FIONBIO, &_true) == -1) + { + Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO:", strerror(qerrno)); + } + + if( bind (sock, (void *)&address, sizeof(address)) == -1) + { + closesocket(sock); + return INVALID_SOCKET; + } + + listen(sock, 1); + + return sock; +} +int FTP_CL_makeconnectsocket(char *ftpdest) +{ + unsigned long _true = true; + int sock; + + struct sockaddr_in address; + + if (!ftpdest) + return 0; + if (*ftpdest == '(') + ftpdest++; + + if ((sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + IWebWarnPrintf ("FTP_UDP_OpenSocket: socket:", strerror(qerrno)); + return INVALID_SOCKET; + } + + if (ioctlsocket (sock, FIONBIO, &_true) == -1) + { + closesocket(sock); + IWebWarnPrintf ("FTTP_UDP_OpenSocket: ioctl FIONBIO:", strerror(qerrno)); + return INVALID_SOCKET; + } + + address.sin_family = AF_INET; + + address.sin_addr.s_addr = INADDR_ANY; + + address.sin_port = 0; + + if( bind (sock, (void *)&address, sizeof(address)) == -1) + { + closesocket(sock); + + IWebWarnPrintf ("FTTP_UDP_OpenSocket: bind:", strerror(qerrno)); + return INVALID_SOCKET; + } + + FTP_StringToAdr(ftpdest, (qbyte *)&address.sin_addr, (qbyte *)&address.sin_port); + + //this is commented out because connect always reports would_block, no matter what happens. So why check? + //if ( + connect(sock, (struct sockaddr *)&address, sizeof(address));// == -1) +/* { + closesocket(sock); + + Con_Printf ("FTTP_UDP_OpenSocket: ioctl FIONBIO:", strerror(qerrno)); + return INVALID_SOCKET; + } +*/ + return sock; +} + +iwboolean FTP_SocketToString (int socket, char *s) +{ + struct sockaddr_in addr; + int adrlen = sizeof(addr); + + if (getsockname(socket, (struct sockaddr*)&addr, &adrlen) == -1) + return false; + + sprintf(s, "%i,%i,%i,%i,%i,%i", ((qbyte*)&addr.sin_addr)[0], ((qbyte*)&addr.sin_addr)[1], ((qbyte*)&addr.sin_addr)[2], ((qbyte*)&addr.sin_addr)[3], ((qbyte *)&addr.sin_port)[0], ((qbyte *)&addr.sin_port)[1]); + return true; +} + +iwboolean FTP_ClientConnThink (FTPclientconn_t *con) //true to kill con +{ + char *line, *msg; + int ret; + + char readdata[8192]; + char tempbuff[8192]; + + if (con->stage == 6) + { + int len; + if (con->type == ftp_getting) + { + if (!cls.downloadmethod || (cls.downloadmethod == DL_FTP && !strcmp(cls.downloadname, con->localfile))) + { + strcpy(cls.downloadname, con->localfile); + cls.downloadmethod = DL_FTP; + if (con->transfersize == -1) + cls.downloadpercent=50; + else + cls.downloadpercent = con->transfered*100.0f/con->transfersize; + } + while((len = recv(con->datasock, readdata, sizeof(readdata), 0)) >0 ) + { + IWebFWrite(readdata, len, 1, con->f); + con->transfered += len; + } + if (len == 0) + { + closesocket(con->datasock); + con->datasock = INVALID_SOCKET; + } + } + else if (con->type == ftp_putting) + { + int pos, sent; + int ammount, wanted = sizeof(readdata); + + pos = IWebFTell(con->f); + ammount = IWebFRead(readdata, 1, wanted, con->f); + sent = send(con->datasock, readdata, ammount, 0); + if (sent == -1) + IWebFSeek(con->f, pos, SEEK_SET); //go back. Too much data + else + { + IWebFSeek(con->f, pos + sent, SEEK_SET); //written this much + + if (!ammount) //file is over + { + closesocket(con->datasock); + con->datasock = INVALID_SOCKET; + + // msg = "226 Transfer complete.\r\n"; + // send (con->controlsock, msg, strlen(msg), 0); + } + } + } + } + + ret = recv(con->controlsock, (char *)readdata, sizeof(readdata)-1, 0); + if (ret == -1) + { + if (qerrno == EWOULDBLOCK) + return false; + + if (qerrno == ECONNABORTED || qerrno == ECONNRESET) + { + Con_TPrintf (TL_CONNECTIONLOSTORABORTED); + return true; + } + +// Con_TPrintf (TL_NETGETPACKETERROR, strerror(qerrno)); + return true; + } + + readdata[ret] = '\0'; //null terminate. (it's a string) + + //we now have a message. + //We've got to work out what has happened already. + + //a server can send many lines of text for one reply + //220-hello + // this + //220-is + // 220-all + //220 one reply + //so we only read lines that contain number space words, without any leading space + line = readdata; + while (1) + { + msg = line; + while (*line) + { + if (*line == '\n') + break; + line++; + } + if (!*line) //broken message + break; + *line = '\0'; + line++; + + if (*con->server) + IWebDPrintf("^2FTP: %s\n", COM_TrimString(msg)); + + if (*msg < '0' || *msg > '9') //make sure it starts with number + continue; + ret = atoi(msg); + while(*msg >= '0' && *msg <= '9') //find next non number + msg++; + if (*msg != ' ') //must be a space (definatly not a '-') + continue; + msg++; + + if (ret == 220) + { + sprintf(tempbuff, "USER %s\r\n", con->name); + send(con->controlsock, tempbuff, strlen(tempbuff), 0); + con->stage = 1; + } + else if (ret == 331) + { + if (con->type == ftp_control) + sprintf(tempbuff, "PASS %s\r\nPWD %s\r\n", con->pwd, con->path); + else + sprintf(tempbuff, "PASS %s\r\n", con->pwd); + send(con->controlsock, tempbuff, strlen(tempbuff), 0); + con->stage = 2; + } + else if (ret == 230) //we must now do something useful + { + char adr[64]; + if (con->type == ftp_control) //control is for browsing and duplicating + continue; + + con->datasock = FTP_CL_makelistensocket(); + if (!con->datasock || !FTP_SocketToString(con->datasock, adr)) + { + return true; + } + + sprintf(tempbuff, "CWD %s%s\r\nPORT %s\r\n", con->pathprefix, con->path, adr); + send(con->controlsock, tempbuff, strlen(tempbuff), 0); + con->stage = 3; + } + else if (ret == 200) + { + struct sockaddr addr; + int addrlen = sizeof(addr); + int temp; + if (con->type == ftp_control) + continue; + if (con->stage == 3) + { + temp = accept(con->datasock, &addr, &addrlen); + closesocket(con->datasock); + con->datasock = temp; + + if (temp != INVALID_SOCKET) + { + con->stage = 6; + if (con->type == ftp_getting) + { + con->f = IWebFOpenWrite(con->localfile, false); + if (con->f) + { + sprintf(tempbuff, "RETR %s\r\n", con->file); + con->stage = 6; + con->transfered = 0; + con->transfersize = -1; + } + else + { + sprintf(tempbuff, "QUIT\r\n"); + con->stage = 7; + } + } + else if (con->type == ftp_putting) + { + con->f = IWebFOpenRead(con->localfile); + if (con->f) + { + sprintf(tempbuff, "STOR %s\r\n", con->file); + con->stage = 6; + con->transfered = 0; + con->transfersize = con->f->length; + } + else + { + sprintf(tempbuff, "QUIT\r\n"); + con->stage = 7; + } + } + else + sprintf(tempbuff, "LIST %s\r\n", con->pwd); + send(con->controlsock, tempbuff, strlen(tempbuff), 0); + } + else + { + Con_Printf("FTP: Trying passive server mode\n"); + msg = va("PASV\r\n"); + send(con->controlsock, msg, strlen(msg), 0); + con->stage = 4; + } + } + } + else if (ret == 213) + { + con->transfersize = atoi(msg); + msg = va("RETR %s\r\n", con->file); + con->stage = 6; + con->transfered = 0; + send(con->controlsock, msg, strlen(msg), 0); + } + else if (ret == 125) //begining transfer + { + if (con->type == ftp_getting) + { + COM_StripExtension(con->localfile, msg); + strcat(msg, ".tmp"); + con->f = IWebFOpenWrite(msg, false); + if (!con->f) + { + msg = va("ABOR\r\nQUIT\r\n"); //bummer. we couldn't open this file to output to. + send(con->controlsock, msg, strlen(msg), 0); + con->stage = 7; + return true; + } + } +// msg = va("LIST\r\n"); +// send(con->controlsock, msg, strlen(msg), 0); + con->stage = 6; + } + else if (ret == 226) //transfer complete + { + int len; + char data[1024]; + if (con->f) + { + if (con->type == ftp_getting) + { + while(1) //this is potentially dodgy. + { + len = recv(con->datasock, data, sizeof(data), 0); + if (len == 0) + break; + if (len == -1) + { + if (qerrno != EWOULDBLOCK) + break; + continue; + } + con->transfered+=len; + data[len] = 0; + IWebFWrite(data, len, 1, con->f); + } + } + IWebFClose(con->f); + con->f = NULL; + closesocket(con->datasock); + con->datasock = INVALID_SOCKET; + + if (con->transfersize != -1 && con->transfered != con->transfersize) + { + IWebPrintf("Transfer corrupt\nTransfered %i of %i bytes\n", con->transfered, con->transfersize); + } + else + IWebPrintf("Transfer compleate\n"); + } + else + { + while((len = recv(con->datasock, data, sizeof(data), 0)) >0 ) + { + data[len] = 0; + if (strchr(data, '\r')) + { + line = data; + for(;;) + { + msg = strchr(line, '\r'); + if (!msg) + break; + *msg = '\0'; + Con_Printf("%s", line); + line = msg+1; + } + Con_Printf("%s", line); + } + else + Con_Printf("%s", data); + } + closesocket(con->datasock); + con->datasock = INVALID_SOCKET; + } + msg = va("QUIT\r\n"); + send(con->controlsock, msg, strlen(msg), 0); + + con->stage = 7; + } + else if (ret == 227) + { +// Con_Printf("FTP: Got passive server mode\n"); + if (con->datasock != INVALID_SOCKET) + closesocket(con->datasock); + con->datasock = INVALID_SOCKET; + + con->datasock = FTP_CL_makeconnectsocket(strchr(msg, '(')); + if (con->datasock != INVALID_SOCKET) + { + if (con->type == ftp_getting) + { + con->f = IWebFOpenWrite(con->localfile, false); + if (con->f) + { + con->stage = 8; + msg = va("TYPE I\r\nSIZE %s\r\n", con->file); + con->transfersize = -1; + + /* + msg = va("RETR %s\r\n", con->file); + con->stage = 6; + con->transfered = 0; + */ + } + else + { + msg = va("QUIT\r\n"); + con->stage = 7; + Con_Printf("FTP: Failed to open local file %s\n", con->localfile); + } + } + else if (con->type == ftp_putting) + { + con->f = IWebFOpenRead(con->localfile); + if (con->f) + { + msg = va("STOR %s\r\n", con->file); + con->stage = 6; + con->transfered = 0; + con->transfersize = con->f->length; + } + else + { + msg = va("QUIT\r\n"); + con->stage = 7; + Con_Printf("FTP: Failed to open local file %s\n", con->localfile); + } + } + else + { + msg = "LIST\r\n"; + con->stage = 6; + } + } + else + { + msg = "QUIT\r\n"; + con->stage = 7; + Con_Printf("FTP: Didn't connect\n"); + } + + send (con->controlsock, msg, strlen(msg), 0); + } + else if (ret == 250) + { + Con_Printf("FTP: %i %s\n", ret, msg); + } + else if (ret == 257) + { //stick it on the beginning. + Con_Printf("FTP: %i %s\n", ret, msg); + msg = strchr(msg, '"'); + if (msg) + { + Q_strncpyz(con->pathprefix, msg+1, sizeof(con->pathprefix)); + msg = strchr(con->pathprefix, '"'); + if (msg) + *msg = '\0'; + } + else + Q_strcpyline(con->pathprefix, msg+4, sizeof(con->pathprefix)-1); + } + else + { + if (ret < 200) + continue; + if (con->stage == 5) + { + Con_DPrintf("FTP: Trying passive server mode\n"); + msg = va("PASV\r\n"); + send(con->controlsock, msg, strlen(msg), 0); + con->stage = 4; + continue; + } + if (ret != 221) + Con_Printf("^1FTP: %i %s\n", ret, msg); + return true; + } + + continue; + } + return false; +} + +void FTP_ClientThink (void) +{ + FTPclientconn_t *con, *old=NULL; + for (con = FTPclientconn; con; con = con->next) + { + if (FTP_ClientConnThink(con)) + { + if (cls.downloadmethod == DL_FTP && !strcmp(cls.downloadname, con->localfile)) + { //this was us + cls.downloadmethod = DL_NONE; + } + if (con->f) + IWebFClose(con->f); + if (con->controlsock != INVALID_SOCKET) + closesocket(con->controlsock); + if (con->datasock != INVALID_SOCKET) + closesocket(con->datasock); + if (!old) + { + FTPclientconn = con->next; + IWebFree(con); + break; + } + else + { + old->next = con->next; + IWebFree(con); + + break; + } + } + old = con; + } +} + +FTPclientconn_t *FTP_FindControl(void) +{ + FTPclientconn_t *con; + + for (con = FTPclientconn; con; con = con->next) + { + if (*con->server) + return con; + } + return NULL; +} +void FTP_Client_Command (char *cmd) +{ + char command[64]; + char server[MAX_OSPATH]; + FTPclientconn_t *con; + + cmd = COM_ParseOut(cmd, command, sizeof(command)); + if (!stricmp(command, "open")) + { + if (FTP_FindControl()) + Con_Printf("You are already connected\n"); + else + { + cmd = COM_ParseOut(cmd, server, sizeof(server)); + if ((con = FTP_CreateConnection(server))) + { + Con_Printf("FTP connect succeded\n"); + cmd = COM_ParseOut(cmd, command, sizeof(command)); + if (cmd) + { + Q_strncpyz(con->name, command, sizeof(con->name)); + cmd = COM_ParseOut(cmd, command, sizeof(command)); + if (cmd) + Q_strncpyz(con->pwd, command, sizeof(con->pwd)); + } + } + else + Con_Printf("FTP connect failed\n"); + } + } + else if (!stricmp(command, "download")) + { + cmd = COM_ParseOut(cmd, server, sizeof(server)); + con = FTP_CreateConnection(server); + if (!con) + { + Con_Printf("FTP: Couldn't connect\n"); + return; + } + *con->server = '\0'; + con->type = ftp_getting; + cmd = COM_ParseOut(cmd, server, sizeof(server)); + Q_strncpyz(con->file, server, sizeof(con->file)); + Q_strncpyz(con->localfile, server, sizeof(con->localfile)); + + if (cmd = COM_ParseOut(cmd, server, sizeof(server))) + Q_strncpyz(con->localfile, server, sizeof(con->localfile)); + } + else if (!stricmp(command, "quit")) + { + con = FTP_FindControl(); + if (con) + { + char *msg; + msg = va("QUIT\r\n"); + send(con->controlsock, msg, strlen(msg), 0); +// if (con->datasock) +// closesocket(con->datasock); +// closesocket(con->controlsock); + } + else + Con_Printf("No main FTP connection\n"); + } + else if (!stricmp(command, "list")) + { + FTPclientconn_t *new, *con = FTP_FindControl(); + if (!con) + { + Con_Printf("Not connected\n"); + return; + } + + new = FTP_DuplicateConnection(con); + if (!new) + { + Con_Printf("Failed duplicate connection\n"); + return; + } + new->type = ftp_listing; + } + else if (!stricmp(command, "get")) + { + FTPclientconn_t *new, *con = FTP_FindControl(); + if (!con) + { + Con_Printf("Not connected\n"); + return; + } + + cmd = COM_ParseOut(cmd, command, sizeof(command)); + if (!cmd) + { + Con_Printf("No file specified\n"); + return; + } + + new = FTP_DuplicateConnection(con); + if (!new) + { + Con_Printf("Failed duplicate connection\n"); + return; + } + new->type = ftp_getting; + sprintf(new->file, command); + sprintf(new->localfile, "%s%s", new->path, command); + } + else if (!stricmp(command, "put")) + { + FTPclientconn_t *new, *con = FTP_FindControl(); + if (!con) + { + Con_Printf("Not connected\n"); + return; + } + + cmd = COM_ParseOut(cmd, command, sizeof(command)); + if (!cmd) + { + Con_Printf("No file specified\n"); + return; + } + + new = FTP_DuplicateConnection(con); + if (!new) + { + Con_Printf("Failed duplicate connection\n"); + return; + } + new->type = ftp_putting; + sprintf(new->file, command); + sprintf(new->localfile, "%s%s", new->path, command); + } + else if (!stricmp(command, "cwd")) + { + FTPclientconn_t *con = FTP_FindControl(); + if (!con) + { + Con_Printf("Not connected\n"); + return; + } + Con_Printf("%s\n", con->path); + } + else if (!stricmp(command, "cd")) + { + char *msg; + FTPclientconn_t *con = FTP_FindControl(); + if (!con) + { + Con_Printf("Not connected\n"); + return; + } + + cmd = COM_ParseOut(cmd, command, sizeof(command)); + + if (*command == '/') //absolute + Q_strncpyz(con->path, command, sizeof(con->path)); + else //bung it on the end + { + strncat(con->path, "/", sizeof(con->path)-1); + strncat(con->path, command, sizeof(con->path)-1); + } + + msg = va("CWD %s%s\r\n", con->pathprefix, con->path); + send(con->controlsock, msg, strlen(msg), 0); + } + else + Con_Printf("Unrecognised FTP command\n"); + /* + com = COM_ParseOut(com, command, sizeof(command)); + com = COM_ParseOut(com, command, sizeof(command)); + com = COM_ParseOut(com, command, sizeof(command)); + */ +} + +#endif diff --git a/engine/http/ftpserver.c b/engine/http/ftpserver.c new file mode 100644 index 00000000..03eaccac --- /dev/null +++ b/engine/http/ftpserver.c @@ -0,0 +1,875 @@ +#include "bothdefs.h" + +#ifdef WEBSERVER + +#include "iweb.h" + +//hows this as a bug. +//TCP data can travel at different speeds. +//If the later bits of a data channel arrive after the message saying that a transfer was compleate, +//the later bits of the file may not arrive before the client closes the conenction. +//this is a major bug and can prevent the server from giving files at a high pl/ping + +#ifdef _WIN32 + +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EMSGSIZE WSAEMSGSIZE +#define ECONNRESET WSAECONNRESET +#define ECONNABORTED WSAECONNABORTED +#define ECONNREFUSED WSAECONNREFUSED +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL + +#define qerrno WSAGetLastError() +#else +#define qerrno errno + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef sun +#include +#endif + +#ifdef NeXT +#include +#endif + +#define closesocket close +#define ioctlsocket ioctl +#endif + + + +char *COM_ParseOut (char *data, char *out, int outlen); + +static iwboolean ftpserverinitied = false; +static int ftpserversocket; + + +typedef struct FTPclient_s{ + char name[64]; + char pwd[64]; + int auth; //has it got auth? + char path[256]; + + char commandbuffer[256]; + char messagebuffer[256]; + int cmdbuflen; + int msgbuflen; + + int controlsock; + int datasock; //FTP only allows one transfer per connection. + int dataislisten; + int datadir; //0 no data, 1 reading, 2 writing + IWEBFILE *file; + + unsigned long blocking; + + struct FTPclient_s *next; +} FTPclient_t; + +FTPclient_t *FTPclient; + +void FTP_ServerInit(void) +{ + struct sockaddr_in address; + unsigned long _true = true; + int i; + int port = 21; + + if ((ftpserversocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + Sys_Error ("FTP_TCP_OpenSocket: socket:", strerror(qerrno)); + } + + if (ioctlsocket (ftpserversocket, FIONBIO, &_true) == -1) + { + Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO:", strerror(qerrno)); + } + + address.sin_family = AF_INET; +//ZOID -- check for interface binding option + if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) { + address.sin_addr.s_addr = inet_addr(com_argv[i+1]); + Con_TPrintf(TL_NETBINDINTERFACE, + inet_ntoa(address.sin_addr)); + } else + address.sin_addr.s_addr = INADDR_ANY; + + if (port == PORT_ANY) + address.sin_port = 0; + else + address.sin_port = htons((short)port); + + if( bind (ftpserversocket, (void *)&address, sizeof(address)) == -1) + { + closesocket(ftpserversocket); + return; + } + + listen(ftpserversocket, 3); + + ftpserverinitied = true; + + + IWebPrintf("FTP server is running\n"); + return; +} + +void FTP_ServerShutdown(void) +{ + closesocket(ftpserversocket); + ftpserverinitied = false; + IWebPrintf("FTP server is deactivated\n"); +} + +int SendFileNameTo(char *fname, int size, void *socket) +{ +// int i; + char buffer[256+1]; + char *slash; + int isdir = fname[strlen(fname)-1] == '/'; + +#ifndef WEBSVONLY //copy protection of the like that QWSV normally has. + if (!isdir) + if (!SV_AllowDownload(fname)) //don't advertise if we're going to disallow it + return true; +#endif + + if (isdir) + fname[strlen(fname)-1] = '\0'; + + while((slash = strchr(fname, '/'))) + fname = slash+1; + + if (isdir) + sprintf(buffer, "drw-r--r--\t1\troot\troot\t%8i Jan 1 12:00 %s\r\n", size, fname); + else + sprintf(buffer, "-rw-r--r--\t1\troot\troot\t%8i Jan 1 12:00 %s\r\n", size, fname); + +// strcpy(buffer, fname); +// for (i = strlen(buffer); i < 40; i+=8) +// strcat(buffer, "\t"); + send((int)socket, buffer, strlen(buffer), 0); + + return true; +} + +int FTP_SV_makelistensocket(unsigned long blocking) +{ + char name[256]; + unsigned long _true = true; + int sock; + struct hostent *hent; + + struct sockaddr_in address; +// int fromlen; + + address.sin_family = AF_INET; + if (gethostname(name, sizeof(name)) == -1) + return INVALID_SOCKET; + hent = gethostbyname(name); + if (!hent) + return INVALID_SOCKET; + address.sin_addr.s_addr = *(int *)(hent->h_addr_list[0]); + address.sin_port = 0; + + + + if ((sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + Sys_Error ("FTP_TCP_OpenSocket: socket:", strerror(qerrno)); + } + + if (ioctlsocket (sock, FIONBIO, &blocking) == -1) + { + Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO:", strerror(qerrno)); + } + + if( bind (sock, (void *)&address, sizeof(address)) == -1) + { + closesocket(sock); + return INVALID_SOCKET; + } + + listen(sock, 2); + + return sock; +} +iwboolean FTP_SVSocketToString (int socket, char *s) +{ + struct sockaddr_in addr; + int adrlen = sizeof(addr); + + if (getsockname(socket, (struct sockaddr*)&addr, &adrlen) == -1) + return false; + + sprintf(s, "%i,%i,%i,%i,%i,%i", ((qbyte *)&addr.sin_addr)[0], ((qbyte *)&addr.sin_addr)[1], ((qbyte *)&addr.sin_addr)[2], ((qbyte *)&addr.sin_addr)[3], ((qbyte *)&addr.sin_port)[0], ((qbyte *)&addr.sin_port)[1]); + return true; +} +iwboolean FTP_SVRemoteSocketToString (int socket, char *s) +{ + struct sockaddr_in addr; + int adrlen = sizeof(addr); + + addr.sin_family = AF_INET; + if (getpeername(socket, (struct sockaddr*)&addr, &adrlen) == -1) + return false; + + sprintf(s, "%i,%i,%i,%i,%i,%i", ((qbyte *)&addr.sin_addr)[0], ((qbyte *)&addr.sin_addr)[1], ((qbyte *)&addr.sin_addr)[2], ((qbyte *)&addr.sin_addr)[3], ((qbyte *)&addr.sin_port)[0], ((qbyte *)&addr.sin_port)[1]); + return true; +} +/* + * Responsable for sending all control server -> client messages. + * Queues the message if it cannot send now. + * Kicks if too big a queue. +*/ +void QueueMessage(FTPclient_t *cl, char *msg) +{ + if (send (cl->controlsock, msg, strlen(msg), 0) == -1) + { //wasn't sent + if (strlen(msg) + strlen(cl->messagebuffer) >= sizeof(cl->messagebuffer)-1) + closesocket(cl->controlsock); //but don't mark it as closed, so we get errors later (for this is how we shall tell). + strcat(cl->messagebuffer, msg); + } +} +void VARGS QueueMessageva(FTPclient_t *cl, char *fmt, ...) +{ + va_list argptr; + char msg[1024]; + + va_start (argptr, fmt); + _vsnprintf (msg,sizeof(msg)-1, fmt,argptr); + va_end (argptr); + + if (send (cl->controlsock, msg, strlen(msg), 0) == -1) + { //wasn't sent + if (strlen(msg) + strlen(cl->messagebuffer) >= sizeof(cl->messagebuffer)-1) + closesocket(cl->controlsock); //but don't mark it as closed, so we get errors later (for this is how we shall tell). + strcat(cl->messagebuffer, msg); + } +} +iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) +{ + int ret; + struct sockaddr_in from; + int fromlen; + char *msg, *line; +unsigned long _true = true; + + char mode[64]; + static char resource[8192]; + + if (cl->datadir == 1) + { + int pos, sent; + int ammount, wanted = sizeof(resource); + + pos = IWebFTell(cl->file); + ammount = IWebFRead(resource, 1, wanted, cl->file); + sent = send(cl->datasock, resource, ammount, 0); + + if (sent == -1) + { + IWebFSeek(cl->file, pos, SEEK_SET); + if (qerrno != EWOULDBLOCK) + { + closesocket(cl->datasock); + cl->datasock = INVALID_SOCKET; + IWebFClose(cl->file); + cl->file = NULL; + + QueueMessage (cl, "226 Transfer complete .\r\n"); + cl->datadir = 0; + } + } + else + { + if (sent != ammount) + IWebFSeek(cl->file, pos + sent, SEEK_SET); + + if (ammount != wanted && sent == ammount) //file is over + { + send(cl->datasock, resource, 0, 0); + send(cl->datasock, resource, 0, 0); + send(cl->datasock, resource, 0, 0); + closesocket(cl->datasock); + cl->datasock = INVALID_SOCKET; + IWebFClose(cl->file); + cl->file = NULL; + + QueueMessage (cl, "226 Transfer complete .\r\n"); + cl->datadir = 0; + } + } + } + else if (cl->datadir == 2) + { + int len; + while((len = recv(cl->datasock, resource, sizeof(resource), 0)) >0 ) + { + IWebFWrite(resource, len, 1, cl->file); + } + if (len == -1) + { + if (qerrno != EWOULDBLOCK) + { + closesocket(cl->datasock); + cl->datasock = INVALID_SOCKET; + if (cl->file) + IWebFClose(cl->file); + cl->file = NULL; + + QueueMessage (cl, "226 Transfer complete .\r\n"); + cl->datadir = 0; + } + } + if (len == 0) + { + QueueMessage (cl, "226 Transfer complete .\r\n"); + IWebFClose(cl->file); + cl->file = NULL; + cl->datadir = 0; + } + } + + ret = recv(cl->controlsock, cl->commandbuffer+cl->cmdbuflen, sizeof(cl->commandbuffer)-1 - cl->cmdbuflen, 0); + if (ret == -1) + { + if (qerrno == EWOULDBLOCK) + return false; //remove + + if (qerrno == ECONNABORTED || qerrno == ECONNRESET) + return true; + + Con_TPrintf (TL_NETGETPACKETERROR, strerror(qerrno)); + return true; + } + if (*cl->messagebuffer) + { + if (send (cl->controlsock, cl->messagebuffer, strlen(cl->messagebuffer), 0) != -1) + *cl->messagebuffer = '\0'; //YAY! It went! + + } + + if (ret == 0) + return false; + cl->cmdbuflen += ret; + cl->commandbuffer[cl->cmdbuflen] = 0; + + line = cl->commandbuffer; + while (1) + { + msg = line; + while (*line) + { + if (*line == '\r') + *line = ' '; + if (*line == '\n') + break; + line++; + } + if (!*line) //broken client + { + memmove(cl->commandbuffer, line, strlen(line)+1); + cl->cmdbuflen = strlen(line); + break; + } + *line = '\0'; + line++; + IWebPrintf("FTP: %s\n", msg); + + msg = COM_ParseOut(msg, mode, sizeof(mode)); + if (!stricmp(mode, "SYST")) + { + QueueMessage (cl, "215 UNIX Type: L8.\r\n"); //some browsers can be wierd about things. + } + else if (!stricmp(mode, "user")) + { + msg = COM_ParseOut(msg, cl->name, sizeof(cl->name)); + + QueueMessage (cl, "331 User name received, will be checked with password.\r\n"); + } + else if (!stricmp(mode, "pass")) + { + msg = COM_ParseOut(msg, cl->pwd, sizeof(cl->pwd)); + + cl->auth = IWebAuthorize(cl->name, cl->pwd); + + if (cl->auth) + QueueMessage (cl, "230 User logged in.\r\n"); + else + QueueMessage (cl, "530 Username or Password was incorrect or otherwise invalid.\r\n"); + } + else if (!stricmp(mode, "TYPE")) + { + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + msg = COM_ParseOut(msg, resource, sizeof(resource)); + + if (!stricmp(resource, "A")) //ascii + { + QueueMessage (cl, "200 asci selected.\r\n"); + } + else if (!stricmp(resource, "I")) //binary + { + QueueMessage (cl, "200 binary selected.\r\n"); + } + else + { + QueueMessage (cl, "200 asci selected.\r\n"); + } + } + else if (!stricmp(mode, "PWD")) + { + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + QueueMessageva (cl, "257 \"%s\"\r\n", cl->path); + } + else if (!stricmp(mode, "CWD")) + { + char *p; + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + Q_strcpyline(cl->path, msg+1, sizeof(cl->path));//path starts after cmd and single space + for (p = cl->path+strlen(cl->path)-1; *p == ' ' && p >= cl->path; p--) + *p = '\0'; + QueueMessage (cl, "200 directory changed.\r\n"); + } + else if (!stricmp(mode, "PASV")) + { + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + if (cl->datasock != INVALID_SOCKET) + { + closesocket(cl->datasock); + cl->datasock = INVALID_SOCKET; + } + + cl->datasock = FTP_SV_makelistensocket(cl->blocking); + if (cl->datasock == INVALID_SOCKET) + QueueMessage (cl, "425 server was unable to make a listen socket\r\n"); + else + { + FTP_SVSocketToString(cl->datasock, resource); + QueueMessageva (cl, "227 Entering Passive Mode (%s).\r\n", resource); + } + cl->dataislisten = true; + } + else if (!stricmp(mode, "PORT")) + { + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + if (cl->datasock != INVALID_SOCKET) + { + closesocket(cl->datasock); + cl->datasock = INVALID_SOCKET; + } + msg = COM_ParseOut(msg, resource, sizeof(resource)); + + cl->dataislisten = false; + + if ((cl->datasock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + Sys_Error ("FTP_UDP_OpenSocket: socket:", strerror(qerrno)); + } + + if (ioctlsocket (cl->datasock, FIONBIO, &cl->blocking) == -1) + { + Sys_Error ("FTTP_UDP_OpenSocket: ioctl FIONBIO:", strerror(qerrno)); + } + + from.sin_family = AF_INET; + + from.sin_addr.s_addr = INADDR_ANY; + + from.sin_port = 0; + + if( bind (cl->datasock, (void *)&from, sizeof(from)) == -1) + { + closesocket(cl->datasock); + cl->datasock=INVALID_SOCKET; + + QueueMessage (cl, "425 server bind error.\r\n"); + continue; + } + + + fromlen = sizeof(from); + FTP_StringToAdr(resource, (qbyte *)&from.sin_addr, (qbyte *)&from.sin_port); + connect(cl->datasock, (struct sockaddr *)&from, fromlen); + + QueueMessage (cl, "200 Opened data channel.\r\n"); + } + else if (!stricmp(mode, "LIST")) + { + char buffer[256]; + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + if (cl->dataislisten) //accept a connect. + { + int temp; + struct sockaddr_in adr; + int adrlen = sizeof(adr); + temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen); + closesocket(cl->datasock); + cl->datasock = temp; + cl->dataislisten = false; + + if (cl->datasock == INVALID_SOCKET) + { + QueueMessageva (cl, "425 Your client connected too slowly - %i.\r\n", qerrno); + continue; + } + } + if (cl->datasock == INVALID_SOCKET) + { + QueueMessage (cl, "503 Bad sequence of commands.\r\n"); + continue; + } + if (*cl->path == '/') + strcpy(buffer, cl->path+1); + else + strcpy(buffer, cl->path); + strcat(buffer, "*"); + QueueMessage (cl, "125 Opening FAKE ASCII mode data connection for file.\r\n"); + + COM_EnumerateFiles(buffer, SendFileNameTo, (void *)cl->datasock); + + QueueMessage (cl, "226 Transfer complete.\r\n"); + + closesocket(cl->datasock); + cl->datasock = INVALID_SOCKET; + } +// else if (!stricmp(mode, "SIZE")) //why IE can't use the list command to find file length, I've no idea. +// { +// msg = COM_ParseOut(msg, resource, sizeof(resource)); +// } + else if (!stricmp(mode, "RETR")) + { + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + if (cl->dataislisten) //accept a connect. + { + int temp; + struct sockaddr_in adr; + int adrlen = sizeof(adr); + temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen); + closesocket(cl->datasock); + cl->datasock = temp; + cl->dataislisten = false; + + if (cl->datasock == INVALID_SOCKET) + { + QueueMessageva (cl, "425 Your client connected too slowly - %i.\r\n", qerrno); + continue; + } + } + if (cl->datasock == INVALID_SOCKET) + { + QueueMessage (cl, "503 Bad sequence of commands.\r\n"); + continue; + } + msg = COM_ParseOut(msg, resource, sizeof(resource)); + + if (!cl->auth & IWEBACC_READ) + { + QueueMessage (cl, "550 No read access.\r\n"); + continue; + } + + if (!*resource == '/') + { + memmove(resource+strlen(cl->path), resource, strlen(resource)+1); + memcpy(resource, cl->path, strlen(cl->path)); + } + if (*resource == '/') + { + if (SV_AllowDownload(resource+1)) + cl->file = IWebFOpenRead(resource+1); + else + cl->file = IWebGenerateFile(resource+1, NULL, 0); + } + else + { + if (SV_AllowDownload(resource)) + cl->file = IWebFOpenRead(resource); + else + cl->file = IWebGenerateFile(resource, NULL, 0); + } + + if (!cl->file) + { + QueueMessage (cl, "550 File not found.\r\n"); + } + else + { //send data + QueueMessage (cl, "125 Opening BINARY mode data connection for file.\r\n"); + + cl->datadir = 1; + } + } + else if (!stricmp(mode, "STOR")) + { + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + Q_strcpyline(mode, msg+1, sizeof(mode)); + + + if (!(cl->auth & IWEBACC_FULL) && (((cl->auth & IWEBACC_WRITE && !IWebAllowUpLoad(cl->path+1, cl->name)) || !(cl->auth & IWEBACC_WRITE)))) + { + QueueMessage (cl, "550 Permission denied.\r\n"); + } + else + { + if (cl->dataislisten) //accept a connect. + { + int temp; + struct sockaddr_in adr; + int adrlen = sizeof(adr); + temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen); + closesocket(cl->datasock); + cl->datasock = temp; + cl->dataislisten = false; + + if (cl->datasock == INVALID_SOCKET) + { + QueueMessageva (cl, "425 Your client connected too slowly - %i.\r\n", qerrno); + continue; + } + } + if (cl->datasock == INVALID_SOCKET) + { + QueueMessage (cl, "502 Bad sequence of commands.\r\n"); + continue; + } +// msg = COM_ParseOut(msg, mode, sizeof(mode)); + + if (*mode == '/') + sprintf(resource, "%s%s", cl->path, mode); + else + sprintf(resource, "%s%s", cl->path, mode); + + cl->file = IWebFOpenRead(resource); + if (cl->file) + { + IWebFClose(cl->file); + QueueMessage (cl, "550 File already exists.\r\n"); + continue; + } + cl->file = IWebFOpenWrite(resource, false); + + if (!cl->file) + { + QueueMessage (cl, "550 Couldn't open output.\r\n"); + } + else + { //send data + QueueMessage (cl, "125 Opening BINARY mode data connection for input.\r\n"); + + cl->datadir = 2; + } + } + } + else if (!stricmp(mode, "STRU")) + { + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + msg = COM_ParseOut(msg, resource, sizeof(resource)); + if (!strcmp(resource, "F")) + { + QueueMessage (cl, "200 recordless structure selected.\r\n"); + } + else + { + QueueMessage (cl, "504 not implemented (it's a simple server).\r\n"); + } + } + else if (!stricmp(mode, "NOOP")) + { + QueueMessage (cl, "200 Do something then!\r\n"); + } + else if (!stricmp(mode, "QUIT")) + { + QueueMessage (cl, "200 About to quit.\r\n"); + return true; + } + else + { + QueueMessage (cl, "502 Command not implemented.\r\n"); + } + } + return false; +} + +#if defined(WEBSVONLY) && defined(_WIN32) +unsigned int WINAPI BlockingClient(FTPclient_t *cl) +{ + unsigned long _false = false; + if (ioctlsocket (cl->controlsock, FIONBIO, &_false) == -1) + { + IWebPrintf ("FTP_ServerRun: blocking error: %s\n", strerror(qerrno)); + return 0; + } + + cl->blocking = false; + + while (!FTP_ServerThinkForConnection(cl)) + { + Sleep(10); + } + + if (cl->file) + IWebFClose(cl->file); + closesocket(cl->controlsock); + if (cl->datasock) + closesocket(cl->datasock); + + IWebFree(cl); + return 0; +} +#endif + +iwboolean FTP_ServerRun(iwboolean ftpserverwanted) +{ + FTPclient_t *cl, *prevcl; + struct sockaddr_in from; + int fromlen; + int clientsock; +unsigned long _true = true; + + if (!ftpserverinitied) + { + if (ftpserverwanted) + FTP_ServerInit(); + return false; + } + else if (!ftpserverwanted) + { + FTP_ServerShutdown(); + return false; + } + + prevcl = NULL; + for (cl = FTPclient; cl; cl = cl->next) + { + if (FTP_ServerThinkForConnection(cl)) + { + if (cl->file) + IWebFClose(cl->file); + closesocket(cl->controlsock); + if (cl->datasock) + closesocket(cl->datasock); + + if (prevcl) + { + prevcl->next = cl->next; + IWebFree(cl); + cl = prevcl; + + if (!cl) //kills loop + break; + } + else + { + FTPclient = cl->next; + IWebFree(cl); + cl = FTPclient; + + if (!cl) //kills loop + break; + } + } + prevcl = cl; + } + + fromlen = sizeof(from); + clientsock = accept(ftpserversocket, (struct sockaddr *)&from, &fromlen); + + if (clientsock == -1) + { + if (qerrno == EWOULDBLOCK) + return false; + + if (qerrno == ECONNABORTED || qerrno == ECONNRESET) + { + Con_TPrintf (TL_CONNECTIONLOSTORABORTED); + return false; + } + + + Con_TPrintf (TL_NETGETPACKETERROR, strerror(qerrno)); + return false; + } + + //is this needed? + if (ioctlsocket (clientsock, FIONBIO, &_true) == -1) + { + IWebPrintf ("FTP_ServerRun: blocking error: %s\n", strerror(qerrno)); + return false; + } + cl = IWebMalloc(sizeof(FTPclient_t)); + if (!cl) //iwebmalloc is allowed to fail. + { + char *msg = "421 Not enough memory is allocated.\r\n"; //don't be totally anti social + send(clientsock, msg, strlen(msg), 0); + closesocket(clientsock); //try to forget this ever happend + return true; + } + { + char resource[256]; + FTP_SVRemoteSocketToString(clientsock, resource); + IWebPrintf("FTP connect from %s\n", resource); + } + cl->controlsock = clientsock; + cl->datasock = INVALID_SOCKET; + cl->next = FTPclient; + cl->blocking = false; + strcpy(cl->path, "/"); + + QueueMessage(cl, "220-QuakeWorld FTP Server.\r\n220 Welcomes all new users.\r\n"); + +#if defined(WEBSVONLY) && defined(_WIN32) + if (!CreateThread(NULL, 128, BlockingClient, cl, 0, NULL)) +#endif + FTPclient = cl; + return true; +} + +#endif diff --git a/engine/http/httpclient.c b/engine/http/httpclient.c new file mode 100644 index 00000000..3d3037b5 --- /dev/null +++ b/engine/http/httpclient.c @@ -0,0 +1,473 @@ +#include "bothdefs.h" + +#ifdef WEBCLIENT + +#include "iweb.h" + +#ifdef _WIN32 + +//msvc crap + +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EMSGSIZE WSAEMSGSIZE +#define ECONNRESET WSAECONNRESET +#define ECONNABORTED WSAECONNABORTED +#define ECONNREFUSED WSAECONNREFUSED +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL + +#define snprintf _snprintf + +#define qerrno WSAGetLastError() +#else + +//gcc stuff + +#define qerrno errno + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef sun +#include +#endif + +#ifdef NeXT +#include +#endif + +#define closesocket close +#define ioctlsocket ioctl +#endif + + +/* +Test files/servers.: + +http://mywebpages.comcast.net/jsgeneric/prog5.asm +http://mywebpages.comcast.net/jsgeneric/sshot001.jpg +http://spike.corecodec.org/ftemqwtest.zip +http://www.fuhquake.net/files/releases/v0.31/fuhquake-win32-v0.31.zip +http://download.microsoft.com/download/d/c/3/dc37439a-172b-4f20-beac-bab52cdd38bc/Windows-KB833330-ENU.exe +*/ + + + +/* +This file does one thing. Connects to servers and grabs the specified file. It doesn't do any uploading whatsoever. Live with it. +It doesn't use persistant connections. + +*/ + +qboolean HTTP_CL_Get(char *url, char *localfile); + +typedef struct { + int sock; + + enum {HC_REQUESTING,HC_GETTINGHEADER, HC_GETTING} state; + + char *buffer; + char filename[MAX_QPATH]; + int bufferused; + int bufferlen; + + qboolean chunking; + int chunksize; + int chunked; + + int contentlength; + +} http_con_t; + +static http_con_t *httpcl; + +static void ExpandBuffer(http_con_t *con, int quant) +{ + int newlen; + newlen = con->bufferlen + quant; + con->buffer = IWebRealloc(con->buffer, newlen); + con->bufferlen = newlen; +} + +static qboolean HTTP_CL_Run(http_con_t *con) +{ + char buffer[256]; + char Location[256]; + char *nl; + char *msg; + int ammount; + switch(con->state) + { + case HC_REQUESTING: + ammount = send(con->sock, con->buffer, con->bufferused, 0); + if (!ammount) + return false; + + if (ammount < 0) + { + if (qerrno != EWOULDBLOCK) + return false; + return true; + } + + con->bufferused -= ammount; + memmove(con->buffer, con->buffer+ammount, con->bufferused); + if (!con->bufferused) //that's it, all sent. + con->state = HC_GETTINGHEADER; + break; + + case HC_GETTINGHEADER: + if (con->bufferlen - con->bufferused < 1530) + ExpandBuffer(con, 1530); + + ammount = recv(con->sock, con->buffer+con->bufferused, con->bufferlen-con->bufferused-15, 0); + if (!ammount) + return false; + if (ammount < 0) + { + if (qerrno != EWOULDBLOCK) + return false; + return true; + } + + con->bufferused+=ammount; + con->buffer[con->bufferused] = '\0'; + //have we got the entire thing yet? + + msg = con->buffer; + con->chunking = false; + if (strnicmp(msg, "HTTP/", 5)) + { //pre version 1. (lame servers. + con->state = HC_GETTING; + con->contentlength = -1; //meaning end of stream. + } + else + { + while(*msg) + { + if (*msg == '\n') + { + if (msg[1] == '\n') + { //tut tut, not '\r'? that's not really allowed... + msg+=1; + break; + } + if (msg[2] == '\n') + { + msg+=2; + break; + } + } + msg++; + if (!strnicmp(msg, "Content-Length: ", 16)) + con->contentlength = atoi(msg+16); + else if (!strnicmp(msg, "Location: ", 10)) + { + nl = strchr(msg, '\n'); + if (nl) + { + *nl = '\0'; + Q_strncpyz(Location, COM_TrimString(msg+10), sizeof(Location)); + *nl = '\n'; + } + } + else if (!strnicmp(msg, "Transfer-Encoding: ", 19)) + { + char *chunk = strstr(msg, "chunked"); + nl = strchr(msg, '\n'); + if (nl) + if (chunk < nl) + con->chunking = true; + } + } + if (!*msg) + break;//switch + msg++; + + ammount = msg - con->buffer; + + msg = COM_ParseOut(con->buffer, buffer, sizeof(buffer)); + msg = COM_ParseOut(msg, buffer, sizeof(buffer)); + if (!stricmp(buffer, "100")) + { //http/1.1 servers can give this. We ignore it. + + con->bufferused -= ammount; + memmove(con->buffer, con->buffer+ammount, con->bufferused); + return true; + } + + if (!stricmp(buffer, "301") || !stricmp(buffer, "302") || !stricmp(buffer, "303")) + { + nl = strchr(msg, '\n'); + if (nl) + *nl = '\0'; + Con_Printf("HTTP: %s %s\n", buffer, COM_TrimString(msg)); + if (!*Location) + Con_Printf("Server redirected to null location\n"); + else + HTTP_CL_Get(Location, con->filename); + return false; + } + + if (stricmp(buffer, "200")) + { + nl = strchr(msg, '\n'); + if (!nl) + return false; //eh? + if (nl>msg&&nl[-1] == '\r') + nl--; + *nl = '\0'; + Con_Printf("HTTP: %s%s\n", buffer, msg); + return false; //something went wrong. + } + + con->bufferused -= ammount; + memmove(con->buffer, con->buffer+ammount, con->bufferused); + + con->state = HC_GETTING; + + } + //Fall through + + case HC_GETTING: + if (con->bufferlen - con->bufferused < 1530) + ExpandBuffer(con, 1530); + + ammount = recv(con->sock, con->buffer+con->bufferused, con->bufferlen-con->bufferused-1, 0); + if (ammount < 0) + { + if (qerrno != EWOULDBLOCK) + return false; + return true; + } + + con->bufferused+=ammount; + + if (con->chunking) //FIXME: NEEDS TESTING!!! + { + int trim; + char *nl; + con->buffer[con->bufferused] = '\0'; + for(;;) + { //work out as we go. + if (con->chunksize)//we are trying to parse a chunk. + { + trim = con->bufferused - con->chunked; + if (trim > con->chunksize) + trim = con->chunksize; //don't go into the next size field. + con->chunksize -= trim; + con->chunked += trim; + + if (!con->chunksize) + { //we need to find the next \n and trim it. + nl = strchr(con->buffer+con->chunked, '\n'); + if (!nl) + break; + nl++; + trim = nl - (con->buffer+con->chunked); + memmove(con->buffer + con->chunked, nl, con->buffer+con->bufferused-nl+1); + con->bufferused -= trim; + } + if (!(con->bufferused - con->chunked)) + break; + } + else + { + nl = strchr(con->buffer+con->chunked, '\n'); + if (!nl) + break; + con->chunksize = strtol(con->buffer+con->chunked, NULL, 16); //it's hex. + nl++; + trim = nl - (con->buffer+con->chunked); + memmove(con->buffer + con->chunked, nl, con->buffer+con->bufferused-nl+1); + con->bufferused -= trim; + } + } + } + + if (!ammount) + { //server closed off the connection. + if (con->chunksize) + Con_Printf("Download was part way through chunking - must be corrupt - %s\n", con->filename); + else if (con->bufferused != con->contentlength) + Con_Printf("Recieved file isn't the correct length - must be corrupt - %s\n", con->filename); + Con_Printf("Retrieved %s\n", con->filename); + snprintf(Location, sizeof(Location)-1, "%s/%s", com_gamedir, con->filename); + COM_CreatePath(Location); + COM_WriteFile(con->filename, con->buffer, con->bufferused); + return false; + } + + break; + } + + return true; +} + +void HTTP_CL_Think(void) +{ + http_con_t *con = httpcl; + if (con) + { + if (!HTTP_CL_Run(con)) + { + if (cls.downloadmethod == DL_HTTP) + cls.downloadmethod = DL_NONE; + closesocket(con->sock); + if (con->buffer) + IWebFree(con->buffer); + IWebFree(con); + if (con == httpcl) + { + httpcl = NULL; + return; + } + con = NULL; + } + else if (!cls.downloadmethod) + { + cls.downloadmethod = DL_HTTP; + if (con->state != HC_GETTING) + cls.downloadpercent = 0; + else if (con->contentlength <= 0) + cls.downloadpercent = 50; + else + cls.downloadpercent = con->bufferused*100.0f/con->contentlength; + strcpy(cls.downloadname, con->filename); + } + else if (cls.downloadmethod == DL_HTTP) + { + if (!strcmp(cls.downloadname, con->filename)) + { + if (con->state != HC_GETTING) + cls.downloadpercent = 0; + else if (con->contentlength <= 0) + cls.downloadpercent = 50; + else + cls.downloadpercent = con->bufferused*100.0f/con->contentlength; + } + } + } +} + +qboolean HTTP_CL_Get(char *url, char *localfile) +{ + unsigned long _true = true; + struct sockaddr_qstorage from; + http_con_t *con; + + char server[128]; + char uri[MAX_OSPATH]; + char *slash; + + if (localfile) + if (!*localfile) + localfile = NULL; + + if (!strnicmp(url, "http://", 7)) + url+=7; + else if (!strnicmp(url, "ftp://", 6)) + { + url+=6; + slash = strchr(url, '/'); + if (!slash) + { + Q_strncpyz(server, url, sizeof(server)); + Q_strncpyz(uri, "/", sizeof(uri)); + } + else + { + Q_strncpyz(uri, slash, sizeof(uri)); + Q_strncpyz(server, url, sizeof(server)); + server[slash-url] = '\0'; + } + + if (!localfile) + localfile = uri+1; + + FTP_Client_Command(va("download %s \"%s\" \"%s\"", server, uri+1, localfile)); + return true; + } + else + { + Con_Printf("Bad URL: %s\n", url); + return false; + } + + slash = strchr(url, '/'); + if (!slash) + { + Q_strncpyz(server, url, sizeof(server)); + Q_strncpyz(uri, "/", sizeof(uri)); + } + else + { + Q_strncpyz(uri, slash, sizeof(uri)); + Q_strncpyz(server, url, sizeof(server)); + server[slash-url] = '\0'; + } + + if (!localfile) + localfile = uri+1; + + con = IWebMalloc(sizeof(http_con_t)); + + if ((con->sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + Sys_Error ("HTTPCL_TCP_OpenSocket: socket: %s\n", strerror(qerrno)); + } + + + {//quake routines using dns and stuff (Really, I wanna keep quake and ftp fairly seperate) + netadr_t qaddy; + if (!NET_StringToAdr (server, &qaddy)) + { + IWebWarnPrintf ("HTTPCL_TCP_OpenSocket: Failed to resolve host: %s\n", server); + closesocket(con->sock); + IWebFree(con); + return false; + } + if (!qaddy.port) + qaddy.port = htons(80); + NetadrToSockadr(&qaddy, &from); + }//end of quake. + + //not yet blocking. + if (connect(con->sock, (struct sockaddr *)&from, sizeof(from)) == -1) + { + IWebWarnPrintf ("HTTPCL_TCP_OpenSocket: connect: %i %s\n", qerrno, strerror(qerrno)); + closesocket(con->sock); + IWebFree(con); + return false; + } + + if (ioctlsocket (con->sock, FIONBIO, &_true) == -1) //now make it non blocking. + { + Sys_Error ("HTTPCL_TCP_OpenSocket: ioctl FIONBIO: %s\n", strerror(qerrno)); + } + + ExpandBuffer(con, 2048); + sprintf(con->buffer, "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "Connection: close\r\n" "User-Agent: FTE\r\n" "\r\n", uri, server); + con->bufferused = strlen(con->buffer); + con->contentlength = -1; + strcpy(con->filename, localfile); + +/* slash = strchr(con->filename, '?'); + if (slash) + *slash = '\0';*/ + + httpcl = con; + + + return true; +} + +#endif diff --git a/engine/http/httpserver.c b/engine/http/httpserver.c new file mode 100644 index 00000000..99133fb2 --- /dev/null +++ b/engine/http/httpserver.c @@ -0,0 +1,565 @@ +#include "bothdefs.h" + +#ifdef WEBSERVER + +#include "iweb.h" + +//FIXME: Before any admins use this for any serious usage, make the server send bits of file slowly. + + +#ifdef _WIN32 + +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EMSGSIZE WSAEMSGSIZE +#define ECONNRESET WSAECONNRESET +#define ECONNABORTED WSAECONNABORTED +#define ECONNREFUSED WSAECONNREFUSED +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL + +#define qerrno WSAGetLastError() +#else +#define qerrno errno + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef sun +#include +#endif + +#ifdef NeXT +#include +#endif + +#define closesocket close +#define ioctlsocket ioctl +#endif + +static qboolean httpserverinitied = false; +static int httpserversocket; + +typedef enum {HTTP_WAITINGFORREQUEST,HTTP_SENDING,HTTP_RECEIVING} http_mode_t; + + + +void HTTP_ServerInit(void) +{ + struct sockaddr_in address; + unsigned long _true = true; + int i; + int port = 80; + + if ((httpserversocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + Sys_Error ("HTTP_UDP_OpenSocket: socket:", strerror(qerrno)); + } + + if (ioctlsocket (httpserversocket, FIONBIO, &_true) == -1) + { + Sys_Error ("HTTP_UDP_OpenSocket: ioctl FIONBIO:", strerror(qerrno)); + } + + address.sin_family = AF_INET; +//check for interface binding option + if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) + { + address.sin_addr.s_addr = inet_addr(com_argv[i+1]); + Con_TPrintf(TL_NETBINDINTERFACE, + inet_ntoa(address.sin_addr)); + } + else + address.sin_addr.s_addr = INADDR_ANY; + + if (port == PORT_ANY) + address.sin_port = 0; + else + address.sin_port = htons((short)port); + + if( bind (httpserversocket, (void *)&address, sizeof(address)) == -1) + { + closesocket(httpserversocket); + return; + } + + listen(httpserversocket, 3); + + httpserverinitied = true; + + + IWebPrintf("HTTP server is running\n"); + return; +} + +void HTTP_ServerShutdown(void) +{ + closesocket(httpserversocket); + IWebPrintf("HTTP server closed\n"); + + httpserverinitied = false; +} + +typedef struct HTTP_active_connections_s { + int datasock; + IWEBFILE *file; + struct HTTP_active_connections_s *next; + + http_mode_t mode; + qboolean modeswitched; + qboolean closeaftertransaction; + qboolean close; + + char *inbuffer; + int inbuffersize; + int inbufferused; + + char *outbuffer; + int outbuffersize; + int outbufferused; +} HTTP_active_connections_t; +static HTTP_active_connections_t *HTTP_ServerConnections; +static int httpconnectioncount; + +static void ExpandInBuffer(HTTP_active_connections_t *cl, int quant, qboolean fixedsize) +{ + int newsize; + if (fixedsize) + newsize = quant; + else + newsize = cl->inbuffersize+quant; + if (newsize <= cl->inbuffersize) + return; + + cl->inbuffer = IWebRealloc(cl->inbuffer, newsize); + cl->inbuffersize = newsize; +} +static void ExpandOutBuffer(HTTP_active_connections_t *cl, int quant, qboolean fixedsize) +{ + int newsize; + if (fixedsize) + newsize = quant; + else + newsize = cl->outbuffersize+quant; + if (newsize <= cl->outbuffersize) + return; + + cl->outbuffer = IWebRealloc(cl->outbuffer, newsize); + cl->outbuffersize = newsize; +} + +void HTTP_RunExisting (void) +{ + char *content; + char *msg, *nl; + char buf2[256]; //short lived temp buffer. + char resource[256]; + char mode[8]; + qboolean hostspecified; + int contentlen; + + int HTTPmarkup; //version + int errno; + + HTTP_active_connections_t *prev, *cl = HTTP_ServerConnections; + + prev = NULL; + for (prev = NULL; cl; cl=(prev=cl)->next) + { + int ammount, wanted; + + if (cl->close) + { + if (prev) + prev->next = cl->next; + else + HTTP_ServerConnections = cl->next; + closesocket(cl->datasock); + cl->datasock = INVALID_SOCKET; + if (cl->inbuffer) + IWebFree(cl->inbuffer); + if (cl->outbuffer) + IWebFree(cl->outbuffer); + if (cl->file) + IWebFClose(cl->file); + IWebFree(cl); + httpconnectioncount--; + cl = prev; + break; + } + + switch(cl->mode) + { + case HTTP_WAITINGFORREQUEST: + if (cl->outbufferused) + Sys_Error("Persistant connection was waiting for input with unsent output"); + ammount = cl->inbuffersize - cl->inbufferused - 1; + if (ammount < 128) + { + if (cl->inbuffersize>128*1024) + { + cl->close = true; //that's just taking the piss. + continue; + } + + ExpandInBuffer(cl, 1500, false); + ammount = cl->inbuffersize - cl->inbufferused - 1; + } + if (cl->modeswitched) + { + ammount = 0; + } + else + { + //we can't try and recv 0 bytes as we use an expanding buffer + ammount = recv(cl->datasock, cl->inbuffer+cl->inbufferused, ammount, 0); + if (ammount < 0) + { + if (qerrno != EWOULDBLOCK) //they closed on us. Assume end. + { + cl->close = true; + } + continue; + } + if (ammount == 0) + { + cl->close = true; + continue; + } + } + cl->modeswitched = false; + + cl->inbufferused += ammount; + cl->inbuffer[cl->inbufferused] = '\0'; + + content = NULL; + msg = cl->inbuffer; + nl = strchr(msg, '\n'); + if (!nl) + { +cont: + continue; //we need more... MORE!!! MORE I TELL YOU!!!! + } + + msg = COM_ParseOut(msg, mode, sizeof(mode)); + + msg = COM_ParseOut(msg, resource, sizeof(resource)); + + if (!*resource) + { + cl->close = true; //even if they forgot to specify a resource, we didn't find an HTTP so we have no option but to close. + continue; + } + + hostspecified = false; + if (!strnicmp(resource, "http://", 7)) + { //groan... 1.1 compliance requires parsing this correctly, without the client ever specifiying it. + char *slash; //we don't do multiple hosts. + hostspecified=true; + slash = strchr(resource+7, '/'); + if (!slash) + strcpy(resource, "/"); + else + memmove(resource, slash, strlen(slash+1)); //just get rid of the http:// stuff. + } + + if (!strcmp(resource, "/")) + strcpy(resource, "/index.html"); + + msg = COM_ParseOut(msg, buf2, sizeof(buf2)); + contentlen = 0; + if (!strnicmp(buf2, "HTTP/", 5)) + { + if (!strncmp(buf2, "HTTP/1.1", 8)) + HTTPmarkup = 3; + else if (!strncmp(buf2, "HTTP/1", 6)) + HTTPmarkup = 2; + else + { + HTTPmarkup = 1; //0.9... lamer. + cl->closeaftertransaction = true; + } + + //expect X lines containing options. + //then a blank line. Don't continue till we have that. + + msg = nl+1; + while (1) + { + if (*msg == '\r') + msg++; + if (*msg == '\n') + { + msg++; + break; //that was our blank line. + } + + while(*msg == ' ') + msg++; + + if (!strnicmp(msg, "Host: ", 6)) //parse needed header fields + hostspecified = true; + else if (!strnicmp(msg, "Content-Length: ", 16)) //parse needed header fields + contentlen = atoi(msg+16); + else if (!strnicmp(msg, "Transfer-Encoding: ", 18)) //parse needed header fields + { + cl->closeaftertransaction = true; + goto notimplemented; + } + else if (!strnicmp(msg, "Connection: close", 17)) + cl->closeaftertransaction = true; + + while(*msg != '\n') + { + if (!*msg) + { + goto cont; + } + msg++; + } + msg++; + } + } + else + { + HTTPmarkup = 0; //strimmed... totally... + cl->closeaftertransaction = true; + //don't bother running to nl. + } + + if (cl->inbufferused-(msg-cl->inbuffer) < contentlen) + continue; + + cl->modeswitched = true; + + if (contentlen) + { + content = BZ_Malloc(contentlen+1); + memcpy(content, msg, contentlen+1); + } + + memmove(cl->inbuffer, cl->inbuffer+(msg-cl->inbuffer+contentlen), cl->inbufferused-(msg-cl->inbuffer+contentlen)); + cl->inbufferused -= msg-cl->inbuffer+contentlen; + + + if (HTTPmarkup == 3 && !hostspecified) //1.1 requires the host to be specified... we ca,just ignore it as we're not routing or imitating two servers. (for complience we need to encourage the client to send - does nothing for compatability or anything, just compliance to spec. not always the same thing) + { + msg = "HTTP/1.1 400 Bad Request\r\n" "Content-Type: text/plain\r\n" "Content-Length: 69\r\n" "Server: FTE/0\r\n" "\r\n" "400 Bad Request\r\nYour client failed to provide the host header line"; + + ammount = strlen(msg); + ExpandOutBuffer(cl, ammount, true); + memcpy(cl->outbuffer, msg, ammount); + cl->outbufferused = ammount; + cl->mode = HTTP_SENDING; + } + else if (!stricmp(mode, "GET") || !stricmp(mode, "HEAD") || !stricmp(mode, "POST")) + { + if (!strnicmp(mode, "P", 1)) //when stuff is posted, data is provided. Give an error message if we couldn't do anything with that data. + cl->file = IWebGenerateFile(resource+1, content, contentlen); + else + cl->file = IWebFOpenRead(resource); + if (!cl->file) + { + if (HTTPmarkup >= 3) + msg = "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/plain\r\n" "Content-Length: 15\r\n" "Server: FTE/0\r\n" "\r\n" "404 Bad address"; + else if (HTTPmarkup == 2) + msg = "HTTP/1.0 404 Not Found\r\n" "Content-Type: text/plain\r\n" "Content-Length: 15\r\n" "Server: FTE/0\r\n" "\r\n" "404 Bad address"; + else if (HTTPmarkup) + msg = "HTTP/0.9 404 Not Found\r\n" "\r\n" "404 Bad address"; + else + msg = "404 Not Found404 Not Found
The specified file could not be found on the server"; + + ammount = strlen(msg); + ExpandOutBuffer(cl, ammount, true); + memcpy(cl->outbuffer, msg, ammount); + cl->outbufferused = ammount; + cl->mode = HTTP_SENDING; + } + else + { + if (HTTPmarkup>=3) + sprintf(resource, "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %i\r\n" "Server: FTE/0\r\n" "\r\n", strstr(resource, ".htm")?"text/html":"text/plain", cl->file->length); + else if (HTTPmarkup==2) + sprintf(resource, "HTTP/1.0 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %i\r\n" "Server: FTE/0\r\n" "\r\n", strstr(resource, ".htm")?"text/html":"text/plain", cl->file->length); + else if (HTTPmarkup) + sprintf(resource, "HTTP/0.9 200 OK\r\n\r\n"); + else + sprintf(resource, ""); + msg = resource; + + if (*mode == 'H' || *mode == 'h') + { + + IWebFClose(cl->file); + cl->file = NULL; + } + + ammount = strlen(msg); + ExpandOutBuffer(cl, ammount, true); + memcpy(cl->outbuffer, msg, ammount); + cl->outbufferused = ammount; + cl->mode = HTTP_SENDING; + } + } + //PUT/POST must support chunked transfer encoding for 1.1 compliance. +/* else if (!stricmp(mode, "PUT")) //put is replacement of a resource. (file uploads) + { + } +*/ + else + { +notimplemented: + if (HTTPmarkup >= 3) + msg = "HTTP/1.1 501 Not Implemented\r\n\r\n"; + else if (HTTPmarkup == 2) + msg = "HTTP/1.0 501 Not Implemented\r\n\r\n"; + else if (HTTPmarkup) + msg = "HTTP/0.9 501 Not Implemented\r\n\r\n"; + else + { + msg = NULL; + cl->close = true; + } + + if (msg) + { + ammount = strlen(msg); + ExpandOutBuffer(cl, ammount, true); + memcpy(cl->outbuffer, msg, ammount); + cl->outbufferused = ammount; + cl->mode = HTTP_SENDING; + } + } + + if (content) + BZ_Free(content); + break; + + case HTTP_SENDING: + if (cl->outbufferused < 128) + { + if (cl->file) + { + ExpandOutBuffer(cl, 1500, true); + wanted = cl->outbuffersize - cl->outbufferused; + ammount = IWebFRead(cl->outbuffer+cl->outbufferused, 1, wanted, cl->file); + + if (!ammount) + { + IWebFClose(cl->file); + cl->file = NULL; + } + else + cl->outbufferused+=ammount; + } + } + + ammount = send(cl->datasock, cl->outbuffer, cl->outbufferused, 0); + + if (ammount == -1) + { + errno = qerrno; + if (errno != EWOULDBLOCK) + { + cl->close = true; + } + } + else if (ammount||!cl->outbufferused) + { + memcpy(cl->outbuffer, cl->outbuffer+ammount, cl->outbufferused-ammount); + cl->outbufferused -= ammount; + if (!cl->outbufferused && !cl->file) + { + cl->modeswitched = true; + cl->mode = HTTP_WAITINGFORREQUEST; + if (cl->closeaftertransaction) + cl->close = true; + } + } + else + cl->close = true; + break; + + /* case HTTP_RECEIVING: + sent = recv(cl->datasock, resource, ammount, 0); + if (sent == -1) + { + if (qerrno != EWOULDBLOCK) //they closed on us. Assume end. + { + IWebFClose(cl->file); + cl->file = NULL; + cl->close = true; + continue; + } + } + if (sent != 0) + IWebFWrite(resource, 1, sent, cl->file); + break;*/ + } + } +} + +qboolean HTTP_ServerPoll(qboolean httpserverwanted) //loop while true +{ + struct sockaddr_in from; + int fromlen; + int clientsock; + + HTTP_active_connections_t *cl; + + if (!httpserverinitied) + { + if (httpserverwanted) + HTTP_ServerInit(); + return false; + } + else if (!httpserverwanted) + { + HTTP_ServerShutdown(); + return false; + } + + if (httpconnectioncount>32) + return false; + + fromlen = sizeof(from); + clientsock = accept(httpserversocket, (struct sockaddr *)&from, &fromlen); + + if (clientsock == -1) + { + if (qerrno == EWOULDBLOCK) + { + HTTP_RunExisting(); + return false; + } + + if (qerrno == ECONNABORTED || qerrno == ECONNRESET) + { + Con_TPrintf (TL_CONNECTIONLOSTORABORTED); + return false; + } + + + Con_TPrintf (TL_NETGETPACKETERROR, strerror(qerrno)); + return false; + } + + cl = IWebMalloc(sizeof(HTTP_active_connections_t)); + + cl->datasock = clientsock; + + cl->next = HTTP_ServerConnections; + HTTP_ServerConnections = cl; + httpconnectioncount++; + + return true; +} + +#endif diff --git a/engine/http/iweb.h b/engine/http/iweb.h new file mode 100644 index 00000000..6a779c39 --- /dev/null +++ b/engine/http/iweb.h @@ -0,0 +1,162 @@ +#ifdef WEBSERVER + +#ifndef IWEB_H__ +#define IWEB_H__ + + +#ifdef WEBSVONLY + +typedef unsigned char qbyte; +typedef enum {false, true} qboolean; +typedef enum {NA_INVALID, NA_LOOPBACK, NA_IP, NA_IPX, NA_BROADCAST_IP, NA_BROADCAST_IPX} netadrtype_t; +typedef struct +{ + netadrtype_t type; + + qbyte ip[4]; + qbyte ipx[10]; + + unsigned short port; +} netadr_t; +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + + +#define PORT_ANY 0 +void Sys_Error(char *fmt, ...); +int COM_CheckParm(char *parm); +int com_argc; +char **com_argv; + +#define Con_TPrintf IWebPrintf +#define TL_NETBINDINTERFACE "binding to %s" +#define TL_CONNECTIONLOSTORABORTED "connection lost or aborted" +#define TL_NETGETPACKETERROR "get packet error" + +#define IWebPrintf printf +#define com_gamedir "." //current dir. + + +#define IWebMalloc(x) calloc(x, 1) +#define IWebRealloc(x, y) realloc(x, y) +#define IWebFree free + +#define MAX_OSPATH 1024 + +#else + +#ifndef QUAKEDEF_H__ + +#include "quakedef.h" +#ifdef _WIN32 +#include "winquake.h" +#endif + +#else +//#include +#endif +#endif + +#define IWEBACC_READ 1 +#define IWEBACC_WRITE 2 +#define IWEBACC_FULL 4 +struct sockaddr_in; +struct sockaddr; +struct sockaddr_qstorage; +void NetadrToSockadr (netadr_t *a, struct sockaddr_qstorage *s); + +qboolean SV_AllowDownload (char *name); + + +typedef qboolean iwboolean; + +#ifndef _WIN32 +#define INVALID_SOCKET ~0 +#endif + +//it's not allowed to error. +#ifndef WEBSVONLY +void VARGS IWebDPrintf(char *fmt, ...); +void VARGS IWebPrintf(char *fmt, ...); +void VARGS IWebWarnPrintf(char *fmt, ...); +#endif + +typedef struct { + float gentime; //useful for generating a new file (if too old, removes reference) + int references; //freed if 0 + char *data; + int len; +} IWeb_FileGen_t; + +typedef struct { +//one or the other + FILE *f; + int pos; + + IWeb_FileGen_t *bufferdata; + + int start; + int length; + int end; +} IWEBFILE; +IWEBFILE *IWebFOpenRead(char *name); //fread(name, "rb"); +IWEBFILE *IWebFOpenWrite(char *name, int append); //fopen(name, append?"ab":"wb"); +int IWebFWrite(void *data, int s1, int s2, IWEBFILE *); //fwrite +int IWebFRead(void *data, int s1, int s2, IWEBFILE *); //fwrite +void IWebFClose(IWEBFILE *); +void IWebFSeek(IWEBFILE *file, long pos, int type); +int IWebFTell(IWEBFILE *file); + +#ifndef WEBSVONLY +void *IWebMalloc(int size); +void *IWebRealloc(void *old, int size); +void IWebFree(void *mem); +#define IWebFree Z_Free +#endif + +int IWebAuthorize(char *name, char *password); +iwboolean IWebAllowUpLoad(char *fname, char *uname); + +IWEBFILE *IWebGenerateFile(char *name, char *content, int contentlength); + + + +char *COM_ParseOut (char *data, char *out, int outlen); +void COM_EnumerateFiles (char *match, int (*func)(char *, int, void *), void *parm); + + +char *Q_strcpyline(char *out, char *in, int maxlen); + + + + +iwboolean FTP_StringToAdr (char *s, qbyte ip[4], qbyte port[2]); + +//server tick/control functions +iwboolean FTP_ServerRun(iwboolean ftpserverwanted); +qboolean HTTP_ServerPoll(qboolean httpserverwanted); + +void HTTP_CL_Think(void); +qboolean HTTP_CL_Get(char *url, char *localfile); + +//server interface called from main server routines. +void IWebInit(void); +void IWebRun(void); +void IWebShutdown(void); + +void FTP_Client_Command (char *cmd); +void IRC_Command(char *imsg); +void FTP_ClientThink (void); +void IRC_Frame(void); + +qboolean SV_POP3(qboolean activewanted); +qboolean SV_SMTP(qboolean activewanted); + +#endif + +#endif diff --git a/engine/http/iwebiface.c b/engine/http/iwebiface.c new file mode 100644 index 00000000..5693a1ed --- /dev/null +++ b/engine/http/iwebiface.c @@ -0,0 +1,668 @@ +#ifdef WEBSVONLY + #define WEBSERVER +#else + #include "bothdefs.h" +#endif + +#ifdef WEBSERVER + +#include "iweb.h" + +#ifdef WEBSVONLY //we need some functions from quake + +/*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]; +}*/ + +void Sys_Error(char *format, ...) +{ + va_list argptr; + char string[1024]; + + va_start (argptr, format); + _vsnprintf (string,sizeof(string)-1, format,argptr); + va_end (argptr); + + printf("%s", string); + getchar(); + exit(1000); +} + +int COM_CheckParm(char *parm) +{ + return 0; +} + +char *authedusername; +char *autheduserpassword; +int main(int argc, char **argv) +{ + WSADATA pointlesscrap; + WSAStartup(2, &pointlesscrap); + + if (argc == 3) + { + authedusername = argv[1]; + autheduserpassword = argv[2]; + printf("Username = \"%s\"\nPassword = \"%s\"\n", authedusername, autheduserpassword); + } + else + printf("Server is read only\n"); + + while(1) + { + FTP_ServerRun(1); + HTTP_ServerPoll(1); +// SV_POP3(1); +// SV_SMTP(1); + Sleep(1); + } +} + + +void COM_EnumerateFiles (char *match, int (*func)(char *, int, void *), void *parm) +{ + HANDLE r; + WIN32_FIND_DATA 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 = FindFirstFile(file, &fd); + if (r==(HANDLE)-1) + return; + go = true; + do + { + if (*fd.cFileName == '.'); + else if (fd.dwFileAttributes != 16) //is a directory + { + sprintf(file, "%s%s", apath, fd.cFileName); + go = func(file, fd.nFileSizeLow, parm); + } + else + { + sprintf(file, "%s%s/", apath, fd.cFileName); + go = func(file, fd.nFileSizeLow, parm); + } + } + while(FindNextFile(r, &fd) && go); + FindClose(r); +} + + + +enum {TTP_UNKNOWN, TTP_STRING} com_tokentype; +char *COM_ParseOut (char *data, char *out, int outlen) +{ + 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 data; + + c = *data++; + if (c=='\"' || !c) + { + out[len] = 0; + return data; + } + out[len] = c; + len++; + } + } + + com_tokentype = TTP_UNKNOWN; + +// parse a regular word + do + { + if (len >= outlen-1) + return data; + + out[len] = c; + data++; + len++; + c = *data; + } while (c>32); + + out[len] = 0; + return data; +} + +char com_token[2048]; +char *COM_ParseToken (char *data) +{ + 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 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 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 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 + +#ifndef CLIENTONLY +cvar_t ftpserver = {"sv_ftp", "0"}; +cvar_t httpserver = {"sv_http", "0"}; +cvar_t pop3server = {"sv_pop3", "0"}; +cvar_t smtpserver = {"sv_smtp", "0"}; +cvar_t sv_readlevel = {"sv_readlevel", "0"}; //default to allow anyone +cvar_t sv_writelevel = {"sv_writelevel", "35"}; //allowed to write to uploads/uname +cvar_t sv_fulllevel = {"sv_fulllevel", "49"}; //allowed to write anywhere, replace any file... +#endif + +//this file contains functions called from each side. + +void VARGS IWebDPrintf(char *fmt, ...) +{ + va_list argptr; + char msg[4096]; + + if (!developer.value) + return; + + va_start (argptr,fmt); + _vsnprintf (msg,sizeof(msg)-10, fmt,argptr); //catch any nasty bugs... (this is hopefully impossible) + va_end (argptr); + + Con_Printf("%s", msg); +} +void VARGS IWebPrintf(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("%s", msg); +} +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("^1%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(&httpserver, "Internet Server Access"); +#endif + +#ifdef EMAILSERVER + Cvar_Register(&pop3server, "Internet Server Access"); + Cvar_Register(&smtpserver, "Internet Server Access"); +#endif +} +void IWebRun(void) +{ +#ifdef WEBSERVER + FTP_ServerRun(ftpserver.value!= 0); + HTTP_ServerPoll(httpserver.value!=0); +#endif +#ifdef EMAILSERVER + SV_POP3(pop3server.value!=0); + SV_SMTP(smtpserver.value!=0); +#endif +} +void IWebShutdown(void) +{ +} + +extern int file_from_pak; +IWEBFILE *IWebFOpenRead(char *name) //fread(name, "rb"); +{ + IWEBFILE *gf; + FILE *f; + + if (*name == '/') + name++; + + if (strstr(name, "..")) + return NULL; + + if ((com_filesize=COM_FOpenFile(name, &f)) >= 0) + { + IWEBFILE *ret = IWebMalloc(sizeof(IWEBFILE)); + if (!ret) + { + fclose(f); + return NULL; + } + ret->f = f; + ret->length = com_filesize; + ret->start = ftell(f); + ret->end = ret->start+ret->length; + return ret; + } + + if (file_from_pak==2) + { + char *buffer; + IWEBFILE *ret; + + return NULL; //reject - we don't want to have this hanging over us + //big files take a LOT of memory. + + buffer = COM_LoadMallocFile(name); + if (buffer) + { + ret = IWebMalloc(sizeof(IWEBFILE) + sizeof(IWeb_FileGen_t)); + ret->bufferdata = (IWeb_FileGen_t *)(ret+1); + ret->length = ret->bufferdata->len = com_filesize; + ret->bufferdata->data = buffer; + ret->bufferdata->references=-1000; + ret->start = 0; + ret->pos = 0; + ret->end = com_filesize; + + return ret; + } + } + +#ifndef CLIENTONLY + gf = IWebGenerateFile(name, NULL, 0); + if (gf) + return gf; +#endif + return NULL; +} +#endif +IWEBFILE *IWebFOpenWrite(char *name, int append) //fopen(name, append?"ab":"wb"); +{ + FILE *f; + char name2[512]; + if (strstr(name, "..")) + return NULL; + if (*name == '/') + sprintf(name2, "%s%s", com_gamedir, name); + else + sprintf(name2, "%s/%s", com_gamedir, name); +// COM_CreatePath(name2); + f = fopen(name2, append?"ab":"wb"); + if (f) + { + IWEBFILE *ret = IWebMalloc(sizeof(IWEBFILE)); + if (!ret) + { + fclose(f); + return NULL; + } + ret->f = f; + ret->length = 0; + ret->start = 0; + ret->end = ret->start+ret->length; + return ret; + } + return NULL; +} +int IWebFWrite(void *data, int s1, int s2, IWEBFILE *file) //fwrite +{ + return fwrite(data, s1, s2, file->f); +} +int IWebFRead(void *data, int s1, int s2, IWEBFILE *file) //fread +{ +#ifdef PARANOID + if (s1 != 1) + Sys_Error("IWebFRead: s1 must be 1"); //should never happen. It's a debugging thing. +#endif + if (!file->f) + { + int readable; + readable = s2; + if (s1*readable + file->pos >= file->bufferdata->len) + readable = (file->length - file->pos)/s1; + memcpy(data, file->bufferdata->data+file->pos, readable*s1); + file->pos += readable*s1; + return readable; + } + + if (s2 + ftell(file->f) > file->end) //cut down the ammount readable. + s2 = file->end - ftell(file->f); + return fread(data, s1, s2, file->f); +} +void IWebFClose(IWEBFILE *file) +{ + if (file->f) + fclose(file->f); + else + { + if (file->bufferdata->references == -1000) //temp condition where buffer->data is malloc, and the buffer header is part of the file info + IWebFree(file->bufferdata->data); + else + { + file->bufferdata->references--; + } + } + IWebFree(file); +} + +void IWebFSeek(IWEBFILE *file, long pos, int type) +{ + if (!file->f) + { + file->pos = pos; + return; + } + if (type == SEEK_SET) + fseek(file->f, pos + file->start, SEEK_SET); + else + Sys_Error("IWebFSeek: Bad seek type\n"); +} +int IWebFTell(IWEBFILE *file) +{ + if (!file->f) + return file->pos; + return ftell(file->f) - file->start; +} + +#ifndef WEBSVONLY +//replacement for Z_Malloc. It simply allocates up to a reserve ammount. +void *IWebMalloc(int size) +{ + char *mem = Z_TagMalloc(size+32768, 15); + if (!mem) + return NULL; //bother + + Z_Free(mem); + return Z_Malloc(size); //allocate the real ammount +} + +void *IWebRealloc(void *old, int size) +{ + char *mem = Z_TagMalloc(size+32768, 15); + if (!mem) //make sure there will be padding left + return NULL; //bother + + Z_Free(mem); + return BZ_Realloc(old, size); +} +#endif + + + + +int IWebAuthorize(char *name, char *password) +{ +#ifdef WEBSVONLY + if (authedusername) + if (!strcmp(name, authedusername)) + if (!strcmp(password, autheduserpassword)) + return IWEBACC_FULL; + return IWEBACC_READ; +#else +#ifndef CLIENTONLY + int id = Rank_GetPlayerID(name, atoi(password), false); + rankinfo_t info; + if (!id) + { + if (!sv_readlevel.value) + 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(char *fname, 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; +} + +iwboolean FTP_StringToAdr (char *s, qbyte ip[4], qbyte port[2]) +{ + s = COM_ParseToken(s); + ip[0] = atoi(com_token); + + s = COM_ParseToken(s); + if (*com_token != ',') + return false; + + s = COM_ParseToken(s); + ip[1] = atoi(com_token); + + s = COM_ParseToken(s); + if (*com_token != ',') + return false; + + s = COM_ParseToken(s); + ip[2] = atoi(com_token); + + s = COM_ParseToken(s); + if (*com_token != ',') + return false; + + s = COM_ParseToken(s); + ip[3] = atoi(com_token); + + s = COM_ParseToken(s); + if (*com_token != ',') + return false; + + s = COM_ParseToken(s); + port[0] = atoi(com_token); + + s = COM_ParseToken(s); + if (*com_token != ',') + return false; + + s = COM_ParseToken(s); + port[1] = atoi(com_token); + + + return true; +} + +char *Q_strcpyline(char *out, 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 diff --git a/engine/http/webgen.c b/engine/http/webgen.c new file mode 100644 index 00000000..67e23e81 --- /dev/null +++ b/engine/http/webgen.c @@ -0,0 +1,407 @@ +#include "bothdefs.h" + +#ifdef WEBSERVER + +#include "iweb.h" + +#ifdef CLIENTONLY +IWEBFILE *IWebGenerateFile(char *name) +{ + return NULL; +} +#else + +char lastrecordedmvd[MAX_QPATH]; + +IWeb_FileGen_t *IWeb_GenerationBuffer; +int IWeb_GenerationBufferTotal; + +void IWeb_MoreGeneratedResize(int newsize) +{ + IWeb_FileGen_t *ob; + + if (IWeb_GenerationBuffer && IWeb_GenerationBufferTotal >= newsize) + return; //already big enough + + ob = IWeb_GenerationBuffer; + IWeb_GenerationBuffer = BZ_Malloc(sizeof(IWeb_GenerationBuffer) + newsize); + + IWeb_GenerationBuffer->data = (char *)(IWeb_GenerationBuffer+1); + if (ob) + { + memcpy(IWeb_GenerationBuffer->data, ob->data, ob->len); + BZ_Free(ob); + } + + IWeb_GenerationBufferTotal = newsize; +} +void IWeb_Generate(const char *buf) +{ + long count = strlen(buf); + if (!IWeb_GenerationBuffer || IWeb_GenerationBuffer->len + count >= IWeb_GenerationBufferTotal) + IWeb_MoreGeneratedResize(IWeb_GenerationBufferTotal + count+(16*1024)); + + memcpy(&IWeb_GenerationBuffer->data[IWeb_GenerationBuffer->len], buf, count); + IWeb_GenerationBuffer->len+=count; +} + +int Rank_Enumerate (unsigned int first, unsigned int last, void (*callback) (const rankinfo_t *ri)); //leader first. + +void IWeb_ParseForm(char *info, int infolen, char *text) +{ + char *eq, *and; + char *token, *out; + *info = '\0'; + if (!text) + return; + while(*text) + { + eq = strchr(text, '='); + if (!eq) + break; + *eq = '\0'; + and = strchr(eq+1, '&'); + if (and) + *and = '\0'; + + for (out = token = eq+1; *token;) + { + if (*token == '+') + { + *out++ = ' '; + token++; + } + else if (*token == '%' && token[1] && token[2]) + { + int c = 0; + + if (token[1] >= '0' && token[1] <= '9') + { + c += token[1] - '0'; + } + else if (token[1] >= 'a' && token[1] <= 'f') + { + c += token[1] - 'a'+10; + } + else if (token[1] >= 'A' && token[1] <= 'F') + { + c += token[1] - 'A'+10; + } + c*=16; + if (token[2] >= '0' && token[2] <= '9') + { + c += token[2] - '0'; + } + else if (token[2] >= 'a' && token[2] <= 'f') + { + c += token[2] - 'a'+10; + } + else if (token[2] >= 'A' && token[2] <= 'F') + { + c += token[2] - 'A'+10; + } + + *out++ = c; + token+=3; + } + else + *out++ = *token++; + } + *out = '\0'; + + Info_SetValueForKey(info, text, eq+1, infolen); + if (!and) + return; + text = and+1; + } +} + +void IWeb_GenerateAdminFile(char *parms, char *content, int contentlength) +{ + extern char outputbuf[]; //redirected buffer - always null termed. + char info[16384]; + char *pwd; + char *cmd; + char *mark, *start; + + extern cvar_t rcon_password; + IWeb_Generate("FTEQWSV - admin"); + if (*rcon_password.string) + { + IWeb_ParseForm(info, sizeof(info), content); + pwd = Info_ValueForKey(info, "pwd"); + cmd = Info_ValueForKey(info, "cmd"); + + IWeb_Generate("
"); + IWeb_Generate("
"); + IWeb_Generate(""); + IWeb_Generate("
"); + IWeb_Generate(""); + IWeb_Generate("
"); + IWeb_Generate(""); + IWeb_Generate("
"); + IWeb_Generate("
"); + + if (!strcmp(rcon_password.string, pwd)) + { + Con_Printf("Web based rcon: %s\n", cmd); + SV_BeginRedirect(-1); + Cmd_ExecuteString(cmd, RESTRICT_RCON); + for (mark = start = outputbuf; *mark; mark++) + { + if (*mark == '\n') + { + *mark = '\0'; + IWeb_Generate(start); + IWeb_Generate("
"); + start = mark+1; + } + + } + IWeb_Generate(start); + SV_EndRedirect(); + } + else if (*pwd) + IWeb_Generate("Password is incorrect."); + } + else + IWeb_Generate("

Remote administration is not enabled.

"); + IWeb_Generate(""); +} + + +void IWeb_GenerateRankingsFileCallback(const rankinfo_t *ri) +{ + IWeb_Generate(""); + IWeb_Generate(ri->h.name); + IWeb_Generate(""); + IWeb_Generate(va("%i", ri->s.kills)); + IWeb_Generate(""); + IWeb_Generate(va("%i", ri->s.deaths)); + IWeb_Generate(""); + IWeb_Generate(""); +} + +void IWeb_GenerateRankingsFile (char *parms, char *content, int contentlength) +{ + IWeb_Generate(""); + + if (Rank_OpenRankings()) + { + IWeb_Generate(""); + IWeb_Generate(""); + if (Rank_Enumerate(atoi(parms), atoi(parms)+20, IWeb_GenerateRankingsFileCallback) == 20) + { + IWeb_Generate("
Players
"); + if (atoi(parms) >= 20) + IWeb_Generate(va("Less", atoi(parms)-20)); + else if (atoi(parms) > 0) + IWeb_Generate(va("Less", 0)); + IWeb_Generate(va("More", atoi(parms)+20)); + } + else + { + IWeb_Generate(""); + if (atoi(parms) >= 20) + IWeb_Generate(va("Less", atoi(parms)-20)); + else if (atoi(parms) > 0) + IWeb_Generate(va("Less", 0)); + } + } + else + IWeb_Generate("

Rankings are disabled. Sorry.

"); + IWeb_Generate(""); +} + +void IWeb_GenerateIndexFile (char *parms, char *content, int contentlength) +{ + extern cvar_t rcon_password; + char *s, *o; + char key[128], value[128]; + int l; + qboolean added; + client_t *cl; + IWeb_Generate("FTEQWSV"); + IWeb_Generate("

"); + IWeb_Generate(hostname.string); + IWeb_Generate("

"); + + IWeb_Generate("Server website

"); + + if (Rank_OpenRankings()) + IWeb_Generate("Click here to see ranked players.

"); + if (*rcon_password.string) + IWeb_Generate("Admin.

"); + + s = svs.info; + + IWeb_Generate(""); + IWeb_Generate(""); + if (*s == '\\') + s++; + while (*s) + { + o = key; + while (*s && *s != '\\') + *o++ = *s++; + + l = o - key; +// if (l < 20) +// { +// memset (o, ' ', 20-l); +// key[20] = 0; +// } +// else + *o = 0; + + IWeb_Generate(""); + } + + IWeb_Generate("
Server Info
"); + IWeb_Generate(key); + + if (!*s) + { + IWeb_Generate("MISSING VALUE\n"); + return; + } + + o = value; + s++; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (*s) + s++; + + IWeb_Generate(""); + IWeb_Generate(value); + IWeb_Generate("
"); + + IWeb_Generate("

"); + IWeb_Generate(""); + added = false; + for (l = 0, cl = svs.clients; l < sv.allocated_client_slots; l++, cl++) + { + if (cl->state <= cs_zombie) + continue; + + IWeb_Generate(""); + added = true; + } + if (!added) + { + IWeb_Generate("
Players
"); + IWeb_Generate(cl->name); + IWeb_Generate(""); + IWeb_Generate(va("%i", cl->old_frags)); + IWeb_Generate("
"); + IWeb_Generate("No players on server"); + IWeb_Generate(""); + } + + IWeb_Generate("
"); + + IWeb_Generate(""); +} + +typedef struct { + char *name; + void (*GenerationFunction) (char *parms, char *content, int contentlength); + float lastgenerationtime; + float oldbysecs; + IWeb_FileGen_t *buffer; + int genid; +} IWebFile_t; + +IWebFile_t IWebFiles[] = { + {"allplayers.html", IWeb_GenerateRankingsFile}, + {"index.html", IWeb_GenerateIndexFile}, + {"admin.html", IWeb_GenerateAdminFile} +}; + +IWEBFILE *IWebGenerateFile(char *name, char *content, int contentlength) +{ + int fnum; + char *parms; + int len; + + if (!sv.state) + return NULL; + + if (*lastrecordedmvd && !strcmp(name, "lastdemo.mvd")) + if (strcmp(name, "lastdemo.mvd")) //no infinate loops please... + return IWebFOpenRead(lastrecordedmvd); + + parms = strchr(name, '?'); + if (!parms) + parms = name + strlen(name); + len = parms-name; + if (*parms) + parms++; + + if (!*name) + return NULL; + + + for (fnum = 0; fnum < sizeof(IWebFiles) / sizeof(IWebFile_t); fnum++) + { + if (!Q_strncasecmp(name, IWebFiles[fnum].name, len+1)) + { + IWEBFILE *ret; + if (IWebFiles[fnum].buffer) + { + if (IWebFiles[fnum].lastgenerationtime+10 < Sys_DoubleTime() || contentlength||*parms) //10 sec lifetime + { + IWebFiles[fnum].buffer->references--; //remove our reference and check free + if (IWebFiles[fnum].buffer->references<=0) + { + BZ_Free(IWebFiles[fnum].buffer); + IWebFiles[fnum].buffer = NULL; + } + } + } + if (!IWebFiles[fnum].buffer) + { + if (IWeb_GenerationBuffer!=NULL) + Sys_Error("Recursive file generation\n"); + IWebFiles[fnum].GenerationFunction(parms, content, contentlength); + IWebFiles[fnum].buffer = IWeb_GenerationBuffer; + + if (!contentlength) + { + IWeb_GenerationBuffer->references++; //so it can't be sent once and freed instantly. + IWebFiles[fnum].lastgenerationtime = Sys_DoubleTime(); + } + else + IWebFiles[fnum].lastgenerationtime = -10; + } + + ret = IWebMalloc(sizeof(IWEBFILE)); + if (!ret) + { + BZ_Free(IWeb_GenerationBuffer); + return NULL; + } + ret->f = NULL; + ret->bufferdata = IWebFiles[fnum].buffer; + ret->length = ret->bufferdata->len; + ret->bufferdata->references++; + ret->pos = 0; + ret->start = 0; + ret->end = ret->start+ret->length; + + IWeb_GenerationBuffer = NULL; + + return ret; + } + } + return NULL; +} + +#endif +#endif