xref: /haiku/src/apps/codycam/FtpClient.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
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 
21 FtpClient::FtpClient()
22 	:
23 	FileUploadClient(),
24 	fState(0),
25 	fControl(NULL),
26 	fData(NULL)
27 {
28 }
29 
30 
31 FtpClient::~FtpClient()
32 {
33 	delete fControl;
34 	delete fData;
35 }
36 
37 
38 bool
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
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
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
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
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
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
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
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
414 FtpClient::SetPassive(bool on)
415 {
416 	if (on)
417 		_SetState(ftp_passive);
418 	else
419 		_ClearState(ftp_passive);
420 }
421 
422 
423 bool
424 FtpClient::_TestState(unsigned long state)
425 {
426 	return ((fState & state) != 0);
427 }
428 
429 
430 void
431 FtpClient::_SetState(unsigned long state)
432 {
433 	fState |= state;
434 }
435 
436 
437 void
438 FtpClient::_ClearState(unsigned long state)
439 {
440 	fState &= ~state;
441 }
442 
443 
444 bool
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 			printf(B_TRANSLATE("PASS <suppressed>  (real password sent)\n"));
453 		else
454 			printf("%s\n", ccmd.c_str());
455 
456 		ccmd += "\r\n";
457 		if (fControl->Send(ccmd.c_str(), ccmd.length()) >= 0)
458 			rc = true;
459 	}
460 
461 	return rc;
462 }
463 
464 
465 bool
466 FtpClient::_GetReplyLine(string& line)
467 {
468 	bool rc = false;
469 	int c = 0;
470 	bool done = false;
471 
472 	line = "";
473 		// Thanks to Stephen van Egmond for catching a bug here
474 
475 	if (fControl != NULL) {
476 		rc = true;
477 		while (done == false && fControl->Receive(&c, 1) > 0) {
478 			if (c == EOF || c == xEOF || c == '\n') {
479 				done = true;
480 			} else {
481 				if (c == IAC) {
482 					fControl->Receive(&c, 1);
483 					switch (c) {
484 						unsigned char treply[3];
485 						case WILL:
486 						case WONT:
487 							fControl->Receive(&c, 1);
488 							treply[0] = IAC;
489 							treply[1] = DONT;
490 							treply[2] = c;
491 							fControl->Send(treply, 3);
492 						break;
493 
494 						case DO:
495 						case DONT:
496 							fControl->Receive(&c, 1);
497 							fControl->Receive(&c, 1);
498 							treply[0] = IAC;
499 							treply[1] = WONT;
500 							treply[2] = c;
501 							fControl->Send(treply, 3);
502 						break;
503 
504 						case EOF:
505 						case xEOF:
506 							done = true;
507 						break;
508 
509 						default:
510 							line += c;
511 						break;
512 					}
513 				} else {
514 					// normal char
515 					if (c != '\r')
516 						line += c;
517 				}
518 			}
519 		}
520 	}
521 
522 	return rc;
523 }
524 
525 
526 bool
527 FtpClient::_GetReply(string& outString, int& outCode, int& codeType)
528 {
529 	bool rc = false;
530 	string line, tempString;
531 
532 	//
533 	// comment from the ncftp source:
534 	//
535 
536 	/* RFC 959 states that a reply may span multiple lines.  A single
537 	 * line message would have the 3-digit code <space> then the msg.
538 	 * A multi-line message would have the code <dash> and the first
539 	 * line of the msg, then additional lines, until the last line,
540 	 * which has the code <space> and last line of the msg.
541 	 *
542 	 * For example:
543 	 *	123-First line
544 	 *	Second line
545 	 *	234 A line beginning with numbers
546 	 *	123 The last line
547 	 */
548 
549 	rc = _GetReplyLine(line);
550 	if (rc == true) {
551 		outString = line;
552 		outString += '\n';
553 		printf(outString.c_str());
554 		tempString = line.substr(0, 3);
555 		outCode = atoi(tempString.c_str());
556 
557 		if (line[3] == '-') {
558 			rc = _GetReplyLine(line);
559 			while (rc == true) {
560 				outString += line;
561 				outString += '\n';
562 				printf(outString.c_str());
563 				// we're done with nnn when we get to a "nnn blahblahblah"
564 				if ((line.find(tempString) == 0) && line[3] == ' ')
565 					break;
566 
567 				rc = _GetReplyLine(line);
568 			}
569 		}
570 	}
571 
572 	if (!rc && outCode != 421) {
573 		outString += B_TRANSLATE("Remote host has closed the connection.\n");
574 		outCode = 421;
575 	}
576 
577 	if (outCode == 421) {
578 		delete fControl;
579 		fControl = NULL;
580 		_ClearState(ftp_connected);
581 	}
582 
583 	codeType = outCode / 100;
584 
585 	return rc;
586 }
587 
588 
589 bool
590 FtpClient::_OpenDataConnection()
591 {
592 	string host, cmd, replyString;
593 	unsigned short port;
594 	BNetAddress addr;
595 	int i, code, codeType;
596 	bool rc = false;
597 	struct sockaddr_in sa;
598 
599 	delete fData;
600 	fData = NULL;
601 
602 	fData = new BNetEndpoint();
603 
604 	if (_TestState(ftp_passive)) {
605 		// Here we send a "pasv" command and connect to the remote server
606 		// on the port it sends back to us
607 		cmd = "PASV";
608 		if (_SendRequest(cmd)) {
609 			if (_GetReply(replyString, code, codeType)) {
610 
611 				if (codeType == 2) {
612 					 //  It should give us something like:
613 			 		 // "227 Entering Passive Mode (192,168,1,1,10,187)"
614 					int paddr[6];
615 					unsigned char ucaddr[6];
616 
617 					i = replyString.find('(');
618 					i++;
619 
620 					replyString = replyString.substr(i,
621 						replyString.find(')') - i);
622 					if (sscanf(replyString.c_str(), "%d,%d,%d,%d,%d,%d",
623 						&paddr[0], &paddr[1], &paddr[2], &paddr[3],
624 						&paddr[4], &paddr[5]) != 6) {
625 							// Cannot do passive.
626 							// Do a little harmless rercursion here.
627 							_ClearState(ftp_passive);
628 							return _OpenDataConnection();
629 						}
630 
631 					for (i = 0; i < 6; i++)
632 						ucaddr[i] = (unsigned char)(paddr[i] & 0xff);
633 
634 					memcpy(&sa.sin_addr, &ucaddr[0], (size_t) 4);
635 					memcpy(&sa.sin_port, &ucaddr[4], (size_t) 2);
636 					addr.SetTo(sa);
637 					if (fData->Connect(addr) == B_NO_ERROR)
638 						rc = true;
639 
640 				}
641 			}
642 		} else {
643 			// cannot do passive.  Do a little harmless rercursion here
644 			_ClearState(ftp_passive);
645 			rc = _OpenDataConnection();
646 		}
647 
648 	} else {
649 		// Here we bind to a local port and send a PORT command
650 		if (fData->Bind() == B_NO_ERROR) {
651 			char buf[255];
652 
653 			fData->Listen();
654 			addr = fData->LocalAddr();
655 			addr.GetAddr(buf, &port);
656 			host = buf;
657 
658 			i = 0;
659 			while (i >= 0) {
660 				i = host.find('.', i);
661 				if (i >= 0)
662 					host[i] = ',';
663 			}
664 
665 			sprintf(buf, ",%d,%d", (port & 0xff00) >> 8, port & 0x00ff);
666 			cmd = "PORT ";
667 			cmd += host;
668 			cmd += buf;
669 			_SendRequest(cmd);
670 			_GetReply(replyString, code, codeType);
671 			// PORT failure is in the 500-range
672 			if (codeType == 2)
673 				rc = true;
674 		}
675 	}
676 
677 	return rc;
678 }
679 
680 
681 bool
682 FtpClient::_AcceptDataConnection()
683 {
684 	BNetEndpoint* endPoint;
685 	bool rc = false;
686 
687 	if (_TestState(ftp_passive) == false) {
688 		if (fData != NULL) {
689 			endPoint = fData->Accept();
690 			if (endPoint != NULL) {
691 				delete fData;
692 				fData = endPoint;
693 				rc = true;
694 			}
695 		}
696 
697 	}
698 	else
699 		rc = true;
700 
701 	return rc;
702 }
703