fteqw/engine/http/ftpclient.c

869 lines
19 KiB
C

#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 <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#ifdef sun
#include <sys/filio.h>
#endif
#ifdef NeXT
#include <libc.h>
#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