2 * $Logfile: /Freespace2/code/Inetfile/CFtp.cpp $
7 * FTP Client class (get only)
10 * Revision 1.6 2002/05/26 21:06:44 relnev
13 * Revision 1.5 2002/05/26 20:32:24 theoddone33
14 * Fix some minor stuff
16 * Revision 1.4 2002/05/26 20:20:53 relnev
21 * Revision 1.3 2002/05/26 19:55:20 relnev
22 * unix.h: winsock defines
24 * cftp.cpp: now compiles!
26 * Revision 1.2 2002/05/07 03:16:45 theoddone33
27 * The Great Newline Fix
29 * Revision 1.1.1.1 2002/05/03 03:28:09 root
33 * 3 5/04/99 7:34p Dave
34 * Fixed slow HTTP get problem.
36 * 2 4/20/99 6:39p Dave
37 * Almost done with artillery targeting. Added support for downloading
38 * images on the PXO screen.
40 * 1 4/20/99 4:37p Dave
51 #include <sys/types.h>
52 #include <sys/socket.h>
53 #include <netinet/in.h>
54 #include <arpa/inet.h>
57 #include "unix.h" // unix.h
66 void FTPObjThread( void * obj )
68 ((CFtpGet *)obj)->WorkerThread();
71 void CFtpGet::AbortGet()
74 while(!m_Aborted) ; //Wait for the thread to end
78 CFtpGet::CFtpGet(char *URL,char *localfile,char *Username,char *Password)
80 SOCKADDR_IN listensockaddr;
81 m_State = FTP_STATE_STARTUP;
83 m_ListenSock = INVALID_SOCKET;
84 m_DataSock = INVALID_SOCKET;
85 m_ControlSock = INVALID_SOCKET;
91 LOCALFILE = fopen(localfile,"wb");
94 m_State = FTP_STATE_CANT_WRITE_FILE;
100 strcpy(m_szUserName,Username);
104 strcpy(m_szUserName,"anonymous");
108 strcpy(m_szPassword,Password);
112 strcpy(m_szPassword,"pxouser@pxo.net");
114 m_ListenSock = socket(AF_INET, SOCK_STREAM, 0);
115 if(INVALID_SOCKET == m_ListenSock)
117 // vint iWinsockErr = WSAGetLastError();
118 m_State = FTP_STATE_SOCKET_ERROR;
123 listensockaddr.sin_family = AF_INET;
124 listensockaddr.sin_port = 0;
125 listensockaddr.sin_addr.s_addr = INADDR_ANY;
127 // Bind the listen socket
128 if (bind(m_ListenSock, (SOCKADDR *)&listensockaddr, sizeof(SOCKADDR)))
130 //Couldn't bind the socket
131 // int iWinsockErr = WSAGetLastError();
132 m_State = FTP_STATE_SOCKET_ERROR;
136 // Listen for the server connection
137 if (listen(m_ListenSock, 1))
139 //Couldn't listen on the socket
140 // int iWinsockErr = WSAGetLastError();
141 m_State = FTP_STATE_SOCKET_ERROR;
145 m_ControlSock = socket(AF_INET, SOCK_STREAM, 0);
146 if(INVALID_SOCKET == m_ControlSock)
148 m_State = FTP_STATE_SOCKET_ERROR;
152 //Get rid of any extra ftp:// stuff
154 if(_strnicmp(URL,"ftp:",4)==0)
162 //There shouldn't be any : in this string
165 m_State = FTP_STATE_URL_PARSING_ERROR;
168 //read the filename by searching backwards for a /
169 //then keep reading until you find the first /
170 //when you found it, you have the host and dir
171 char *filestart = NULL;
172 char *dirstart = NULL;
173 for(int i = strlen(pURL);i>=0;i--)
179 filestart = pURL+i+1;
181 strcpy(m_szFilename,filestart);
189 if((dirstart==NULL) || (filestart==NULL))
191 m_State = FTP_STATE_URL_PARSING_ERROR;
196 strncpy(m_szDir,dirstart,(filestart-dirstart));
197 m_szDir[(filestart-dirstart)] = NULL;
198 strncpy(m_szHost,pURL,(dirstart-pURL));
199 m_szHost[(dirstart-pURL)-1] = NULL;
201 //At this point we should have a nice host,dir and filename
203 //if(NULL==CreateThread(NULL,0,ObjThread,this,0,&m_dwThreadId))
204 if(NULL==_beginthread(FTPObjThread,0,this))
206 m_State = FTP_STATE_INTERNAL_ERROR;
209 m_State = FTP_STATE_CONNECTING;
216 if(m_ListenSock != INVALID_SOCKET)
218 shutdown(m_ListenSock,2);
219 closesocket(m_ListenSock);
221 if(m_DataSock != INVALID_SOCKET)
223 shutdown(m_DataSock,2);
224 closesocket(m_DataSock);
226 if(m_ControlSock != INVALID_SOCKET)
228 shutdown(m_ControlSock,2);
229 closesocket(m_ControlSock);
235 //Returns a value to specify the status (ie. connecting/connected/transferring/done)
236 int CFtpGet::GetStatus()
241 unsigned int CFtpGet::GetBytesIn()
246 unsigned int CFtpGet::GetTotalBytes()
249 return m_iBytesTotal;
252 //This function does all the work -- connects on a blocking socket
253 //then sends the appropriate user and password commands
254 //and then the cwd command, the port command then get and finally the quit
255 void CFtpGet::WorkerThread()
257 ConnectControlSocket();
258 if(m_State != FTP_STATE_LOGGING_IN)
263 if(m_State != FTP_STATE_LOGGED_IN)
269 //We are all done now, and state has the current state.
275 unsigned int CFtpGet::GetFile()
277 //Start off by changing into the proper dir.
278 char szCommandString[200];
281 sprintf(szCommandString,"TYPE I\r\n");
282 rcode = SendFTPCommand(szCommandString);
285 m_State = FTP_STATE_UNKNOWN_ERROR;
292 sprintf(szCommandString,"CWD %s\r\n",m_szDir);
293 rcode = SendFTPCommand(szCommandString);
296 m_State = FTP_STATE_DIRECTORY_INVALID;
304 m_State = FTP_STATE_UNKNOWN_ERROR;
309 sprintf(szCommandString,"RETR %s\r\n",m_szFilename);
310 rcode = SendFTPCommand(szCommandString);
313 m_State = FTP_STATE_FILE_NOT_FOUND;
318 //Now we will try to determine the file size...
320 p = strchr(recv_buffer,'(');
326 m_iBytesTotal = atoi(p);
331 m_DataSock = accept(m_ListenSock, NULL,NULL);//(SOCKADDR *)&sockaddr,&iAddrLength);
332 // Close the listen socket
333 closesocket(m_ListenSock);
334 if (m_DataSock == INVALID_SOCKET)
336 // int iWinsockErr = WSAGetLastError();
337 m_State = FTP_STATE_SOCKET_ERROR;
344 m_State = FTP_STATE_FILE_RECEIVED;
348 unsigned int CFtpGet::IssuePort()
351 char szCommandString[200];
352 SOCKADDR_IN listenaddr; // Socket address structure
354 int iLength; // Length of the address structure
358 UINT nLocalPort; // Local port for listening
359 UINT nReplyCode; // FTP server reply code
362 // Get the address for the hListenSocket
363 iLength = sizeof(listenaddr);
364 if (getsockname(m_ListenSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
366 // int iWinsockErr = WSAGetLastError();
367 m_State = FTP_STATE_SOCKET_ERROR;
371 // Extract the local port from the hListenSocket
372 nLocalPort = listenaddr.sin_port;
374 // Now, reuse the socket address structure to
375 // get the IP address from the control socket.
376 if (getsockname(m_ControlSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
378 // int iWinsockErr = WSAGetLastError();
379 m_State = FTP_STATE_SOCKET_ERROR;
383 // Format the PORT command with the correct numbers.
385 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
386 listenaddr.sin_addr.S_un.S_un_b.s_b1,
387 listenaddr.sin_addr.S_un.S_un_b.s_b2,
388 listenaddr.sin_addr.S_un.S_un_b.s_b3,
389 listenaddr.sin_addr.S_un.S_un_b.s_b4,
393 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
394 (listenaddr.sin_addr.s_addr >> 0) & 0xFF,
395 (listenaddr.sin_addr.s_addr >> 8) & 0xFF,
396 (listenaddr.sin_addr.s_addr >> 16) & 0xFF,
397 (listenaddr.sin_addr.s_addr >> 24) & 0xFF,
402 // Tell the server which port to use for data.
403 nReplyCode = SendFTPCommand(szCommandString);
404 if (nReplyCode != 200)
406 // int iWinsockErr = WSAGetLastError();
407 m_State = FTP_STATE_SOCKET_ERROR;
413 int CFtpGet::ConnectControlSocket()
417 SOCKADDR_IN hostaddr;
418 he = gethostbyname(m_szHost);
421 m_State = FTP_STATE_HOST_NOT_FOUND;
427 se = getservbyname("ftp", NULL);
431 hostaddr.sin_port = htons(21);
435 hostaddr.sin_port = se->s_port;
437 hostaddr.sin_family = AF_INET;
438 memcpy(&hostaddr.sin_addr,he->h_addr_list[0],4);
441 //Now we will connect to the host
442 if(connect(m_ControlSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR)))
444 // int iWinsockErr = WSAGetLastError();
445 m_State = FTP_STATE_CANT_CONNECT;
448 m_State = FTP_STATE_LOGGING_IN;
453 int CFtpGet::LoginHost()
455 char szLoginString[200];
458 sprintf(szLoginString,"USER %s\r\n",m_szUserName);
459 rcode = SendFTPCommand(szLoginString);
462 m_State = FTP_STATE_LOGIN_ERROR;
465 sprintf(szLoginString,"PASS %s\r\n",m_szPassword);
466 rcode = SendFTPCommand(szLoginString);
469 m_State = FTP_STATE_LOGIN_ERROR;
473 m_State = FTP_STATE_LOGGED_IN;
478 unsigned int CFtpGet::SendFTPCommand(char *command)
481 FlushControlChannel();
482 // Send the FTP command
483 if (SOCKET_ERROR ==(send(m_ControlSock,command,strlen(command), 0)))
485 // int iWinsockErr = WSAGetLastError();
486 // Return 999 to indicate an error has occurred
490 // Read the server's reply and return the reply code as an integer
491 return(ReadFTPServerReply());
496 unsigned int CFtpGet::ReadFTPServerReply()
499 unsigned int iBytesRead;
502 unsigned int igotcrlf = 0;
503 memset(recv_buffer,0,1000);
507 iBytesRead = recv(m_ControlSock,chunk,1,0);
509 if (iBytesRead == SOCKET_ERROR)
511 // int iWinsockErr = WSAGetLastError();
512 // Return 999 to indicate an error has occurred
516 if((chunk[0]==0x0a) || (chunk[0]==0x0d))
518 if(recv_buffer[0]!=0)
525 strcat(recv_buffer,chunk);
531 if(recv_buffer[3] == '-')
533 //Hack -- must be a MOTD
534 return ReadFTPServerReply();
536 if(recv_buffer[3] != ' ')
538 //We should have 3 numbers then a space
541 memcpy(szcode,recv_buffer,3);
543 rcode = atoi(szcode);
544 // Extract the reply code from the server reply and return as an integer
549 unsigned int CFtpGet::ReadDataChannel()
551 char sDataBuffer[4096]; // Data-storage buffer for the data channel
552 int nBytesRecv; // Bytes received from the data channel
553 m_State = FTP_STATE_RECEIVING;
560 nBytesRecv = recv(m_DataSock, (LPSTR)&sDataBuffer,sizeof(sDataBuffer), 0);
562 m_iBytesIn += nBytesRecv;
565 fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
566 //Write sDataBuffer, nBytesRecv
570 }while (nBytesRecv > 0);
572 // Close the file and check for error returns.
573 if (nBytesRecv == SOCKET_ERROR)
575 //Ok, we got a socket error -- xfer aborted?
576 m_State = FTP_STATE_RECV_FAILED;
582 m_State = FTP_STATE_FILE_RECEIVED;
588 void CFtpGet::FlushControlChannel()
598 FD_SET(m_ControlSock,&read_fds);
600 while(select(0,&read_fds,NULL,NULL,&timeout))
602 recv(m_ControlSock,flushbuff,1,0);