1 /* open.c 2 * 3 * Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft. 4 * All rights reserved. 5 * 6 */ 7 8 #include "syshdrs.h" 9 10 static void 11 FTPDeallocateHost(const FTPCIPtr cip) 12 { 13 /* Requires the cip->bufSize field set, 14 * and the cip->buf set if the 15 * buffer is allocated. 16 */ 17 if (cip->buf != NULL) { 18 (void) memset(cip->buf, 0, cip->bufSize); 19 free(cip->buf); 20 cip->buf = NULL; 21 } 22 23 if (cip->startingWorkingDirectory != NULL) { 24 free(cip->startingWorkingDirectory); 25 cip->startingWorkingDirectory = NULL; 26 } 27 28 #if USE_SIO 29 DisposeSReadlineInfo(&cip->ctrlSrl); 30 #endif 31 DisposeLineListContents(&cip->lastFTPCmdResultLL); 32 } /* FTPDeallocateHost */ 33 34 35 36 37 38 static int 39 FTPAllocateHost(const FTPCIPtr cip) 40 { 41 char *buf; 42 43 /* Requires the cip->bufSize field set, 44 * and the cip->buf cleared if the 45 * buffer is not allocated. 46 */ 47 if (cip->buf == NULL) { 48 buf = (char *) calloc((size_t) 1, cip->bufSize); 49 if (buf == NULL) { 50 Error(cip, kDontPerror, "Malloc failed.\n"); 51 cip->errNo = kErrMallocFailed; 52 return (kErrMallocFailed); 53 } 54 cip->buf = buf; 55 } 56 return (kNoErr); 57 } /* FTPAllocateHost */ 58 59 60 61 62 void 63 FTPInitializeOurHostName(const FTPLIPtr lip) 64 { 65 if (lip == NULL) 66 return; 67 if (strcmp(lip->magic, kLibraryMagic)) 68 return; 69 70 if (lip->htried == 0) { 71 (void) memset(lip->ourHostName, 0, sizeof(lip->ourHostName)); 72 lip->hresult = GetOurHostName(lip->ourHostName, sizeof(lip->ourHostName)); 73 } 74 lip->htried++; 75 } /* FTPInitializeOurHostName */ 76 77 78 79 80 void 81 FTPInitializeAnonPassword(const FTPLIPtr lip) 82 { 83 if (lip == NULL) 84 return; 85 if (strcmp(lip->magic, kLibraryMagic)) 86 return; 87 88 FTPInitializeOurHostName(lip); 89 90 if (lip->defaultAnonPassword[0] == '\0') { 91 #ifdef SPAM_PROBLEM_HAS_BEEN_SOLVED_FOREVER 92 GetUsrName(lip->defaultAnonPassword, sizeof(lip->defaultAnonPassword)); 93 (void) STRNCAT(lip->defaultAnonPassword, "@"); 94 95 /* Default to the "user@" notation 96 * supported by NcFTPd and wu-ftpd. 97 */ 98 if (lip->htried > 0) 99 (void) STRNCAT(lip->defaultAnonPassword, lip->ourHostName); 100 #else 101 (void) STRNCPY(lip->defaultAnonPassword, "NcFTP@"); 102 #endif 103 } 104 } /* FTPInitializeAnonPassword */ 105 106 107 108 109 int 110 FTPLoginHost(const FTPCIPtr cip) 111 { 112 ResponsePtr rp; 113 int result = kErrLoginFailed; 114 int anonLogin; 115 int sentpass = 0; 116 int fwloggedin; 117 int firstTime; 118 char cwd[512]; 119 120 if (cip == NULL) 121 return (kErrBadParameter); 122 if ((cip->firewallType < kFirewallNotInUse) || (cip->firewallType > kFirewallLastType)) 123 return (kErrBadParameter); 124 125 if (strcmp(cip->magic, kLibraryMagic)) 126 return (kErrBadMagic); 127 128 anonLogin = 0; 129 if (cip->user[0] == '\0') 130 (void) STRNCPY(cip->user, "anonymous"); 131 if ((strcmp(cip->user, "anonymous") == 0) || (strcmp(cip->user, "ftp") == 0)) { 132 anonLogin = 1; 133 /* Try to get the email address if you didn't specify 134 * a password when the user is anonymous. 135 */ 136 if (cip->pass[0] == '\0') { 137 FTPInitializeAnonPassword(cip->lip); 138 (void) STRNCPY(cip->pass, cip->lip->defaultAnonPassword); 139 } 140 } 141 142 rp = InitResponse(); 143 if (rp == NULL) { 144 result = kErrMallocFailed; 145 cip->errNo = kErrMallocFailed; 146 goto done2; 147 } 148 149 for (firstTime = 1, fwloggedin = 0; ; ) { 150 /* Here's a mini finite-automaton for the login process. 151 * 152 * Originally, the FTP protocol was designed to be entirely 153 * implementable from a FA. It could be done, but I don't think 154 * it's something an interactive process could be the most 155 * effective with. 156 */ 157 158 if (firstTime != 0) { 159 rp->code = 220; 160 firstTime = 0; 161 } else if (result < 0) { 162 goto done; 163 } 164 165 switch (rp->code) { 166 case 220: /* Welcome, ready for new user. */ 167 if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) { 168 ReInitResponse(cip, rp); 169 result = RCmd(cip, rp, "USER %s", cip->user); 170 } else if (cip->firewallType == kFirewallUserAtSite) { 171 ReInitResponse(cip, rp); 172 result = RCmd(cip, rp, "USER %s@%s", cip->user, cip->host); 173 } else if (cip->firewallType == kFirewallUserAtUserPassAtPass) { 174 ReInitResponse(cip, rp); 175 result = RCmd(cip, rp, "USER %s@%s@%s", cip->user, cip->firewallUser, cip->host); 176 } else if (cip->firewallType == kFirewallUserAtSiteFwuPassFwp) { 177 ReInitResponse(cip, rp); 178 result = RCmd(cip, rp, "USER %s@%s %s", cip->user, cip->host, cip->firewallUser); 179 } else if (cip->firewallType == kFirewallFwuAtSiteFwpUserPass) { 180 /* only reached when !fwloggedin */ 181 ReInitResponse(cip, rp); 182 result = RCmd(cip, rp, "USER %s@%s", cip->firewallUser, cip->host); 183 } else if (cip->firewallType > kFirewallNotInUse) { 184 ReInitResponse(cip, rp); 185 result = RCmd(cip, rp, "USER %s", cip->firewallUser); 186 } else { 187 goto unknown; 188 } 189 break; 190 191 case 230: /* 230 User logged in, proceed. */ 192 case 231: /* User name accepted. */ 193 case 202: /* Command not implemented, superfluous at this site. */ 194 if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) 195 goto okay; 196 197 /* Now logged in to the firewall. */ 198 fwloggedin++; 199 200 if (cip->firewallType == kFirewallLoginThenUserAtSite) { 201 ReInitResponse(cip, rp); 202 result = RCmd(cip, rp, "USER %s@%s", cip->user, cip->host); 203 } else if (cip->firewallType == kFirewallUserAtUserPassAtPass) { 204 goto okay; 205 } else if (cip->firewallType == kFirewallOpenSite) { 206 ReInitResponse(cip, rp); 207 result = RCmd(cip, rp, "OPEN %s", cip->host); 208 } else if (cip->firewallType == kFirewallSiteSite) { 209 ReInitResponse(cip, rp); 210 result = RCmd(cip, rp, "SITE %s", cip->host); 211 } else if (cip->firewallType == kFirewallFwuAtSiteFwpUserPass) { 212 /* only reached when !fwloggedin */ 213 ReInitResponse(cip, rp); 214 result = RCmd(cip, rp, "USER %s", cip->user); 215 } else /* kFirewallUserAtSite */ { 216 goto okay; 217 } 218 break; 219 220 case 421: /* 421 Service not available, closing control connection. */ 221 result = kErrHostDisconnectedDuringLogin; 222 goto done; 223 224 case 331: /* 331 User name okay, need password. */ 225 if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) { 226 if ((cip->pass[0] == '\0') && (cip->passphraseProc != NoGetPassphraseProc)) 227 (*cip->passphraseProc)(cip, &rp->msg, cip->pass, sizeof(cip->pass)); 228 ReInitResponse(cip, rp); 229 result = RCmd(cip, rp, "PASS %s", cip->pass); 230 } else if (cip->firewallType == kFirewallUserAtSite) { 231 ReInitResponse(cip, rp); 232 result = RCmd(cip, rp, "PASS %s", cip->pass); 233 } else if (cip->firewallType == kFirewallUserAtUserPassAtPass) { 234 ReInitResponse(cip, rp); 235 result = RCmd(cip, rp, "PASS %s@%s", cip->pass, cip->firewallPass); 236 } else if (cip->firewallType == kFirewallUserAtSiteFwuPassFwp) { 237 ReInitResponse(cip, rp); 238 result = RCmd(cip, rp, "PASS %s", cip->pass); 239 } else if (cip->firewallType == kFirewallFwuAtSiteFwpUserPass) { 240 /* only reached when !fwloggedin */ 241 ReInitResponse(cip, rp); 242 result = RCmd(cip, rp, "PASS %s", cip->firewallPass); 243 } else if (cip->firewallType > kFirewallNotInUse) { 244 ReInitResponse(cip, rp); 245 result = RCmd(cip, rp, "PASS %s", cip->firewallPass); 246 } else { 247 goto unknown; 248 } 249 sentpass++; 250 break; 251 252 case 332: /* 332 Need account for login. */ 253 case 532: /* 532 Need account for storing files. */ 254 if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) { 255 ReInitResponse(cip, rp); 256 result = RCmd(cip, rp, "ACCT %s", cip->acct); 257 } else if (cip->firewallType == kFirewallUserAtSiteFwuPassFwp) { 258 ReInitResponse(cip, rp); 259 result = RCmd(cip, rp, "ACCT %s", cip->firewallPass); 260 } else { 261 /* ACCT not supported on firewall. */ 262 goto unknown; 263 } 264 break; 265 266 case 530: /* Not logged in. */ 267 result = (sentpass != 0) ? kErrBadRemoteUserOrPassword : kErrBadRemoteUser; 268 goto done; 269 270 case 501: /* Syntax error in parameters or arguments. */ 271 case 503: /* Bad sequence of commands. */ 272 case 550: /* Can't set guest privileges. */ 273 goto done; 274 275 default: 276 unknown: 277 if (rp->msg.first == NULL) { 278 Error(cip, kDontPerror, "Lost connection during login.\n"); 279 } else { 280 Error(cip, kDontPerror, "Unexpected response: %s\n", 281 rp->msg.first->line 282 ); 283 } 284 goto done; 285 } 286 } 287 288 okay: 289 /* Do the application's connect message callback, if present. */ 290 if (cip->onLoginMsgProc != 0) 291 (*cip->onLoginMsgProc)(cip, rp); 292 DoneWithResponse(cip, rp); 293 result = 0; 294 cip->loggedIn = 1; 295 296 /* Make a note of what our root directory is. 297 * This is often different from "/" when not 298 * logged in anonymously. 299 */ 300 if (cip->startingWorkingDirectory != NULL) { 301 free(cip->startingWorkingDirectory); 302 cip->startingWorkingDirectory = NULL; 303 } 304 if ((cip->doNotGetStartingWorkingDirectory == 0) && 305 (FTPGetCWD(cip, cwd, sizeof(cwd)) == kNoErr)) 306 { 307 cip->startingWorkingDirectory = StrDup(cwd); 308 } 309 310 /* When a new site is opened, ASCII mode is assumed (by protocol). */ 311 cip->curTransferType = 'A'; 312 PrintF(cip, "Logged in to %s as %s.\n", cip->host, cip->user); 313 314 /* Don't leave cleartext password in memory. */ 315 if ((anonLogin == 0) && (cip->leavePass == 0)) 316 (void) memset(cip->pass, '*', strlen(cip->pass)); 317 318 if (result < 0) 319 cip->errNo = result; 320 return result; 321 322 done: 323 DoneWithResponse(cip, rp); 324 325 done2: 326 /* Don't leave cleartext password in memory. */ 327 if ((anonLogin == 0) && (cip->leavePass == 0)) 328 (void) memset(cip->pass, '*', strlen(cip->pass)); 329 if (result < 0) 330 cip->errNo = result; 331 return result; 332 } /* FTPLoginHost */ 333 334 335 336 337 static void 338 FTPExamineMlstFeatures(const FTPCIPtr cip, const char *features) 339 { 340 char buf[256], *feat; 341 int flags; 342 343 flags = 0; 344 STRNCPY(buf, features); 345 feat = strtok(buf, ";*"); 346 while (feat != NULL) { 347 if (ISTRNEQ(feat, "OS.", 3)) 348 feat += 3; 349 if (ISTREQ(feat, "type")) { 350 flags |= kMlsOptType; 351 } else if (ISTREQ(feat, "size")) { 352 flags |= kMlsOptSize; 353 } else if (ISTREQ(feat, "modify")) { 354 flags |= kMlsOptModify; 355 } else if (ISTREQ(feat, "UNIX.mode")) { 356 flags |= kMlsOptUNIXmode; 357 } else if (ISTREQ(feat, "UNIX.owner")) { 358 flags |= kMlsOptUNIXowner; 359 } else if (ISTREQ(feat, "UNIX.group")) { 360 flags |= kMlsOptUNIXgroup; 361 } else if (ISTREQ(feat, "perm")) { 362 flags |= kMlsOptPerm; 363 } else if (ISTREQ(feat, "UNIX.uid")) { 364 flags |= kMlsOptUNIXuid; 365 } else if (ISTREQ(feat, "UNIX.gid")) { 366 flags |= kMlsOptUNIXgid; 367 } else if (ISTREQ(feat, "UNIX.gid")) { 368 flags |= kMlsOptUnique; 369 } 370 feat = strtok(NULL, ";*"); 371 } 372 373 cip->mlsFeatures = flags; 374 } /* FTPExamineMlstFeatures */ 375 376 377 378 379 int 380 FTPQueryFeatures(const FTPCIPtr cip) 381 { 382 ResponsePtr rp; 383 int result; 384 LinePtr lp; 385 char *cp, *p; 386 387 if (cip == NULL) 388 return (kErrBadParameter); 389 if (strcmp(cip->magic, kLibraryMagic)) 390 return (kErrBadMagic); 391 392 if (cip->serverType == kServerTypeNetWareFTP) { 393 /* NetWare 5.00 server freaks out when 394 * you give it a command it doesn't 395 * recognize, so cheat here and return. 396 */ 397 cip->hasPASV = kCommandAvailable; 398 cip->hasSIZE = kCommandNotAvailable; 399 cip->hasMDTM = kCommandNotAvailable; 400 cip->hasREST = kCommandNotAvailable; 401 cip->NLSTfileParamWorks = kCommandAvailable; 402 cip->hasUTIME = kCommandNotAvailable; 403 cip->hasCLNT = kCommandNotAvailable; 404 cip->hasMLST = kCommandNotAvailable; 405 cip->hasMLSD = kCommandNotAvailable; 406 return (kNoErr); 407 } 408 409 rp = InitResponse(); 410 if (rp == NULL) { 411 cip->errNo = kErrMallocFailed; 412 result = cip->errNo; 413 } else { 414 rp->printMode = (kResponseNoPrint|kResponseNoSave); 415 result = RCmd(cip, rp, "FEAT"); 416 if (result < kNoErr) { 417 DoneWithResponse(cip, rp); 418 return (result); 419 } else if (result != 2) { 420 /* We cheat here and pre-populate some 421 * fields when the server is wu-ftpd. 422 * This server is very common and we 423 * know it has always had these. 424 */ 425 if (cip->serverType == kServerTypeWuFTPd) { 426 cip->hasPASV = kCommandAvailable; 427 cip->hasSIZE = kCommandAvailable; 428 cip->hasMDTM = kCommandAvailable; 429 cip->hasREST = kCommandAvailable; 430 cip->NLSTfileParamWorks = kCommandAvailable; 431 } else if (cip->serverType == kServerTypeNcFTPd) { 432 cip->hasPASV = kCommandAvailable; 433 cip->hasSIZE = kCommandAvailable; 434 cip->hasMDTM = kCommandAvailable; 435 cip->hasREST = kCommandAvailable; 436 cip->NLSTfileParamWorks = kCommandAvailable; 437 } 438 439 /* Newer commands are only shown in FEAT, 440 * so we don't have to do the "try it, 441 * then save that it didn't work" thing. 442 */ 443 cip->hasMLST = kCommandNotAvailable; 444 cip->hasMLSD = kCommandNotAvailable; 445 } else { 446 cip->hasFEAT = kCommandAvailable; 447 448 for (lp = rp->msg.first; lp != NULL; lp = lp->next) { 449 /* If first character was not a space it is 450 * either: 451 * 452 * (a) The header line in the response; 453 * (b) The trailer line in the response; 454 * (c) A protocol violation. 455 */ 456 cp = lp->line; 457 if (*cp++ != ' ') 458 continue; 459 if (ISTRNCMP(cp, "PASV", 4) == 0) { 460 cip->hasPASV = kCommandAvailable; 461 } else if (ISTRNCMP(cp, "SIZE", 4) == 0) { 462 cip->hasSIZE = kCommandAvailable; 463 } else if (ISTRNCMP(cp, "MDTM", 4) == 0) { 464 cip->hasMDTM = kCommandAvailable; 465 } else if (ISTRNCMP(cp, "REST", 4) == 0) { 466 cip->hasREST = kCommandAvailable; 467 } else if (ISTRNCMP(cp, "UTIME", 5) == 0) { 468 cip->hasUTIME = kCommandAvailable; 469 } else if (ISTRNCMP(cp, "MLST", 4) == 0) { 470 cip->hasMLST = kCommandAvailable; 471 cip->hasMLSD = kCommandAvailable; 472 FTPExamineMlstFeatures(cip, cp + 5); 473 } else if (ISTRNCMP(cp, "CLNT", 4) == 0) { 474 cip->hasCLNT = kCommandAvailable; 475 } else if (ISTRNCMP(cp, "Compliance Level: ", 18) == 0) { 476 /* Probably only NcFTPd will ever implement this. 477 * But we use it internally to differentiate 478 * between different NcFTPd implementations of 479 * IETF extensions. 480 */ 481 cip->ietfCompatLevel = atoi(cp + 18); 482 } 483 } 484 } 485 486 ReInitResponse(cip, rp); 487 result = RCmd(cip, rp, "HELP SITE"); 488 if (result == 2) { 489 for (lp = rp->msg.first; lp != NULL; lp = lp->next) { 490 cp = lp->line; 491 if (strstr(cp, "RETRBUFSIZE") != NULL) 492 cip->hasRETRBUFSIZE = kCommandAvailable; 493 if (strstr(cp, "RBUFSZ") != NULL) 494 cip->hasRBUFSZ = kCommandAvailable; 495 /* See if RBUFSIZ matches (but not STORBUFSIZE) */ 496 if ( 497 ((p = strstr(cp, "RBUFSIZ")) != NULL) && 498 ( 499 (p == cp) || 500 ((p > cp) && (!isupper(p[-1]))) 501 ) 502 ) 503 cip->hasRBUFSIZ = kCommandAvailable; 504 if (strstr(cp, "STORBUFSIZE") != NULL) 505 cip->hasSTORBUFSIZE = kCommandAvailable; 506 if (strstr(cp, "SBUFSIZ") != NULL) 507 cip->hasSBUFSIZ = kCommandAvailable; 508 if (strstr(cp, "SBUFSZ") != NULL) 509 cip->hasSBUFSZ = kCommandAvailable; 510 if (strstr(cp, "BUFSIZE") != NULL) 511 cip->hasBUFSIZE = kCommandAvailable; 512 } 513 } 514 DoneWithResponse(cip, rp); 515 } 516 517 return (kNoErr); 518 } /* FTPQueryFeatures */ 519 520 521 522 int 523 FTPCloseHost(const FTPCIPtr cip) 524 { 525 ResponsePtr rp; 526 int result; 527 528 if (cip == NULL) 529 return (kErrBadParameter); 530 if (strcmp(cip->magic, kLibraryMagic)) 531 return (kErrBadMagic); 532 533 /* Data connection shouldn't be open normally. */ 534 if (cip->dataSocket != kClosedFileDescriptor) 535 FTPAbortDataTransfer(cip); 536 537 result = kNoErr; 538 if (cip->connected != 0) { 539 rp = InitResponse(); 540 if (rp == NULL) { 541 cip->errNo = kErrMallocFailed; 542 result = cip->errNo; 543 } else { 544 rp->eofOkay = 1; /* We are expecting EOF after this cmd. */ 545 cip->eofOkay = 1; 546 (void) RCmd(cip, rp, "QUIT"); 547 DoneWithResponse(cip, rp); 548 } 549 } 550 551 CloseControlConnection(cip); 552 553 /* Dispose dynamic data structures, so you won't leak 554 * if you OpenHost with this again. 555 */ 556 FTPDeallocateHost(cip); 557 return (result); 558 } /* FTPCloseHost */ 559 560 561 562 563 void 564 FTPShutdownHost(const FTPCIPtr cip) 565 { 566 #ifdef SIGPIPE 567 FTPSigProc osigpipe; 568 #endif 569 570 if (cip == NULL) 571 return; 572 if (strcmp(cip->magic, kLibraryMagic)) 573 return; 574 575 #ifdef SIGPIPE 576 osigpipe = signal(SIGPIPE, (FTPSigProc) SIG_IGN); 577 #endif 578 579 /* Linger could cause close to block, so unset it. */ 580 if (cip->dataSocket != kClosedFileDescriptor) 581 (void) SetLinger(cip, cip->dataSocket, 0); 582 CloseDataConnection(cip); /* Shouldn't be open normally. */ 583 584 /* Linger should already be turned off for this. */ 585 CloseControlConnection(cip); 586 587 FTPDeallocateHost(cip); 588 589 #ifdef SIGPIPE 590 (void) signal(SIGPIPE, (FTPSigProc) osigpipe); 591 #endif 592 } /* FTPShutdownHost */ 593 594 595 596 597 void 598 URLCopyToken(char *dst, size_t dsize, const char *src, size_t howmuch) 599 { 600 char *dlim; 601 const char *slim; 602 unsigned int hc; 603 int c; 604 char h[4]; 605 606 dlim = dst + dsize - 1; /* leave room for \0 */ 607 slim = src + howmuch; 608 while (src < slim) { 609 c = *src++; 610 if (c == '\0') 611 break; 612 if (c == '%') { 613 /* hex representation */ 614 if (src < slim + 2) { 615 h[0] = *src++; 616 h[1] = *src++; 617 h[2] = '\0'; 618 hc = 0xeeff; 619 if ((sscanf(h, "%x", &hc) >= 0) && (hc != 0xeeff)) { 620 if (dst < dlim) { 621 *(unsigned char *)dst = (unsigned char) hc; 622 dst++; 623 } 624 } 625 } else { 626 break; 627 } 628 } else { 629 *dst++ = (char) c; 630 } 631 } 632 *dst = '\0'; 633 } /* URLCopyToken */ 634 635 636 637 638 int 639 FTPDecodeURL( 640 const FTPCIPtr cip, /* area pointed to may be modified */ 641 char *const url, /* always modified */ 642 LineListPtr cdlist, /* always modified */ 643 char *const fn, /* always modified */ 644 const size_t fnsize, 645 int *const xtype, /* optional; may be modified */ 646 int *const wantnlst /* optional; always modified */ 647 ) 648 { 649 char *cp; 650 char *hstart, *hend; 651 char *h2start; 652 char *at1; 653 char portstr[32]; 654 int port; 655 int sc; 656 char *lastslash; 657 char *parsestr; 658 char *tok; 659 char subdir[128]; 660 char *semi; 661 662 InitLineList(cdlist); 663 *fn = '\0'; 664 if (wantnlst != NULL) 665 *wantnlst = 0; 666 if (xtype != NULL) 667 *xtype = kTypeBinary; 668 669 cp = NULL; /* shut up warnings */ 670 #ifdef HAVE_STRCASECMP 671 if (strncasecmp(url, "<URL:ftp://", 11) == 0) { 672 cp = url + strlen(url) - 1; 673 if (*cp != '>') 674 return (kMalformedURL); /* missing closing > */ 675 *cp = '\0'; 676 cp = url + 11; 677 } else if (strncasecmp(url, "ftp://", 6) == 0) { 678 cp = url + 6; 679 } else { 680 return (-1); /* not a RFC 1738 URL */ 681 } 682 #else /* HAVE_STRCASECMP */ 683 if (strncmp(url, "<URL:ftp://", 11) == 0) { 684 cp = url + strlen(url) - 1; 685 if (*cp != '>') 686 return (kMalformedURL); /* missing closing > */ 687 *cp = '\0'; 688 cp = url + 11; 689 } else if (strncmp(url, "ftp://", 6) == 0) { 690 cp = url + 6; 691 } else { 692 return (-1); /* not a RFC 1738 URL */ 693 } 694 #endif /* HAVE_STRCASECMP */ 695 696 /* //<user>:<password>@<host>:<port>/<url-path> */ 697 698 at1 = NULL; 699 for (hstart = cp; ; cp++) { 700 if (*cp == '@') { 701 if (at1 == NULL) 702 at1 = cp; 703 else 704 return (kMalformedURL); 705 } else if ((*cp == '\0') || (*cp == '/')) { 706 hend = cp; 707 break; 708 } 709 } 710 711 sc = *hend; 712 *hend = '\0'; 713 if (at1 == NULL) { 714 /* no user or password */ 715 h2start = hstart; 716 } else { 717 *at1 = '\0'; 718 cp = strchr(hstart, ':'); 719 if (cp == NULL) { 720 /* whole thing is the user name then */ 721 URLCopyToken(cip->user, sizeof(cip->user), hstart, (size_t) (at1 - hstart)); 722 } else if (strchr(cp + 1, ':') != NULL) { 723 /* Too many colons */ 724 return (kMalformedURL); 725 } else { 726 URLCopyToken(cip->user, sizeof(cip->user), hstart, (size_t) (cp - hstart)); 727 URLCopyToken(cip->pass, sizeof(cip->pass), cp + 1, (size_t) (at1 - (cp + 1))); 728 } 729 *at1 = '@'; 730 h2start = at1 + 1; 731 } 732 733 cp = strchr(h2start, ':'); 734 if (cp == NULL) { 735 /* whole thing is the host then */ 736 URLCopyToken(cip->host, sizeof(cip->host), h2start, (size_t) (hend - h2start)); 737 } else if (strchr(cp + 1, ':') != NULL) { 738 /* Too many colons */ 739 return (kMalformedURL); 740 } else { 741 URLCopyToken(cip->host, sizeof(cip->host), h2start, (size_t) (cp - h2start)); 742 URLCopyToken(portstr, sizeof(portstr), cp + 1, (size_t) (hend - (cp + 1))); 743 port = atoi(portstr); 744 if (port > 0) 745 cip->port = port; 746 } 747 748 *hend = (char) sc; 749 if ((*hend == '\0') || ((*hend == '/') && (hend[1] == '\0'))) { 750 /* no path, okay */ 751 return (0); 752 } 753 754 lastslash = strrchr(hend, '/'); 755 if (lastslash == NULL) { 756 /* no path, okay */ 757 return (0); 758 } 759 *lastslash = '\0'; 760 761 if ((semi = strchr(lastslash + 1, ';')) != NULL) { 762 *semi++ = '\0'; 763 #ifdef HAVE_STRCASECMP 764 if (strcasecmp(semi, "type=i") == 0) { 765 if (xtype != NULL) 766 *xtype = kTypeBinary; 767 } else if (strcasecmp(semi, "type=a") == 0) { 768 if (xtype != NULL) 769 *xtype = kTypeAscii; 770 } else if (strcasecmp(semi, "type=b") == 0) { 771 if (xtype != NULL) 772 *xtype = kTypeBinary; 773 } else if (strcasecmp(semi, "type=d") == 0) { 774 if (wantnlst != NULL) { 775 *wantnlst = 1; 776 if (xtype != NULL) 777 *xtype = kTypeAscii; 778 } else { 779 /* You didn't want these. */ 780 return (kMalformedURL); 781 } 782 } 783 #else /* HAVE_STRCASECMP */ 784 if (strcmp(semi, "type=i") == 0) { 785 if (xtype != NULL) 786 *xtype = kTypeBinary; 787 } else if (strcmp(semi, "type=a") == 0) { 788 if (xtype != NULL) 789 *xtype = kTypeAscii; 790 } else if (strcmp(semi, "type=b") == 0) { 791 if (xtype != NULL) 792 *xtype = kTypeBinary; 793 } else if (strcmp(semi, "type=d") == 0) { 794 if (wantnlst != NULL) { 795 *wantnlst = 1; 796 if (xtype != NULL) 797 *xtype = kTypeAscii; 798 } else { 799 /* You didn't want these. */ 800 return (kMalformedURL); 801 } 802 } 803 #endif /* HAVE_STRCASECMP */ 804 } 805 URLCopyToken(fn, fnsize, lastslash + 1, strlen(lastslash + 1)); 806 for (parsestr = hend; (tok = strtok(parsestr, "/")) != NULL; parsestr = NULL) { 807 URLCopyToken(subdir, sizeof(subdir), tok, strlen(tok)); 808 (void) AddLine(cdlist, subdir); 809 } 810 *lastslash = '/'; 811 return (kNoErr); 812 } /* FTPDecodeURL */ 813 814 815 816 817 int 818 FTPOpenHost(const FTPCIPtr cip) 819 { 820 int result; 821 time_t t0, t1; 822 int elapsed; 823 int dials; 824 825 if (cip == NULL) 826 return (kErrBadParameter); 827 if (strcmp(cip->magic, kLibraryMagic)) 828 return (kErrBadMagic); 829 830 if (cip->host[0] == '\0') { 831 cip->errNo = kErrBadParameter; 832 return (kErrBadParameter); 833 } 834 835 for ( result = kErrConnectMiscErr, dials = 0; 836 cip->maxDials < 0 || dials < cip->maxDials; 837 dials++) 838 { 839 /* Allocate (or if the host was closed, reallocate) 840 * the transfer data buffer. 841 */ 842 result = FTPAllocateHost(cip); 843 if (result < 0) 844 return (result); 845 846 if (dials > 0) 847 PrintF(cip, "Retry Number: %d\n", dials); 848 if (cip->redialStatusProc != 0) 849 (*cip->redialStatusProc)(cip, kRedialStatusDialing, dials); 850 (void) time(&t0); 851 result = OpenControlConnection(cip, cip->host, cip->port); 852 (void) time(&t1); 853 if (result == kNoErr) { 854 /* We were hooked up successfully. */ 855 PrintF(cip, "Connected to %s.\n", cip->host); 856 857 result = FTPLoginHost(cip); 858 if (result == kNoErr) { 859 (void) FTPQueryFeatures(cip); 860 break; 861 } 862 863 /* Close and try again. */ 864 (void) FTPCloseHost(cip); 865 866 /* Originally we also stopped retyring if 867 * we got kErrBadRemoteUser and non-anonymous, 868 * but some FTP servers apparently do their 869 * max user check after the username is sent. 870 */ 871 if (result == kErrBadRemoteUserOrPassword /* || (result == kErrBadRemoteUser) */) { 872 if (strcmp(cip->user, "anonymous") != 0) { 873 /* Non-anonymous login was denied, and 874 * retrying is not going to help. 875 */ 876 break; 877 } 878 } 879 } else if ((result != kErrConnectRetryableErr) && (result != kErrConnectRefused) && (result != kErrRemoteHostClosedConnection)) { 880 /* Irrecoverable error, so don't bother redialing. 881 * The error message should have already been printed 882 * from OpenControlConnection(). 883 */ 884 PrintF(cip, "Cannot recover from miscellaneous open error %d.\n", result); 885 return result; 886 } 887 888 /* Retryable error, wait and then redial. */ 889 if (cip->redialDelay > 0) { 890 /* But don't sleep if this is the last loop. */ 891 if ((cip->maxDials < 0) || (dials < (cip->maxDials - 1))) { 892 elapsed = (int) (t1 - t0); 893 if (elapsed < cip->redialDelay) { 894 PrintF(cip, "Sleeping %u seconds.\n", 895 (unsigned) cip->redialDelay - elapsed); 896 if (cip->redialStatusProc != 0) 897 (*cip->redialStatusProc)(cip, kRedialStatusSleeping, cip->redialDelay - elapsed); 898 (void) sleep((unsigned) cip->redialDelay - elapsed); 899 } 900 } 901 } 902 } 903 return (result); 904 } /* FTPOpenHost */ 905 906 907 908 909 int 910 FTPOpenHostNoLogin(const FTPCIPtr cip) 911 { 912 int result; 913 time_t t0, t1; 914 int elapsed; 915 int dials; 916 917 if (cip == NULL) 918 return (kErrBadParameter); 919 if (strcmp(cip->magic, kLibraryMagic)) 920 return (kErrBadMagic); 921 922 if (cip->host[0] == '\0') { 923 cip->errNo = kErrBadParameter; 924 return (kErrBadParameter); 925 } 926 927 for ( result = kErrConnectMiscErr, dials = 0; 928 cip->maxDials < 0 || dials < cip->maxDials; 929 dials++) 930 { 931 932 /* Allocate (or if the host was closed, reallocate) 933 * the transfer data buffer. 934 */ 935 result = FTPAllocateHost(cip); 936 if (result < 0) 937 return (result); 938 939 if (dials > 0) 940 PrintF(cip, "Retry Number: %d\n", dials); 941 if (cip->redialStatusProc != 0) 942 (*cip->redialStatusProc)(cip, kRedialStatusDialing, dials); 943 (void) time(&t0); 944 result = OpenControlConnection(cip, cip->host, cip->port); 945 (void) time(&t1); 946 if (result == kNoErr) { 947 /* We were hooked up successfully. */ 948 PrintF(cip, "Connected to %s.\n", cip->host); 949 950 /* Not logging in... */ 951 if (result == kNoErr) 952 break; 953 } else if ((result != kErrConnectRetryableErr) && (result != kErrConnectRefused) && (result != kErrRemoteHostClosedConnection)) { 954 /* Irrecoverable error, so don't bother redialing. 955 * The error message should have already been printed 956 * from OpenControlConnection(). 957 */ 958 PrintF(cip, "Cannot recover from miscellaneous open error %d.\n", result); 959 return result; 960 } 961 962 /* Retryable error, wait and then redial. */ 963 if (cip->redialDelay > 0) { 964 /* But don't sleep if this is the last loop. */ 965 if ((cip->maxDials < 0) || (dials < (cip->maxDials - 1))) { 966 elapsed = (int) (t1 - t0); 967 if (elapsed < cip->redialDelay) { 968 PrintF(cip, "Sleeping %u seconds.\n", 969 (unsigned) cip->redialDelay - elapsed); 970 if (cip->redialStatusProc != 0) 971 (*cip->redialStatusProc)(cip, kRedialStatusSleeping, cip->redialDelay - elapsed); 972 (void) sleep((unsigned) cip->redialDelay - elapsed); 973 } 974 } 975 } 976 } 977 return (result); 978 } /* FTPOpenHostNoLogin */ 979 980 981 982 983 int 984 FTPInitConnectionInfo(const FTPLIPtr lip, const FTPCIPtr cip, size_t bufSize) 985 { 986 size_t siz; 987 988 if ((lip == NULL) || (cip == NULL) || (bufSize == 0)) 989 return (kErrBadParameter); 990 991 siz = sizeof(FTPConnectionInfo); 992 (void) memset(cip, 0, siz); 993 994 if (strcmp(lip->magic, kLibraryMagic)) 995 return (kErrBadMagic); 996 997 cip->buf = NULL; /* denote that it needs to be allocated. */ 998 cip->bufSize = bufSize; 999 cip->port = lip->defaultPort; 1000 cip->firewallPort = lip->defaultPort; 1001 cip->maxDials = kDefaultMaxDials; 1002 cip->redialDelay = kDefaultRedialDelay; 1003 cip->xferTimeout = kDefaultXferTimeout; 1004 cip->connTimeout = kDefaultConnTimeout; 1005 cip->ctrlTimeout = kDefaultCtrlTimeout; 1006 cip->abortTimeout = kDefaultAbortTimeout; 1007 cip->ctrlSocketR = kClosedFileDescriptor; 1008 cip->ctrlSocketW = kClosedFileDescriptor; 1009 cip->dataPortMode = kSendPortMode; 1010 cip->dataSocket = kClosedFileDescriptor; 1011 cip->lip = lip; 1012 cip->hasPASV = kCommandAvailabilityUnknown; 1013 cip->hasSIZE = kCommandAvailabilityUnknown; 1014 cip->hasMDTM = kCommandAvailabilityUnknown; 1015 cip->hasREST = kCommandAvailabilityUnknown; 1016 cip->hasNLST_d = kCommandAvailabilityUnknown; 1017 cip->hasUTIME = kCommandAvailabilityUnknown; 1018 cip->hasFEAT = kCommandAvailabilityUnknown; 1019 cip->hasMLSD = kCommandAvailabilityUnknown; 1020 cip->hasMLST = kCommandAvailabilityUnknown; 1021 cip->hasCLNT = kCommandAvailabilityUnknown; 1022 cip->hasRETRBUFSIZE = kCommandAvailabilityUnknown; 1023 cip->hasRBUFSIZ = kCommandAvailabilityUnknown; 1024 cip->hasRBUFSZ = kCommandAvailabilityUnknown; 1025 cip->hasSTORBUFSIZE = kCommandAvailabilityUnknown; 1026 cip->hasSBUFSIZ = kCommandAvailabilityUnknown; 1027 cip->hasSBUFSZ = kCommandAvailabilityUnknown; 1028 cip->STATfileParamWorks = kCommandAvailabilityUnknown; 1029 cip->NLSTfileParamWorks = kCommandAvailabilityUnknown; 1030 cip->firewallType = kFirewallNotInUse; 1031 cip->startingWorkingDirectory = NULL; 1032 (void) STRNCPY(cip->magic, kLibraryMagic); 1033 (void) STRNCPY(cip->user, "anonymous"); 1034 return (kNoErr); 1035 } /* FTPInitConnectionInfo */ 1036 1037 1038 1039 1040 int 1041 FTPRebuildConnectionInfo(const FTPLIPtr lip, const FTPCIPtr cip) 1042 { 1043 char *buf; 1044 1045 cip->lip = lip; 1046 cip->debugLog = NULL; 1047 cip->errLog = NULL; 1048 cip->debugLogProc = NULL; 1049 cip->errLogProc = NULL; 1050 cip->buf = NULL; 1051 cip->cin = NULL; 1052 cip->cout = NULL; 1053 cip->errNo = 0; 1054 cip->progress = NULL; 1055 cip->rname = NULL; 1056 cip->lname = NULL; 1057 cip->onConnectMsgProc = NULL; 1058 cip->redialStatusProc = NULL; 1059 cip->printResponseProc = NULL; 1060 cip->onLoginMsgProc = NULL; 1061 cip->passphraseProc = NULL; 1062 cip->startingWorkingDirectory = NULL; 1063 cip->asciiFilenameExtensions = NULL; 1064 1065 (void) memset(&cip->lastFTPCmdResultLL, 0, sizeof(LineList)); 1066 1067 /* Allocate a new buffer. */ 1068 buf = (char *) calloc((size_t) 1, cip->bufSize); 1069 if (buf == NULL) { 1070 cip->errNo = kErrMallocFailed; 1071 return (kErrMallocFailed); 1072 } 1073 cip->buf = buf; 1074 1075 /* Reattach the FILE pointers for use with the Std I/O library 1076 * routines. 1077 */ 1078 if ((cip->cin = _fdopen(cip->ctrlSocketR, "r")) == NULL) { 1079 cip->errNo = kErrFdopenR; 1080 cip->ctrlSocketR = kClosedFileDescriptor; 1081 cip->ctrlSocketW = kClosedFileDescriptor; 1082 return (kErrFdopenR); 1083 } 1084 1085 if ((cip->cout = _fdopen(cip->ctrlSocketW, "w")) == NULL) { 1086 CloseFile(&cip->cin); 1087 cip->errNo = kErrFdopenW; 1088 cip->ctrlSocketR = kClosedFileDescriptor; 1089 cip->ctrlSocketW = kClosedFileDescriptor; 1090 return (kErrFdopenW); 1091 } 1092 1093 #if USE_SIO 1094 if (InitSReadlineInfo(&cip->ctrlSrl, cip->ctrlSocketR, cip->srlBuf, sizeof(cip->srlBuf), (int) cip->ctrlTimeout, 1) < 0) { 1095 cip->errNo = kErrFdopenW; 1096 CloseFile(&cip->cin); 1097 cip->errNo = kErrFdopenW; 1098 cip->ctrlSocketR = kClosedFileDescriptor; 1099 cip->ctrlSocketW = kClosedFileDescriptor; 1100 return (kErrFdopenW); 1101 } 1102 #endif 1103 return (kNoErr); 1104 } /* FTPRebuildConnectionInfo */ 1105 1106 1107 1108 1109 int 1110 FTPInitLibrary(const FTPLIPtr lip) 1111 { 1112 struct servent *ftp; 1113 1114 if (lip == NULL) 1115 return (kErrBadParameter); 1116 1117 (void) memset(lip, 0, sizeof(FTPLibraryInfo)); 1118 if ((ftp = getservbyname("ftp", "tcp")) == NULL) 1119 lip->defaultPort = (unsigned int) kDefaultFTPPort; 1120 else 1121 lip->defaultPort = (unsigned int) ntohs(ftp->s_port); 1122 1123 lip->init = 1; 1124 (void) STRNCPY(lip->magic, kLibraryMagic); 1125 1126 /* We'll initialize the defaultAnonPassword field 1127 * later when we try the first anon ftp connection. 1128 */ 1129 1130 #ifdef HAVE_LIBSOCKS 1131 SOCKSinit("libncftp"); 1132 lip->socksInit = 1; 1133 #endif 1134 return (kNoErr); 1135 } /* FTPInitLibrary */ 1136 1137 /* Open.c */ 1138