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