/* * Descent 3 * Copyright (C) 2024 Parallax Software * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . --- HISTORICAL COMMENTS FOLLOW --- * $Logfile: /DescentIII/Main/inetfile/CFtp.cpp $ * $Revision: 1.3 $ * $Date: 2001/01/13 21:48:46 $ * $Author: icculus $ * * FTP Client class (get only) * * $Log: CFtp.cpp,v $ * Revision 1.3 2001/01/13 21:48:46 icculus * patched to (re)compile on win32. * * Revision 1.2 2000/06/03 14:30:21 icculus * 1.4 code merge and pthread->SDL thread conversion. * * Revision 1.1.1.1 2000/04/18 00:00:38 icculus * initial checkin * * * 8 10/21/99 9:27p Jeff * B.A. Macintosh code merge * * 7 9/08/99 6:37p Jeff * fixed http/ftp downloading for Linux, should all work fine now. * * 6 8/22/99 12:32a Jeff * fixed select calls for Linux. Ported Kevin's new http stuff to Linux * * 5 8/21/99 6:48a Jeff * Linux port * * 4 4/14/99 1:20a Jeff * fixed case mismatched #includes * * 3 7/31/98 11:40a Kevin * * 2 6/01/98 10:10a Kevin * Added DLL connection interface and auto update DLL * * 1 5/27/98 9:52a Kevin * * 1 5/25/98 5:31p Kevin * Initial version * * $NoKeywords: $ */ #ifdef WIN32 #include #include #include typedef int socklen_t; #endif #include #include #include #ifdef __LINUX__ // sorry, I'm lazy, I guess we could copy the defines // that we need to transalte winsock->linux into this header...but no need to now #include "SDL_thread.h" #include "inetgetfile.h" #endif #include "CFtp.h" // MTS: only used in this file? #ifdef __LINUX__ int FTPObjThread(void *obj) #else void FTPObjThread(void *obj) #endif { ((CFtpGet *)obj)->WorkerThread(); #ifdef __LINUX__ return 0; #endif } void CFtpGet::AbortGet() { m_Aborting = true; while (!m_Aborted) ; // Wait for the thread to end fclose(LOCALFILE); } CFtpGet::CFtpGet(char *URL, char *localfile, char *Username, char *Password) { SOCKADDR_IN listensockaddr; m_State = FTP_STATE_STARTUP; m_ListenSock = INVALID_SOCKET; m_DataSock = INVALID_SOCKET; m_ControlSock = INVALID_SOCKET; m_iBytesIn = 0; m_iBytesTotal = 0; m_Aborting = false; m_Aborted = false; LOCALFILE = fopen(localfile, "wb"); if (NULL == LOCALFILE) { m_State = FTP_STATE_CANT_WRITE_FILE; return; } if (Username) { strcpy(m_szUserName, Username); } else { strcpy(m_szUserName, "anonymous"); } if (Password) { strcpy(m_szPassword, Password); } else { strcpy(m_szPassword, "pxouser@pxo.net"); } m_ListenSock = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == m_ListenSock) { int iWinsockErr = WSAGetLastError(); m_State = FTP_STATE_SOCKET_ERROR; return; } else { listensockaddr.sin_family = AF_INET; listensockaddr.sin_port = 0; listensockaddr.sin_addr.s_addr = INADDR_ANY; // Bind the listen socket if (bind(m_ListenSock, (SOCKADDR *)&listensockaddr, sizeof(SOCKADDR))) { // Couldn't bind the socket int iWinsockErr = WSAGetLastError(); m_State = FTP_STATE_SOCKET_ERROR; return; } // Listen for the server connection if (listen(m_ListenSock, 1)) { // Couldn't listen on the socket int iWinsockErr = WSAGetLastError(); m_State = FTP_STATE_SOCKET_ERROR; return; } } m_ControlSock = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == m_ControlSock) { m_State = FTP_STATE_SOCKET_ERROR; return; } // Parse the URL // Get rid of any extra ftp:// stuff char *pURL = URL; if (strnicmp(URL, "ftp:", 4) == 0) { pURL += 4; while (*pURL == '/') { pURL++; } } // There shouldn't be any : in this string if (strchr(pURL, ':')) { m_State = FTP_STATE_URL_PARSING_ERROR; return; } // read the filename by searching backwards for a / // then keep reading until you find the first / // when you found it, you have the host and dir char *filestart = NULL; char *dirstart = NULL; for (int i = strlen(pURL); i >= 0; i--) { if (pURL[i] == '/') { if (!filestart) { filestart = pURL + i + 1; dirstart = pURL + i + 1; strcpy(m_szFilename, filestart); } else { dirstart = pURL + i + 1; } } } if ((dirstart == NULL) || (filestart == NULL)) { m_State = FTP_STATE_URL_PARSING_ERROR; return; } else { strncpy(m_szDir, dirstart, (filestart - dirstart)); m_szDir[(filestart - dirstart)] = '\0'; strncpy(m_szHost, pURL, (dirstart - pURL)); m_szHost[(dirstart - pURL) - 1] = '\0'; } // At this point we should have a nice host,dir and filename #ifdef WIN32 if (NULL == _beginthread(FTPObjThread, 0, this)) { m_State = FTP_STATE_INTERNAL_ERROR; return; } #elif defined(__LINUX__) // pthread_t thread; SDL_Thread *thread; if (!inet_LoadThreadLib()) { m_State = FTP_STATE_INTERNAL_ERROR; return; } // if(df_pthread_create(&thread,NULL,FTPObjThread,this)!=0) thread = SDL_CreateThread(FTPObjThread, "ftpget", this); if (thread == NULL) { m_State = FTP_STATE_INTERNAL_ERROR; return; } #endif m_State = FTP_STATE_CONNECTING; } CFtpGet::~CFtpGet() { if (m_ListenSock != INVALID_SOCKET) { shutdown(m_ListenSock, 2); #ifndef __LINUX__ closesocket(m_ListenSock); #else close(m_ListenSock); #endif } if (m_DataSock != INVALID_SOCKET) { shutdown(m_DataSock, 2); #ifndef __LINUX__ closesocket(m_DataSock); #else close(m_DataSock); #endif } if (m_ControlSock != INVALID_SOCKET) { shutdown(m_ControlSock, 2); #ifndef __LINUX__ closesocket(m_ControlSock); #else close(m_ControlSock); #endif } } // Returns a value to specify the status (ie. connecting/connected/transferring/done) int CFtpGet::GetStatus() { return m_State; } uint32_t CFtpGet::GetBytesIn() { return m_iBytesIn; } uint32_t CFtpGet::GetTotalBytes() { return m_iBytesTotal; } // This function does all the work -- connects on a blocking socket // then sends the appropriate user and password commands // and then the cwd command, the port command then get and finally the quit void CFtpGet::WorkerThread() { ConnectControlSocket(); if (m_State != FTP_STATE_LOGGING_IN) { return; } LoginHost(); if (m_State != FTP_STATE_LOGGED_IN) { return; } GetFile(); // We are all done now, and state has the current state. m_Aborted = true; } uint32_t CFtpGet::GetFile() { // Start off by changing into the proper dir. char szCommandString[256]; int rcode; snprintf(szCommandString, sizeof(szCommandString), "TYPE I\r\n"); rcode = SendFTPCommand(szCommandString); if (rcode >= 400) { m_State = FTP_STATE_UNKNOWN_ERROR; return 0; } if (m_Aborting) return 0; if (m_szDir[0]) { snprintf(szCommandString, sizeof(szCommandString), "CWD %s\r\n", m_szDir); rcode = SendFTPCommand(szCommandString); if (rcode >= 400) { m_State = FTP_STATE_DIRECTORY_INVALID; return 0; } } if (m_Aborting) return 0; if (!IssuePort()) { m_State = FTP_STATE_UNKNOWN_ERROR; return 0; } if (m_Aborting) return 0; snprintf(szCommandString, sizeof(szCommandString), "RETR %s\r\n", m_szFilename); rcode = SendFTPCommand(szCommandString); if (rcode >= 400) { m_State = FTP_STATE_FILE_NOT_FOUND; return 0; } if (m_Aborting) return 0; // Now we will try to determine the file size... char *p, *s; p = strchr(recv_buffer, '('); if (p) { p++; s = strchr(p, ' '); *s = '\0'; m_iBytesTotal = atoi(p); } if (m_Aborting) return 0; m_DataSock = accept(m_ListenSock, NULL, NULL); //(SOCKADDR *)&sockaddr,&iAddrLength); // Close the listen socket #ifndef __LINUX__ closesocket(m_ListenSock); #else close(m_ListenSock); #endif if (m_DataSock == INVALID_SOCKET) { int iWinsockErr = WSAGetLastError(); m_State = FTP_STATE_SOCKET_ERROR; return 0; } if (m_Aborting) return 0; ReadDataChannel(); m_State = FTP_STATE_FILE_RECEIVED; return 1; } uint32_t CFtpGet::IssuePort() { char szCommandString[200]; SOCKADDR_IN listenaddr; // Socket address structure socklen_t iLength; // Length of the address structure uint32_t nLocalPort; // Local port for listening uint32_t nReplyCode; // FTP server reply code // Get the address for the hListenSocket iLength = sizeof(listenaddr); if (getsockname(m_ListenSock, (SOCKADDR *)&listenaddr, &iLength) == SOCKET_ERROR) { int iWinsockErr = WSAGetLastError(); m_State = FTP_STATE_SOCKET_ERROR; return 0; } // Extract the local port from the hListenSocket nLocalPort = listenaddr.sin_port; // Now, reuse the socket address structure to // get the IP address from the control socket. if (getsockname(m_ControlSock, (SOCKADDR *)&listenaddr, &iLength) == SOCKET_ERROR) { int iWinsockErr = WSAGetLastError(); m_State = FTP_STATE_SOCKET_ERROR; return 0; } #ifdef WIN32 // Format the PORT command with the correct numbers. sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n", listenaddr.sin_addr.S_un.S_un_b.s_b1, listenaddr.sin_addr.S_un.S_un_b.s_b2, listenaddr.sin_addr.S_un.S_un_b.s_b3, listenaddr.sin_addr.S_un.S_un_b.s_b4, nLocalPort & 0xFF, nLocalPort >> 8); #else union { struct { uint8_t s_b1, s_b2, s_b3, s_b4; } S_un_b; struct { uint16_t s_w1, s_w2; } S_un_w; uint32_t S_addr; } S_un; S_un.S_addr = (uint32_t)listenaddr.sin_addr.s_addr; // Format the PORT command with the correct numbers. snprintf(szCommandString, sizeof(szCommandString), "PORT %d,%d,%d,%d,%d,%d\r\n", S_un.S_un_b.s_b1, S_un.S_un_b.s_b2, S_un.S_un_b.s_b3, S_un.S_un_b.s_b4, nLocalPort & 0xFF, nLocalPort >> 8); #endif // Tell the server which port to use for data. nReplyCode = SendFTPCommand(szCommandString); if (nReplyCode != 200) { #ifdef __LINUX__ // I don't know if this is just Linux or do to a bug I fixed while porting to linux // for some reason I kept getting reply 250 here and have to read again to get the // "200 PORT Command OK" or whatever if (nReplyCode != 250 || (ReadFTPServerReply() != 200)) // ummmmmmmm #endif { int iWinsockErr = WSAGetLastError(); m_State = FTP_STATE_SOCKET_ERROR; return 0; } } return 1; } int CFtpGet::ConnectControlSocket() { HOSTENT *he; SERVENT *se; SOCKADDR_IN hostaddr; he = gethostbyname(m_szHost); if (he == NULL) { m_State = FTP_STATE_HOST_NOT_FOUND; return 0; } // m_ControlSock if (m_Aborting) return 0; se = getservbyname("ftp", NULL); if (se == NULL) { hostaddr.sin_port = htons(21); } else { hostaddr.sin_port = se->s_port; } hostaddr.sin_family = AF_INET; memcpy(&hostaddr.sin_addr, he->h_addr_list[0], 4); if (m_Aborting) return 0; // Now we will connect to the host if (connect(m_ControlSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR))) { int iWinsockErr = WSAGetLastError(); m_State = FTP_STATE_CANT_CONNECT; return 0; } m_State = FTP_STATE_LOGGING_IN; return 1; } int CFtpGet::LoginHost() { char szLoginString[200]; int rcode; snprintf(szLoginString, sizeof(szLoginString), "USER %s\r\n", m_szUserName); rcode = SendFTPCommand(szLoginString); if (rcode >= 400) { m_State = FTP_STATE_LOGIN_ERROR; return 0; } snprintf(szLoginString, sizeof(szLoginString), "PASS %s\r\n", m_szPassword); rcode = SendFTPCommand(szLoginString); if (rcode >= 400) { m_State = FTP_STATE_LOGIN_ERROR; return 0; } m_State = FTP_STATE_LOGGED_IN; return 1; } uint32_t CFtpGet::SendFTPCommand(char *command) { FlushControlChannel(); // Send the FTP command if (SOCKET_ERROR == (send(m_ControlSock, command, strlen(command), 0))) { int iWinsockErr = WSAGetLastError(); // Return 999 to indicate an error has occurred return (999); } // Read the server's reply and return the reply code as an integer return (ReadFTPServerReply()); } uint32_t CFtpGet::ReadFTPServerReply() { uint32_t rcode; uint32_t iBytesRead; char chunk[2]; char szcode[5]; uint32_t igotcrlf = 0; memset(recv_buffer, 0, 1000); do { chunk[0] = '\0'; iBytesRead = recv(m_ControlSock, chunk, 1, 0); if (iBytesRead == SOCKET_ERROR) { int iWinsockErr = WSAGetLastError(); #ifdef __LINUX__ if (0 == iWinsockErr) { continue; } #endif // Return 999 to indicate an error has occurred return (999); } if ((chunk[0] == 0x0a) || (chunk[0] == 0x0d)) { if (recv_buffer[0] != '\0') { igotcrlf = 1; } } else { chunk[1] = '\0'; strcat(recv_buffer, chunk); } } while (igotcrlf == 0); if (recv_buffer[3] == '-') { // Hack -- must be a MOTD return ReadFTPServerReply(); } if (recv_buffer[3] != ' ') { // We should have 3 numbers then a space return 999; } memcpy(szcode, recv_buffer, 3); szcode[3] = '\0'; rcode = atoi(szcode); // Extract the reply code from the server reply and return as an integer return (rcode); } uint32_t CFtpGet::ReadDataChannel() { char sDataBuffer[4096]; // Data-storage buffer for the data channel int nBytesRecv; // Bytes received from the data channel m_State = FTP_STATE_RECEIVING; if (m_Aborting) return 0; do { if (m_Aborting) return 0; nBytesRecv = recv(m_DataSock, (char *)&sDataBuffer, sizeof(sDataBuffer), 0); #ifdef __LINUX__ if (nBytesRecv == -1) { int iWinsockErr = WSAGetLastError(); if (iWinsockErr == 0) { nBytesRecv = 1; continue; } } #endif m_iBytesIn += nBytesRecv; if (nBytesRecv > 0) { fwrite(sDataBuffer, nBytesRecv, 1, LOCALFILE); // Write sDataBuffer, nBytesRecv } } while (nBytesRecv > 0); fclose(LOCALFILE); // Close the file and check for error returns. if (nBytesRecv == SOCKET_ERROR) { // Ok, we got a socket error -- xfer aborted? m_State = FTP_STATE_RECV_FAILED; return 0; } else { // done! m_State = FTP_STATE_FILE_RECEIVED; return 1; } } void CFtpGet::FlushControlChannel() { fd_set read_fds; TIMEVAL timeout; int bytesin = 0; char flushbuff[3]; timeout.tv_sec = 0; timeout.tv_usec = 0; FD_ZERO(&read_fds); FD_SET(m_ControlSock, &read_fds); while (select(m_ControlSock + 1, &read_fds, NULL, NULL, &timeout)) { recv(m_ControlSock, flushbuff, 1, 0); } }