📄 sftpd.cpp
字号:
case CMD_USER: case CMD_ACCT: { if (state != STATE_UNAUTHENTICATED) { clientReply(RC_PROTECTION_LEVEL_DENIED, "To enter RFC 959 (unencrypted) compatibility mode, " "USER or ACCT must be the *first* command."); break; } char const *loginName = request.getData(); if (checkLoginName(loginName)) { break; // it took action } // this is the *only* place that 'state' might be changed to // STATE_959_INTEROP, and therefore the only place that needs to // check allowRfc959 if (allowRfc959 || (allowRfc959_anon && isAnonLogin(loginName))) { // 959 dropdown state = STATE_959_INTEROP; log(LE_HANDOFF, "dropping down to 959 compatibility mode"); // call this function recursively to handle 959 command handleOriginalRequest(request); } else { // disallow Reply r(RC_PROTECTION_LEVEL_DENIED, "Must send AUTH first. RFC 959 compatibility is off."); r.append("This is a secure FTP server, and you are running an insecure client."); r.append("You can get software to secure your FTP client at:"); r.append("http://safetp.cs.berkeley.edu/"); clientReply(r); } break; } case CMD_QUIT: case CMD_HELP: // could offer info about AUTH/ADAT here... // relay to server, regardless of our state send(request, server_control); break; case CMD_MIC: // supported as if it is ENC case CMD_CONF: // same deal case CMD_ENC: { if (!controlChannelEncrypted()) { clientReply(RC_BAD_COMMAND_SEQUENCE, "Cannot send encrypted requests until authenticated."); break; } xassert(security); try { // allocate and prepare the translation buffer int requestLen = base64length(request.getData()); int bufferLen = security->control().maximumDecodedSize( base64decoder.maxOutputSize(requestLen)) + 1; bufferLen = mymax(bufferLen, requestLen); DataBlock requestBlock((byte const*)request.getData(), requestLen, bufferLen); // un-64 the request base64decoder.trans(requestBlock); // decrypt the request security->control().decode(requestBlock); // add a null terminator requestBlock.addNull(); // debugging output; note that the password protection offered // here is NOT sufficient to conceal the password from the // debug output (because debug output has keys, etc.) Request newReq((char const*)requestBlock.getDataC()); diagnostic("decrypted request: " << newReq.getTextNoPassword()); // handle the request handleDecryptedRequest(newReq); } catch (xSecurity &x) { log(LE_WARNING, "SECURITY VIOLATION: " << x.why()); // go ahead and tell the client.. if a hacker is really // present, this may not get there; but the hacker won't // be able to decrypt it, so there's no harm done (unless // this reveals something critical about the internal // state to a client who is a hacker...) clientReply( Reply(RC_FAILED_SECURITY_CHECK, x.why())); // but, since we may be being hacked, and recovery from things // like sequence # invalid is not generally possible, close // the connection closeConnections(); } break; } case CMD_UNKNOWN: clientReply(RC_UNRECOGNIZED_COMMAND, stringb("I don't understand \"" << request.getCommand() << "\".")); break; default: // firewall friendliness: The issue here is that while many // firewalls (including NAT firewalls) already have provisions // for allowing FTP connections to penetrate, but when we // encrypt the control channel, we hide the protocol commands // necessary for the firewall to know which ports to open. // // So, a solution that works with at least some firewalls // (specifically known to work with Linux IP masquerading and // Windows Internet Connection Sharing) is to send redundant // control traffic in the clear. When the firewall sees this // traffic, it will open the right ports. The same commands // are repeated within in the integrity- and privacy-protected // control channel. // // This traffic is *ignored* by the state machine logic on both // sides -- it is purely for the firewall to act upon. An // attacker who sees the traffic learns nothing useful *if* data // encryption is on; an attacker who modifies this traffic // simply DOSes the connection. #if 1 // set to 0 to disable firewall-friendliness behavior bool supportClientSideFirewallFriendliness = true; if (supportClientSideFirewallFriendliness) { if (request.getCmd() == CMD_PORT) { string portstr = request.getData(); // be careful to strip off any trailing garbage while (portstr.length() && !isdigit(portstr[portstr.length()-1])) { portstr = string(portstr.pchar(), portstr.length()-1); } stringBuilder s("Accepting cleartext PORT command to provide client-side firewall friendliness."); s << " (PORT=" << portstr << ")"; unencryptedClientReply(Reply(RC_COMMAND_OK, s)); break; } else if (request.getCmd() == CMD_LIST) { // we accept LIST too because there might be a firewall that // doesn't open the ports until it sees a real data-transfer // command; since no directory list is actually returned, this // is presumably safe unencryptedClientReply(Reply(RC_FILE_STATUS_OK, "Accepting cleartext LIST command to provide client-side firewall friendliness.")); break; } } #endif clientReply(RC_PROTECTION_LEVEL_DENIED, "I will not accept cleartext commands."); break; }}// the returned value is used in two places:// - as the address supplied in the protocol stream during negotiation;// the client checks this against the address it specified in the// connect() call// - as the address in a reply to PASV; the client will then try to// connect to the address during a data transferIPAddress SFTPD::getMyIPAsClientSeesIt() const{ // decide which local address to claim; normal case (no firewalls, // etc.) is it's just my control channel interface address IPAddress localToUse = getLocalAddress(client_control); // -i: use of an alternate ip address if ( useFakeIPAddress && (getLocalAddress(client_control) != getRemoteAddress(client_control)) ) { // second condition is so localhost isn't faked out // (for testing purposes) localToUse = fakeIPAddress; } return localToUse;}void SFTPD::closeConnections(){ closeIf(client_control); closeIf(server_control); closeDataSockets();}// in addition to being called from closeConnections(), this is called// when we received a PORT or PASV command; it is needed because if// e.g. we got two PASV commands in a row, we want to shut down the// sockets associated with the first before opening new ones (usually// on different ports) for the secondvoid SFTPD::closeDataSockets(){ closeIf(client_data); closeIf(server_data); closeIf(client_listen); closeIf(server_listen);}void SFTPD::closeIf(SOCKET &s){ if (s != INVALID_SOCKET) { closeSocket(s); }}void SFTPD::closeSocket(SOCKET &s){ socket_diagnostic("closing socket " << s); close_socket(s); s = INVALID_SOCKET;}// handle a request that was encryped (it has now been decrypted)void SFTPD::handleDecryptedRequest(Request const &request){ xassert(controlChannelEncrypted()); switch (request.getCmd()) { // --------------- 2228 commands ------------------------- case CMD_PBSZ: { if (state != STATE_AUTHENTICATED) { clientReply(RC_BAD_COMMAND_SEQUENCE, "Can send PBSZ only once (for this server), " "after authentication."); break; } // parse the buffer length, represented as decimal integer // as per RFC2228 { unsigned long dataBufferSize = f_ing_strtoul(request.getData(), 10 /*radix*/); if (dataBufferSize < MIN_BUFFER_SIZE) { // 2228 departure; the semantics of RC_PBSZ_TOO_SMALL are that // the client must re-submit the PBSZ request clientReply( RC_PBSZ_TOO_SMALL, stringb("PBSZ is too small; PBSZmin=" << (int)MIN_BUFFER_SIZE)); break; } // store the value, having passed policy tests maxBlockSize = dataBufferSize; } // if it's too big, we just use and send back a smaller number bool wasTooBig = false; if (maxBlockSize > MAX_BUFFER_SIZE) { maxBlockSize = MAX_BUFFER_SIZE; wasTooBig = true; } // compute maximum length of cleartext to fill such a block xassert(security); maxCleartextBlockSize = security-> data().maximumCleartextSizeForBlock(maxBlockSize); // maximum x, such that there is no size s <= x such that // maximumEncodedSize(s) > maxBlockSize // allocate buffer.. we're tacitly assuming that the encoded size // is always at least as large as any of the intermediate forms // (if this turns out to be wrong, then it's a flaw in either our // design, or 2228, or both) dataBuffer.setAllocated(mymax(maxBlockSize, maxCleartextBlockSize)); state = STATE_GOT_PBSZ; if (!wasTooBig) { clientReply(RC_COMMAND_OK, "The PBSZ is ok."); } else { // client must parse this to see the "PBSZ=" clientReply(RC_COMMAND_OK, stringb("PBSZ=" << maxBlockSize)); } break; } case CMD_PROT: { if (state != STATE_GOT_PBSZ) { clientReply(RC_BAD_COMMAND_SEQUENCE, "Can only send PROT after PBSZ."); break; } if (!security) { // not entirely sure what circumstances might provoke this, // but it gets the security==NULL issue out of the following code clientReply(RC_BAD_COMMAND_SEQUENCE, "The current security mechanism does not support PROT."); break; } // map the code into a protection level char protCode = request.getData()[0]; if (protCode != 'C') { DataSecurityLevel level = security->data().getLevelForCode(protCode); if (level == DSL_NONE) { clientReply(RC_PARAMETER_SYNTAX_ERROR, "Unknown PROT command code."); break; // 9/22/00 00:28 bugfix: 'break' was missing } // verify we can support it (should never fail, but now is as good // as time as any to find out if there is a problem) dataSecLevel = level; xassert(dataSecLevel & security->data().getSupportedProtLevels()); } else { // cleartext is implemented directly in sftpd dataSecLevel = DSL_CLEAR; } clientReply(RC_COMMAND_OK, stringb("Data channel protection set to: " << getDSLString(dataSecLevel))); break; } case CMD_AUTH: case CMD_ADAT: clientReply(RC_BAD_COMMAND_SEQUENCE, "Cannot send AUTH nor ADAT after authentication (for this server)."); break; case CMD_CCC: clientReply(RC_REQUEST_DENIED, "CCC not allowed by this server."); break; case CMD_MIC: case CMD_CONF: case CMD_ENC: // this was *already* an encrypted request clientReply(RC_BAD_COMMAND_SEQUENCE, "Cannot nest MIC, CONF, nor ENC."); break; // ----------------- 959 commands --------------------------- case CMD_PORT: handlePortCommand(request); break; // data-channel commands case CMD_RETR: // read from server case CMD_STOR: // write to server case CMD_STOU: // write to server, but don't overwrite existing case CMD_APPE: // append to file on server case CMD_NLST: // names-only directory listing case CMD_LIST: // names + stats directory listing // protocol semantics are that the incrementing is associated // with the *attempt*, so at the earliest moment we see that // the client sent a data-transmission command, we inc if (security && dataChannelProtected()) { if (requireDataEncryption) { // paranioa rule: never call newFile with anything other // than DSL_PRIVATE when requireDataEncryption==true security->data().newFile(DSL_PRIVATE); } else { security->data().newFile(dataSecLevel); } } // policy option; test this *after* newFile to obey file-number // increment semantics if (failsRequireEncTest()) { break; } // If the client hasn't sent a PORT command, that means it is // expecting to receive the connection on its default port, which // is getRemotePort(client_control). But it also means that we // haven't taken the opportunity to tell the server where to contact // sftpd. if (!pasvMode() && doingDataRelay() && server_listen == INVALID_SOCKET) { diagnostic("doing extra PORT for default-client-port transfer"); listenServerDataPort(); } // relay command to server, and assuming it responds with 150 (which // we will relay), expect one of: // (1) server will connect directly to the client, if // data channel protection is off, or // (2) server will open a connection to our listening // local socket (opened in previous PORT command), // in which case we will do data encryption and // relay at that time // (3) client will open a connection to our listening // local socket (opened in previous PASV command), //
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -