1#include "FtpClient.h"
2
3#include <stdlib.h>
4#include <string.h>
5
6#include <Catalog.h>
7#include <Locale.h>
8
9
10#undef B_TRANSLATION_CONTEXT
11#define B_TRANSLATION_CONTEXT "FtpClient"
12
13
14FtpClient::FtpClient()
15	: FileUploadClient(),
16	fState(0),
17	fControl(NULL),
18	fData(NULL)
19{
20}
21
22
23FtpClient::~FtpClient()
24{
25	delete fControl;
26	delete fData;
27}
28
29
30bool
31FtpClient::ChangeDir(const string& dir)
32{
33	bool rc = false;
34	int code, codeType;
35	string cmd = "CWD ", replyString;
36
37	cmd += dir;
38
39	if (dir.length() == 0)
40		cmd += '/';
41
42	if (_SendRequest(cmd) == true) {
43		if (_GetReply(replyString, code, codeType) == true) {
44			if (codeType == 2)
45				rc = true;
46		}
47	}
48	return rc;
49}
50
51
52bool
53FtpClient::ListDirContents(string& listing)
54{
55	bool rc = false;
56	string cmd, replyString;
57	int code, codeType, numRead;
58	char buf[513];
59
60	cmd = "TYPE A";
61
62	if (_SendRequest(cmd))
63		_GetReply(replyString, code, codeType);
64
65	if (_OpenDataConnection()) {
66		cmd = "LIST";
67
68		if (_SendRequest(cmd)) {
69			if (_GetReply(replyString, code, codeType)) {
70				if (codeType <= 2) {
71					if (_AcceptDataConnection()) {
72						numRead = 1;
73						while (numRead > 0) {
74							memset(buf, 0, sizeof(buf));
75							numRead = fData->Receive(buf, sizeof(buf) - 1);
76							listing += buf;
77							printf(buf);
78						}
79						if (_GetReply(replyString, code, codeType)) {
80							if (codeType <= 2)
81								rc = true;
82						}
83					}
84				}
85			}
86		}
87	}
88
89	delete fData;
90	fData = 0;
91
92	return rc;
93}
94
95
96bool
97FtpClient::PrintWorkingDir(string& dir)
98{
99	bool rc = false;
100	int code, codeType;
101	string cmd = "PWD", replyString;
102	long i;
103
104	if (_SendRequest(cmd) == true) {
105		if (_GetReply(replyString, code, codeType) == true) {
106			if (codeType == 2) {
107				i = replyString.find('"');
108				if (i != -1) {
109					i++;
110					dir = replyString.substr(i, replyString.find('"') - i);
111					rc = true;
112				}
113			}
114		}
115	}
116
117	return rc;
118}
119
120
121bool
122FtpClient::Connect(const string& server, const string& login, const string& passwd)
123{
124	bool rc = false;
125	int code, codeType;
126	string cmd, replyString;
127	BNetAddress addr;
128
129	delete fControl;
130	delete fData;
131
132	fControl = new BNetEndpoint;
133
134	if (fControl->InitCheck() != B_NO_ERROR)
135		return false;
136
137	addr.SetTo(server.c_str(), "tcp", "ftp");
138	if (fControl->Connect(addr) == B_NO_ERROR) {
139		// read the welcome message, do the login
140
141		if (_GetReply(replyString, code, codeType)) {
142			if (code != 421 && codeType != 5) {
143				cmd = "USER ";
144				cmd += login;
145				_SendRequest(cmd);
146
147				if (_GetReply(replyString, code, codeType)) {
148					switch (code) {
149						case 230:
150						case 202:
151							rc = true;
152							break;
153
154						case 331:  // password needed
155							cmd = "PASS ";
156							cmd += passwd;
157							_SendRequest(cmd);
158							if (_GetReply(replyString, code, codeType)) {
159								if (codeType == 2)
160									rc = true;
161							}
162							break;
163
164						default:
165							break;
166
167					}
168				}
169			}
170		}
171	}
172
173	if (rc == true)
174		_SetState(ftp_connected);
175	else {
176		delete fControl;
177		fControl = 0;
178	}
179
180	return rc;
181}
182
183
184bool
185FtpClient::PutFile(const string& local, const string& remote, ftp_mode mode)
186{
187	bool rc = false;
188	string cmd, replyString;
189	int code, codeType, rlen, slen, i;
190	BFile infile(local.c_str(), B_READ_ONLY);
191	char buf[8192], sbuf[16384], *stmp;
192
193	if (infile.InitCheck() != B_NO_ERROR)
194		return false;
195
196	if (mode == binary_mode)
197		cmd = "TYPE I";
198	else
199		cmd = "TYPE A";
200
201	if (_SendRequest(cmd))
202		_GetReply(replyString, code, codeType);
203
204	try {
205		if (_OpenDataConnection()) {
206			cmd = "STOR ";
207			cmd += remote;
208
209			if (_SendRequest(cmd)) {
210				if (_GetReply(replyString, code, codeType)) {
211					if (codeType <= 2) {
212						if (_AcceptDataConnection()) {
213							rlen = 1;
214							while (rlen > 0) {
215								memset(buf, 0, sizeof(buf));
216								memset(sbuf, 0, sizeof(sbuf));
217								rlen = infile.Read((void*)buf, sizeof(buf));
218								slen = rlen;
219								stmp = buf;
220								if (mode == ascii_mode) {
221									stmp = sbuf;
222									slen = 0;
223									for (i = 0; i < rlen; i++) {
224										if (buf[i] == '\n') {
225											*stmp = '\r';
226											stmp++;
227											slen++;
228										}
229										*stmp = buf[i];
230										stmp++;
231										slen++;
232									}
233									stmp = sbuf;
234								}
235								if (slen > 0) {
236									if (fData->Send(stmp, slen) < 0)
237										throw "bail";
238								}
239							}
240
241							rc = true;
242						}
243					}
244				}
245			}
246		}
247	}
248
249	catch(const char* errorString)
250	{
251	}
252
253	delete fData;
254	fData = 0;
255
256	if (rc) {
257		_GetReply(replyString, code, codeType);
258		rc = codeType <= 2;
259	}
260
261	return rc;
262}
263
264
265bool
266FtpClient::GetFile(const string& remote, const string& local, ftp_mode mode)
267{
268	bool rc = false;
269	string cmd, replyString;
270	int code, codeType, rlen, slen, i;
271	BFile outfile(local.c_str(), B_READ_WRITE | B_CREATE_FILE);
272	char buf[8192], sbuf[16384], *stmp;
273	bool writeError = false;
274
275	if (outfile.InitCheck() != B_NO_ERROR)
276		return false;
277
278	if (mode == binary_mode)
279		cmd = "TYPE I";
280	else
281		cmd = "TYPE A";
282
283	if (_SendRequest(cmd))
284		_GetReply(replyString, code, codeType);
285
286	if (_OpenDataConnection()) {
287		cmd = "RETR ";
288		cmd += remote;
289
290		if (_SendRequest(cmd)) {
291			if (_GetReply(replyString, code, codeType)) {
292				if (codeType <= 2) {
293					if (_AcceptDataConnection()) {
294						rlen = 1;
295						rc = true;
296						while (rlen > 0) {
297							memset(buf, 0, sizeof(buf));
298							memset(sbuf, 0, sizeof(sbuf));
299							rlen = fData->Receive(buf, sizeof(buf));
300
301							if (rlen > 0) {
302
303								slen = rlen;
304								stmp = buf;
305								if (mode == ascii_mode) {
306									stmp = sbuf;
307									slen = 0;
308									for (i = 0; i < rlen; i++) {
309										if (buf[i] == '\r')
310											i++;
311										*stmp = buf[i];
312										stmp++;
313										slen++;
314									}
315									stmp = sbuf;
316								}
317
318								if (slen > 0) {
319									if (outfile.Write(stmp, slen) < 0)
320										writeError = true;
321								}
322							}
323						}
324					}
325				}
326			}
327		}
328	}
329
330	delete fData;
331	fData = 0;
332
333	if (rc) {
334		_GetReply(replyString, code, codeType);
335		rc = (codeType <= 2 && writeError == false);
336	}
337	return rc;
338}
339
340
341// Note: this only works for local remote moves, cross filesystem moves
342// will not work
343bool
344FtpClient::MoveFile(const string& oldPath, const string& newPath)
345{
346	bool rc = false;
347	string from = "RNFR ";
348	string to = "RNTO ";
349	string  replyString;
350	int code, codeType;
351
352	from += oldPath;
353	to += newPath;
354
355	if (_SendRequest(from)) {
356		if (_GetReply(replyString, code, codeType)) {
357			if (codeType == 3) {
358				if (_SendRequest(to)) {
359					if (_GetReply(replyString, code, codeType)) {
360						if(codeType == 2)
361							rc = true;
362					}
363				}
364			}
365		}
366	}
367	return rc;
368}
369
370
371bool
372FtpClient::Chmod(const string& path, const string& mod)
373{
374	bool rc = false;
375	int code, codeType;
376	string cmd = "SITE CHMOD ", replyString;
377
378	cmd += mod;
379	cmd += " ";
380	cmd += path;
381
382	if (path.length() == 0)
383		cmd += '/';
384printf(B_TRANSLATE("cmd: '%s'\n"), cmd.c_str());
385	if (_SendRequest(cmd) == true) {
386		if (_GetReply(replyString, code, codeType) == true) {
387printf(B_TRANSLATE("reply: %d, %d\n"), code, codeType);
388			if (codeType == 2)
389				rc = true;
390		}
391	}
392	return rc;
393}
394
395
396void
397FtpClient::SetPassive(bool on)
398{
399	if (on)
400		_SetState(ftp_passive);
401	else
402		_ClearState(ftp_passive);
403}
404
405
406bool
407FtpClient::_TestState(unsigned long state)
408{
409	return ((fState & state) != 0);
410}
411
412
413void
414FtpClient::_SetState(unsigned long state)
415{
416	fState |= state;
417}
418
419
420void
421FtpClient::_ClearState(unsigned long state)
422{
423	fState &= ~state;
424}
425
426
427bool
428FtpClient::_SendRequest(const string& cmd)
429{
430	bool rc = false;
431	string ccmd = cmd;
432
433	if (fControl != 0) {
434		if (cmd.find("PASS") != string::npos)
435			printf(B_TRANSLATE("PASS <suppressed>  (real password sent)\n"));
436		else
437			printf("%s\n", ccmd.c_str());
438
439		ccmd += "\r\n";
440		if (fControl->Send(ccmd.c_str(), ccmd.length()) >= 0)
441			rc = true;
442	}
443
444	return rc;
445}
446
447
448bool
449FtpClient::_GetReplyLine(string& line)
450{
451	bool rc = false;
452	int c = 0;
453	bool done = false;
454
455	line = "";  // Thanks to Stephen van Egmond for catching a bug here
456
457	if (fControl != 0) {
458		rc = true;
459		while (done == false && fControl->Receive(&c, 1) > 0) {
460			if (c == EOF || c == xEOF || c == '\n') {
461				done = true;
462			} else {
463				if (c == IAC) {
464					fControl->Receive(&c, 1);
465					switch (c) {
466						unsigned char treply[3];
467						case WILL:
468						case WONT:
469							fControl->Receive(&c, 1);
470							treply[0] = IAC;
471							treply[1] = DONT;
472							treply[2] = c;
473							fControl->Send(treply, 3);
474						break;
475
476						case DO:
477						case DONT:
478							fControl->Receive(&c, 1);
479							fControl->Receive(&c, 1);
480							treply[0] = IAC;
481							treply[1] = WONT;
482							treply[2] = c;
483							fControl->Send(treply, 3);
484						break;
485
486						case EOF:
487						case xEOF:
488							done = true;
489						break;
490
491						default:
492							line += c;
493						break;
494					}
495				} else {
496					// normal char
497					if (c != '\r')
498						line += c;
499				}
500			}
501		}
502	}
503
504	return rc;
505}
506
507
508bool
509FtpClient::_GetReply(string& outString, int& outCode, int& codeType)
510{
511	bool rc = false;
512	string line, tempString;
513
514	//
515	// comment from the ncftp source:
516	//
517
518	/* RFC 959 states that a reply may span multiple lines.  A single
519	 * line message would have the 3-digit code <space> then the msg.
520	 * A multi-line message would have the code <dash> and the first
521	 * line of the msg, then additional lines, until the last line,
522	 * which has the code <space> and last line of the msg.
523	 *
524	 * For example:
525	 *	123-First line
526	 *	Second line
527	 *	234 A line beginning with numbers
528	 *	123 The last line
529	 */
530
531	if ((rc = _GetReplyLine(line)) == true) {
532		outString = line;
533		outString += '\n';
534		printf(outString.c_str());
535		tempString = line.substr(0, 3);
536		outCode = atoi(tempString.c_str());
537
538		if (line[3] == '-') {
539			while ((rc = _GetReplyLine(line)) == true) {
540				outString += line;
541				outString += '\n';
542				printf(outString.c_str());
543				// we're done with nnn when we get to a "nnn blahblahblah"
544				if ((line.find(tempString) == 0) && line[3] == ' ')
545					break;
546			}
547		}
548	}
549
550	if (!rc && outCode != 421) {
551		outString += B_TRANSLATE("Remote host has closed the connection.\n");
552		outCode = 421;
553	}
554
555	if (outCode == 421) {
556		delete fControl;
557		fControl = 0;
558		_ClearState(ftp_connected);
559	}
560
561	codeType = outCode / 100;
562
563	return rc;
564}
565
566
567bool
568FtpClient::_OpenDataConnection()
569{
570	string host, cmd, replyString;
571	unsigned short port;
572	BNetAddress addr;
573	int i, code, codeType;
574	bool rc = false;
575	struct sockaddr_in sa;
576
577	delete fData;
578	fData = 0;
579
580	fData = new BNetEndpoint;
581
582	if (_TestState(ftp_passive)) {
583		// Here we send a "pasv" command and connect to the remote server
584		// on the port it sends back to us
585		cmd = "PASV";
586		if (_SendRequest(cmd)) {
587			if (_GetReply(replyString, code, codeType)) {
588
589				if (codeType == 2) {
590					 //  It should give us something like:
591			 		 // "227 Entering Passive Mode (192,168,1,1,10,187)"
592					int paddr[6];
593					unsigned char ucaddr[6];
594
595					i = replyString.find('(');
596					i++;
597
598					replyString = replyString.substr(i, replyString.find(')') - i);
599					if (sscanf(replyString.c_str(), "%d,%d,%d,%d,%d,%d",
600						&paddr[0], &paddr[1], &paddr[2], &paddr[3],
601						&paddr[4], &paddr[5]) != 6) {
602						// cannot do passive.  Do a little harmless rercursion here
603						_ClearState(ftp_passive);
604						return _OpenDataConnection();
605						}
606
607					for (i = 0; i < 6; i++)
608						ucaddr[i] = (unsigned char)(paddr[i] & 0xff);
609
610					memcpy(&sa.sin_addr, &ucaddr[0], (size_t) 4);
611					memcpy(&sa.sin_port, &ucaddr[4], (size_t) 2);
612					addr.SetTo(sa);
613					if (fData->Connect(addr) == B_NO_ERROR)
614						rc = true;
615
616				}
617			}
618		} else {
619			// cannot do passive.  Do a little harmless rercursion here
620			_ClearState(ftp_passive);
621			rc = _OpenDataConnection();
622		}
623
624	} else {
625		// Here we bind to a local port and send a PORT command
626		if (fData->Bind() == B_NO_ERROR) {
627			char buf[255];
628
629			fData->Listen();
630			addr = fData->LocalAddr();
631			addr.GetAddr(buf, &port);
632			host = buf;
633
634			i = 0;
635			while (i >= 0) {
636				i = host.find('.', i);
637				if (i >= 0)
638					host[i] = ',';
639			}
640
641			sprintf(buf, ",%d,%d", (port & 0xff00) >> 8, port & 0x00ff);
642			cmd = "PORT ";
643			cmd += host;
644			cmd += buf;
645			_SendRequest(cmd);
646			_GetReply(replyString, code, codeType);
647			// PORT failure is in the 500-range
648			if (codeType == 2)
649				rc = true;
650		}
651	}
652
653	return rc;
654}
655
656
657bool
658FtpClient::_AcceptDataConnection()
659{
660	BNetEndpoint* endPoint;
661	bool rc = false;
662
663	if (_TestState(ftp_passive) == false) {
664		if (fData != 0) {
665			endPoint = fData->Accept();
666			if (endPoint != 0) {
667				delete fData;
668				fData = endPoint;
669				rc = true;
670			}
671		}
672
673	}
674	else
675		rc = true;
676
677	return rc;
678}
679