diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d5f704..47f520e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ add_compile_definitions(CHECKPOINT_VERSION="${CHECKPOINT_VERSION}") find_package(PkgConfig REQUIRED) -pkg_check_modules(deps IMPORTED_TARGET openssl re2) +pkg_check_modules(deps IMPORTED_TARGET openssl re2 libmagic) target_include_directories(checkpoint PRIVATE diff --git a/README.md b/README.md index ca122c1..9202a46 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ If you are using this, it's almost certain search engines will stop indexing you ## Setup guide -1. Clone and build this repo. You will need `openssl`, `g++>=12`, `re2`, and deps for `pistache`, `fmt` and `tinylates`. +1. Clone and build this repo. You will need `openssl`, `g++>=12`, `re2`, `libmagic`, and deps for `pistache`, `fmt` and `tinylates`. 2. Create a `config.jsonc`. An example one is in `example/`. 3. Adjust the config to your needs. Options are documented with comments in the example config. 4. Set up your IP rules if you want. These allow you to set up IPs that are automatically blocked, or allowed to access without a challenge. This is useful for e.g. search engine scrapers. Some IP ranges can be found in `example/index_bots.jsonc`. diff --git a/src/core/Handler.cpp b/src/core/Handler.cpp index 50cf42f..5f8e111 100644 --- a/src/core/Handler.cpp +++ b/src/core/Handler.cpp @@ -23,6 +23,7 @@ #include #include #include +#include constexpr const uint64_t TOKEN_MAX_AGE_MS = 1000 * 60 * 60; // 1hr constexpr const char* TOKEN_COOKIE_NAME = "checkpoint-token"; @@ -94,7 +95,7 @@ std::string CServerHandler::fingerprintForRequest(const Pistache::Http::Request& } bool CServerHandler::isResourceCheckpoint(const std::string_view& res) { - return res == "/checkpoint/NotoSans.woff"; + return res.starts_with("/checkpoint/"); } std::string CServerHandler::ipForRequest(const Pistache::Http::Request& req) { @@ -200,12 +201,6 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt return; } - if (isResourceCheckpoint(req.resource())) { - // no directory traversal is possible when resource is checkpoint - response.send(Pistache::Http::Code::Ok, NFsUtils::readFileAsString(NFsUtils::htmlPath(req.resource().substr(req.resource().find("checkpoint/") + 11))).value()); - return; - } - if (g_pConfig->m_config.git_host) { // TODO: ratelimit this, probably. @@ -289,6 +284,42 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt } else Debug::log(LOG, " | Action: CHALLENGE (no token)"); + if (isResourceCheckpoint(req.resource())) { + static const auto HTML_ROOT = std::filesystem::canonical(NFsUtils::htmlPath("")).string(); + + const auto RESOURCE_PATH = req.resource().substr(req.resource().find("checkpoint/") + 11); + const auto PATH_RAW = NFsUtils::htmlPath(RESOURCE_PATH); + + std::error_code ec; + const auto PATH_ABSOLUTE = std::filesystem::canonical(PATH_RAW, ec); + + if (ec) { + // bad resource + response.send(Pistache::Http::Code::Bad_Request, "Bad Request"); + return; + } + + if (!PATH_ABSOLUTE.string().starts_with(HTML_ROOT)) { + // directory traversal + response.send(Pistache::Http::Code::Bad_Request, "Bad Request"); + return; + } + + // attempt to handle mime + magic_t magic = magic_open(MAGIC_MIME_TYPE); + if (magic && magic_load(magic, nullptr) == 0) { + const char* m = magic_file(magic, PATH_ABSOLUTE.c_str()); + auto mimeType = Pistache::Http::Mime::MediaType::fromString(m ? std::string(m) : std::string("application/octet-stream")); + response.headers().add(mimeType); + } + if (magic) + magic_close(magic); + + auto body = NFsUtils::readFileAsString(PATH_ABSOLUTE).value_or(""); + response.send(body.empty() ? Pistache::Http::Code::Internal_Server_Error : Pistache::Http::Code::Ok, body); + return; + } + serveStop(req, response, challengeDifficulty); }