2 * $Logfile: /Freespace2/code/Inetfile/CFtp.cpp $
7 * FTP Client class (get only)
10 * Revision 1.5 2002/05/26 20:32:24 theoddone33
11 * Fix some minor stuff
13 * Revision 1.4 2002/05/26 20:20:53 relnev
18 * Revision 1.3 2002/05/26 19:55:20 relnev
19 * unix.h: winsock defines
21 * cftp.cpp: now compiles!
23 * Revision 1.2 2002/05/07 03:16:45 theoddone33
24 * The Great Newline Fix
26 * Revision 1.1.1.1 2002/05/03 03:28:09 root
30 * 3 5/04/99 7:34p Dave
31 * Fixed slow HTTP get problem.
33 * 2 4/20/99 6:39p Dave
34 * Almost done with artillery targeting. Added support for downloading
35 * images on the PXO screen.
37 * 1 4/20/99 4:37p Dave
48 #include <sys/types.h>
49 #include <sys/socket.h>
50 #include <netinet/in.h>
51 #include <arpa/inet.h>
54 #include "unix.h" // unix.h
63 void FTPObjThread( void * obj )
65 ((CFtpGet *)obj)->WorkerThread();
68 void CFtpGet::AbortGet()
71 while(!m_Aborted) ; //Wait for the thread to end
75 CFtpGet::CFtpGet(char *URL,char *localfile,char *Username,char *Password)
77 SOCKADDR_IN listensockaddr;
78 m_State = FTP_STATE_STARTUP;
80 m_ListenSock = INVALID_SOCKET;
81 m_DataSock = INVALID_SOCKET;
82 m_ControlSock = INVALID_SOCKET;
88 LOCALFILE = fopen(localfile,"wb");
91 m_State = FTP_STATE_CANT_WRITE_FILE;
97 strcpy(m_szUserName,Username);
101 strcpy(m_szUserName,"anonymous");
105 strcpy(m_szPassword,Password);
109 strcpy(m_szPassword,"pxouser@pxo.net");
111 m_ListenSock = socket(AF_INET, SOCK_STREAM, 0);
112 if(INVALID_SOCKET == m_ListenSock)
114 // vint iWinsockErr = WSAGetLastError();
115 m_State = FTP_STATE_SOCKET_ERROR;
120 listensockaddr.sin_family = AF_INET;
121 listensockaddr.sin_port = 0;
122 listensockaddr.sin_addr.s_addr = INADDR_ANY;
124 // Bind the listen socket
125 if (bind(m_ListenSock, (SOCKADDR *)&listensockaddr, sizeof(SOCKADDR)))
127 //Couldn't bind the socket
128 // int iWinsockErr = WSAGetLastError();
129 m_State = FTP_STATE_SOCKET_ERROR;
133 // Listen for the server connection
134 if (listen(m_ListenSock, 1))
136 //Couldn't listen on the socket
137 // int iWinsockErr = WSAGetLastError();
138 m_State = FTP_STATE_SOCKET_ERROR;
142 m_ControlSock = socket(AF_INET, SOCK_STREAM, 0);
143 if(INVALID_SOCKET == m_ControlSock)
145 m_State = FTP_STATE_SOCKET_ERROR;
149 //Get rid of any extra ftp:// stuff
151 if(_strnicmp(URL,"ftp:",4)==0)
159 //There shouldn't be any : in this string
162 m_State = FTP_STATE_URL_PARSING_ERROR;
165 //read the filename by searching backwards for a /
166 //then keep reading until you find the first /
167 //when you found it, you have the host and dir
168 char *filestart = NULL;
169 char *dirstart = NULL;
170 for(int i = strlen(pURL);i>=0;i--)
176 filestart = pURL+i+1;
178 strcpy(m_szFilename,filestart);
186 if((dirstart==NULL) || (filestart==NULL))
188 m_State = FTP_STATE_URL_PARSING_ERROR;
193 strncpy(m_szDir,dirstart,(filestart-dirstart));
194 m_szDir[(filestart-dirstart)] = NULL;
195 strncpy(m_szHost,pURL,(dirstart-pURL));
196 m_szHost[(dirstart-pURL)-1] = NULL;
198 //At this point we should have a nice host,dir and filename
200 //if(NULL==CreateThread(NULL,0,ObjThread,this,0,&m_dwThreadId))
201 if(NULL==_beginthread(FTPObjThread,0,this))
203 m_State = FTP_STATE_INTERNAL_ERROR;
206 m_State = FTP_STATE_CONNECTING;
213 if(m_ListenSock != INVALID_SOCKET)
215 shutdown(m_ListenSock,2);
216 closesocket(m_ListenSock);
218 if(m_DataSock != INVALID_SOCKET)
220 shutdown(m_DataSock,2);
221 closesocket(m_DataSock);
223 if(m_ControlSock != INVALID_SOCKET)
225 shutdown(m_ControlSock,2);
226 closesocket(m_ControlSock);
232 //Returns a value to specify the status (ie. connecting/connected/transferring/done)
233 int CFtpGet::GetStatus()
238 unsigned int CFtpGet::GetBytesIn()
243 unsigned int CFtpGet::GetTotalBytes()
246 return m_iBytesTotal;
249 //This function does all the work -- connects on a blocking socket
250 //then sends the appropriate user and password commands
251 //and then the cwd command, the port command then get and finally the quit
252 void CFtpGet::WorkerThread()
254 ConnectControlSocket();
255 if(m_State != FTP_STATE_LOGGING_IN)
260 if(m_State != FTP_STATE_LOGGED_IN)
266 //We are all done now, and state has the current state.
272 unsigned int CFtpGet::GetFile()
274 //Start off by changing into the proper dir.
275 char szCommandString[200];
278 sprintf(szCommandString,"TYPE I\r\n");
279 rcode = SendFTPCommand(szCommandString);
282 m_State = FTP_STATE_UNKNOWN_ERROR;
289 sprintf(szCommandString,"CWD %s\r\n",m_szDir);
290 rcode = SendFTPCommand(szCommandString);
293 m_State = FTP_STATE_DIRECTORY_INVALID;
301 m_State = FTP_STATE_UNKNOWN_ERROR;
306 sprintf(szCommandString,"RETR %s\r\n",m_szFilename);
307 rcode = SendFTPCommand(szCommandString);
310 m_State = FTP_STATE_FILE_NOT_FOUND;
315 //Now we will try to determine the file size...
317 p = strchr(recv_buffer,'(');
323 m_iBytesTotal = atoi(p);
328 m_DataSock = accept(m_ListenSock, NULL,NULL);//(SOCKADDR *)&sockaddr,&iAddrLength);
329 // Close the listen socket
330 closesocket(m_ListenSock);
331 if (m_DataSock == INVALID_SOCKET)
333 // int iWinsockErr = WSAGetLastError();
334 m_State = FTP_STATE_SOCKET_ERROR;
341 m_State = FTP_STATE_FILE_RECEIVED;
345 unsigned int CFtpGet::IssuePort()
348 char szCommandString[200];
349 SOCKADDR_IN listenaddr; // Socket address structure
351 int iLength; // Length of the address structure
355 UINT nLocalPort; // Local port for listening
356 UINT nReplyCode; // FTP server reply code
359 // Get the address for the hListenSocket
360 iLength = sizeof(listenaddr);
361 if (getsockname(m_ListenSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
363 // int iWinsockErr = WSAGetLastError();
364 m_State = FTP_STATE_SOCKET_ERROR;
368 // Extract the local port from the hListenSocket
369 nLocalPort = listenaddr.sin_port;
371 // Now, reuse the socket address structure to
372 // get the IP address from the control socket.
373 if (getsockname(m_ControlSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
375 // int iWinsockErr = WSAGetLastError();
376 m_State = FTP_STATE_SOCKET_ERROR;
380 // Format the PORT command with the correct numbers.
382 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
383 listenaddr.sin_addr.S_un.S_un_b.s_b1,
384 listenaddr.sin_addr.S_un.S_un_b.s_b2,
385 listenaddr.sin_addr.S_un.S_un_b.s_b3,
386 listenaddr.sin_addr.S_un.S_un_b.s_b4,
390 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
391 (listenaddr.sin_addr.s_addr >> 0) & 0x000000FF,
392 (listenaddr.sin_addr.s_addr >> 8) & 0x0000FF00,
393 (listenaddr.sin_addr.s_addr >> 16) & 0x00FF0000,
394 (listenaddr.sin_addr.s_addr >> 24) & 0xFF000000,
399 // Tell the server which port to use for data.
400 nReplyCode = SendFTPCommand(szCommandString);
401 if (nReplyCode != 200)
403 // int iWinsockErr = WSAGetLastError();
404 m_State = FTP_STATE_SOCKET_ERROR;
410 int CFtpGet::ConnectControlSocket()
414 SOCKADDR_IN hostaddr;
415 he = gethostbyname(m_szHost);
418 m_State = FTP_STATE_HOST_NOT_FOUND;
424 se = getservbyname("ftp", NULL);
428 hostaddr.sin_port = htons(21);
432 hostaddr.sin_port = se->s_port;
434 hostaddr.sin_family = AF_INET;
435 memcpy(&hostaddr.sin_addr,he->h_addr_list[0],4);
438 //Now we will connect to the host
439 if(connect(m_ControlSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR)))
441 // int iWinsockErr = WSAGetLastError();
442 m_State = FTP_STATE_CANT_CONNECT;
445 m_State = FTP_STATE_LOGGING_IN;
450 int CFtpGet::LoginHost()
452 char szLoginString[200];
455 sprintf(szLoginString,"USER %s\r\n",m_szUserName);
456 rcode = SendFTPCommand(szLoginString);
459 m_State = FTP_STATE_LOGIN_ERROR;
462 sprintf(szLoginString,"PASS %s\r\n",m_szPassword);
463 rcode = SendFTPCommand(szLoginString);
466 m_State = FTP_STATE_LOGIN_ERROR;
470 m_State = FTP_STATE_LOGGED_IN;
475 unsigned int CFtpGet::SendFTPCommand(char *command)
478 FlushControlChannel();
479 // Send the FTP command
480 if (SOCKET_ERROR ==(send(m_ControlSock,command,strlen(command), 0)))
482 // int iWinsockErr = WSAGetLastError();
483 // Return 999 to indicate an error has occurred
487 // Read the server's reply and return the reply code as an integer
488 return(ReadFTPServerReply());
493 unsigned int CFtpGet::ReadFTPServerReply()
496 unsigned int iBytesRead;
499 unsigned int igotcrlf = 0;
500 memset(recv_buffer,0,1000);
504 iBytesRead = recv(m_ControlSock,chunk,1,0);
506 if (iBytesRead == SOCKET_ERROR)
508 // int iWinsockErr = WSAGetLastError();
509 // Return 999 to indicate an error has occurred
513 if((chunk[0]==0x0a) || (chunk[0]==0x0d))
515 if(recv_buffer[0]!=0)
522 strcat(recv_buffer,chunk);
528 if(recv_buffer[3] == '-')
530 //Hack -- must be a MOTD
531 return ReadFTPServerReply();
533 if(recv_buffer[3] != ' ')
535 //We should have 3 numbers then a space
538 memcpy(szcode,recv_buffer,3);
540 rcode = atoi(szcode);
541 // Extract the reply code from the server reply and return as an integer
546 unsigned int CFtpGet::ReadDataChannel()
548 char sDataBuffer[4096]; // Data-storage buffer for the data channel
549 int nBytesRecv; // Bytes received from the data channel
550 m_State = FTP_STATE_RECEIVING;
557 nBytesRecv = recv(m_DataSock, (LPSTR)&sDataBuffer,sizeof(sDataBuffer), 0);
559 m_iBytesIn += nBytesRecv;
562 fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
563 //Write sDataBuffer, nBytesRecv
567 }while (nBytesRecv > 0);
569 // Close the file and check for error returns.
570 if (nBytesRecv == SOCKET_ERROR)
572 //Ok, we got a socket error -- xfer aborted?
573 m_State = FTP_STATE_RECV_FAILED;
579 m_State = FTP_STATE_FILE_RECEIVED;
585 void CFtpGet::FlushControlChannel()
595 FD_SET(m_ControlSock,&read_fds);
597 while(select(0,&read_fds,NULL,NULL,&timeout))
599 recv(m_ControlSock,flushbuff,1,0);