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