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