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