"Fossies" - the Fresh Open Source Software Archive

Member "filezilla-3.48.1/src/engine/ftp/ftpcontrolsocket.cpp" (29 Apr 2020, 21575 Bytes) of package /linux/misc/FileZilla_3.48.1_src.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ftpcontrolsocket.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.48.0_vs_3.48.1.

    1 #include <filezilla.h>
    2 
    3 #include "cwd.h"
    4 #include "chmod.h"
    5 #include "delete.h"
    6 #include "../directorycache.h"
    7 #include "directorylistingparser.h"
    8 #include "engineprivate.h"
    9 #include "externalipresolver.h"
   10 #include "filetransfer.h"
   11 #include "ftpcontrolsocket.h"
   12 #include "iothread.h"
   13 #include "list.h"
   14 #include "logon.h"
   15 #include "mkd.h"
   16 #include "pathcache.h"
   17 #include "proxy.h"
   18 #include "rawcommand.h"
   19 #include "rawtransfer.h"
   20 #include "rename.h"
   21 #include "rmd.h"
   22 #include "servercapabilities.h"
   23 #include "transfersocket.h"
   24 
   25 #include <libfilezilla/file.hpp>
   26 #include <libfilezilla/iputils.hpp>
   27 #include <libfilezilla/local_filesys.hpp>
   28 #include <libfilezilla/tls_layer.hpp>
   29 #include <libfilezilla/util.hpp>
   30 
   31 #include <algorithm>
   32 
   33 #include <assert.h>
   34 
   35 CFtpControlSocket::CFtpControlSocket(CFileZillaEnginePrivate & engine)
   36     : CRealControlSocket(engine)
   37 {
   38 }
   39 
   40 CFtpControlSocket::~CFtpControlSocket()
   41 {
   42     remove_handler();
   43 
   44     DoClose();
   45 }
   46 
   47 void CFtpControlSocket::OnReceive()
   48 {
   49     log(logmsg::debug_verbose, L"CFtpControlSocket::OnReceive()");
   50 
   51     size_t const max = 65536;
   52 
   53     for (;;) {
   54         int error;
   55 
   56         size_t const toRead = max - receiveBuffer_.size();
   57         int read = active_layer_->read(receiveBuffer_.get(toRead), toRead, error);
   58         if (read < 0) {
   59             if (error != EAGAIN) {
   60                 log(logmsg::error, _("Could not read from socket: %s"), fz::socket_error_description(error));
   61                 if (GetCurrentCommandId() != Command::connect) {
   62                     log(logmsg::error, _("Disconnected from server"));
   63                 }
   64                 DoClose();
   65             }
   66             return;
   67         }
   68 
   69         if (!read) {
   70             auto messageType = (GetCurrentCommandId() == Command::none) ? logmsg::status : logmsg::error;
   71             log(messageType, _("Connection closed by server"));
   72             DoClose();
   73             return;
   74         }
   75 
   76         size_t i = receiveBuffer_.size();
   77         receiveBuffer_.add(read);
   78 
   79         SetActive(CFileZillaEngine::recv);
   80 
   81         while (i < receiveBuffer_.size()) {
   82             auto const& p = receiveBuffer_[i];
   83             if (p == '\r' ||
   84                 p == '\n' ||
   85                 p == 0)
   86             {
   87                 if (!i) {
   88                     receiveBuffer_.consume(1);
   89                 }
   90                 else {
   91                     std::wstring line = ConvToLocal(reinterpret_cast<char const*>(receiveBuffer_.get()), i);
   92                     receiveBuffer_.consume(i + 1);
   93                     i = 0;
   94 
   95                     ParseLine(line);
   96 
   97                     // Abort if connection got closed
   98                     if (!active_layer_) {
   99                         return;
  100                     }
  101                 }
  102             }
  103             else {
  104                 ++i;
  105             }
  106         }
  107         if (receiveBuffer_.size() == max) {
  108             log(fz::logmsg::error, _("Received too long response line from server, closing connection."));
  109             DoClose();
  110             return;
  111         }
  112     }
  113 }
  114 
  115 void CFtpControlSocket::ParseLine(std::wstring line)
  116 {
  117     m_rtt.Stop();
  118     log_raw(logmsg::reply, line);
  119     SetAlive();
  120 
  121     if (!operations_.empty() && operations_.back()->opId == Command::connect) {
  122         auto & data = static_cast<CFtpLogonOpData &>(*operations_.back());
  123         if (data.waitChallenge) {
  124             std::wstring& challenge = data.challenge;
  125             if (!challenge.empty())
  126 #ifdef FZ_WINDOWS
  127                 challenge += L"\r\n";
  128 #else
  129                 challenge += L"\n";
  130 #endif
  131             challenge += line;
  132         }
  133         else if (data.opState == LOGON_FEAT) {
  134             data.ParseFeat(line);
  135         }
  136         else if (data.opState == LOGON_WELCOME) {
  137             if (!data.gotFirstWelcomeLine) {
  138                 if (fz::str_tolower_ascii(line).substr(0, 3) == L"ssh") {
  139                     log(logmsg::error, _("Cannot establish FTP connection to an SFTP server. Please select proper protocol."));
  140                     DoClose(FZ_REPLY_CRITICALERROR);
  141                     return;
  142                 }
  143                 data.gotFirstWelcomeLine = true;
  144             }
  145         }
  146     }
  147     //Check for multi-line responses
  148     if (line.size() > 3) {
  149         if (!m_MultilineResponseCode.empty()) {
  150             if (line.substr(0, 4) == m_MultilineResponseCode) {
  151                 // end of multi-line found
  152                 m_MultilineResponseCode.clear();
  153                 m_Response = line;
  154                 ParseResponse();
  155                 m_Response.clear();
  156                 m_MultilineResponseLines.clear();
  157             }
  158             else {
  159                 m_MultilineResponseLines.push_back(line);
  160             }
  161         }
  162         // start of new multi-line
  163         else if (line[3] == '-') {
  164             // DDD<SP> is the end of a multi-line response
  165             m_MultilineResponseCode = line.substr(0, 3) + L" ";
  166             m_MultilineResponseLines.push_back(line);
  167         }
  168         else {
  169             m_Response = line;
  170             ParseResponse();
  171             m_Response.clear();
  172         }
  173     }
  174 }
  175 
  176 void CFtpControlSocket::OnConnect()
  177 {
  178     m_lastTypeBinary = -1;
  179     m_sentRestartOffset = false;
  180     m_protectDataChannel = false;
  181 
  182     SetAlive();
  183 
  184     if (currentServer_.GetProtocol() == FTPS) {
  185         if (!tls_layer_) {
  186             log(logmsg::status, _("Connection established, initializing TLS..."));
  187 
  188             tls_layer_ = std::make_unique<fz::tls_layer>(event_loop_, this, *active_layer_, &engine_.GetContext().GetTlsSystemTrustStore(), logger_);
  189             active_layer_ = tls_layer_.get();
  190 
  191             if (!tls_layer_->client_handshake(this)) {
  192                 DoClose();
  193             }
  194 
  195             return;
  196         }
  197         else {
  198             log(logmsg::status, _("TLS connection established, waiting for welcome message..."));
  199         }
  200     }
  201     else if ((currentServer_.GetProtocol() == FTPES || currentServer_.GetProtocol() == FTP) && tls_layer_) {
  202         log(logmsg::status, _("TLS connection established."));
  203         SendNextCommand();
  204         return;
  205     }
  206     else {
  207         log(logmsg::status, _("Connection established, waiting for welcome message..."));
  208     }
  209     m_pendingReplies = 1;
  210 }
  211 
  212 void CFtpControlSocket::ParseResponse()
  213 {
  214     if (m_Response.empty()) {
  215         log(logmsg::debug_warning, L"No reply in ParseResponse");
  216         return;
  217     }
  218 
  219     if (m_Response[0] != '1') {
  220         if (m_pendingReplies > 0) {
  221             --m_pendingReplies;
  222         }
  223         else {
  224             log(logmsg::debug_warning, L"Unexpected reply, no reply was pending.");
  225             return;
  226         }
  227     }
  228 
  229     if (m_repliesToSkip) {
  230         log(logmsg::debug_info, L"Skipping reply after cancelled operation or keepalive command.");
  231         if (m_Response[0] != '1') {
  232             --m_repliesToSkip;
  233         }
  234 
  235         if (!m_repliesToSkip) {
  236             SetWait(false);
  237             if (operations_.empty()) {
  238                 StartKeepaliveTimer();
  239             }
  240             else if (!m_pendingReplies) {
  241                 SendNextCommand();
  242             }
  243         }
  244 
  245         return;
  246     }
  247 
  248     if (operations_.empty()) {
  249         log(logmsg::debug_info, L"Skipping reply without active operation.");
  250         return;
  251     }
  252 
  253     auto & data = *operations_.back();
  254     log(logmsg::debug_verbose, L"%s::ParseResponse() in state %d", data.name_, data.opState);
  255     int res = data.ParseResponse();
  256     if (res == FZ_REPLY_OK) {
  257         ResetOperation(FZ_REPLY_OK);
  258     }
  259     else if (res == FZ_REPLY_CONTINUE) {
  260         SendNextCommand();
  261     }
  262     else if (res & FZ_REPLY_DISCONNECTED) {
  263         DoClose(res);
  264     }
  265     else if (res & FZ_REPLY_ERROR) {
  266         if (operations_.back()->opId == Command::connect) {
  267             DoClose(res | FZ_REPLY_DISCONNECTED);
  268         }
  269         else {
  270             ResetOperation(res);
  271         }
  272     }
  273 }
  274 
  275 int CFtpControlSocket::GetReplyCode() const
  276 {
  277     if (m_Response.empty()) {
  278         return 0;
  279     }
  280     else if (m_Response[0] < '0' || m_Response[0] > '9') {
  281         return 0;
  282     }
  283     else {
  284         return m_Response[0] - '0';
  285     }
  286 }
  287 
  288 int CFtpControlSocket::SendCommand(std::wstring const& str, bool maskArgs, bool measureRTT)
  289 {
  290     size_t pos;
  291     if (maskArgs && (pos = str.find(' ')) != std::wstring::npos) {
  292         std::wstring stars(str.size() - pos - 1, '*');
  293         log_raw(logmsg::command, str.substr(0, pos + 1) + stars);
  294     }
  295     else {
  296         log_raw(logmsg::command, str);
  297     }
  298 
  299     std::string buffer = ConvToServer(str);
  300     if (buffer.empty()) {
  301         log(logmsg::error, _("Failed to convert command to 8 bit charset"));
  302         return FZ_REPLY_ERROR;
  303     }
  304     buffer += "\r\n";
  305     bool res = CRealControlSocket::Send(buffer.c_str(), buffer.size());
  306     if (res) {
  307         ++m_pendingReplies;
  308     }
  309 
  310     if (measureRTT) {
  311         m_rtt.Start();
  312     }
  313 
  314     return res ? FZ_REPLY_WOULDBLOCK : FZ_REPLY_ERROR;
  315 }
  316 
  317 void CFtpControlSocket::List(CServerPath const& path, std::wstring const& subDir, int flags)
  318 {
  319     Push(std::make_unique<CFtpListOpData>(*this, path, subDir, flags));
  320 }
  321 
  322 int CFtpControlSocket::ResetOperation(int nErrorCode)
  323 {
  324     log(logmsg::debug_verbose, L"CFtpControlSocket::ResetOperation(%d)", nErrorCode);
  325 
  326     m_pTransferSocket.reset();
  327     m_pIPResolver.reset();
  328 
  329     m_repliesToSkip = m_pendingReplies;
  330 
  331     if (!operations_.empty() && operations_.back()->opId == Command::transfer) {
  332         auto & data = static_cast<CFtpFileTransferOpData &>(*operations_.back());
  333         if (data.tranferCommandSent) {
  334             if (data.transferEndReason == TransferEndReason::transfer_failure_critical) {
  335                 nErrorCode |= FZ_REPLY_CRITICALERROR | FZ_REPLY_WRITEFAILED;
  336             }
  337             if (data.transferEndReason != TransferEndReason::transfer_command_failure_immediate || GetReplyCode() != 5) {
  338                 data.transferInitiated_ = true;
  339             }
  340             else {
  341                 if (nErrorCode == FZ_REPLY_ERROR) {
  342                     nErrorCode |= FZ_REPLY_CRITICALERROR;
  343                 }
  344             }
  345         }
  346         if (nErrorCode != FZ_REPLY_OK && data.download_ && !data.fileDidExist_) {
  347             data.ioThread_.reset();
  348             int64_t size;
  349             bool isLink;
  350             if (fz::local_filesys::get_file_info(fz::to_native(data.localFile_), isLink, &size, nullptr, nullptr) == fz::local_filesys::file && size == 0) {
  351                 // Download failed and a new local file was created before, but
  352                 // nothing has been written to it. Remove it again, so we don't
  353                 // leave a bunch of empty files all over the place.
  354                 log(logmsg::debug_verbose, L"Deleting empty file");
  355                 fz::remove_file(fz::to_native(data.localFile_));
  356             }
  357         }
  358     }
  359 
  360     if (!operations_.empty() && operations_.back()->opId == PrivCommand::rawtransfer &&
  361         nErrorCode != FZ_REPLY_OK)
  362     {
  363         auto & data = static_cast<CFtpRawTransferOpData &>(*operations_.back());
  364         if (data.pOldData->transferEndReason == TransferEndReason::successful) {
  365             if ((nErrorCode & FZ_REPLY_TIMEOUT) == FZ_REPLY_TIMEOUT) {
  366                 data.pOldData->transferEndReason = TransferEndReason::timeout;
  367             }
  368             else if (!data.pOldData->tranferCommandSent) {
  369                 data.pOldData->transferEndReason = TransferEndReason::pre_transfer_command_failure;
  370             }
  371             else {
  372                 data.pOldData->transferEndReason = TransferEndReason::failure;
  373             }
  374         }
  375     }
  376 
  377     m_lastCommandCompletionTime = fz::monotonic_clock::now();
  378     if (!operations_.empty() && !(nErrorCode & FZ_REPLY_DISCONNECTED)) {
  379         StartKeepaliveTimer();
  380     }
  381     else {
  382         stop_timer(m_idleTimer);
  383         m_idleTimer = 0;
  384     }
  385 
  386     return CControlSocket::ResetOperation(nErrorCode);
  387 }
  388 
  389 bool CFtpControlSocket::CanSendNextCommand()
  390 {
  391     if (m_repliesToSkip) {
  392         log(logmsg::status, L"Waiting for replies to skip before sending next command...");
  393         return false;
  394     }
  395 
  396     return true;
  397 }
  398 
  399 void CFtpControlSocket::ChangeDir(CServerPath const& path, std::wstring const& subDir, bool link_discovery)
  400 {
  401     auto pData = std::make_unique<CFtpChangeDirOpData>(*this);
  402     pData->path_ = path;
  403     pData->subDir_ = subDir;
  404     pData->link_discovery_ = link_discovery;
  405 
  406     if (!operations_.empty() && operations_.back()->opId == Command::transfer &&
  407         !static_cast<CFtpFileTransferOpData &>(*operations_.back()).download_)
  408     {
  409         pData->tryMkdOnFail_ = true;
  410         assert(subDir.empty());
  411     }
  412 
  413     Push(std::move(pData));
  414 }
  415 
  416 void CFtpControlSocket::FileTransfer(std::wstring const& localFile, CServerPath const& remotePath,
  417                                     std::wstring const& remoteFile, bool download,
  418                                     CFileTransferCommand::t_transferSettings const& transferSettings)
  419 {
  420     log(logmsg::debug_verbose, L"CFtpControlSocket::FileTransfer()");
  421 
  422     auto pData = std::make_unique<CFtpFileTransferOpData>(*this, download, localFile, remoteFile, remotePath, transferSettings);
  423     Push(std::move(pData));
  424 }
  425 
  426 void CFtpControlSocket::TransferEnd()
  427 {
  428     log(logmsg::debug_verbose, L"CFtpControlSocket::TransferEnd()");
  429 
  430     // If m_pTransferSocket is zero, the message was sent by the previous command.
  431     // We can safely ignore it.
  432     // It does not cause problems, since before creating the next transfer socket, other
  433     // messages which were added to the queue later than this one will be processed first.
  434     if (operations_.empty() || !m_pTransferSocket || operations_.back()->opId != PrivCommand::rawtransfer) {
  435         log(logmsg::debug_verbose, L"Call to TransferEnd at unusual time, ignoring");
  436         return;
  437     }
  438 
  439     TransferEndReason reason = m_pTransferSocket->GetTransferEndreason();
  440     if (reason == TransferEndReason::none) {
  441         log(logmsg::debug_info, L"Call to TransferEnd at unusual time");
  442         return;
  443     }
  444 
  445     if (reason == TransferEndReason::successful) {
  446         SetAlive();
  447     }
  448 
  449     auto & data = static_cast<CFtpRawTransferOpData &>(*operations_.back());
  450     if (data.pOldData->transferEndReason == TransferEndReason::successful) {
  451         data.pOldData->transferEndReason = reason;
  452     }
  453 
  454     switch (data.opState)
  455     {
  456     case rawtransfer_transfer:
  457         data.opState = rawtransfer_waittransferpre;
  458         break;
  459     case rawtransfer_waitfinish:
  460         data.opState = rawtransfer_waittransfer;
  461         break;
  462     case rawtransfer_waitsocket:
  463         ResetOperation((reason == TransferEndReason::successful) ? FZ_REPLY_OK : FZ_REPLY_ERROR);
  464         break;
  465     default:
  466         log(logmsg::debug_info, L"TransferEnd at unusual op state %d, ignoring", data.opState);
  467         break;
  468     }
  469 }
  470 
  471 bool CFtpControlSocket::SetAsyncRequestReply(CAsyncRequestNotification *pNotification)
  472 {
  473     log(logmsg::debug_verbose, L"CFtpControlSocket::SetAsyncRequestReply");
  474 
  475     const RequestId requestId = pNotification->GetRequestID();
  476     switch (requestId)
  477     {
  478     case reqId_fileexists:
  479         {
  480             if (operations_.empty() || operations_.back()->opId != Command::transfer) {
  481                 log(logmsg::debug_info, L"No or invalid operation in progress, ignoring request reply %d", pNotification->GetRequestID());
  482                 return false;
  483             }
  484 
  485             CFileExistsNotification *pFileExistsNotification = static_cast<CFileExistsNotification *>(pNotification);
  486             return SetFileExistsAction(pFileExistsNotification);
  487         }
  488         break;
  489     case reqId_interactiveLogin:
  490         {
  491             if (operations_.empty() || operations_.back()->opId != Command::connect) {
  492                 log(logmsg::debug_info, L"No or invalid operation in progress, ignoring request reply %d", pNotification->GetRequestID());
  493                 return false;
  494             }
  495 
  496             CInteractiveLoginNotification *pInteractiveLoginNotification = static_cast<CInteractiveLoginNotification *>(pNotification);
  497             if (!pInteractiveLoginNotification->passwordSet) {
  498                 ResetOperation(FZ_REPLY_CANCELED);
  499                 return false;
  500             }
  501             credentials_.SetPass(pInteractiveLoginNotification->credentials.GetPass());
  502             SendNextCommand();
  503         }
  504         break;
  505     case reqId_certificate:
  506         {
  507             if (!tls_layer_ || tls_layer_->get_state() != fz::socket_state::connecting) {
  508                 log(logmsg::debug_info, L"No or invalid operation in progress, ignoring request reply %d", pNotification->GetRequestID());
  509                 return false;
  510             }
  511 
  512             CCertificateNotification* pCertificateNotification = static_cast<CCertificateNotification *>(pNotification);
  513             tls_layer_->set_verification_result(pCertificateNotification->trusted_);
  514 
  515             if (!pCertificateNotification->trusted_) {
  516                 DoClose(FZ_REPLY_CRITICALERROR);
  517                 return false;
  518             }
  519 
  520             if (!operations_.empty() && operations_.back()->opId == Command::connect &&
  521                 operations_.back()->opState == LOGON_AUTH_WAIT)
  522             {
  523                 operations_.back()->opState = LOGON_LOGON;
  524             }
  525         }
  526         break;
  527     case reqId_insecure_connection:
  528         {
  529             auto & notification = static_cast<CInsecureConnectionNotification&>(*pNotification);
  530             if (!notification.allow_) {
  531                 ResetOperation(FZ_REPLY_CANCELED);
  532                 return false;
  533             }
  534             else {
  535                 SendNextCommand();
  536                 return true;
  537             }
  538         }
  539     default:
  540         log(logmsg::debug_warning, L"Unknown request %d", pNotification->GetRequestID());
  541         ResetOperation(FZ_REPLY_INTERNALERROR);
  542         return false;
  543     }
  544 
  545     return true;
  546 }
  547 
  548 void CFtpControlSocket::RawCommand(std::wstring const& command)
  549 {
  550     assert(!command.empty());
  551     Push(std::make_unique<CFtpRawCommandOpData>(*this, command));
  552 }
  553 
  554 void CFtpControlSocket::Delete(CServerPath const& path, std::vector<std::wstring>&& files)
  555 {
  556     auto pData = std::make_unique<CFtpDeleteOpData>(*this);
  557     pData->path_ = path;
  558     pData->files_ = std::move(files);
  559     pData->omitPath_ = true;
  560 
  561     Push(std::move(pData));
  562 }
  563 
  564 void CFtpControlSocket::RemoveDir(CServerPath const& path, std::wstring const& subDir)
  565 {
  566     auto pData = std::make_unique<CFtpRemoveDirOpData>(*this);
  567     pData->path_ = path;
  568     pData->subDir_ = subDir;
  569     pData->omitPath_ = true;
  570     pData->fullPath_ = path;
  571     Push(std::move(pData));
  572 }
  573 
  574 void CFtpControlSocket::Mkdir(CServerPath const& path)
  575 {
  576     auto pData = std::make_unique<CFtpMkdirOpData>(*this);
  577     pData->path_ = path;
  578 
  579     Push(std::move(pData));
  580 }
  581 
  582 void CFtpControlSocket::Rename(CRenameCommand const& command)
  583 {
  584     Push(std::make_unique<CFtpRenameOpData>(*this, command));
  585 }
  586 
  587 void CFtpControlSocket::Chmod(CChmodCommand const& command)
  588 {
  589     Push(std::make_unique<CFtpChmodOpData>(*this, command));
  590 }
  591 
  592 int CFtpControlSocket::GetExternalIPAddress(std::string& address)
  593 {
  594     // Local IP should work. Only a complete moron would use IPv6
  595     // and NAT at the same time.
  596     if (socket_->address_family() != fz::address_type::ipv6) {
  597         int mode = engine_.GetOptions().GetOptionVal(OPTION_EXTERNALIPMODE);
  598 
  599         if (mode) {
  600             if (engine_.GetOptions().GetOptionVal(OPTION_NOEXTERNALONLOCAL) &&
  601                 !fz::is_routable_address(socket_->peer_ip()))
  602                 // Skip next block, use local address
  603                 goto getLocalIP;
  604         }
  605 
  606         if (mode == 1) {
  607             std::wstring ip = engine_.GetOptions().GetOption(OPTION_EXTERNALIP);
  608             if (!ip.empty()) {
  609                 address = fz::to_string(ip);
  610                 return FZ_REPLY_OK;
  611             }
  612 
  613             log(logmsg::debug_warning, _("No external IP address set, trying default."));
  614         }
  615         else if (mode == 2) {
  616             if (!m_pIPResolver) {
  617                 std::string localAddress = socket_->local_ip(true);
  618 
  619                 if (!localAddress.empty() && localAddress == fz::to_string(engine_.GetOptions().GetOption(OPTION_LASTRESOLVEDIP))) {
  620                     log(logmsg::debug_verbose, L"Using cached external IP address");
  621 
  622                     address = localAddress;
  623                     return FZ_REPLY_OK;
  624                 }
  625 
  626                 std::wstring resolverAddress = engine_.GetOptions().GetOption(OPTION_EXTERNALIPRESOLVER);
  627 
  628                 log(logmsg::debug_info, _("Retrieving external IP address from %s"), resolverAddress);
  629 
  630                 m_pIPResolver = std::make_unique<CExternalIPResolver>(engine_.GetThreadPool(), *this);
  631                 m_pIPResolver->GetExternalIP(resolverAddress, fz::address_type::ipv4);
  632                 if (!m_pIPResolver->Done()) {
  633                     log(logmsg::debug_verbose, L"Waiting for resolver thread");
  634                     return FZ_REPLY_WOULDBLOCK;
  635                 }
  636             }
  637             if (!m_pIPResolver->Successful()) {
  638                 m_pIPResolver.reset();
  639 
  640                 log(logmsg::debug_warning, _("Failed to retrieve external IP address, using local address"));
  641             }
  642             else {
  643                 log(logmsg::debug_info, L"Got external IP address");
  644                 address = m_pIPResolver->GetIP();
  645 
  646                 engine_.GetOptions().SetOption(OPTION_LASTRESOLVEDIP, fz::to_wstring(address));
  647 
  648                 m_pIPResolver.reset();
  649 
  650                 return FZ_REPLY_OK;
  651             }
  652         }
  653     }
  654 
  655 getLocalIP:
  656     address = socket_->local_ip(true);
  657     if (address.empty()) {
  658         log(logmsg::error, _("Failed to retrieve local IP address."), 1);
  659         return FZ_REPLY_ERROR;
  660     }
  661 
  662     return FZ_REPLY_OK;
  663 }
  664 
  665 void CFtpControlSocket::OnExternalIPAddress()
  666 {
  667     log(logmsg::debug_verbose, L"CFtpControlSocket::OnExternalIPAddress()");
  668     if (!m_pIPResolver) {
  669         log(logmsg::debug_info, L"Ignoring event");
  670         return;
  671     }
  672 
  673     SendNextCommand();
  674 }
  675 
  676 void CFtpControlSocket::Transfer(std::wstring const& cmd, CFtpTransferOpData* oldData)
  677 {
  678     assert(oldData);
  679     oldData->tranferCommandSent = false;
  680 
  681     auto pData = std::make_unique<CFtpRawTransferOpData>(*this);
  682 
  683     pData->cmd_ = cmd;
  684     pData->pOldData = oldData;
  685     pData->pOldData->transferEndReason = TransferEndReason::successful;
  686 
  687     Push(std::move(pData));
  688 }
  689 
  690 void CFtpControlSocket::Connect(CServer const& server, Credentials const& credentials)
  691 {
  692     if (!operations_.empty()) {
  693         log(logmsg::debug_warning, L"CFtpControlSocket::Connect(): deleting stale operations");
  694         operations_.clear();
  695     }
  696 
  697     currentServer_ = server;
  698     credentials_ = credentials;
  699 
  700     Push(std::make_unique<CFtpLogonOpData>(*this));
  701 }
  702 
  703 void CFtpControlSocket::OnTimer(fz::timer_id id)
  704 {
  705     if (id != m_idleTimer) {
  706         CControlSocket::OnTimer(id);
  707         return;
  708     }
  709 
  710     if (!operations_.empty()) {
  711         return;
  712     }
  713 
  714     if (m_pendingReplies || m_repliesToSkip) {
  715         return;
  716     }
  717 
  718     log(logmsg::status, _("Sending keep-alive command"));
  719 
  720     std::wstring cmd;
  721     auto i = fz::random_number(0, 2);
  722     if (!i) {
  723         cmd = L"NOOP";
  724     }
  725     else if (i == 1) {
  726         if (m_lastTypeBinary) {
  727             cmd = L"TYPE I";
  728         }
  729         else {
  730             cmd = L"TYPE A";
  731         }
  732     }
  733     else {
  734         cmd = L"PWD";
  735     }
  736 
  737     int res = SendCommand(cmd);
  738     if (res == FZ_REPLY_WOULDBLOCK) {
  739         ++m_repliesToSkip;
  740     }
  741     else {
  742         DoClose(res);
  743     }
  744 }
  745 
  746 void CFtpControlSocket::StartKeepaliveTimer()
  747 {
  748     if (!engine_.GetOptions().GetOptionVal(OPTION_FTP_SENDKEEPALIVE)) {
  749         return;
  750     }
  751 
  752     if (m_repliesToSkip || m_pendingReplies) {
  753         return;
  754     }
  755 
  756     if (!m_lastCommandCompletionTime) {
  757         return;
  758     }
  759 
  760     fz::duration const span = fz::monotonic_clock::now() - m_lastCommandCompletionTime;
  761     if (span.get_minutes() >= 30) {
  762         return;
  763     }
  764 
  765     stop_timer(m_idleTimer);
  766     m_idleTimer = add_timer(fz::duration::from_seconds(30), true);
  767 }
  768 
  769 void CFtpControlSocket::operator()(fz::event_base const& ev)
  770 {
  771     if (fz::dispatch<fz::timer_event>(ev, this, &CFtpControlSocket::OnTimer)) {
  772         return;
  773     }
  774 
  775     if (fz::dispatch<CExternalIPResolveEvent>(ev, this, &CFtpControlSocket::OnExternalIPAddress)) {
  776         return;
  777     }
  778 
  779     if (fz::dispatch<TransferEndEvent>(ev, this, &CFtpControlSocket::TransferEnd)) {
  780         return;
  781     }
  782 
  783     if (fz::dispatch<fz::certificate_verification_event>(ev, this, &CFtpControlSocket::OnVerifyCert)) {
  784         return;
  785     }
  786 
  787     CRealControlSocket::operator()(ev);
  788 }
  789 
  790 void CFtpControlSocket::ResetSocket()
  791 {
  792     receiveBuffer_.clear();
  793     tls_layer_.reset();
  794     m_pendingReplies = 0;
  795     m_repliesToSkip = 0;
  796     CRealControlSocket::ResetSocket();
  797 }
  798 
  799 void CFtpControlSocket::OnVerifyCert(fz::tls_layer* source, fz::tls_session_info & info)
  800 {
  801     if (!tls_layer_ || source != tls_layer_.get()) {
  802         return;
  803     }
  804 
  805     SendAsyncRequest(new CCertificateNotification(std::move(info)));
  806 }
  807 
  808 void CFtpControlSocket::Push(std::unique_ptr<COpData> && pNewOpData)
  809 {
  810     CRealControlSocket::Push(std::move(pNewOpData));
  811     if (operations_.size() == 1 && operations_.back()->opId != Command::connect) {
  812         if (!active_layer_) {
  813             std::unique_ptr<COpData> connOp = std::make_unique<CFtpLogonOpData>(*this);
  814             connOp->topLevelOperation_ = true;
  815             CRealControlSocket::Push(std::move(connOp));
  816         }
  817     }
  818 }