1 #include "FtpClient.h" 2 3 FtpClient::FtpClient() 4 : 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 void 361 FtpClient::SetPassive(bool on) 362 { 363 if (on) 364 _SetState(ftp_passive); 365 else 366 _ClearState(ftp_passive); 367 } 368 369 370 bool 371 FtpClient::_TestState(unsigned long state) 372 { 373 return ((fState & state) != 0); 374 } 375 376 377 void 378 FtpClient::_SetState(unsigned long state) 379 { 380 fState |= state; 381 } 382 383 384 void 385 FtpClient::_ClearState(unsigned long state) 386 { 387 fState &= ~state; 388 } 389 390 391 bool 392 FtpClient::_SendRequest(const string& cmd) 393 { 394 bool rc = false; 395 string ccmd = cmd; 396 397 if (fControl != 0) { 398 if (cmd.find("PASS") != string::npos) 399 printf("PASS <suppressed> (real password sent)\n"); 400 else 401 printf("%s\n", ccmd.c_str()); 402 403 ccmd += "\r\n"; 404 if (fControl->Send(ccmd.c_str(), ccmd.length()) >= 0) 405 rc = true; 406 } 407 408 return rc; 409 } 410 411 412 bool 413 FtpClient::_GetReplyLine(string& line) 414 { 415 bool rc = false; 416 int c = 0; 417 bool done = false; 418 419 line = ""; // Thanks to Stephen van Egmond for catching a bug here 420 421 if (fControl != 0) { 422 rc = true; 423 while (done == false && fControl->Receive(&c, 1) > 0) { 424 if (c == EOF || c == xEOF || c == '\n') { 425 done = true; 426 } else { 427 if (c == IAC) { 428 fControl->Receive(&c, 1); 429 switch (c) { 430 unsigned char treply[3]; 431 case WILL: 432 case WONT: 433 fControl->Receive(&c, 1); 434 treply[0] = IAC; 435 treply[1] = DONT; 436 treply[2] = c; 437 fControl->Send(treply, 3); 438 break; 439 440 case DO: 441 case DONT: 442 fControl->Receive(&c, 1); 443 fControl->Receive(&c, 1); 444 treply[0] = IAC; 445 treply[1] = WONT; 446 treply[2] = c; 447 fControl->Send(treply, 3); 448 break; 449 450 case EOF: 451 case xEOF: 452 done = true; 453 break; 454 455 default: 456 line += c; 457 break; 458 } 459 } else { 460 // normal char 461 if (c != '\r') 462 line += c; 463 } 464 } 465 } 466 } 467 468 return rc; 469 } 470 471 472 bool 473 FtpClient::_GetReply(string& outString, int& outCode, int& codeType) 474 { 475 bool rc = false; 476 string line, tempString; 477 478 // 479 // comment from the ncftp source: 480 // 481 482 /* RFC 959 states that a reply may span multiple lines. A single 483 * line message would have the 3-digit code <space> then the msg. 484 * A multi-line message would have the code <dash> and the first 485 * line of the msg, then additional lines, until the last line, 486 * which has the code <space> and last line of the msg. 487 * 488 * For example: 489 * 123-First line 490 * Second line 491 * 234 A line beginning with numbers 492 * 123 The last line 493 */ 494 495 if ((rc = _GetReplyLine(line)) == true) { 496 outString = line; 497 outString += '\n'; 498 printf(outString.c_str()); 499 tempString = line.substr(0, 3); 500 outCode = atoi(tempString.c_str()); 501 502 if (line[3] == '-') { 503 while ((rc = _GetReplyLine(line)) == true) { 504 outString += line; 505 outString += '\n'; 506 printf(outString.c_str()); 507 // we're done with nnn when we get to a "nnn blahblahblah" 508 if ((line.find(tempString) == 0) && line[3] == ' ') 509 break; 510 } 511 } 512 } 513 514 if (!rc && outCode != 421) { 515 outString += "Remote host has closed the connection.\n"; 516 outCode = 421; 517 } 518 519 if (outCode == 421) { 520 delete fControl; 521 fControl = 0; 522 _ClearState(ftp_connected); 523 } 524 525 codeType = outCode / 100; 526 527 return rc; 528 } 529 530 531 bool 532 FtpClient::_OpenDataConnection() 533 { 534 string host, cmd, replyString; 535 unsigned short port; 536 BNetAddress addr; 537 int i, code, codeType; 538 bool rc = false; 539 struct sockaddr_in sa; 540 541 delete fData; 542 fData = 0; 543 544 fData = new BNetEndpoint; 545 546 if (_TestState(ftp_passive)) { 547 // Here we send a "pasv" command and connect to the remote server 548 // on the port it sends back to us 549 cmd = "PASV"; 550 if (_SendRequest(cmd)) { 551 if (_GetReply(replyString, code, codeType)) { 552 553 if (codeType == 2) { 554 // It should give us something like: 555 // "227 Entering Passive Mode (192,168,1,1,10,187)" 556 int paddr[6]; 557 unsigned char ucaddr[6]; 558 559 i = replyString.find('('); 560 i++; 561 562 replyString = replyString.substr(i, replyString.find(')') - i); 563 if (sscanf(replyString.c_str(), "%d,%d,%d,%d,%d,%d", 564 &paddr[0], &paddr[1], &paddr[2], &paddr[3], 565 &paddr[4], &paddr[5]) != 6) { 566 // cannot do passive. Do a little harmless rercursion here 567 _ClearState(ftp_passive); 568 return _OpenDataConnection(); 569 } 570 571 for (i = 0; i < 6; i++) 572 ucaddr[i] = (unsigned char)(paddr[i] & 0xff); 573 574 memcpy(&sa.sin_addr, &ucaddr[0], (size_t) 4); 575 memcpy(&sa.sin_port, &ucaddr[4], (size_t) 2); 576 addr.SetTo(sa); 577 if (fData->Connect(addr) == B_NO_ERROR) 578 rc = true; 579 580 } 581 } 582 } else { 583 // cannot do passive. Do a little harmless rercursion here 584 _ClearState(ftp_passive); 585 rc = _OpenDataConnection(); 586 } 587 588 } else { 589 // Here we bind to a local port and send a PORT command 590 if (fData->Bind() == B_NO_ERROR) { 591 char buf[255]; 592 593 fData->Listen(); 594 addr = fData->LocalAddr(); 595 addr.GetAddr(buf, &port); 596 host = buf; 597 598 i = 0; 599 while (i >= 0) { 600 i = host.find('.', i); 601 if (i >= 0) 602 host[i] = ','; 603 } 604 605 sprintf(buf, ",%d,%d", (port & 0xff00) >> 8, port & 0x00ff); 606 cmd = "PORT "; 607 cmd += host; 608 cmd += buf; 609 _SendRequest(cmd); 610 _GetReply(replyString, code, codeType); 611 // PORT failure is in the 500-range 612 if (codeType == 2) 613 rc = true; 614 } 615 } 616 617 return rc; 618 } 619 620 621 bool 622 FtpClient::_AcceptDataConnection() 623 { 624 BNetEndpoint* endPoint; 625 bool rc = false; 626 627 if (_TestState(ftp_passive) == false) { 628 if (fData != 0) { 629 endPoint = fData->Accept(); 630 if (endPoint != 0) { 631 delete fData; 632 fData = endPoint; 633 rc = true; 634 } 635 } 636 637 } 638 else 639 rc = true; 640 641 return rc; 642 } 643