/* */ #include "SftpNegotiationCommand.h" #include #include #include "Request.h" #include "DownloadEngine.h" #include "RequestGroup.h" #include "PieceStorage.h" #include "FileEntry.h" #include "message.h" #include "util.h" #include "Option.h" #include "Logger.h" #include "LogFactory.h" #include "Segment.h" #include "DownloadContext.h" #include "DefaultBtProgressInfoFile.h" #include "RequestGroupMan.h" #include "SocketCore.h" #include "fmt.h" #include "DiskAdaptor.h" #include "SegmentMan.h" #include "AuthConfigFactory.h" #include "AuthConfig.h" #include "a2functional.h" #include "URISelector.h" #include "CheckIntegrityEntry.h" #include "NullProgressInfoFile.h" #include "ChecksumCheckIntegrityEntry.h" #include "SftpDownloadCommand.h" namespace aria2 { SftpNegotiationCommand::SftpNegotiationCommand (cuid_t cuid, const std::shared_ptr& req, const std::shared_ptr& fileEntry, RequestGroup* requestGroup, DownloadEngine* e, const std::shared_ptr& socket, Seq seq) : AbstractCommand(cuid, req, fileEntry, requestGroup, e, socket), sequence_(seq), authConfig_(e->getAuthConfigFactory()->createAuthConfig (req, requestGroup->getOption().get())) { path_ = getPath(); setWriteCheckSocket(getSocket()); const std::string& checksum = getOption()->get(PREF_SSH_HOST_KEY_MD); if (!checksum.empty()) { auto p = util::divide(std::begin(checksum), std::end(checksum), '='); hashType_.assign(p.first.first, p.first.second); util::lowercase(hashType_); digest_ = util::fromHex(p.second.first, p.second.second); } } SftpNegotiationCommand::~SftpNegotiationCommand() {} bool SftpNegotiationCommand::executeInternal() { disableWriteCheckSocket(); for (;;) { switch(sequence_) { case SEQ_HANDSHAKE: setReadCheckSocket(getSocket()); if (!getSocket()->sshHandshake(hashType_, digest_)) { goto again; } A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - SSH handshake success", getCuid())); sequence_ = SEQ_AUTH_PASSWORD; break; case SEQ_AUTH_PASSWORD: if (!getSocket()->sshAuthPassword(authConfig_->getUser(), authConfig_->getPassword())) { goto again; } A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - SSH authentication success", getCuid())); sequence_ = SEQ_SFTP_OPEN; break; case SEQ_SFTP_OPEN: if (!getSocket()->sshSFTPOpen(path_)) { goto again; } A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - SFTP file %s opened", getCuid(), path_.c_str())); sequence_ = SEQ_SFTP_STAT; break; case SEQ_SFTP_STAT: { int64_t totalLength; time_t mtime; if (!getSocket()->sshSFTPStat(totalLength, mtime, path_)) { goto again; } Time t(mtime); A2_LOG_INFO(fmt("CUID#%" PRId64 " - SFTP File %s, size=%" PRId64 ", mtime=%s", getCuid(), path_.c_str(), totalLength, t.toHTTPDate().c_str())); if (!getPieceStorage()) { getRequestGroup()->updateLastModifiedTime(Time(mtime)); onFileSizeDetermined(totalLength); } else { getRequestGroup()->validateTotalLength(getFileEntry()->getLength(), totalLength); sequence_ = SEQ_SFTP_SEEK; } break; } case SEQ_SFTP_SEEK: { sequence_ = SEQ_NEGOTIATION_COMPLETED; if (getSegments().empty()) { break; } auto& segment = getSegments().front(); A2_LOG_INFO(fmt("CUID#%" PRId64 " - SFTP seek to %" PRId64, getCuid(), segment->getPositionToWrite())); getSocket()->sshSFTPSeek(segment->getPositionToWrite()); break; } case SEQ_FILE_PREPARATION: sequence_ = SEQ_SFTP_SEEK; disableReadCheckSocket(); disableWriteCheckSocket(); return false; case SEQ_NEGOTIATION_COMPLETED: { auto command = make_unique (getCuid(), getRequest(), getFileEntry(), getRequestGroup(), getDownloadEngine(), getSocket(), std::move(authConfig_)); command->setStartupIdleTime (getOption()->getAsInt(PREF_STARTUP_IDLE_TIME)); command->setLowestDownloadSpeedLimit (getOption()->getAsInt(PREF_LOWEST_SPEED_LIMIT)); command->setStatus(Command::STATUS_ONESHOT_REALTIME); getDownloadEngine()->setNoWait(true); if(getFileEntry()->isUniqueProtocol()) { getFileEntry()->removeURIWhoseHostnameIs(getRequest()->getHost()); } getRequestGroup()->getURISelector()->tuneDownloadCommand (getFileEntry()->getRemainingUris(), command.get()); getDownloadEngine()->addCommand(std::move(command)); return true; } case SEQ_DOWNLOAD_ALREADY_COMPLETED: case SEQ_HEAD_OK: case SEQ_EXIT: return true; }; } again: addCommandSelf(); if (getSocket()->wantWrite()) { setWriteCheckSocket(getSocket()); } return false; } void SftpNegotiationCommand::onFileSizeDetermined(int64_t totalLength) { getFileEntry()->setLength(totalLength); if(getFileEntry()->getPath().empty()) { auto suffixPath = util::createSafePath (util::percentDecode(std::begin(getRequest()->getFile()), std::end(getRequest()->getFile()))); getFileEntry()->setPath (util::applyDir(getOption()->get(PREF_DIR), suffixPath)); getFileEntry()->setSuffixPath(suffixPath); } getRequestGroup()->preDownloadProcessing(); if(totalLength == 0) { sequence_ = SEQ_NEGOTIATION_COMPLETED; if(getOption()->getAsBool(PREF_DRY_RUN)) { getRequestGroup()->initPieceStorage(); onDryRunFileFound(); return; } if(getDownloadContext()->knowsTotalLength() && getRequestGroup()->downloadFinishedByFileLength()) { // TODO Known issue: if .aria2 file exists, it will not be // deleted on successful verification, because .aria2 file is // not loaded. See also // HttpResponseCommand::handleOtherEncoding() getRequestGroup()->initPieceStorage(); if(getDownloadContext()->isChecksumVerificationNeeded()) { A2_LOG_DEBUG("Zero length file exists. Verify checksum."); auto entry = make_unique (getRequestGroup()); entry->initValidator(); getPieceStorage()->getDiskAdaptor()->openExistingFile(); getDownloadEngine()->getCheckIntegrityMan()->pushEntry (std::move(entry)); sequence_ = SEQ_EXIT; } else { getPieceStorage()->markAllPiecesDone(); getDownloadContext()->setChecksumVerified(true); sequence_ = SEQ_DOWNLOAD_ALREADY_COMPLETED; A2_LOG_NOTICE (fmt(MSG_DOWNLOAD_ALREADY_COMPLETED, GroupId::toHex(getRequestGroup()->getGID()).c_str(), getRequestGroup()->getFirstFilePath().c_str())); } poolConnection(); return; } getRequestGroup()->adjustFilename (std::make_shared()); getRequestGroup()->initPieceStorage(); getPieceStorage()->getDiskAdaptor()->initAndOpenFile(); if(getDownloadContext()->knowsTotalLength()) { A2_LOG_DEBUG("File length becomes zero and it means download completed."); // TODO Known issue: if .aria2 file exists, it will not be // deleted on successful verification, because .aria2 file is // not loaded. See also // HttpResponseCommand::handleOtherEncoding() if(getDownloadContext()->isChecksumVerificationNeeded()) { A2_LOG_DEBUG("Verify checksum for zero-length file"); auto entry = make_unique (getRequestGroup()); entry->initValidator(); getDownloadEngine()->getCheckIntegrityMan()->pushEntry (std::move(entry)); sequence_ = SEQ_EXIT; } else { sequence_ = SEQ_DOWNLOAD_ALREADY_COMPLETED; getPieceStorage()->markAllPiecesDone(); } poolConnection(); return; } // We have to make sure that command that has Request object must // have segment after PieceStorage is initialized. See // AbstractCommand::execute() getSegmentMan()->getSegmentWithIndex(getCuid(), 0); return; } else { auto progressInfoFile = std::make_shared (getDownloadContext(), nullptr, getOption().get()); getRequestGroup()->adjustFilename(progressInfoFile); getRequestGroup()->initPieceStorage(); if(getOption()->getAsBool(PREF_DRY_RUN)) { onDryRunFileFound(); return; } auto checkIntegrityEntry = getRequestGroup()->createCheckIntegrityEntry(); if(!checkIntegrityEntry) { sequence_ = SEQ_DOWNLOAD_ALREADY_COMPLETED; poolConnection(); return; } checkIntegrityEntry->pushNextCommand(std::unique_ptr(this)); // We have to make sure that command that has Request object must // have segment after PieceStorage is initialized. See // AbstractCommand::execute() getSegmentMan()->getSegmentWithIndex(getCuid(), 0); prepareForNextAction(std::move(checkIntegrityEntry)); disableReadCheckSocket(); sequence_ = SEQ_FILE_PREPARATION; } } void SftpNegotiationCommand::poolConnection() const { if(getOption()->getAsBool(PREF_FTP_REUSE_CONNECTION)) { // TODO we don't need options. Probably, we need to pool socket // using scheme, port and auth info as key getDownloadEngine()->poolSocket(getRequest(), authConfig_->getUser(), createProxyRequest(), getSocket(), ""); } } void SftpNegotiationCommand::onDryRunFileFound() { getPieceStorage()->markAllPiecesDone(); getDownloadContext()->setChecksumVerified(true); poolConnection(); sequence_ = SEQ_HEAD_OK; } std::string SftpNegotiationCommand::getPath() const { auto &req = getRequest(); auto path = req->getDir() + req->getFile(); return util::percentDecode(std::begin(path), std::end(path)); } } // namespace aria2