]> icculus.org git repositories - taylor/freespace2.git/blob - src/inetfile/cftp.cpp
MacOS X updates (Ryan)
[taylor/freespace2.git] / src / inetfile / cftp.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
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
6  * the source.
7  */
8
9  /*
10  * $Logfile: /Freespace2/code/Inetfile/CFtp.cpp $
11  * $Revision$
12  * $Date$
13  *  $Author$
14  *
15  * FTP Client class (get only)
16  *
17  * $Log$
18  * Revision 1.10  2002/06/19 04:52:45  relnev
19  * MacOS X updates (Ryan)
20  *
21  * Revision 1.9  2002/06/17 06:33:09  relnev
22  * ryan's struct patch for gcc 2.95
23  *
24  * Revision 1.8  2002/06/09 04:41:21  relnev
25  * added copyright header
26  *
27  * Revision 1.7  2002/06/02 04:26:34  relnev
28  * warning cleanup
29  *
30  * Revision 1.6  2002/05/26 21:06:44  relnev
31  * oops
32  *
33  * Revision 1.5  2002/05/26 20:32:24  theoddone33
34  * Fix some minor stuff
35  *
36  * Revision 1.4  2002/05/26 20:20:53  relnev
37  * unix.h: updated
38  *
39  * inetfile/: complete
40  *
41  * Revision 1.3  2002/05/26 19:55:20  relnev
42  * unix.h: winsock defines
43  *
44  * cftp.cpp: now compiles!
45  *
46  * Revision 1.2  2002/05/07 03:16:45  theoddone33
47  * The Great Newline Fix
48  *
49  * Revision 1.1.1.1  2002/05/03 03:28:09  root
50  * Initial import.
51  *
52  * 
53  * 3     5/04/99 7:34p Dave
54  * Fixed slow HTTP get problem.
55  * 
56  * 2     4/20/99 6:39p Dave
57  * Almost done with artillery targeting. Added support for downloading
58  * images on the PXO screen.
59  * 
60  * 1     4/20/99 4:37p Dave
61  * 
62  * Initial version
63  *
64  * $NoKeywords: $
65  */
66
67 #ifndef PLAT_UNIX
68 #include <windows.h>
69 #include <process.h>
70 #else
71 #include <sys/types.h>
72 #include <sys/socket.h>
73 #include <netinet/in.h>
74 #include <arpa/inet.h>
75 #include <netdb.h>
76 #include <sys/time.h>
77 #include <unistd.h>
78
79 #include "unix.h" // unix.h
80 #endif
81
82 #include <stdio.h>
83 #include <stdlib.h>
84 #include <string.h>
85
86 #include "pstypes.h"
87 #include "cftp.h"
88
89 void FTPObjThread( void * obj )
90 {
91         ((CFtpGet *)obj)->WorkerThread();
92 }
93
94 void CFtpGet::AbortGet()
95 {
96         m_Aborting = true;
97         while(!m_Aborted) ; //Wait for the thread to end
98         fclose(LOCALFILE);
99 }
100
101 CFtpGet::CFtpGet(char *URL,char *localfile,char *Username,char *Password)
102 {
103         SOCKADDR_IN listensockaddr;
104         m_State = FTP_STATE_STARTUP;
105
106         m_ListenSock = INVALID_SOCKET;
107         m_DataSock = INVALID_SOCKET;
108         m_ControlSock = INVALID_SOCKET;
109         m_iBytesIn = 0;
110         m_iBytesTotal = 0;
111         m_Aborting = false;
112         m_Aborted = false;
113
114         LOCALFILE = fopen(localfile,"wb");
115         if(NULL == LOCALFILE)
116         {
117                 m_State = FTP_STATE_CANT_WRITE_FILE;
118                 return;
119         }
120
121         if(Username)
122         {
123                 strcpy(m_szUserName,Username);
124         }
125         else
126         {
127                 strcpy(m_szUserName,"anonymous");
128         }
129         if(Password)
130         {
131                 strcpy(m_szPassword,Password);
132         }
133         else
134         {
135                 strcpy(m_szPassword,"pxouser@pxo.net");
136         }
137         m_ListenSock = socket(AF_INET, SOCK_STREAM, 0);
138         if(INVALID_SOCKET == m_ListenSock)
139         {
140                 // vint iWinsockErr = WSAGetLastError();
141                 m_State = FTP_STATE_SOCKET_ERROR;
142                 return;
143         }
144         else
145         {
146                 listensockaddr.sin_family = AF_INET;            
147                 listensockaddr.sin_port = 0;
148                 listensockaddr.sin_addr.s_addr = INADDR_ANY;
149                                                         
150                 // Bind the listen socket
151                 if (bind(m_ListenSock, (SOCKADDR *)&listensockaddr, sizeof(SOCKADDR)))
152                 {
153                         //Couldn't bind the socket
154                         // int iWinsockErr = WSAGetLastError();
155                         m_State = FTP_STATE_SOCKET_ERROR;
156                         return;
157                 }
158
159                 // Listen for the server connection
160                 if (listen(m_ListenSock, 1))    
161                 {
162                         //Couldn't listen on the socket
163                         // int iWinsockErr = WSAGetLastError();
164                         m_State = FTP_STATE_SOCKET_ERROR;
165                         return;
166                 }
167         }
168         m_ControlSock = socket(AF_INET, SOCK_STREAM, 0);
169         if(INVALID_SOCKET == m_ControlSock)
170         {
171                 m_State = FTP_STATE_SOCKET_ERROR;
172                 return;
173         }
174         //Parse the URL
175         //Get rid of any extra ftp:// stuff
176         char *pURL = URL;
177         if(_strnicmp(URL,"ftp:",4)==0)
178         {
179                 pURL +=4;
180                 while(*pURL == '/')
181                 {
182                         pURL++;
183                 }
184         }
185         //There shouldn't be any : in this string
186         if(strchr(pURL,':'))
187         {
188                 m_State = FTP_STATE_URL_PARSING_ERROR;
189                 return;
190         }
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--)
197         {
198                 if(pURL[i]== '/')
199                 {
200                         if(!filestart)
201                         {
202                                 filestart = pURL+i+1;
203                                 dirstart = pURL+i+1;
204                                 strcpy(m_szFilename,filestart);
205                         }
206                         else
207                         {
208                                 dirstart = pURL+i+1;
209                         }
210                 }
211         }
212         if((dirstart==NULL) || (filestart==NULL))
213         {
214                 m_State = FTP_STATE_URL_PARSING_ERROR;
215                 return;
216         }
217         else
218         {
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;
223         }
224         //At this point we should have a nice host,dir and filename
225         
226         //if(NULL==CreateThread(NULL,0,ObjThread,this,0,&m_dwThreadId))
227         if(NULL==_beginthread(FTPObjThread,0,this))
228         {
229                 m_State = FTP_STATE_INTERNAL_ERROR;
230                 return;
231         }
232         m_State = FTP_STATE_CONNECTING;
233 }
234
235
236
237 CFtpGet::~CFtpGet()
238 {
239         if(m_ListenSock != INVALID_SOCKET)
240         {
241                 shutdown(m_ListenSock,2);
242                 closesocket(m_ListenSock);
243         }
244         if(m_DataSock != INVALID_SOCKET)
245         {
246                 shutdown(m_DataSock,2);
247                 closesocket(m_DataSock);
248         }
249         if(m_ControlSock != INVALID_SOCKET)
250         {
251                 shutdown(m_ControlSock,2);
252                 closesocket(m_ControlSock);
253         }
254
255
256 }
257
258 //Returns a value to specify the status (ie. connecting/connected/transferring/done)
259 int CFtpGet::GetStatus()
260 {
261         return m_State;
262 }
263
264 unsigned int CFtpGet::GetBytesIn()
265 {
266         return m_iBytesIn;
267 }
268
269 unsigned int CFtpGet::GetTotalBytes()
270 {
271
272         return m_iBytesTotal;
273 }
274
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()
279 {
280         ConnectControlSocket();
281         if(m_State != FTP_STATE_LOGGING_IN)
282         {
283                 return;
284         }
285         LoginHost();
286         if(m_State != FTP_STATE_LOGGED_IN)
287         {
288                 return;
289         }
290         GetFile();
291
292         //We are all done now, and state has the current state.
293         m_Aborted = true;
294         
295
296 }
297
298 unsigned int CFtpGet::GetFile()
299 {
300         //Start off by changing into the proper dir.
301         char szCommandString[200];
302         int rcode;
303         
304         sprintf(szCommandString,"TYPE I\r\n");
305         rcode = SendFTPCommand(szCommandString);
306         if(rcode >=400)
307         {
308                 m_State = FTP_STATE_UNKNOWN_ERROR;      
309                 return 0;
310         }
311         if(m_Aborting)
312                 return 0;
313         if(m_szDir[0])
314         {
315                 sprintf(szCommandString,"CWD %s\r\n",m_szDir);
316                 rcode = SendFTPCommand(szCommandString);
317                 if(rcode >=400)
318                 {
319                         m_State = FTP_STATE_DIRECTORY_INVALID;  
320                         return 0;
321                 }
322         }
323         if(m_Aborting)
324                 return 0;
325         if(!IssuePort())
326         {
327                 m_State = FTP_STATE_UNKNOWN_ERROR;
328                 return 0;
329         }
330         if(m_Aborting)
331                 return 0;
332         sprintf(szCommandString,"RETR %s\r\n",m_szFilename);
333         rcode = SendFTPCommand(szCommandString);
334         if(rcode >=400)
335         {
336                 m_State = FTP_STATE_FILE_NOT_FOUND;     
337                 return 0;
338         }
339         if(m_Aborting)
340                 return 0;
341         //Now we will try to determine the file size...
342         char *p,*s;
343         p = strchr(recv_buffer,'(');
344         p++;
345         if(p)
346         {
347                 s = strchr(p,' ');
348                 *s = 0;
349                 m_iBytesTotal = atoi(p);
350         }
351         if(m_Aborting)
352                 return 0;
353
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)
358         {
359                 // int iWinsockErr = WSAGetLastError();
360                 m_State = FTP_STATE_SOCKET_ERROR;
361                 return 0;
362         }
363         if(m_Aborting)
364                 return 0;
365         ReadDataChannel();
366         
367         m_State = FTP_STATE_FILE_RECEIVED;
368         return 1;
369 }
370
371 unsigned int CFtpGet::IssuePort()
372 {
373
374         char szCommandString[200];
375         SOCKADDR_IN listenaddr;                                 // Socket address structure
376 #ifndef PLAT_UNIX       
377    int iLength;                                                                 // Length of the address structure
378 #else
379    socklen_t iLength;
380 #endif   
381    UINT nLocalPort;                                                     // Local port for listening
382         UINT nReplyCode;                                                        // FTP server reply code
383
384
385    // Get the address for the hListenSocket
386         iLength = sizeof(listenaddr);
387         if (getsockname(m_ListenSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
388         {
389                 // int iWinsockErr = WSAGetLastError();
390                 m_State = FTP_STATE_SOCKET_ERROR;
391                 return 0;
392         }
393
394         // Extract the local port from the hListenSocket
395         nLocalPort = listenaddr.sin_port;
396                                                         
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)
400         {
401                 // int iWinsockErr = WSAGetLastError();
402                 m_State = FTP_STATE_SOCKET_ERROR;
403                 return 0;
404         }
405                                 
406         // Format the PORT command with the correct numbers.
407 #ifndef PLAT_UNIX
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,
413                                 nLocalPort & 0xFF,      
414                                 nLocalPort >> 8);
415 #else
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,
421                                 nLocalPort & 0xFF,
422                                 nLocalPort >> 8);
423 #endif
424                                                                                                                 
425         // Tell the server which port to use for data.
426         nReplyCode = SendFTPCommand(szCommandString);
427         if (nReplyCode != 200)
428         {
429                 // int iWinsockErr = WSAGetLastError();
430                 m_State = FTP_STATE_SOCKET_ERROR;
431                 return 0;
432         }
433         return 1;
434 }
435
436 int CFtpGet::ConnectControlSocket()
437 {
438         HOSTENT *he;
439         SERVENT *se;
440         SOCKADDR_IN hostaddr;
441         he = gethostbyname(m_szHost);
442         if(he == NULL)
443         {
444                 m_State = FTP_STATE_HOST_NOT_FOUND;
445                 return 0;
446         }
447         //m_ControlSock
448         if(m_Aborting)
449                 return 0;
450         se = getservbyname("ftp", NULL);
451
452         if(se == NULL)
453         {
454                 hostaddr.sin_port = htons(21);
455         }
456         else
457         {
458                 hostaddr.sin_port = se->s_port;
459         }
460         hostaddr.sin_family = AF_INET;          
461         memcpy(&hostaddr.sin_addr,he->h_addr_list[0],4);
462         if(m_Aborting)
463                 return 0;
464         //Now we will connect to the host                                       
465         if(connect(m_ControlSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR)))
466         {
467                 // int iWinsockErr = WSAGetLastError();
468                 m_State = FTP_STATE_CANT_CONNECT;
469                 return 0;
470         }
471         m_State = FTP_STATE_LOGGING_IN;
472         return 1;
473 }
474
475
476 int CFtpGet::LoginHost()
477 {
478         char szLoginString[200];
479         int rcode;
480         
481         sprintf(szLoginString,"USER %s\r\n",m_szUserName);
482         rcode = SendFTPCommand(szLoginString);
483         if(rcode >=400)
484         {
485                 m_State = FTP_STATE_LOGIN_ERROR;        
486                 return 0;
487         }
488         sprintf(szLoginString,"PASS %s\r\n",m_szPassword);
489         rcode = SendFTPCommand(szLoginString);
490         if(rcode >=400)
491         {
492                 m_State = FTP_STATE_LOGIN_ERROR;        
493                 return 0;
494         }
495
496         m_State = FTP_STATE_LOGGED_IN;
497         return 1;
498 }
499
500
501 unsigned int CFtpGet::SendFTPCommand(char *command)
502 {
503
504         FlushControlChannel();
505         // Send the FTP command
506         if (SOCKET_ERROR ==(send(m_ControlSock,command,strlen(command), 0)))
507                 {
508                         // int iWinsockErr = WSAGetLastError();
509                   // Return 999 to indicate an error has occurred
510                         return(999);
511                 } 
512                 
513         // Read the server's reply and return the reply code as an integer
514         return(ReadFTPServerReply());               
515 }       
516
517
518
519 unsigned int CFtpGet::ReadFTPServerReply()
520 {
521         unsigned int rcode;
522         int iBytesRead;
523         char chunk[2];
524         char szcode[5];
525         unsigned int igotcrlf = 0;
526         memset(recv_buffer,0,1000);
527         do
528         {
529                 chunk[0]=0;
530                 iBytesRead = recv(m_ControlSock,chunk,1,0);
531
532                 if (iBytesRead == SOCKET_ERROR)
533                 {
534                         // int iWinsockErr = WSAGetLastError();
535                   // Return 999 to indicate an error has occurred
536                         return(999);
537                 }
538                 
539                 if((chunk[0]==0x0a) || (chunk[0]==0x0d))
540                 {
541                         if(recv_buffer[0]!=0) 
542                         {
543                                 igotcrlf = 1;   
544                         }
545                 }
546                 else
547                 {       chunk[1] = 0;
548                         strcat(recv_buffer,chunk);
549                 }
550                 
551                 Sleep(1);       
552         }while(igotcrlf==0);
553                                         
554         if(recv_buffer[3] == '-')
555         {
556                 //Hack -- must be a MOTD
557                 return ReadFTPServerReply();
558         }
559         if(recv_buffer[3] != ' ')
560         {
561                 //We should have 3 numbers then a space
562                 return 999;
563         }
564         memcpy(szcode,recv_buffer,3);
565         szcode[3] = 0;
566         rcode = atoi(szcode);
567     // Extract the reply code from the server reply and return as an integer
568         return(rcode);              
569 }       
570
571
572 unsigned int CFtpGet::ReadDataChannel()
573 {
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;                  
577    if(m_Aborting)
578                 return 0;
579         do      
580    {
581                 if(m_Aborting)
582                         return 0;
583                 nBytesRecv = recv(m_DataSock, (LPSTR)&sDataBuffer,sizeof(sDataBuffer), 0);
584                                         
585                 m_iBytesIn += nBytesRecv;
586                 if (nBytesRecv > 0 )
587                 {
588                         fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
589                         //Write sDataBuffer, nBytesRecv
590         }
591
592                 Sleep(1);
593         }while (nBytesRecv > 0);
594         fclose(LOCALFILE);                                                      
595         // Close the file and check for error returns.
596         if (nBytesRecv == SOCKET_ERROR)
597         { 
598                 //Ok, we got a socket error -- xfer aborted?
599                 m_State = FTP_STATE_RECV_FAILED;
600                 return 0;
601         }
602         else
603         {
604                 //done!
605                 m_State = FTP_STATE_FILE_RECEIVED;
606                 return 1;
607         }
608 }       
609
610
611 void CFtpGet::FlushControlChannel()
612 {
613         fd_set read_fds;                   
614         TIMEVAL timeout;        
615         char flushbuff[3];
616
617         timeout.tv_sec=0;            
618         timeout.tv_usec=0;
619         
620         FD_ZERO(&read_fds);
621         FD_SET(m_ControlSock,&read_fds);    
622         
623         while(select(0,&read_fds,NULL,NULL,&timeout))
624         {
625                 recv(m_ControlSock,flushbuff,1,0);
626
627                 Sleep(1);
628         }
629 }