📄 sftpd.cpp
字号:
SOCKET *active = NULL; try { // bugfix for my 226 nemesis: we might have a *complete* // response buffered in a StreamLineReader, and if so, select() // won't tell us if (clientControlStream->hasUnprocessedData()) { socket_diagnostic("Unprocessed data on client control channel"); // NOTE: this code is repeated below active = &client_control; handleClientRequest(); continue; } if (serverControlStream->hasUnprocessedData()) { socket_diagnostic("Unprocessed data on server control channel"); // NOTE: this code is repeated below active = &server_control; handleServerReply(); continue; } // add all sockets we have; if any are INVALID_SOCKET, sockset // will simply ignore them SocketSet sockset; sockset.add(client_control); sockset.add(server_control); sockset.add(client_data); sockset.add(server_data); if (sockset.numAdded() == 0) { // all the sockets are INVALID_SOCKET -- there's no connection, // so let's exit the process break; } // add listeners after checking numAdded, because if all we have // are listeners, then the connection is dead sockset.add(client_listen); sockset.add(server_listen); socket_diagnostic( "blocking on select" ", cc=" << (int)client_control << ", sc=" << (int)server_control << ", cd=" << (int)client_data << ", sd=" << (int)server_data << ", cl=" << (int)client_listen << ", sl=" << (int)server_listen); // debugging code that was here, which checked sockets // for exception info, has been moved to end of file // wait until there is activity somewhere sockset.blockUntilReadable(); addEntropy(); // see which socket had activity (ignore case where more than one // did, since the other socket activity will be handled // on a future iteration); once one peer starts to talk, we // ignore the other until the talking peer finishes (there // isn't anything good to do if one starts but never finishes) // --- client sends a request --- if (sockset.contains(client_control)) { socket_diagnostic("select(): activity on client_control"); // NOTE: this code is repeated above active = &client_control; handleClientRequest(); } // --- server sends a reply --- else if (sockset.contains(server_control)) { socket_diagnostic("select(): activity on server_control"); // NOTE: this code is repeated above active = &server_control; handleServerReply(); } // --- ftpd connects to me --- // start of an encrypted non-PASV data transmission else if (sockset.contains(server_listen)) { socket_diagnostic("select(): activity on server_listen"); active = &server_listen; // should not be in PASV mode xassert(!pasvMode()); // connect to client diagnostic("received connection from ftpd, trying to connect to client"); relayDataConnection( server_listen, server_data, relayClientAddress, relayClientPort, client_data, &activeRange); } // --- client connects to me --- // start of an encrypted PASV data transmission else if (sockset.contains(client_listen)) { socket_diagnostic("select(): activity on client_listen"); active = &client_listen; // we should be in PASV mode for this xassert(pasvMode()); // connect to server diagnostic("received connection from client, trying to connect to ftpd"); relayDataConnection( client_listen, client_data, getRemoteAddress(server_control), serverPassivePort, server_data, NULL /*port range is unrestricted*/); } // --- ftpd sends me data --- // activity on the ftpd-side data channel: handling // an encrypted data transmission, server -> client // (PASV and non-PASV) else if (sockset.contains(server_data)) { socket_diagnostic("select(): activity on server_data"); active = &server_data; //diagnostic("received data block from ftpd on local port " << // getLocalPort(server_data)); if (dataChannelProtected()) { encryptDataBlock(); } else { relaySocketData(client_data /*dest*/, server_data /*src*/); } } // --- client sends me data --- // activity on the client-side data channel: handling // an encrypted data transmission, client -> server // (PASV and non-PASV) else if (sockset.contains(client_data)) { socket_diagnostic("select(): activity on client_data"); active = &client_data; //diagnostic("received data block from client on local port " << // getLocalPort(client_data)); if (dataChannelProtected()) { decryptDataBlock(); } else { relaySocketData(server_data /*dest*/, client_data /*src*/); } } // --- select() screws up --- else { // I had been throwing an exception here, but recovery really // is trivial in this case, and all I really need is a report. log(LE_ERROR, "SFTPD INTERNAL BUG: select() call returned but nothing " "was set... unexpected but nonfatal"); } } // --- handle a socket closure (unexpected here) --- catch (xSocket &x) { // this is due to a socket closure log(LE_ERROR, "socket " << sockInfo(x.socket) << " closed: " << x.why()); if (*active == client_control || *active == server_control) { // this should not be possible because handleClientRequest // and handleServerReply should get these... breaker(); // breakpoint if debugger attached closeIf(client_control); closeIf(server_control); break; } if (*active == client_data || *active == server_data) { // close both data channels closeIf(client_data); closeIf(server_data); // we don't expect to detect data channel closures here, // because the data-channel relay code is supposed to // detect and handle them; so this is probably an unexpected // network failure, and the client may want to know (and I // hope this 'unprompted' reply doesn't screw up the // client's internal state machine) unexpected(x); } } catch (xBase &x) { // unknown internal error; tell client and log it unexpected(x); } } // success return;}void SFTPD::handleClientRequest(){ try { // here, and below, I'm giving myself the opportunity // to do more graceful shutdown than via an exception // (without the check, we learn of it from StreamLineReader // throwing an exception of a general nature) checkClosed(client_control); // get request diagnostic("got request from client, parsing..."); Request request(*clientControlStream); diagnostic("request from client: " << request.getTextNoPassword()); try { // handle it handleOriginalRequest(request); } catch (xBase &x) { unexpected(x); } } catch (xBase &x) { // client closed connection, do same to server log(LE_CONTROL_CHANNEL, "client control connection closed: " << x.why()); closeSocket(server_control); closeSocket(client_control); }}void SFTPD::handleServerReply(){ try { // more-graceful shutdown thing checkClosed(server_control); // get reply diagnostic("ftpd sent a reply, parsing it..."); Reply reply(*serverControlStream); diagnosticNoLF("reply from ftpd: " << reply.getAllText()); try { // handle it handleReply(reply); } catch (xBase &x) { unexpected(x); } } catch (xBase &x) { // server closed connection, do same to client log(LE_CONTROL_CHANNEL, "server control connection closed: " << x.why()); closeSocket(client_control); closeSocket(server_control); }}// called when an unexpected exception is caughtvoid SFTPD::unexpected(xBase &x){ // log it log(LE_ERROR, "UNEXPECTED: " << x.why()); try { // Q: is this a possible security hole? i.e., could it be that // a client could cause an error such that the resulting // exception text would reveal private data (like a key)? // tell client clientReply(RC_INTERNAL_ERROR, x.why()); } catch (xBase &x) { log(LE_ERROR, "while relaying UNEXPECTED: " << x.why()); }}// We've just been contacted by a peer "A", and must establish contact with// the other peer, "B", then accept A's connection. Either A is ftpd and// B is the client, or vice-versa.// peerA_listen - listen()ing socket on which we were just contacted// by peer "A"// peerA_data - socket for accepting A's connection//// addrB, portB - where to connect 'peerB_data'// peerB_data - socket to connect to other peer, "B"// range - if non-NULL, constrain local ports we bindbool SFTPD::relayDataConnection( SOCKET &peerA_listen, SOCKET &peerA_data, IPAddress addrB, int portB, SOCKET &peerB_data, PortRange const *range){ // for errors and diagnostics, we need to determine // who is client and who is server bool AisClient = (peerA_listen == client_listen); char const *A = AisClient? "client" : "server"; char const *B = AisClient? "server" : "client"; // this has been a source of bugs: prevent this fn from // using any of these names (must use peer{A,B}_{listen,data}) #define client_listen No! #define server_listen No! #define client_data No! #define server_data No! try { // try to connect to B first, so if that fails, // we can cause A's conn attempt to fail (this prevents // 0-length files from being created in the case of a // data command that fails this way) // establish the data connection to B diagnostic("trying to connect to " << B << " at " << formatAddress(addrB) << ", port " << portB); peerB_data = connect_socket(addrB, portB); addEntropy(); } catch (xSocket &x) { diagnostic("failed to connect to " << B << "'s data port"); // cause a conn-refused for A closeSocket(peerA_listen); // tell client about the problem if (AisClient) { // ftpd refused the conn.. that's strange unexpected(x); } else { // failed to connect to the client clientReply(RC_FAILED_DATA_CONN_OPEN, stringb("Failed to connect to " << formatAddress(addrB) << ", port " << portB << ": " << x.why())); } return false; } // accept the connection peerA_data = accept_socket(peerA_listen); diagnostic("accepted data connection from " << A); //diagnosticSocketInfo(peerA_data); // close the listener socket; we won't need it anymore closeSocket(peerA_listen); // re-enable use of these names #undef client_listen #undef server_listen #undef client_data #undef server_data return true;}bool SFTPD::controlChannelEncrypted() const{ return STATE_AUTHENTICATED <= state && state <= STATE_GOT_PBSZ;}bool SFTPD::dataChannelProtected() const{ return dataSecLevel != DSL_CLEAR;}bool SFTPD::pasvMode() const{ return serverPassivePort != NONPASV_PORT;}bool SFTPD::amAuthenticating() const{ return state == STATE_UNAUTHENTICATED || state == STATE_ADAT;}bool SFTPD::doingDataRelay() const{ return dataChannelProtected() || forceDataRelay;}void SFTPD::writeToLog(LogLevel level, LoggableEvent event, char const *msg){ // check masks if (!( (level & logLevelMask) && (event & logEventMask) )) { return; } // decide whether to append a CR or not (LL_NO_LF should be specified // when 'msg' already has a CR; it should *not* be used to try to string
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -