📄 sftpd.cpp
字号:
// several parameters to 'writeToLog' together on a single output line) char const *fmt = (level & LL_NO_LF)? "%s" : "%s\n"; // only UNIX has syslog, and separately encapsulating this nonportability // doesn't seem to have much value# ifdef __UNIX__ if (useStdinStream && !stdoutLogAnyway) { // log to syslog // (this isn't a very good use of syslog's 'priority' parameter... // somehow I need to rethink my overall logging strategy) syslog(level & LL_DIAGNOSTIC? LOG_DEBUG : LOG_INFO, fmt, msg); return; }# endif // __UNIX__ // log to a file if syslog not avail or we're not running under inetd if (logEventMask & LE_PRINT_TIMESTAMP) { // write timestamp and process id -- since we're not using syslog // we don't get this info for free int month, day, year, hour, minute, second; getCurrentDate(month, day, year); getCurrentTime(hour, minute, second); fprintf(logFile, "[%d] %d/%d/%d %02d:%02d:%02d ", getProcessId(), month, day, year, hour, minute, second); } // log the real message fprintf(logFile, fmt, msg); // flush the damned thing! fflush(logFile);}STATICDEF void SFTPD::warnHandler(WarnLevel level, char const *message){ if (level & WARN_DEBUG) { breaker(); // breakpoint } if (instance) { instance->writeToLog(LL_LOG, LE_WARNING, message); } else { // hmm.. this is certainly not good, but I'm not sure // there's anything good to do about it breaker(); // breakpoint, at least }}void SFTPD::logSocketInfo(LoggableEvent event, SOCKET s){ sockaddr_in local, remote; getSockName(s, local); getSockPeer(s, remote); log(event, " local: " << formatAddress(local) << "\n" << " remote: " << formatAddress(remote)); // no final '\n' because log() adds that}void SFTPD::clientReply(ReplyCode code, char const *msg){ clientReply(Reply(code, msg));}void SFTPD::clientReply(Reply const &reply){ if (controlChannelEncrypted()) { encryptedClientReply(reply); } else { unencryptedClientReply(reply); } // SHA replies during authentication if (amAuthenticating()) { // this line can be commented-out to deliberately return wrong // digests, to test client's response, during development digt.add(reply); }}void SFTPD::send(Request const &req, SOCKET s){ diagnostic("request to ftpd: " << req.getTextNoPassword()); req.send(s); addEntropy();}void SFTPD::send(Reply const &reply, SOCKET s){ diagnostic("reply to client: " << reply.getAllText()); // the reply already has a CRLF; this LF adds another // blank line, which tends to separate things nicely reply.send(s); addEntropy();}// ------------------- request handling -----------------------------// substitute my port for the client'svoid SFTPD::portCommandRelay(Request const &request){ // record client port internally if (!getClientPort(request.getData())) { // error reply sent already return; } // bind a socket locally for ftpd to contact eventually listenServerDataPort(); // reply to client clientReply(RC_COMMAND_OK, "PORT command ok.");}// relay a port command unchanged; watch for reflexivity testvoid SFTPD::portCommandStraightRelay(Request const &request){ // send the request to ftpd send(request, server_control); // get reply Reply reply(*serverControlStream); addEntropy(); diagnostic("(in pCSR) reply from ftpd: " << reply.getAllText()); if (first(reply.getCode()) != RCF_POSITIVE_COMPLETION) { // let's assume this is caused by reflexivity test; switch // to indirect mode, issue the relayed port command; throws // an exception if that fails too diagnostic("switching to indirect mode"); forceDataRelay = true; portCommandRelay(request); } else { // it worked; no big deal diagnostic("port command was accepted"); // 9/22/00 02:01 bugfix: wasn't resetting this serverPassivePort = NONPASV_PORT; // but must send ftpd's reply! this was a bug in 1.10 ... :( clientReply(reply); }}// this fn is called in both 959 interop and normal SafeTP modevoid SFTPD::handlePortCommand(Request const &request){ if (failsRequireEncTest()) { return; } if (doingDataRelay()) { // bugfix: clear any prior data-channel socket state closeDataSockets(); // setup our own private listen port, forward that to ftpd, // get its response, and say "ok" to client portCommandRelay(request); } else { // send the port command straight through, but watch for an // error, since we expect certain ftpd's to not like it portCommandStraightRelay(request); }}// the idea is to allow anon connections to be handled entirely// by the normal ftpd, so it can do things like logging and// admission control correctlyvoid SFTPD::possiblyDoAnonExecDropdown(SOCKET whereToSendGreeting){ #ifdef __UNIX__ string userCmd = peekLine(client_control); if (0==missing_stricmp(string(userCmd, 5), "USER ") && isAnonLogin(userCmd + 5)) { // we want to hand things over to the anon ftp // before that, though, we've got to shut down the ftpd we // already started; that's done by closing sockets to it close_socket(server_control); // first, when the anon ftpd gets control, it's going to immediately // send a 220, and we need to protect that from messing up the // client; we do this by sending the 331 the client is expecting, but // NOT sending CRLF, so ftpd's 220 will just be tacked on. // then, ftpd will see the "USER ftp", because we left it in the // buffer // finally, it will respond with a 331. to avoid *that* being seen // as an additional response, we put a dash after the first 331, so // it just looks like a multiline reply. // note that this *will* work even if ftpd sends a multi-line // intro, because RFC 959 specifies that the first line and last // line must have the same reply code (which these do; that's ok), // and that the intermediate lines either start with the same code // or are indented *or* have a dash after the code -- a multi-line // 220 would have intermediate lines that start with 220 but have the // dash sendAllString(whereToSendGreeting, "331-Guest login; handing control to ftpd; its intro: "); // now, since we didn't take the USER command out of the buffer, ftpd // should inherit it // exec the ftpd // "-l" (ell) is wu-ftpd's logging switch execlp(anonFtpdBinary, anonFtpdBinary, "-l", NULL); // only executed if execlp failed xsyserror("execlp"); } // if we didn't see an anon login, drop back into the normal loop // (among other things, this means that the user only has one chance // to trigger this response; but not likely a problem since mostly // people have ftp clients that do it automatically) #else // windows // mainly because we don't have 'exec' under windows.. xfailure("The -e switch is only supported with the Unix SafeTP server"); #endif}// handle a request as it came across the wirevoid SFTPD::handleOriginalRequest(Request const &request){ if (state == STATE_959_INTEROP) { // forced-relay option if (request.getCmd() == CMD_PORT) { handlePortCommand(request); } else { // just emulating 959; relay to server send(request, server_control); } return; } // we SHA requests during authentication (this will also // SHA a 959 USER or ACCT, but that won't matter) if (amAuthenticating()) { // SHA this request digt.add(request); } switch (request.getCmd()) { case CMD_AUTH: { #if 0 // work in progress if (strchr(request.getData(), '@')) { // special behavior for relay service: // pass it unmodified, and unnoticed send(request, server_control); break; } #endif // 0 if (state != STATE_UNAUTHENTICATED) { // main reason we prevent this is it's unlikely to be // used, and to implement it we'd have to reset ftpd ourselves clientReply(RC_BAD_COMMAND_SEQUENCE, // was parameter-not-implemented, I think bad command sequence is better "Can only AUTH once."); break; } // an AUTH command clears any pre-existing authentication // or authorization (above restriction notwithstanding) if (security) { delete security; } security = NULL; // prevent use of x-cleartext char const *methodName = request.getData(); bool skipQuery = false; if (!allowCleartext && (0==missing_stricmp(methodName, "X-Cleartext") || 0==missing_stricmp(methodName, "X-Cleartext2"))) { skipQuery = true; security = NULL; } // kerberos compatibility if (0==missing_stricmp(methodName, "GSSAPI")) { log(LE_HANDOFF, "client requested GSSAPI"); if (kerberosFtpdBinary.isempty()) { /* Quote from RFC 2228: If the server does not understand the named security mechanism, it should respond with reply code 504. If the server is not willing to accept the named security mechanism, it should respond with reply code 534. */ log(LE_HANDOFF, "but Kerberos compatibility is turned off; see -K switch"); clientReply(RC_REQUEST_DENIED /*534*/, "Kerberos compatibility is turned off. If you'd like to use " "Kerberos with this server, contact the system administrator."); break; } #ifdef __UNIX__ // send an affirmative reply (since that's where in the protocol // kftpd will expect things to be) clientReply(RC_FIRST_ADAT /*334*/, "Ah, you want Kerberos. I'll go get him (it?)... (send security data)"); // NOTE: this won't work unless we are using stdin/stdout as our // sockets. // try to exec the kerberos binary with the special switch hacked // into its sources.. assumes there are no *other* arguments that // need to be passed (what if there are..?) // since the main argument of relevance is -a, which prevents // kftpd from accepting unencrypted connections, let's just supply // it always execl(kerberosFtpdBinary, kerberosFtpdBinary, "-S", "-a", NULL); // if execl returns, it failed string reason = stringb("exec'ing " << kerberosFtpdBinary << " failed: " << strerror(errno)); log(LE_HANDOFF, reason); clientReply(RC_INTERNAL_ERROR, reason); // protocol violation.. who cares #else // windows, etc. clientReply(RC_REQUEST_DENIED, "Kerberos compatibility is only implemented for unix servers. Sorry."); #endif break; } // see if we know about this mechanism if (!skipQuery) { // decide which local address to claim IPAddress localToUse = getMyIPAsClientSeesIt(); // create the security provider security = SecurityProvider:: findSecurityMechanism(methodName, NULL /*i.e., am server*/, localToUse, getRemoteAddress(client_control)); } if (!security) { // unknown security mechanism log(LE_HANDOFF, "client requested unknown security mechanism " << request.getData()); clientReply(RC_PARAMETER_NOT_IMPLEMENTED, stringb("I don't know about mechanism \"" << request.getData() << "\". I know about " << (allowCleartext? "ILST" : "LIST") << "=X-SafeTP1")); // ILST ('incomplete list') if we accept X-Cleartext{2,} break; } // indeed we do.. enter ADAT negotiation composeAdatReply(true /*justAuthed*/); break; } case CMD_ADAT: { if (state != STATE_ADAT) { clientReply(RC_BAD_COMMAND_SEQUENCE, "Must send ADAT only after AUTH."); break; } // ensure 'security' is expecting it xassert(security); xassert(security->control().expectingIncomingAdat()); // un-64 the data DataBlock adat; base64decode(adat, request.getData()); if (printAdats) { adat.print("client adat to server"); } // pass it to 'security' security->control().incomingAdat(adat); // ask 'security' what to do next composeAdatReply(false /*justAuthed*/); break; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -