📄 sftpc.cpp
字号:
// sftpc.cc// SafeTP command-line (stand-alone) client// copyright SafeTP Development Group, Inc., 2000 Terms of use are as specified in license.txt#include <iostream.h> // cout#include <stdlib.h> // atoi, system#include <stdio.h> // FILE functions#include <string.h> // strstr#include <ctype.h> // tolower, isspace#include <signal.h> // signal#include "provider.h" // SecurityProvider#include "exc.h" // exceptions#include "base64t.h" // base64transform#include "globrand.h" // {read,save}RandomSeed#include "sockutil.h" // socket funcs, INVALID_SOCKET#include "nonport.h" // getCurrentUsername, readNonechoString#include "filesrc.h" // FileInputSource#include "test.h" // PVAL#include "glob.h" // glob#include "strtokp.h" // StrtokParse#include "keyutils.h" // sm_testKey#include "syserr.h" // xSysError#include "addent.h" // getEntropyFromConsole#include "makekeys.h" // GenerateElgamalKey#include "selgamal.h" // ElGamal_1024bit_parameters#include "sdsa.h" // DSABrandedPublicKey, knownDSAPublicKeyVersions#include "sftpver.h" // SFTP_version#include "sftpcdoc.h" // command documentation and aliases#include "sftpc.h" // this module#ifndef NDEBUG# define diagnostic(expr) printDiagnostic(stringb(expr))#else# define diagnostic(expr) ((void)0)#endif// true: CRLF separates text lines; false: LF separates text lines#ifndef DEFAULT_LOCAL_CONVENTION# ifdef __UNIX__# define DEFAULT_LOCAL_CONVENTION false# else // windows# define DEFAULT_LOCAL_CONVENTION true# endif#endif#ifdef _MSC_VER // this one has to do with comparing signed and unsigned #pragma warning(disable: 4018) // dob: I'm sick of seeing this warning // this one is about 'this' appearing in a member initializer list, // which I think is fine but MS apparently disagrees; // TODO: look at spec to see what they say about 'this' in member inits #pragma warning(disable: 4355) // dob: I'm sick of seeing this warning#endif// utilstring getCurrentUsername(){ char buf[80]; getCurrentUsername(buf, 80); return string(buf);}string readNonechoString(char const *prompt){ char buf[80]; readNonechoString(buf, 80, prompt); return string(buf);}// the initializers here should be maintained in the same// order, and with the same spacing, as they appear in// the class declarationSFTPC::SFTPC() : security(NULL), securityName("X-SafeTP1"), keyDatabase(), keyEnvironment(*this /*policy*/, keyDatabase), serverPort(FTPD_PORT), serverName("localhost"), serverAddress(INADDR_NONE), control(NULL), dataBuffer(), PBSZ(0), digt(), sentFirstAuth(false), authenticated(false), dataSecLevel(DSL_PRIVATE), // default level requestedPBSZ(0x8000), // 32k transferPassively(true), // 9/17/00: new default for ubiquitous firewalls asciiTransfers(false), localConventionIsCRLF(DEFAULT_LOCAL_CONVENTION), printOutgoing(false), interactivePrompting(false), quitProgram(false), printHashMarks(false), useRfc959(false), acceptNewKeysSilently(false), auto959Dropdown(false), alwaysAcceptServerIPMismatch(false), showDiagnostics(false), showAdats(false), useDebuggingLogin(false), quitAfterNegotiation(false), binaryTransferAnyway(false), localGlobbing(true){}SFTPC::~SFTPC(){ if (security) { delete security; } if (control) { delete control; }}void SFTPC::printDiagnostic(char const *msg){ if (showDiagnostics) { cerr << msg << endl; }}// read the next reply, and if it is encrypted, decrypt it; then return itReply SFTPC::readReply(){ // wait for the reply diagnostic("waiting for server reply"); Reply reply(*control); // look at reply code ReplyCode code = reply.getCode(); // decrypt the message if necessary if (first(code) == RCF_ENCRYPTED) { diagnostic((int)reply.getCode() << " " << reply.getAllTextAsOneLine()); // it can only be encrypted if we're authenticated xassert(authenticated); // allocate and prepare block string encoded = reply.getAllTextAsOneLine(); // todo: this is wrong for multiline responses... int encodedLen = encoded.length(); int decodedLen = 1 + // null at end security->control().maximumDecodedSize( base64decoder.maxOutputSize(encodedLen)); decodedLen = mymax(decodedLen, encodedLen); DataBlock block(decodedLen); block.setFromString(encoded); // un-64 base64decoder.trans(block); // decrypt security->control().decode(block); // add a null in case the reply didn't include // a final CRLF (allowed by RFC 2228) block.addNull(); // parse it as a reply, now that it's decoded char const *temp = (char const*)block.getDataC(); Reply decryptedReply(temp); // print it cout << decryptedReply.getAllText(); // already has CRLF return decryptedReply; } else { // print reply if (showAdats || !reply.containsADAT()) { cout << reply.getAllText(); // already has CRLF } // it can only be plaintext if control channel unencrypted xassert(!isControlChannelEncrypted()); // doesn't need to be decrypted return reply; }}Reply SFTPC::readFinalReply(){ // loop while the reply is intermediate int intermediates = 0; for(;;) { // get and decrypt reply, then print it Reply reply = readReply(); // do DIGT and ADAT processing handleAdatAndDigt(reply); // look at reply code if (first(reply.getCode()) != RCF_POSITIVE_PRELIMINARY) { return reply; } else { intermediates++; if (intermediates > 1) { cout << "warning: server has violated FTP protocol by\n" << " sending " << intermediates << " intermediate replies (1 is max)\n"; } } }}// we expect a reply; listen for the reply, decode it if// necessary, display it, then keep listening IF the reply// was a preliminary reply (otherwise return final code)ReplyCode SFTPC::readFinalReplyCode(){ return readFinalReply().getCode();}// get the reply, throw exception on error reply codeReplyCode SFTPC::readAndCheckFinalReplyCode(){ // read, print reply Reply reply = readFinalReply(); // confirm is ok checkReplyCode(reply); // return the code itself return reply.getCode();}// throw xReply if there is a problemvoid SFTPC::checkReplyCode(Reply const &reply) const{ ReplyCodeFirst f = first(reply.getCode()); if (!( f == RCF_POSITIVE_COMPLETION || f == RCF_POSITIVE_INTERMEDIATE )) { xReply x(reply); THROW(x); } // 6/7/99: I had been allowing RCF_POSITIVE_INTERMEDIATE, which // doesn't appear to be a possible return from readFinalReply. // 6/8/99: doh! it's "preliminary" which can't be returned; // "intermediate" can be (and is; e.g., "user" response is 331)}void SFTPC::handleAdatAndDigt(Reply &reply){ // the reply may go into the DIGT calculation if (isDigtActive()) { digt.add(reply); } // see if the reply contains some data bool replyHasAdat = reply.containsADAT(); // process the ADAT if necessary if (replyHasAdat) { // make sure 'security' is expecting this xassert(security && security->control().expectingIncomingAdat()); // get text string text = reply.getAllTextAsOneLine(); // un-64 the reply text DataBlock adat; base64decode(adat, text + Reply::ADATTagLen); // what did we get? if (showAdats) { adat.print("server adat"); } // pass this to the security mechanism security->control().incomingAdat(adat); }}void SFTPC::sendRequestToWire(Request const &req){ // send the request diagnostic("sending request: " << req.getText()); req.send(control->socket); // possibly add it to the digest calculation if (isDigtActive()) { digt.add(req); }}// send a request; this request is *not* encrypted as it is passed// don't process the reply; don't even take it from the socketvoid SFTPC::sendRequest(Request const &req){ if (printOutgoing) { // this is how "ftp" does it cout << "--> " << req.getTextNoPassword() << endl; } if (isControlChannelEncrypted()) { // allocate and prepare buffer string const &decoded = req.getText(); int decodedLen = decoded.length(); int encodedLen = 1 + // null terminator base64encoder.maxOutputSize( security->control().maximumEncodedSize(decodedLen)); DataBlock block((byte const*)decoded.pcharc(), decodedLen, mymax(decodedLen, encodedLen)); // encrypt security->control().encode(block); // base64 base64encoder.trans(block); // construct Request to hold it Request encryptedReq(CMD_ENC, (char const*)block.getDataC()); // send it diagnostic("sending plaintext request: " << req.getTextNoPassword()); sendRequestToWire(encryptedReq); } else { // control channel is in the clear sendRequestToWire(req); }}// send the request and process the reply (expecting a success reply)void SFTPC::checkedRequest(Request const &req){ // send request sendRequest(req); // process reply readAndCheckFinalReplyCode();}// request variantsvoid SFTPC::sendRequest(RequestCommand cmd, char const *args){ sendRequest(Request(cmd, args));}void SFTPC::checkedRequest(RequestCommand cmd, char const *args){ checkedRequest(Request(cmd, args));}// manages all communication from AUTH to "23{4,5} Security data exchange complete."bool SFTPC::negotiationPart2(){ // create the security provider security = SecurityProvider:: findSecurityMechanism(securityName, &keyEnvironment, getLocalAddress(control->socket), getRemoteAddress(control->socket)); if (!security) { xfailure(stringb("unknown security mechanism: " << securityName)); } cout << "Starting negotiation...\n"; // send an auth request (will be added to digt automatically) sentFirstAuth = true; sendRequest(CMD_AUTH, securityName); // get reply, process ADAT if included ReplyCode authReplyCode = readFinalReplyCode(); if (first(authReplyCode) != RCF_POSITIVE_COMPLETION && first(authReplyCode) != RCF_POSITIVE_INTERMEDIATE) { // AUTH not understood delete security; security = NULL; return false; } // send an ADAT if necessary while (security->control().hasOutgoingAdat()) { // get data DataBlock adat; security->control().getNextOutgoingAdat(adat); // what are we sending? if (showAdats) { adat.print("client adat"); } // encode as base64 string b64adat = base64encode(adat); // send it (will be added to digt automatically) sendRequest(CMD_ADAT, b64adat); // get reply, process ADAT if included readAndCheckFinalReplyCode(); } // if we don't have data to send, but are expecting some, that // is an error, because we have no way to 'prompt' the server // for more xassert(!security->control().expectingIncomingAdat()); // from now on all control channel communication is encrypted authenticated = true; // compare digests compareDigests(); cout << "Negotiation completed.\n"; return true;}void SFTPC::compareDigests(){ // ask for server's digest sendRequest(CMD_DIGT); // get server's reply Reply reply = readFinalReply(); #if 0 // old; changed it after talking with Dan on 9/1/99 // check it (we require that the server support this command, // because if we allow it not to, then a hacker has an easy // way to defeat it) xassert(first(reply.getCode()) == RCF_POSITIVE_COMPLETION); #else // we will allow the server to not support DIGT; the rationale // here is that if a hacker can insert a bogus failure response, // he/she can just as easily insert a bogus DIGT value; that is, // we are using a threat model where the control channel is believed // *authentic* but not necessarily *confidential*, as the former // would require an on-line attack but the latter can utilize an // off-line attack (e.g., on a weak encryption algorithm) if (first(reply.getCode()) != RCF_POSITIVE_COMPLETION) { cout << "Server doesn't understand DIGT command. Proceeding anyway.\n"; return; } #endif // find where the base-64 digest string starts enum { DIGTEQLEN = 5 }; // length of "DIGT=" string replyText = reply.getAllTextAsOneLine(); char const *digteq = strstr(replyText, "DIGT="); xassert(digteq); // otherwise the server didn't include a digest!
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -