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