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