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