xref: /haiku/src/apps/codycam/FtpClient.cpp (revision 0d452c8f34013b611a54c746a71c05e28796eae2)
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_TRANSLATE_CONTEXT
11 #define B_TRANSLATE_CONTEXT "FtpClient"
12 
13 
14 FtpClient::FtpClient()
15 	: FileUploadClient(),
16 	fState(0),
17 	fControl(NULL),
18 	fData(NULL)
19 {
20 }
21 
22 
23 FtpClient::~FtpClient()
24 {
25 	delete fControl;
26 	delete fData;
27 }
28 
29 
30 bool
31 FtpClient::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 
52 bool
53 FtpClient::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 
96 bool
97 FtpClient::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 
121 bool
122 FtpClient::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 
184 bool
185 FtpClient::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 
265 bool
266 FtpClient::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
343 bool
344 FtpClient::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 
371 bool
372 FtpClient::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 += '/';
384 printf(B_TRANSLATE("cmd: '%s'\n"), cmd.c_str());
385 	if (_SendRequest(cmd) == true) {
386 		if (_GetReply(replyString, code, codeType) == true) {
387 printf(B_TRANSLATE("reply: %d, %d\n"), code, codeType);
388 			if (codeType == 2)
389 				rc = true;
390 		}
391 	}
392 	return rc;
393 }
394 
395 
396 void
397 FtpClient::SetPassive(bool on)
398 {
399 	if (on)
400 		_SetState(ftp_passive);
401 	else
402 		_ClearState(ftp_passive);
403 }
404 
405 
406 bool
407 FtpClient::_TestState(unsigned long state)
408 {
409 	return ((fState & state) != 0);
410 }
411 
412 
413 void
414 FtpClient::_SetState(unsigned long state)
415 {
416 	fState |= state;
417 }
418 
419 
420 void
421 FtpClient::_ClearState(unsigned long state)
422 {
423 	fState &= ~state;
424 }
425 
426 
427 bool
428 FtpClient::_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 
448 bool
449 FtpClient::_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 
508 bool
509 FtpClient::_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 
567 bool
568 FtpClient::_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 
657 bool
658 FtpClient::_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