bool SFTPClient::connect()

in extensions/sftp/client/SFTPClient.cpp [247:447]


bool SFTPClient::connect() {
  if (connected_) {
    return true;
  }

  /* Setting up curl request */
  std::stringstream uri_ss;
  uri_ss << hostname_ << ":" << port_;
  auto uri = uri_ss.str();
  if (curl_easy_setopt(easy_, CURLOPT_URL, uri.c_str()) != CURLE_OK) {
    return false;
  }
  if (curl_easy_setopt(easy_, CURLOPT_ERRORBUFFER, curl_errorbuffer_.data()) != CURLE_OK) {
    return false;
  }
  if (curl_easy_setopt(easy_, CURLOPT_NOSIGNAL, 1L) != CURLE_OK) {
    return false;
  }
  if (curl_easy_setopt(easy_, CURLOPT_CONNECT_ONLY, 1L) != CURLE_OK) {
    return false;
  }

  /* Connecting to proxy, if needed, then to the host */
  CURLcode curl_res = curl_easy_perform(easy_);
  if (curl_res != CURLE_OK) {
    logger_->log_error("Failed to connect to %s, curl error code: %s, detailed error message: %s",
        uri.c_str(),
        curl_easy_strerror(curl_res),
        curl_errorbuffer_.data());
    return false;
  }

  /* Getting socket from curl */
#ifdef WIN32
  curl_socket_t sockfd;
  /* Only CURLINFO_ACTIVESOCKET works on Win64 */
  curl_res = curl_easy_getinfo(easy_, CURLINFO_ACTIVESOCKET, &sockfd);
#else
  long sockfd;  // NOLINT(runtime/int) long due to libcurl API
  /* Some older cURL versions only support CURLINFO_LASTSOCKET */
  curl_res = curl_easy_getinfo(easy_, CURLINFO_LASTSOCKET, &sockfd);
#endif
  if (curl_res != CURLE_OK) {
    return false;
  }

  /* Establishing SSH connection */
  if (libssh2_session_handshake(ssh_session_, sockfd) != 0) {
    char *err_msg = nullptr;
    libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
    logger_->log_info("Failed to establish SSH connection, error: %s", err_msg);
    return false;
  }

  /* Checking remote host */
  if (ssh_known_hosts_ != nullptr) {
    auto hostkey_opt = [this]() -> std::optional<std::tuple<std::string_view, int>> {
      size_t hostkey_len = 0U;
      int type = LIBSSH2_HOSTKEY_TYPE_UNKNOWN;
      const char *hostkey_ptr = libssh2_session_hostkey(ssh_session_, &hostkey_len, &type);
      if (hostkey_ptr == nullptr) {
        char *err_msg = nullptr;
        libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
        logger_->log_info("Failed to get session hostkey, error: %s", err_msg);
        return std::nullopt;
      }
      const auto hostkey = std::string_view{hostkey_ptr, hostkey_len};
      return std::make_optional(std::make_tuple(hostkey, type));
    }();
    if (!hostkey_opt) return false;
    const auto [hostkey, hostkey_type] = *std::move(hostkey_opt);
    int keybit = 0;
    switch (hostkey_type) {
      case LIBSSH2_HOSTKEY_TYPE_RSA:
        keybit = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
        break;
      case LIBSSH2_HOSTKEY_TYPE_DSS:
        keybit = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
        break;
      default:
        logger_->log_error("Unknown host key type: %d", hostkey_type);
        return false;
    }
    struct libssh2_knownhost* known_host = nullptr;
    int keycheck_result = libssh2_knownhost_checkp(ssh_known_hosts_,
                            hostname_.c_str(),
                            -1 /*port*/,
                            hostkey.data(), hostkey.size(),
                            LIBSSH2_KNOWNHOST_TYPE_PLAIN |
                            LIBSSH2_KNOWNHOST_KEYENC_RAW |
                            keybit,
                            &known_host);
    bool host_match = false;
    switch (keycheck_result) {
      case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
        logger_->log_warn("Failed to verify host key for %s", hostname_.c_str());
        break;
      case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
        logger_->log_warn("Host %s not found in the host key file", hostname_.c_str());
        break;
      case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: {
        auto hostkey_b64 = utils::StringUtils::to_base64(hostkey);
        logger_->log_warn("Host key mismatch for %s, expected: %s, actual: %s", hostname_.c_str(),
                          known_host == nullptr ? "" : known_host->key, hostkey_b64.c_str());
        break;
      }
      case LIBSSH2_KNOWNHOST_CHECK_MATCH:
        host_match = true;
        logger_->log_debug("Host key verification succeeded for %s", hostname_.c_str());
        break;
      default:
        logger_->log_error("Unknown libssh2_knownhost_checkp result: %d", keycheck_result);
        break;
    }
    if (strict_host_checking_ && !host_match) {
      return false;
    }
  } else {
    const char* fingerprint = libssh2_hostkey_hash(ssh_session_, LIBSSH2_HOSTKEY_HASH_SHA1);
    const auto fingerprint_span = gsl::make_span(fingerprint, 20);
    if (fingerprint == nullptr) {
      logger_->log_warn("Cannot get remote server fingerprint");
    } else {
      auto fingerprint_hex = utils::StringUtils::to_hex(fingerprint_span.as_span<const std::byte>());
      std::stringstream fingerprint_hex_colon;
      for (size_t i = 0; i < 20; i++) {
        fingerprint_hex_colon << fingerprint_hex.substr(i * 2, 2);
        if (i != 19) {
          fingerprint_hex_colon << ":";
        }
      }
      logger_->log_debug("SHA1 host key fingerprint for %s is %s", hostname_.c_str(), fingerprint_hex_colon.str().c_str());
    }
  }

  /* Getting possible authentication methods */
  bool authenticated = false;
  std::set<std::string> auth_methods;
  char* userauthlist = libssh2_userauth_list(ssh_session_, username_.c_str(), strlen(username_.c_str()));
  if (userauthlist == nullptr) {
    if (libssh2_userauth_authenticated(ssh_session_) == 1) {
      authenticated = true;
      logger_->log_warn("SSH server authenticated with SSH_USERAUTH_NONE - this is unusual");
    } else {
      logger_->log_error("Failed to get supported SSH authentication methods");
      return false;
    }
  } else {
    auto methods_split = utils::StringUtils::split(userauthlist, ",");
    auth_methods.insert(std::make_move_iterator(methods_split.begin()), std::make_move_iterator(methods_split.end()));
  }

  /* Authenticating */
  if (!authenticated && public_key_authentication_enabled_ && auth_methods.count("publickey") == 1) {
    if (libssh2_userauth_publickey_fromfile_ex(ssh_session_,
                                               username_.c_str(),
                                               username_.length(),
                                               nullptr /*publickey*/,
                                               private_key_file_path_.c_str(),
                                               private_key_passphrase_.c_str()) == 0) {
      authenticated = true;
      logger_->log_debug("Successfully authenticated with publickey");
    } else {
      char *err_msg = nullptr;
      libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
      logger_->log_info("Failed to authenticate with publickey, error: %s", err_msg);
    }
  }
  if (!authenticated && password_authentication_enabled_ && auth_methods.count("password") == 1) {
    if (libssh2_userauth_password(ssh_session_, username_.c_str(), password_.c_str()) == 0) {
      authenticated = true;
      logger_->log_debug("Successfully authenticated with password");
    } else {
      char *err_msg = nullptr;
      libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
      logger_->log_info("Failed to authenticate with password, error: %s", err_msg);
    }
  }
  if (!authenticated) {
    logger_->log_error("Could not authenticate with any available method");
    return false;
  }

  /* Initializing SFTP session */
  sftp_session_ = libssh2_sftp_init(ssh_session_);
  if (sftp_session_ == nullptr) {
    char *err_msg = nullptr;
    libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
    logger_->log_error("Failed to initialize SFTP session, error: %s", err_msg);
    return false;
  }

  connected_ = true;

  /* Set up keepalive config if needed */
  if (send_keepalive_) {
    libssh2_keepalive_config(ssh_session_, 0 /*want_reply*/, 10U /*interval*/);
  }

  return true;
}