xref: /haiku/src/apps/codycam/FtpClient.cpp (revision 6b48b59dafd86493851cb1dd764e034424ce5d9d)
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 
FtpClient()21 FtpClient::FtpClient()
22 	:
23 	FileUploadClient(),
24 	fState(0),
25 	fControl(NULL),
26 	fData(NULL)
27 {
28 }
29 
30 
~FtpClient()31 FtpClient::~FtpClient()
32 {
33 	delete fControl;
34 	delete fData;
35 }
36 
37 
38 bool
ChangeDir(const string & dir)39 FtpClient::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 
61 bool
ListDirContents(string & listing)62 FtpClient::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 
105 bool
PrintWorkingDir(string & dir)106 FtpClient::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 
131 bool
Connect(const string & server,const string & login,const string & passwd)132 FtpClient::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 
195 bool
PutFile(const string & local,const string & remote,ftp_mode mode)196 FtpClient::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 
278 bool
GetFile(const string & remote,const string & local,ftp_mode mode)279 FtpClient::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
358 bool
MoveFile(const string & oldPath,const string & newPath)359 FtpClient::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 
386 bool
Chmod(const string & path,const string & mod)387 FtpClient::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 
413 void
SetPassive(bool on)414 FtpClient::SetPassive(bool on)
415 {
416 	if (on)
417 		_SetState(ftp_passive);
418 	else
419 		_ClearState(ftp_passive);
420 }
421 
422 
423 bool
_TestState(unsigned long state)424 FtpClient::_TestState(unsigned long state)
425 {
426 	return ((fState & state) != 0);
427 }
428 
429 
430 void
_SetState(unsigned long state)431 FtpClient::_SetState(unsigned long state)
432 {
433 	fState |= state;
434 }
435 
436 
437 void
_ClearState(unsigned long state)438 FtpClient::_ClearState(unsigned long state)
439 {
440 	fState &= ~state;
441 }
442 
443 
444 bool
_SendRequest(const string & cmd)445 FtpClient::_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 
466 bool
_GetReplyLine(string & line)467 FtpClient::_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 
527 bool
_GetReply(string & outString,int & outCode,int & codeType)528 FtpClient::_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 
590 bool
_OpenDataConnection()591 FtpClient::_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 
682 bool
_AcceptDataConnection()683 FtpClient::_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