2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Inetfile/CFtp.cpp $
15 * FTP Client class (get only)
18 * Revision 1.10 2002/06/19 04:52:45 relnev
19 * MacOS X updates (Ryan)
21 * Revision 1.9 2002/06/17 06:33:09 relnev
22 * ryan's struct patch for gcc 2.95
24 * Revision 1.8 2002/06/09 04:41:21 relnev
25 * added copyright header
27 * Revision 1.7 2002/06/02 04:26:34 relnev
30 * Revision 1.6 2002/05/26 21:06:44 relnev
33 * Revision 1.5 2002/05/26 20:32:24 theoddone33
34 * Fix some minor stuff
36 * Revision 1.4 2002/05/26 20:20:53 relnev
41 * Revision 1.3 2002/05/26 19:55:20 relnev
42 * unix.h: winsock defines
44 * cftp.cpp: now compiles!
46 * Revision 1.2 2002/05/07 03:16:45 theoddone33
47 * The Great Newline Fix
49 * Revision 1.1.1.1 2002/05/03 03:28:09 root
53 * 3 5/04/99 7:34p Dave
54 * Fixed slow HTTP get problem.
56 * 2 4/20/99 6:39p Dave
57 * Almost done with artillery targeting. Added support for downloading
58 * images on the PXO screen.
60 * 1 4/20/99 4:37p Dave
71 #include <sys/types.h>
72 #include <sys/socket.h>
73 #include <netinet/in.h>
74 #include <arpa/inet.h>
79 #include "unix.h" // unix.h
89 void FTPObjThread( void * obj )
91 ((CFtpGet *)obj)->WorkerThread();
94 void CFtpGet::AbortGet()
97 while(!m_Aborted) ; //Wait for the thread to end
101 CFtpGet::CFtpGet(char *URL,char *localfile,char *Username,char *Password)
103 SOCKADDR_IN listensockaddr;
104 m_State = FTP_STATE_STARTUP;
106 m_ListenSock = INVALID_SOCKET;
107 m_DataSock = INVALID_SOCKET;
108 m_ControlSock = INVALID_SOCKET;
114 LOCALFILE = fopen(localfile,"wb");
115 if(NULL == LOCALFILE)
117 m_State = FTP_STATE_CANT_WRITE_FILE;
123 strcpy(m_szUserName,Username);
127 strcpy(m_szUserName,"anonymous");
131 strcpy(m_szPassword,Password);
135 strcpy(m_szPassword,"pxouser@pxo.net");
137 m_ListenSock = socket(AF_INET, SOCK_STREAM, 0);
138 if(INVALID_SOCKET == m_ListenSock)
140 // vint iWinsockErr = WSAGetLastError();
141 m_State = FTP_STATE_SOCKET_ERROR;
146 listensockaddr.sin_family = AF_INET;
147 listensockaddr.sin_port = 0;
148 listensockaddr.sin_addr.s_addr = INADDR_ANY;
150 // Bind the listen socket
151 if (bind(m_ListenSock, (SOCKADDR *)&listensockaddr, sizeof(SOCKADDR)))
153 //Couldn't bind the socket
154 // int iWinsockErr = WSAGetLastError();
155 m_State = FTP_STATE_SOCKET_ERROR;
159 // Listen for the server connection
160 if (listen(m_ListenSock, 1))
162 //Couldn't listen on the socket
163 // int iWinsockErr = WSAGetLastError();
164 m_State = FTP_STATE_SOCKET_ERROR;
168 m_ControlSock = socket(AF_INET, SOCK_STREAM, 0);
169 if(INVALID_SOCKET == m_ControlSock)
171 m_State = FTP_STATE_SOCKET_ERROR;
175 //Get rid of any extra ftp:// stuff
177 if(_strnicmp(URL,"ftp:",4)==0)
185 //There shouldn't be any : in this string
188 m_State = FTP_STATE_URL_PARSING_ERROR;
191 //read the filename by searching backwards for a /
192 //then keep reading until you find the first /
193 //when you found it, you have the host and dir
194 char *filestart = NULL;
195 char *dirstart = NULL;
196 for(int i = strlen(pURL);i>=0;i--)
202 filestart = pURL+i+1;
204 strcpy(m_szFilename,filestart);
212 if((dirstart==NULL) || (filestart==NULL))
214 m_State = FTP_STATE_URL_PARSING_ERROR;
219 strncpy(m_szDir,dirstart,(filestart-dirstart));
220 m_szDir[(filestart-dirstart)] = 0;
221 strncpy(m_szHost,pURL,(dirstart-pURL));
222 m_szHost[(dirstart-pURL)-1] = 0;
224 //At this point we should have a nice host,dir and filename
226 //if(NULL==CreateThread(NULL,0,ObjThread,this,0,&m_dwThreadId))
227 if(NULL==_beginthread(FTPObjThread,0,this))
229 m_State = FTP_STATE_INTERNAL_ERROR;
232 m_State = FTP_STATE_CONNECTING;
239 if(m_ListenSock != INVALID_SOCKET)
241 shutdown(m_ListenSock,2);
242 closesocket(m_ListenSock);
244 if(m_DataSock != INVALID_SOCKET)
246 shutdown(m_DataSock,2);
247 closesocket(m_DataSock);
249 if(m_ControlSock != INVALID_SOCKET)
251 shutdown(m_ControlSock,2);
252 closesocket(m_ControlSock);
258 //Returns a value to specify the status (ie. connecting/connected/transferring/done)
259 int CFtpGet::GetStatus()
264 unsigned int CFtpGet::GetBytesIn()
269 unsigned int CFtpGet::GetTotalBytes()
272 return m_iBytesTotal;
275 //This function does all the work -- connects on a blocking socket
276 //then sends the appropriate user and password commands
277 //and then the cwd command, the port command then get and finally the quit
278 void CFtpGet::WorkerThread()
280 ConnectControlSocket();
281 if(m_State != FTP_STATE_LOGGING_IN)
286 if(m_State != FTP_STATE_LOGGED_IN)
292 //We are all done now, and state has the current state.
298 unsigned int CFtpGet::GetFile()
300 //Start off by changing into the proper dir.
301 char szCommandString[200];
304 sprintf(szCommandString,"TYPE I\r\n");
305 rcode = SendFTPCommand(szCommandString);
308 m_State = FTP_STATE_UNKNOWN_ERROR;
315 sprintf(szCommandString,"CWD %s\r\n",m_szDir);
316 rcode = SendFTPCommand(szCommandString);
319 m_State = FTP_STATE_DIRECTORY_INVALID;
327 m_State = FTP_STATE_UNKNOWN_ERROR;
332 sprintf(szCommandString,"RETR %s\r\n",m_szFilename);
333 rcode = SendFTPCommand(szCommandString);
336 m_State = FTP_STATE_FILE_NOT_FOUND;
341 //Now we will try to determine the file size...
343 p = strchr(recv_buffer,'(');
349 m_iBytesTotal = atoi(p);
354 m_DataSock = accept(m_ListenSock, NULL,NULL);//(SOCKADDR *)&sockaddr,&iAddrLength);
355 // Close the listen socket
356 closesocket(m_ListenSock);
357 if (m_DataSock == INVALID_SOCKET)
359 // int iWinsockErr = WSAGetLastError();
360 m_State = FTP_STATE_SOCKET_ERROR;
367 m_State = FTP_STATE_FILE_RECEIVED;
371 unsigned int CFtpGet::IssuePort()
374 char szCommandString[200];
375 SOCKADDR_IN listenaddr; // Socket address structure
377 int iLength; // Length of the address structure
381 UINT nLocalPort; // Local port for listening
382 UINT nReplyCode; // FTP server reply code
385 // Get the address for the hListenSocket
386 iLength = sizeof(listenaddr);
387 if (getsockname(m_ListenSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
389 // int iWinsockErr = WSAGetLastError();
390 m_State = FTP_STATE_SOCKET_ERROR;
394 // Extract the local port from the hListenSocket
395 nLocalPort = listenaddr.sin_port;
397 // Now, reuse the socket address structure to
398 // get the IP address from the control socket.
399 if (getsockname(m_ControlSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
401 // int iWinsockErr = WSAGetLastError();
402 m_State = FTP_STATE_SOCKET_ERROR;
406 // Format the PORT command with the correct numbers.
408 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
409 listenaddr.sin_addr.S_un.S_un_b.s_b1,
410 listenaddr.sin_addr.S_un.S_un_b.s_b2,
411 listenaddr.sin_addr.S_un.S_un_b.s_b3,
412 listenaddr.sin_addr.S_un.S_un_b.s_b4,
416 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
417 (listenaddr.sin_addr.s_addr >> 0) & 0xFF,
418 (listenaddr.sin_addr.s_addr >> 8) & 0xFF,
419 (listenaddr.sin_addr.s_addr >> 16) & 0xFF,
420 (listenaddr.sin_addr.s_addr >> 24) & 0xFF,
425 // Tell the server which port to use for data.
426 nReplyCode = SendFTPCommand(szCommandString);
427 if (nReplyCode != 200)
429 // int iWinsockErr = WSAGetLastError();
430 m_State = FTP_STATE_SOCKET_ERROR;
436 int CFtpGet::ConnectControlSocket()
440 SOCKADDR_IN hostaddr;
441 he = gethostbyname(m_szHost);
444 m_State = FTP_STATE_HOST_NOT_FOUND;
450 se = getservbyname("ftp", NULL);
454 hostaddr.sin_port = htons(21);
458 hostaddr.sin_port = se->s_port;
460 hostaddr.sin_family = AF_INET;
461 memcpy(&hostaddr.sin_addr,he->h_addr_list[0],4);
464 //Now we will connect to the host
465 if(connect(m_ControlSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR)))
467 // int iWinsockErr = WSAGetLastError();
468 m_State = FTP_STATE_CANT_CONNECT;
471 m_State = FTP_STATE_LOGGING_IN;
476 int CFtpGet::LoginHost()
478 char szLoginString[200];
481 sprintf(szLoginString,"USER %s\r\n",m_szUserName);
482 rcode = SendFTPCommand(szLoginString);
485 m_State = FTP_STATE_LOGIN_ERROR;
488 sprintf(szLoginString,"PASS %s\r\n",m_szPassword);
489 rcode = SendFTPCommand(szLoginString);
492 m_State = FTP_STATE_LOGIN_ERROR;
496 m_State = FTP_STATE_LOGGED_IN;
501 unsigned int CFtpGet::SendFTPCommand(char *command)
504 FlushControlChannel();
505 // Send the FTP command
506 if (SOCKET_ERROR ==(send(m_ControlSock,command,strlen(command), 0)))
508 // int iWinsockErr = WSAGetLastError();
509 // Return 999 to indicate an error has occurred
513 // Read the server's reply and return the reply code as an integer
514 return(ReadFTPServerReply());
519 unsigned int CFtpGet::ReadFTPServerReply()
525 unsigned int igotcrlf = 0;
526 memset(recv_buffer,0,1000);
530 iBytesRead = recv(m_ControlSock,chunk,1,0);
532 if (iBytesRead == SOCKET_ERROR)
534 // int iWinsockErr = WSAGetLastError();
535 // Return 999 to indicate an error has occurred
539 if((chunk[0]==0x0a) || (chunk[0]==0x0d))
541 if(recv_buffer[0]!=0)
548 strcat(recv_buffer,chunk);
554 if(recv_buffer[3] == '-')
556 //Hack -- must be a MOTD
557 return ReadFTPServerReply();
559 if(recv_buffer[3] != ' ')
561 //We should have 3 numbers then a space
564 memcpy(szcode,recv_buffer,3);
566 rcode = atoi(szcode);
567 // Extract the reply code from the server reply and return as an integer
572 unsigned int CFtpGet::ReadDataChannel()
574 char sDataBuffer[4096]; // Data-storage buffer for the data channel
575 int nBytesRecv; // Bytes received from the data channel
576 m_State = FTP_STATE_RECEIVING;
583 nBytesRecv = recv(m_DataSock, (LPSTR)&sDataBuffer,sizeof(sDataBuffer), 0);
585 m_iBytesIn += nBytesRecv;
588 fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
589 //Write sDataBuffer, nBytesRecv
593 }while (nBytesRecv > 0);
595 // Close the file and check for error returns.
596 if (nBytesRecv == SOCKET_ERROR)
598 //Ok, we got a socket error -- xfer aborted?
599 m_State = FTP_STATE_RECV_FAILED;
605 m_State = FTP_STATE_FILE_RECEIVED;
611 void CFtpGet::FlushControlChannel()
621 FD_SET(m_ControlSock,&read_fds);
623 while(select(0,&read_fds,NULL,NULL,&timeout))
625 recv(m_ControlSock,flushbuff,1,0);