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