/* * $Logfile: /DescentIII/Main/inetfile/CFtp.cpp $ * $Revision: 1.1.1.1 $ * $Date: 2003-08-26 03:58:00 $ * $Author: kevinb $ * * FTP Client class (get only) * * $Log: not supported by cvs2svn $ * * 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 #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 "inetgetfile.h" #endif #ifdef MACINTOSH #include "macsock.h" #endif #include "CFtp.h" #ifdef __LINUX__ void *FTPObjThread( void * obj ) #else void FTPObjThread( void * obj ) #endif { ((CFtpGet *)obj)->WorkerThread(); #ifdef __LINUX__ return NULL; #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; 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; if(!inet_LoadThreadLib()) { m_State = FTP_STATE_INTERNAL_ERROR; return; } if(df_pthread_create(&thread,NULL,FTPObjThread,this)!=0) { 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; } unsigned int CFtpGet::GetBytesIn() { return m_iBytesIn; } unsigned int 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; } unsigned int CFtpGet::GetFile() { //Start off by changing into the proper dir. char szCommandString[200]; int rcode; sprintf(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]) { sprintf(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; sprintf(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; } unsigned int CFtpGet::IssuePort() { char szCommandString[200]; SOCKADDR_IN listenaddr; // Socket address structure int iLength; // Length of the address structure unsigned int nLocalPort; // Local port for listening unsigned int 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{ unsigned char s_b1,s_b2,s_b3,s_b4;}S_un_b; struct{ unsigned short s_w1,s_w2;} S_un_w; unsigned int S_addr; }S_un; S_un.S_addr = (unsigned int)listenaddr.sin_addr.s_addr; // Format the PORT command with the correct numbers. sprintf(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; sprintf(szLoginString,"USER %s\r\n",m_szUserName); rcode = SendFTPCommand(szLoginString); if(rcode >=400) { m_State = FTP_STATE_LOGIN_ERROR; return 0; } sprintf(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; } unsigned int 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()); } unsigned int CFtpGet::ReadFTPServerReply() { unsigned int rcode; unsigned int iBytesRead; char chunk[2]; char szcode[5]; unsigned int 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); } unsigned int 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); } }