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