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