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