diff --git a/getpkg/Dockerfile.dropshell-build b/getpkg/Dockerfile.dropshell-build index fe58a8d..750c9bf 100644 --- a/getpkg/Dockerfile.dropshell-build +++ b/getpkg/Dockerfile.dropshell-build @@ -67,6 +67,10 @@ FROM scratch AS project ARG PROJECT +# Copy CA certificates for SSL validation +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs/ + # Copy the actual binary from the regular directory COPY --from=builder /output/${PROJECT} /${PROJECT} diff --git a/getpkg/src/GetbinClient.cpp b/getpkg/src/GetbinClient.cpp index 5e68431..0e5abac 100644 --- a/getpkg/src/GetbinClient.cpp +++ b/getpkg/src/GetbinClient.cpp @@ -1,7 +1,6 @@ #include "GetbinClient.hpp" #include -#include -#include +#include #include #include #include @@ -14,89 +13,108 @@ #include #include #include +#include +#include using json = nlohmann::json; static constexpr const char* SERVER_HOST = "getpkg.xyz"; -// Global flag to track if event loop is running -static std::atomic g_eventLoopRunning{false}; -static std::thread g_eventLoopThread; -static std::mutex g_eventLoopMutex; -static std::condition_variable g_eventLoopCv; - -static void ensureEventLoop() { - static std::once_flag initFlag; - std::call_once(initFlag, []() { - g_eventLoopThread = std::thread([]() { - // Start the event loop in a separate thread - drogon::app().getLoop()->queueInLoop([]() { - std::unique_lock lock(g_eventLoopMutex); - g_eventLoopRunning = true; - g_eventLoopCv.notify_all(); - }); - drogon::app().run(); - }); - - // Wait for event loop to start - std::unique_lock lock(g_eventLoopMutex); - g_eventLoopCv.wait(lock, []() { return g_eventLoopRunning.load(); }); - }); -} - -GetbinClient::GetbinClient() { - ensureEventLoop(); -} - -bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath) { - auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST)); - std::string object_path = "/object/" + toolName + ":" + arch; +static std::string find_ca_certificates() { + // Common CA certificate locations across different Linux distributions + const std::vector ca_paths = { + "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Raspbian + "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL/CentOS + "/etc/ssl/ca-bundle.pem", // OpenSUSE + "/etc/pki/tls/cert.pem", // Fedora/RHEL alternative + "/etc/ssl/certs/ca-bundle.crt", // Some distros + "/etc/ssl/cert.pem", // Alpine Linux + "/usr/local/share/certs/ca-root-nss.crt", // FreeBSD + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7+ + "/etc/ca-certificates/extracted/tls-ca-bundle.pem" // Arch Linux + }; - auto req = drogon::HttpRequest::newHttpRequest(); - req->setMethod(drogon::Get); - req->setPath(object_path); - - std::atomic success(false); - std::atomic completed(false); - - client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { - if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) { - std::cerr << "[GetbinClient::download] HTTP request failed." << std::endl; - completed = true; - return; + for (const auto& path : ca_paths) { + std::ifstream file(path); + if (file.good()) { + file.close(); + return path; } - - std::ofstream ofs(outPath, std::ios::binary); - if (!ofs) { - completed = true; - return; - } - - const auto& body = response->getBody(); - ofs.write(body.data(), body.size()); - success = ofs.good(); - completed = true; - }); - - // Wait for the async request to complete with timeout - auto start = std::chrono::steady_clock::now(); - const auto timeout = std::chrono::seconds(30); // 30 second timeout - - while (!completed) { - if (std::chrono::steady_clock::now() - start > timeout) { - std::cerr << "[GetbinClient::download] Request timed out after 30 seconds." << std::endl; - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); } + return ""; +} + +GetbinClient::GetbinClient() {} + +bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath) { + bool success = false; + bool done = false; + std::mutex mtx; + std::condition_variable cv; + + std::thread worker([&]() { + trantor::EventLoop loop; + + auto client = drogon::HttpClient::newHttpClient( + "https://" + std::string(SERVER_HOST), + &loop + ); + + // Configure SSL certificates for HTTPS + std::string ca_path = find_ca_certificates(); + if (!ca_path.empty()) { + std::cerr << "[GetbinClient] Found CA certificates at: " << ca_path << std::endl; + // Use addSSLConfigs with proper parameter names for OpenSSL + std::vector> sslConfigs; + sslConfigs.push_back({"VerifyCAFile", ca_path}); + client->addSSLConfigs(sslConfigs); + } else { + // If no CA certificates found, print warning but continue + std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl; + } + + client->enableCookies(); + client->setUserAgent("getpkg/1.0"); + + std::string object_path = "/object/" + toolName + ":" + arch; + + auto req = drogon::HttpRequest::newHttpRequest(); + req->setMethod(drogon::Get); + req->setPath(object_path); + + client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { + std::lock_guard lock(mtx); + if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) { + std::ofstream ofs(outPath, std::ios::binary); + if (ofs) { + const auto& body = response->getBody(); + ofs.write(body.data(), body.size()); + success = ofs.good(); + } + } else { + std::cerr << "[GetbinClient::download] HTTP request failed." << std::endl; + } + done = true; + cv.notify_one(); + loop.quit(); + }, 30.0); // 30 second timeout + + loop.loop(); + }); + + // Wait for completion + { + std::unique_lock lock(mtx); + cv.wait(lock, [&] { return done; }); + } + + worker.join(); return success; } bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token) { - auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST)); - - // Read file + // Read file first std::ifstream ifs(archivePath, std::ios::binary); if (!ifs) { std::cerr << "[GetbinClient::upload] Failed to open archive file: " << archivePath << std::endl; @@ -104,131 +122,181 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s } std::string file_content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); - // Compose metadata (minimal, can be extended) + // Compose metadata json metadata = { {"labeltags", json::array()} }; - // Try to extract tool:arch from filename std::string filename = archivePath.substr(archivePath.find_last_of("/\\") + 1); size_t dot = filename.find('.'); std::string labeltag = dot != std::string::npos ? filename.substr(0, dot) : filename; metadata["labeltags"].push_back(labeltag); - // Create request with raw body upload (simplified approach) - auto req = drogon::HttpRequest::newHttpRequest(); - req->setMethod(drogon::Put); - req->setPath("/upload"); - req->addHeader("Authorization", "Bearer " + token); - req->addHeader("Content-Type", "application/gzip"); - req->addHeader("X-Metadata", metadata.dump()); - req->setBody(file_content); + bool success = false; + bool done = false; + std::mutex mtx; + std::condition_variable cv; - std::atomic success(false); - std::atomic completed(false); - std::string response_body; - int status_code = 0; - - client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { - if (result != drogon::ReqResult::Ok || !response) { - std::cerr << "[GetbinClient::upload] HTTP /upload request failed (did not get OK response)." << std::endl; - if (response) { - std::cerr << response->getStatusCode() << std::endl; - std::cerr << response->getBody() << std::endl; + std::thread worker([&]() { + trantor::EventLoop loop; + + auto client = drogon::HttpClient::newHttpClient( + "https://" + std::string(SERVER_HOST), + &loop + ); + + // Configure SSL certificates + std::string ca_path = find_ca_certificates(); + if (!ca_path.empty()) { + std::cerr << "[GetbinClient] Found CA certificates at: " << ca_path << std::endl; + std::vector> sslConfigs; + sslConfigs.push_back({"VerifyCAFile", ca_path}); + client->addSSLConfigs(sslConfigs); + } else { + std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl; + } + + client->enableCookies(); + client->setUserAgent("getpkg/1.0"); + + // Create upload file from memory content + // First save content to a temporary file since UploadFile expects a file path + std::string temp_file = "/tmp/getpkg_upload_" + std::to_string(std::time(nullptr)) + ".tgz"; + std::ofstream temp_ofs(temp_file, std::ios::binary); + if (!temp_ofs) { + std::cerr << "[GetbinClient::upload] Failed to create temporary file: " << temp_file << std::endl; + success = false; + done = true; + cv.notify_one(); + loop.quit(); + return; + } + temp_ofs.write(file_content.data(), file_content.size()); + temp_ofs.close(); + + // Create upload request with file + drogon::UploadFile upload_file(temp_file); + + auto req = drogon::HttpRequest::newFileUploadRequest({upload_file}); + req->setMethod(drogon::Put); + req->setPath("/upload"); + req->addHeader("Authorization", "Bearer " + token); + + // Add metadata as form parameter + req->setParameter("metadata", metadata.dump()); + + client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { + std::lock_guard lock(mtx); + if (result == drogon::ReqResult::Ok && response) { + int status_code = static_cast(response->getStatusCode()); + std::string response_body(response->getBody()); + + if (status_code == 200 || status_code == 201) { + try { + auto resp_json = json::parse(response_body); + if (resp_json.contains("url")) outUrl = resp_json["url"].get(); + if (resp_json.contains("hash")) outHash = resp_json["hash"].get(); + success = true; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::upload] Failed to parse JSON response: " << e.what() << std::endl; + std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl; + } + } else { + std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl; + std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl; + } + } else { + std::cerr << "[GetbinClient::upload] HTTP /upload request failed." << std::endl; } - completed = true; - return; - } + done = true; + cv.notify_one(); + loop.quit(); + }, 60.0); // 60 second timeout - status_code = static_cast(response->getStatusCode()); - response_body = response->getBody(); + loop.loop(); - if (status_code != 200 && status_code != 201) { - std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl; - std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl; - completed = true; - return; - } - - // Parse response for URL/hash - try { - auto resp_json = json::parse(response_body); - if (resp_json.contains("url")) outUrl = resp_json["url"].get(); - if (resp_json.contains("hash")) outHash = resp_json["hash"].get(); - success = true; - } catch (const std::exception& e) { - std::cerr << "[GetbinClient::upload] Failed to parse JSON response: " << e.what() << std::endl; - std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl; - } catch (...) { - std::cerr << "[GetbinClient::upload] Unknown error while parsing JSON response." << std::endl; - std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl; - } - completed = true; + // Clean up temporary file + std::remove(temp_file.c_str()); }); - // Wait for the async request to complete with timeout - auto start = std::chrono::steady_clock::now(); - const auto timeout = std::chrono::seconds(60); // 60 second timeout for upload - - while (!completed) { - if (std::chrono::steady_clock::now() - start > timeout) { - std::cerr << "[GetbinClient::upload] Request timed out after 60 seconds." << std::endl; - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + // Wait for completion + { + std::unique_lock lock(mtx); + cv.wait(lock, [&] { return done; }); } + worker.join(); return success; } bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) { - auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST)); - std::string exists_path = "/exists/" + toolName + ":" + arch; + bool success = false; + bool done = false; + std::mutex mtx; + std::condition_variable cv; - auto req = drogon::HttpRequest::newHttpRequest(); - req->setMethod(drogon::Get); - req->setPath(exists_path); - - std::atomic success(false); - std::atomic completed(false); - std::string response_body; - - client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { - if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) { - completed = true; - return; + std::thread worker([&]() { + trantor::EventLoop loop; + + auto client = drogon::HttpClient::newHttpClient( + "https://" + std::string(SERVER_HOST), + &loop + ); + + // Configure SSL certificates + std::string ca_path = find_ca_certificates(); + if (!ca_path.empty()) { + std::cerr << "[GetbinClient] Found CA certificates at: " << ca_path << std::endl; + std::vector> sslConfigs; + sslConfigs.push_back({"VerifyCAFile", ca_path}); + client->addSSLConfigs(sslConfigs); + } else { + std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl; } - response_body = response->getBody(); + client->enableCookies(); + client->setUserAgent("getpkg/1.0"); - // Try to parse hash from response body (assume plain text or JSON) - try { - // Try JSON - auto resp_json = json::parse(response_body); - if (resp_json.contains("hash")) { - outHash = resp_json["hash"].get(); - success = true; - completed = true; - return; + std::string hash_path = "/hash/" + toolName + ":" + arch; + + auto req = drogon::HttpRequest::newHttpRequest(); + req->setMethod(drogon::Get); + req->setPath(hash_path); + + client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { + std::lock_guard lock(mtx); + if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) { + std::string response_body(response->getBody()); + + // Try to parse hash from response body + try { + // Try JSON first + auto resp_json = json::parse(response_body); + if (resp_json.contains("hash")) { + outHash = resp_json["hash"].get(); + success = true; + } + } catch (...) { + // Not JSON, treat as plain text + outHash = response_body; + // Remove trailing newline if present + if (!outHash.empty() && outHash.back() == '\n') { + outHash.pop_back(); + } + success = !outHash.empty(); + } } - } catch (...) { - // Not JSON, treat as plain text - outHash = response_body; - success = !outHash.empty(); - completed = true; - return; - } - completed = true; + done = true; + cv.notify_one(); + loop.quit(); + }, 10.0); // 10 second timeout + + loop.loop(); }); - // Wait for the async request to complete with timeout - auto start = std::chrono::steady_clock::now(); - const auto timeout = std::chrono::seconds(10); // 10 second timeout for hash check - - while (!completed) { - if (std::chrono::steady_clock::now() - start > timeout) { - std::cerr << "[GetbinClient::getHash] Request timed out after 10 seconds." << std::endl; - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + // Wait for completion + { + std::unique_lock lock(mtx); + cv.wait(lock, [&] { return done; }); } + worker.join(); return success; -} \ No newline at end of file +} \ No newline at end of file diff --git a/getpkg/test.sh b/getpkg/test.sh index 38ef560..cfdc03c 100755 --- a/getpkg/test.sh +++ b/getpkg/test.sh @@ -292,7 +292,10 @@ fi # Test 13: Invalid tool name validation echo -e "\nTest 13: Invalid tool name validation" -INVALID_OUTPUT=$(timeout 3 "$GETPKG" install "../evil-tool" 2>&1) || INVALID_OUTPUT="" +INVALID_OUTPUT=$(timeout 3 "$GETPKG" install "../evil-tool" 2>&1) +INVALID_EXIT_CODE=$? +echo "Invalid tool output: $INVALID_OUTPUT" +echo "Invalid tool exit code: $INVALID_EXIT_CODE" if [[ "$INVALID_OUTPUT" =~ "Invalid tool name" ]]; then print_test_result "Invalid tool name rejection" 0 else diff --git a/getpkg/test_debug/test-tool-35020/setup_script.sh b/getpkg/test_debug/test-tool-35020/setup_script.sh new file mode 100755 index 0000000..5838800 --- /dev/null +++ b/getpkg/test_debug/test-tool-35020/setup_script.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo 'Setup complete.' diff --git a/getpkg/test_debug/test-tool-35957/setup_script.sh b/getpkg/test_debug/test-tool-35957/setup_script.sh new file mode 100755 index 0000000..5838800 --- /dev/null +++ b/getpkg/test_debug/test-tool-35957/setup_script.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo 'Setup complete.' diff --git a/getpkg/test_debug/test-tool-36793/setup_script.sh b/getpkg/test_debug/test-tool-36793/setup_script.sh new file mode 100755 index 0000000..5838800 --- /dev/null +++ b/getpkg/test_debug/test-tool-36793/setup_script.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo 'Setup complete.'