test-trusted-notification-proxy_cc.cc \
test-tsig.cc \
test-ueberbackend_cc.cc \
+ test-webserver_cc.cc \
test-zonemd_cc.cc \
test-zoneparser_tng_cc.cc \
testrunner.cc \
tsigverifier.cc tsigverifier.hh \
ueberbackend.cc ueberbackend.hh \
unix_utility.cc \
+ uuid-utils.cc \
validate.hh \
+ webserver.cc \
zonemd.cc zonemd.hh \
zoneparser-tng.cc zoneparser-tng.hh
$(RT_LIBS) \
$(LUA_LIBS) \
$(LIBDL) \
- $(IPCRYPT_LIBS)
+ $(IPCRYPT_LIBS) \
+ $(YAHTTP_LIBS) \
+ $(JSON11_LIBS)
if GSS_TSIG
testrunner_LDADD += $(GSS_LIBS)
req.d_slog->error(Logr::Warning, e.what(), "Unable to parse request"));
}
+ if (!validURL(req.url)) {
+ throw PDNSException("Received request with invalid URL");
+ }
logRequest(req, remote);
WebServer::handleRequest(req, resp);
req.d_slog->info(Logr::Error, "Failed sending reply to HTTP client"));
}
handler->close(); // needed to signal "done" to client
+ if (d_loglevel >= WebServer::LogLevel::Normal) {
+ SLOG(g_log << Logger::Notice << logprefix << remote << " \"" << req.method << " " << req.url.path << " HTTP/" << req.versionStr(req.version) << "\" " << resp.status << " " << reply.size() << endl,
+ req.d_slog->info(Logr::Info, "Request", "remote", Logging::Loggable(remote), "method", Logging::Loggable(req.method),
+ "urlpath", Logging::Loggable(req.url.path), "HTTPVersion", Logging::Loggable(req.versionStr(req.version)),
+ "status", Logging::Loggable(resp.status), "respsize", Logging::Loggable(reply.size())));
+ }
}
catch (PDNSException& e) {
SLOG(g_log << Logger::Error << logprefix << "Exception: " << e.reason << endl,
SLOG(g_log << Logger::Error << logprefix << "Unknown exception" << endl,
req.d_slog->error(Logr::Error, "Exception handing request"))
}
-
- if (d_loglevel >= WebServer::LogLevel::Normal) {
- SLOG(g_log << Logger::Notice << logprefix << remote << " \"" << req.method << " " << req.url.path << " HTTP/" << req.versionStr(req.version) << "\" " << resp.status << " " << reply.size() << endl,
- req.d_slog->info(Logr::Info, "Request", "remote", Logging::Loggable(remote), "method", Logging::Loggable(req.method),
- "urlpath", Logging::Loggable(req.url.path), "HTTPVersion", Logging::Loggable(req.versionStr(req.version)),
- "status", Logging::Loggable(resp.status), "respsize", Logging::Loggable(reply.size())));
- }
}
void AsyncWebServer::go()
--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+#include "webserver.hh"
+
+BOOST_AUTO_TEST_SUITE(test_webserver_cc)
+
+BOOST_AUTO_TEST_CASE(test_validURL)
+{
+ // We cannot test\x00 as embedded NULs are not handled by YaHTTP other than stopping the parsing
+ const std::vector<std::pair<string, bool>> urls = {
+ {"http://www.powerdns.com/?foo=123", true},
+ {"http://ww.powerdns.com/?foo=%ff", true},
+ {"http://\x01ww.powerdns.com/?foo=123", false},
+ {"http://\xffwww.powerdns.com/?foo=123", false},
+ {"http://www.powerdns.com/?foo=123\x01", false},
+ {"http://www.powerdns.com/\x7f?foo=123", false},
+ {"http://www.powerdns.com/\x80?foo=123", false},
+ {"http://www.powerdns.com/?\xff", false},
+ };
+
+ for (const auto& testcase : urls) {
+ cerr << testcase.first << endl;
+ BOOST_CHECK_EQUAL(WebServer::validURL(testcase.first), testcase.second);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END();
#include "json.hh"
#include "uuid-utils.hh"
#include <yahttp/router.hpp>
+#include <algorithm>
+#include <unordered_set>
json11::Json HttpRequest::json()
{
}
}
+
+struct ValidChars {
+ ValidChars()
+ {
+ // letter may be signed, but we only pass positive values
+ for (auto letter : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=") {
+ set.set(letter);
+ }
+ }
+ std::bitset<127> set;
+};
+
+static const ValidChars validChars;
+
+static bool validURLChars(const string& str)
+{
+ for (auto iter = str.begin(); iter != str.end(); ++iter) {
+ if (*iter == '%') {
+ ++iter;
+ if (iter == str.end() || isxdigit(static_cast<unsigned char>(*iter)) == 0) {
+ return false;
+ }
+ ++iter;
+ if (iter == str.end() || isxdigit(static_cast<unsigned char>(*iter)) == 0) {
+ return false;
+ }
+ }
+ else if (static_cast<size_t>(*iter) >= validChars.set.size() || !validChars.set[*iter]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool WebServer::validURL(const YaHTTP::URL& url)
+{
+ bool isOK = true;
+ isOK = isOK && validURLChars(url.protocol);
+ isOK = isOK && validURLChars(url.host);
+ isOK = isOK && validURLChars(url.username);
+ isOK = isOK && validURLChars(url.password);
+ isOK = isOK && validURLChars(url.path);
+ isOK = isOK && validURLChars(url.parameters);
+ isOK = isOK && validURLChars(url.anchor);
+ return isOK;
+}
+
void WebServer::serveConnection(const std::shared_ptr<Socket>& client) const {
const auto unique = getUniqueID();
const string logprefix = d_logprefix + to_string(unique) + " ";
d_slog->error(Logr::Warning, e.what(), "Unable to parse request"));
}
+ if (!validURL(req.url)) {
+ throw PDNSException("Received request with invalid URL");
+ }
// Uses of `remote` below guarded by d_loglevel
if (d_loglevel > WebServer::LogLevel::None) {
client->getRemote(remote);
d_acl = nmg;
}
+ static bool validURL(const YaHTTP::URL& url);
+
void bind();
void go();