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