int FTPRemoteHelp(const FTPCIPtr cip, const char *const pattern, const FTPLineListPtr llp) { int result; ResponsePtr rp; if ((cip == NULL) || (llp == NULL)) return (kErrBadParameter); if (strcmp(cip->magic, kLibraryMagic)) return (kErrBadMagic); InitLineList(llp); rp = InitResponse(); if (rp == NULL) { result = kErrMallocFailed; cip->errNo = kErrMallocFailed; FTPLogError(cip, kDontPerror, "Malloc failed.\n"); } else { if ((pattern == NULL) || (*pattern == '\0')) result = RCmd(cip, rp, "HELP"); else result = RCmd(cip, rp, "HELP %s", pattern); if (result < 0) { DoneWithResponse(cip, rp); return (result); } else if (result == 2) { if (CopyLineList(llp, &rp->msg) < 0) { result = kErrMallocFailed; cip->errNo = kErrMallocFailed; FTPLogError(cip, kDontPerror, "Malloc failed.\n"); } else { result = kNoErr; } } else { cip->errNo = kErrHELPFailed; result = kErrHELPFailed; } DoneWithResponse(cip, rp); } return (result); } /* FTPRemoteHelp */
int FTPCloseHost(const FTPCIPtr cip) { ResponsePtr rp; int result; if (cip == NULL) return (kErrBadParameter); if (strcmp(cip->magic, kLibraryMagic)) return (kErrBadMagic); /* Data connection shouldn't be open normally. */ if (cip->dataSocket != kClosedFileDescriptor) FTPAbortDataTransfer(cip); result = kNoErr; if (cip->connected != 0) { rp = InitResponse(); if (rp == NULL) { cip->errNo = kErrMallocFailed; result = cip->errNo; } else { rp->eofOkay = 1; /* We are expecting EOF after this cmd. */ cip->eofOkay = 1; (void) RCmd(cip, rp, "QUIT"); DoneWithResponse(cip, rp); } } CloseControlConnection(cip); /* Dispose dynamic data structures, so you won't leak * if you OpenHost with this again. */ FTPDeallocateHost(cip); return (result); } /* FTPCloseHost */
int FTPQueryFeatures(const FTPCIPtr cip) { ResponsePtr rp; int result; LinePtr lp; char *cp, *p; if (cip == NULL) return (kErrBadParameter); if (strcmp(cip->magic, kLibraryMagic)) return (kErrBadMagic); if (cip->serverType == kServerTypeNetWareFTP) { /* NetWare 5.00 server freaks out when * you give it a command it doesn't * recognize, so cheat here and return. */ cip->hasPASV = kCommandAvailable; cip->hasSIZE = kCommandNotAvailable; cip->hasMDTM = kCommandNotAvailable; cip->hasREST = kCommandNotAvailable; cip->NLSTfileParamWorks = kCommandAvailable; cip->hasUTIME = kCommandNotAvailable; cip->hasCLNT = kCommandNotAvailable; cip->hasMLST = kCommandNotAvailable; cip->hasMLSD = kCommandNotAvailable; return (kNoErr); } rp = InitResponse(); if (rp == NULL) { cip->errNo = kErrMallocFailed; result = cip->errNo; } else { rp->printMode = (kResponseNoPrint|kResponseNoSave); result = RCmd(cip, rp, "FEAT"); if (result < kNoErr) { DoneWithResponse(cip, rp); return (result); } else if (result != 2) { /* We cheat here and pre-populate some * fields when the server is wu-ftpd. * This server is very common and we * know it has always had these. */ if (cip->serverType == kServerTypeWuFTPd) { cip->hasPASV = kCommandAvailable; cip->hasSIZE = kCommandAvailable; cip->hasMDTM = kCommandAvailable; cip->hasREST = kCommandAvailable; cip->NLSTfileParamWorks = kCommandAvailable; } else if (cip->serverType == kServerTypeNcFTPd) { cip->hasPASV = kCommandAvailable; cip->hasSIZE = kCommandAvailable; cip->hasMDTM = kCommandAvailable; cip->hasREST = kCommandAvailable; cip->NLSTfileParamWorks = kCommandAvailable; } /* Newer commands are only shown in FEAT, * so we don't have to do the "try it, * then save that it didn't work" thing. */ cip->hasMLST = kCommandNotAvailable; cip->hasMLSD = kCommandNotAvailable; } else { cip->hasFEAT = kCommandAvailable; for (lp = rp->msg.first; lp != NULL; lp = lp->next) { /* If first character was not a space it is * either: * * (a) The header line in the response; * (b) The trailer line in the response; * (c) A protocol violation. */ cp = lp->line; if (*cp++ != ' ') continue; if (ISTRNCMP(cp, "PASV", 4) == 0) { cip->hasPASV = kCommandAvailable; } else if (ISTRNCMP(cp, "SIZE", 4) == 0) { cip->hasSIZE = kCommandAvailable; } else if (ISTRNCMP(cp, "MDTM", 4) == 0) { cip->hasMDTM = kCommandAvailable; } else if (ISTRNCMP(cp, "REST", 4) == 0) { cip->hasREST = kCommandAvailable; } else if (ISTRNCMP(cp, "UTIME", 5) == 0) { cip->hasUTIME = kCommandAvailable; } else if (ISTRNCMP(cp, "MLST", 4) == 0) { cip->hasMLST = kCommandAvailable; cip->hasMLSD = kCommandAvailable; FTPExamineMlstFeatures(cip, cp + 5); } else if (ISTRNCMP(cp, "CLNT", 4) == 0) { cip->hasCLNT = kCommandAvailable; } else if (ISTRNCMP(cp, "Compliance Level: ", 18) == 0) { /* Probably only NcFTPd will ever implement this. * But we use it internally to differentiate * between different NcFTPd implementations of * IETF extensions. */ cip->ietfCompatLevel = atoi(cp + 18); } } } ReInitResponse(cip, rp); result = RCmd(cip, rp, "HELP SITE"); if (result == 2) { for (lp = rp->msg.first; lp != NULL; lp = lp->next) { cp = lp->line; if (strstr(cp, "RETRBUFSIZE") != NULL) cip->hasRETRBUFSIZE = kCommandAvailable; if (strstr(cp, "RBUFSZ") != NULL) cip->hasRBUFSZ = kCommandAvailable; /* See if RBUFSIZ matches (but not STORBUFSIZE) */ if ( ((p = strstr(cp, "RBUFSIZ")) != NULL) && ( (p == cp) || ((p > cp) && (!isupper(p[-1]))) ) ) cip->hasRBUFSIZ = kCommandAvailable; if (strstr(cp, "STORBUFSIZE") != NULL) cip->hasSTORBUFSIZE = kCommandAvailable; if (strstr(cp, "SBUFSIZ") != NULL) cip->hasSBUFSIZ = kCommandAvailable; if (strstr(cp, "SBUFSZ") != NULL) cip->hasSBUFSZ = kCommandAvailable; if (strstr(cp, "BUFSIZE") != NULL) cip->hasBUFSIZE = kCommandAvailable; } } DoneWithResponse(cip, rp); } return (kNoErr); } /* FTPQueryFeatures */
int FTPLoginHost(const FTPCIPtr cip) { ResponsePtr rp; int result = kErrLoginFailed; int anonLogin; int sentpass = 0; int fwloggedin; int firstTime; char cwd[512]; if (cip == NULL) return (kErrBadParameter); if ((cip->firewallType < kFirewallNotInUse) || (cip->firewallType > kFirewallLastType)) return (kErrBadParameter); if (strcmp(cip->magic, kLibraryMagic)) return (kErrBadMagic); anonLogin = 0; if (cip->user[0] == '\0') (void) STRNCPY(cip->user, "anonymous"); if ((strcmp(cip->user, "anonymous") == 0) || (strcmp(cip->user, "ftp") == 0)) { anonLogin = 1; /* Try to get the email address if you didn't specify * a password when the user is anonymous. */ if (cip->pass[0] == '\0') { FTPInitializeAnonPassword(cip->lip); (void) STRNCPY(cip->pass, cip->lip->defaultAnonPassword); } } rp = InitResponse(); if (rp == NULL) { result = kErrMallocFailed; cip->errNo = kErrMallocFailed; goto done2; } for (firstTime = 1, fwloggedin = 0; ; ) { /* Here's a mini finite-automaton for the login process. * * Originally, the FTP protocol was designed to be entirely * implementable from a FA. It could be done, but I don't think * it's something an interactive process could be the most * effective with. */ if (firstTime != 0) { rp->code = 220; firstTime = 0; } else if (result < 0) { goto done; } switch (rp->code) { case 220: /* Welcome, ready for new user. */ if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "USER %s", cip->user); } else if (cip->firewallType == kFirewallUserAtSite) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "USER %s@%s", cip->user, cip->host); } else if (cip->firewallType == kFirewallUserAtUserPassAtPass) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "USER %s@%s@%s", cip->user, cip->firewallUser, cip->host); } else if (cip->firewallType == kFirewallUserAtSiteFwuPassFwp) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "USER %s@%s %s", cip->user, cip->host, cip->firewallUser); } else if (cip->firewallType == kFirewallFwuAtSiteFwpUserPass) { /* only reached when !fwloggedin */ ReInitResponse(cip, rp); result = RCmd(cip, rp, "USER %s@%s", cip->firewallUser, cip->host); } else if (cip->firewallType > kFirewallNotInUse) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "USER %s", cip->firewallUser); } else { goto unknown; } break; case 230: /* 230 User logged in, proceed. */ case 231: /* User name accepted. */ case 202: /* Command not implemented, superfluous at this site. */ if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) goto okay; /* Now logged in to the firewall. */ fwloggedin++; if (cip->firewallType == kFirewallLoginThenUserAtSite) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "USER %s@%s", cip->user, cip->host); } else if (cip->firewallType == kFirewallUserAtUserPassAtPass) { goto okay; } else if (cip->firewallType == kFirewallOpenSite) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "OPEN %s", cip->host); } else if (cip->firewallType == kFirewallSiteSite) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "SITE %s", cip->host); } else if (cip->firewallType == kFirewallFwuAtSiteFwpUserPass) { /* only reached when !fwloggedin */ ReInitResponse(cip, rp); result = RCmd(cip, rp, "USER %s", cip->user); } else /* kFirewallUserAtSite */ { goto okay; } break; case 421: /* 421 Service not available, closing control connection. */ result = kErrHostDisconnectedDuringLogin; goto done; case 331: /* 331 User name okay, need password. */ if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) { if ((cip->pass[0] == '\0') && (cip->passphraseProc != NoGetPassphraseProc)) (*cip->passphraseProc)(cip, &rp->msg, cip->pass, sizeof(cip->pass)); ReInitResponse(cip, rp); result = RCmd(cip, rp, "PASS %s", cip->pass); } else if (cip->firewallType == kFirewallUserAtSite) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "PASS %s", cip->pass); } else if (cip->firewallType == kFirewallUserAtUserPassAtPass) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "PASS %s@%s", cip->pass, cip->firewallPass); } else if (cip->firewallType == kFirewallUserAtSiteFwuPassFwp) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "PASS %s", cip->pass); } else if (cip->firewallType == kFirewallFwuAtSiteFwpUserPass) { /* only reached when !fwloggedin */ ReInitResponse(cip, rp); result = RCmd(cip, rp, "PASS %s", cip->firewallPass); } else if (cip->firewallType > kFirewallNotInUse) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "PASS %s", cip->firewallPass); } else { goto unknown; } sentpass++; break; case 332: /* 332 Need account for login. */ case 532: /* 532 Need account for storing files. */ if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "ACCT %s", cip->acct); } else if (cip->firewallType == kFirewallUserAtSiteFwuPassFwp) { ReInitResponse(cip, rp); result = RCmd(cip, rp, "ACCT %s", cip->firewallPass); } else { /* ACCT not supported on firewall. */ goto unknown; } break; case 530: /* Not logged in. */ result = (sentpass != 0) ? kErrBadRemoteUserOrPassword : kErrBadRemoteUser; goto done; case 501: /* Syntax error in parameters or arguments. */ case 503: /* Bad sequence of commands. */ case 550: /* Can't set guest privileges. */ goto done; default: unknown: if (rp->msg.first == NULL) { Error(cip, kDontPerror, "Lost connection during login.\n"); } else { Error(cip, kDontPerror, "Unexpected response: %s\n", rp->msg.first->line ); } goto done; } } okay: /* Do the application's connect message callback, if present. */ if (cip->onLoginMsgProc != 0) (*cip->onLoginMsgProc)(cip, rp); DoneWithResponse(cip, rp); result = 0; cip->loggedIn = 1; /* Make a note of what our root directory is. * This is often different from "/" when not * logged in anonymously. */ if (cip->startingWorkingDirectory != NULL) { free(cip->startingWorkingDirectory); cip->startingWorkingDirectory = NULL; } if ((cip->doNotGetStartingWorkingDirectory == 0) && (FTPGetCWD(cip, cwd, sizeof(cwd)) == kNoErr)) { cip->startingWorkingDirectory = StrDup(cwd); } /* When a new site is opened, ASCII mode is assumed (by protocol). */ cip->curTransferType = 'A'; PrintF(cip, "Logged in to %s as %s.\n", cip->host, cip->user); /* Don't leave cleartext password in memory. */ if ((anonLogin == 0) && (cip->leavePass == 0)) (void) memset(cip->pass, '*', strlen(cip->pass)); if (result < 0) cip->errNo = result; return result; done: DoneWithResponse(cip, rp); done2: /* Don't leave cleartext password in memory. */ if ((anonLogin == 0) && (cip->leavePass == 0)) (void) memset(cip->pass, '*', strlen(cip->pass)); if (result < 0) cip->errNo = result; return result; } /* FTPLoginHost */