📄 sftpc.cpp
字号:
// decode the base-64 data DataBlock serverDigest; base64decode(serverDigest, digteq + DIGTEQLEN); //int foobarbaz = replyText.length(); // ? //serverDigest.print("server's DIGT"); // retrieve our own digest value DataBlock clientDigest = digt.getDigt(); //clientDigest.print("client's DIGT"); // compare if (serverDigest != clientDigest) { // This is the first of several similarly-formatted messages that // sftpc can generate. The idea here is to blow the whistle rather // loudly, so an attacker considering mounting an attack that would // provoke such a message will decide it's too high-profile. Of // course, for this to work, the false-positive rate must be low. cout << "\n" " ************************\n" " * Digest Mismatch! *\n" " ************************\n" "\n" "The server saw a different negotiation sequence than I\n" "did. This could be the result of someone tampering with\n" "the data while in transit. You could just try again...\n" "If the problem persists, contact your network administrator;\n" "the situation must be resolved before communications with\n" "this server can be trusted.\n" "\n" ; xfailure("Connection aborted due to digest mismatch."); }}// implements dropdown behaviorbool SFTPC::negotiationPart1(int &retval){ #define FAILRETURN(val) retval=val; return false /* user-; */ // do security mechanism negotiation if (!useRfc959) { if (!negotiationPart2()) { // server responded negatively to AUTH command if (keyDatabase.containsKey(serverAddress)) { cout << "\n" " ************************\n" " * Dropdown Attack! *\n" " ************************\n" "\n" "The server claims to not understand the AUTH mechanism,\n" "but we have a key on record for this server! Possibilities:\n" " - An attacker is attempting to fool sftpc into using an\n" " insecure protocol.\n" " - SafeTP has been turned off at the server.\n" " - The prior contact was on another port; did you try 353?\n" "If you want to force sftpc to connect insecurely anyway, use\n" "the -9 switch.\n" ; FAILRETURN(5); } else if (auto959Dropdown) { cout << "Server doesn't understand AUTH mechanism. Reverting\n" "to insecure protocol (because -Q was specified).\n"; useRfc959 = true; } else { cout << "Exiting because server doesn't understand AUTH mechanism.\n" "There are several ways to proceed:\n" " - Connect to the server using the normal (insecure) FTP\n" " protocol. To do this, give sftpc the -9 flag.\n" " Be aware that your password might be eavesdropped!\n" " - Use a completely different system, such as scp or Kerberos.\n" " - Try a different port. In particular, some sites have\n" " SafeTP on port 353 instead of the default port (21):\n" " % sftpc the.server 353\n" ; FAILRETURN(4); // failed negotiation } } else { // after successful negotiation, do data channel protection, // if the user wants something other than cleartext if (dataSecLevel != DSL_CLEAR && setDataEncryption(dataSecLevel)) { // successfully set prot level; we will assume the server's // printed response is adequate to inform user } else { dataSecLevel = DSL_CLEAR; printf("Data channel is currently unencrypted (change with 'prot').\n"); } } } #undef FAILRETURN return true; // success}// ------------------ data channel stuff ---------------------// bind a listener socket, tell server about it, return itSOCKET SFTPC::setupActiveConnection(){ // bind and listen to a socket SOCKET dataListener = listen_socket(PORT_ANY); // determine which port was bound sockaddr_in localPortAddr; getSockName(dataListener, localPortAddr); // determine the IP address by which the server knows us sockaddr_in localIPAddr; getSockName(control->socket, localIPAddr); // format a string to contain this information for // the PORT command long ip = ntohl(localIPAddr.sin_addr.s_addr); int port = ntohs(localPortAddr.sin_port); diagnostic("bound socket to local port " << port); // write the a,a,a,a,p,p string string commandText = constructPortArg(ip, port); // send the PORT command, process the reply checkedRequest(CMD_PORT, commandText); // return the dataListener socket return dataListener;}// ask the server to bind a listener, and connect to it, and// return the connected socket (this relies on the server being// able to accept() a connection even though the corresponding// data transfer hasn't yet begun)SOCKET SFTPC::setupPassiveConnection(){ // send PASV command sendRequest(CMD_PASV); // get reply Reply reply = readReply(); checkReplyCode(reply); // minor fix: w/o this, errors produce a confusing message // parse the address and port IPAddress addr; int port; parsePasvArgument(reply.getAllTextAsOneLine(), addr, port); // connect to that address and port diagnostic("trying to connect to " << formatAddress(addr) << ", port " << port); return connect_socket(addr, port);}// ask the server to switch to a specific file transfer mode// (ascii or image (binary))void SFTPC::requestAsciiMode(bool ascii){ // debugging: to test my CRLF routines, I want to have the server send // files in binary, but treat them as if they were sent as // ascii if (binaryTransferAnyway) { ascii = false; } // send TYPE A (ascii) or TYPE I (image/binary), // and verify the server is happy with that checkedRequest(CMD_TYPE, ascii? "A" : "I"); // possible optimization: could remember what mode we think // the server is in, and only change it if it's different than // what we want; my usual ftp client does *not* do this // optimization, which makes me suspect some servers could be // flaky about remembering the transfer mode state...}// active: send PORT and return listening socket// passive: send PASV and return connected data channel socketSOCKET SFTPC::dataChannelSetup(RequestCommand cmd){ // set transfer mode requestAsciiMode(transferModeForCmd(cmd)); if (transferPassively) { // PASV and connect() return setupPassiveConnection(); } else { // PORT and listen() return setupActiveConnection(); }}bool SFTPC::transferModeForCmd(RequestCommand cmd) const{ if (cmd == CMD_LIST || cmd == CMD_NLST) { // always do directory tranfers in ASCII mode return true; } else { // file transfers are in mode of user's choosing return asciiTransfers; }}// second phase of establishing a data connection, given// that dataChannelSetup did first part, and 'dataListener'// is its return valueSOCKET SFTPC::openDataChannel(SOCKET dataListener){ // get the data channel if (!transferPassively) { // accept the connection diagnostic("waiting for server to connect to my data port"); SOCKET dataChannel = accept_socket(dataListener); diagnostic("server connected: " << dataChannel); // stop listening close_socket(dataListener); return dataChannel; } else { // the dataListener is actually the already-connected socket return dataListener; }}void SFTPC::check150Reply(){ diagnostic("waiting for the 125/150"); Reply interReply = readReply(); ReplyCode code = interReply.getCode(); if (first(code) == RCF_POSITIVE_PRELIMINARY) { diagnostic("got the 125/150 as expected"); } else if (first(code) == RCF_POSITIVE_COMPLETION || first(code) == RCF_POSITIVE_INTERMEDIATE) { diagnostic("oops.. that was supposed to be a 125/150"); printf("unexpected reply: %d %s\n", code, interReply.getAllTextAsOneLine().pcharc()); // try to keep going anyway } else { // since the reply is an error, its text will already have // been printed xReply x(interReply); THROW(x); // not reached }}SOCKET SFTPC::startDataTransfer(RequestCommand cmd, char const *arg){ // transfer mode, then PORT or PASV SOCKET dataListener = dataChannelSetup(cmd); // protocol semantics are that the incrementing is associated // with the *attempt*, so just before we send the command, we // increment (if the send itself fails, then we've lost our // connection to the server anyway) if (security && isDataChannelEncrypted()) { security->data().newFile(dataSecLevel); } // send the primary request sendRequest(cmd, arg); check150Reply(); // get channel from listener SOCKET dataChannel = openDataChannel(dataListener); // setup buffer if (!isDataChannelEncrypted() && dataBuffer.getAllocated() < UNENCRYPTED_BUFSIZE) { // this was the site of a bug in 1.20, because I was not testing // the sizes; thus, turning encryption off and back on would // truncated dataBuffer, causing an exception fail on data xfer dataBuffer.setAllocated(UNENCRYPTED_BUFSIZE); } return dataChannel;}// convenient way to ensure a socket gets closed, so the protocol// stream remains viableclass SocketCloser { SOCKET s;public: SocketCloser(SOCKET sk) : s(sk) {} ~SocketCloser();};SocketCloser::~SocketCloser(){ close_socket(s);}// do a data transfer// cmd arg localFname// ------ ------------------- -----------------// retr remote fname local fname to write to// stor,stou,appe remote fname local fname to read from// list,nlst optional dir to list unusedvoid SFTPC::dataTransfer(RequestCommand cmd, char const *arg, char const *localFname){ // check local file first FILE *fp = NULL; if (cmd == CMD_RETR || cmd == CMD_STOR || cmd == CMD_STOU || cmd == CMD_APPE) { // open the file xassert(localFname); fp = fopen(localFname, cmd==CMD_RETR? "wb" : "rb"); if (!fp) { THROW(xBase(stringb("failed to open local file " << localFname))); } } // dataChannel scope { SOCKET dataChannel; try { // PORT, etc. dataChannel = startDataTransfer(cmd, arg); } catch (...) { // common cause: server says file doesn't exist for RETR if (fp != NULL) { // Dan caught a bug here.. wasn't checking for fp == NULL.. fclose(fp); // may as well, since we need this before removal } if (cmd == CMD_RETR) { // we already deleted any existing file, and now we failed to get // it from the server, so it's 0-length; go ahead and remove it removeFile(localFname); } diagnostic("re-throwing xReply"); throw; // just relay the error condition itself } SocketCloser closer(dataChannel); // do transfer bool transferMode = transferModeForCmd(cmd); switch (cmd) { case CMD_RETR: { // copy data from server to client FileOutputDest destStream(fp); // copy data from the socket to the file until the // socket is closed int totlen; try { if (!printHashMarks) { totlen = copyFromSocketToStream( dataChannel, destStream, transferMode); } else { EchoOutputStream echo(destStream); totlen = copyFromSocketToStream( dataChannel, echo, transferMode); cout << endl; } } catch (xBase &x) { cout << "WARNING: file " << localFname << " is probably " "incomplete.\n"; // something subtle is happening here -- if the data channel is // closed unexpectedly, an xSocket is thrown.. if the toplevel // loop sees an xSocket, it assumes it was caused by a *control* // connection closure.. so we catch it here, and re-throw it as // an xBase (since C++ exceptions are typed statically), which // the toplevel loop (counterintuitively) assumes is less // serious throw x; // all this nonsense exposes a serious flaw in the way I've // designed my exception-handling here.. unfortunately, I am // unaware of any techniques for improving the design, since // it's very difficult to reason about... } diagnostic("received " << totlen << " bytes to local file " << localFname); fclose(fp); break; } case CMD_STOR: case CMD_STOU: case CMD_APPE: { // all the commands that copy data from the client to // the server FileInputSource sourceStream(fp); // copy data from the file to the socket int totlen; try { if (!printHashMarks) { totlen = copyFromStreamToSocket( sourceStream, dataChannel, transferMode); } else { EchoInputStream echo(sourceStream); totlen = copyFromStreamToSocket( echo, dataChannel, transferMode); cout << endl; } } catch (xBase &x) { cout << "WARNING: remote file " << arg << " is probably " "incomplete.\n"; throw x; // see above } diagnostic("sent " << totlen << " bytes from local file " << localFname); fclose(fp); break; } case CMD_LIST: case CMD_NLST: { // server sends data that is displayed on client's console diagnostic("waiting for directory listing"); // copy data from the socket to the terminal until the // socket is closed FileOutputDest destStream(stdout); int totlen = copyFromSocketToStream(dataChannel, destStream, transferMode); diagnostic("received " << totlen << " bytes as directory listing"); break; } default: xfailure("dataTransfer called with bad ftp command"); } // dataChannel closed by closer } // get and check final 226 readAndCheckFinalReplyCode();}STATICDEF void SFTPC::CRLF_to_LF( StreamInputSource &src, StreamOutputDest &dest){ CRLF_to_something(src, dest, ::CRLF_to_LF);}STATICDEF void SFTPC::CRLF_to_CRLF( StreamInputSource &src, StreamOutputDest &dest){ CRLF_to_something(src, dest, ::CRLF_to_CRLF);}STATICDEF void SFTPC::CRLF_to_something(
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -