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