// Copyright (C) 2021-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #include #include #include #include #include #include #include #include using namespace isc::config; using namespace isc::data; using namespace isc::http; using namespace std; namespace isc { namespace config { HttpAuthConfigPtr CmdResponseCreator::http_auth_config_; unordered_set CmdResponseCreator::command_accept_list_; HttpRequestPtr CmdResponseCreator::createNewHttpRequest() const { return (HttpRequestPtr(new PostHttpRequestJson())); } HttpResponsePtr CmdResponseCreator:: createStockHttpResponse(const HttpRequestPtr& request, const HttpStatusCode& status_code) const { HttpResponsePtr response = createStockHttpResponseInternal(request, status_code); response->finalize(); return (response); } HttpResponsePtr CmdResponseCreator:: createStockHttpResponseInternal(const HttpRequestPtr& request, const HttpStatusCode& status_code) const { // The request hasn't been finalized so the request object // doesn't contain any information about the HTTP version number // used. But, the context should have this data (assuming the // HTTP version is parsed OK). HttpVersion http_version(request->context()->http_version_major_, request->context()->http_version_minor_); // We only accept HTTP version 1.0 or 1.1. If other version number is found // we fall back to HTTP/1.0. if ((http_version < HttpVersion(1, 0)) || (HttpVersion(1, 1) < http_version)) { http_version.major_ = 1; http_version.minor_ = 0; } // This will generate the response holding JSON content. HttpResponsePtr response(new HttpResponseJson(http_version, status_code)); return (response); } HttpResponsePtr CmdResponseCreator::createDynamicHttpResponse(HttpRequestPtr request) { HttpResponseJsonPtr http_response; // Check the basic HTTP authentication. if (http_auth_config_) { http_response = http_auth_config_->checkAuth(*this, request); if (http_response) { return (http_response); } } // The request is always non-null, because this is verified by the // createHttpResponse method. Let's try to convert it to the // PostHttpRequestJson type as this is the type generated by the // createNewHttpRequest. If the conversion result is null it means that // the caller did not use createNewHttpRequest method to create this // instance. This is considered an error in the server logic. PostHttpRequestJsonPtr request_json = boost::dynamic_pointer_cast< PostHttpRequestJson>(request); if (!request_json) { // Notify the client that we have a problem with our server. return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR)); } // We have already checked that the request is finalized so the call // to getBodyAsJson must not trigger an exception. ConstElementPtr command = request_json->getBodyAsJson(); // Filter the command. http_response = filterCommand(request, command, command_accept_list_); if (http_response) { return (http_response); } // Process command doesn't generate exceptions but can possibly return // null response, if the handler is not implemented properly. This is // again an internal server issue. ConstElementPtr response = config::CommandMgr::instance().processCommand(command); if (!response) { // Notify the client that we have a problem with our server. return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR)); } // Normal Responses coming from the Kea Control Agent must always be wrapped in // a list as they may contain responses from multiple daemons. // If we're emulating that for backward compatibility, then we need to wrap // the answer in a list if it isn't in one already. if (emulateAgentResponse() && (response->getType() != Element::list)) { ElementPtr response_list = Element::createList(); response_list->add(boost::const_pointer_cast(response)); response = response_list; } // The response is OK, so let's create new HTTP response with the status OK. http_response = boost::dynamic_pointer_cast< HttpResponseJson>(createStockHttpResponseInternal(request, HttpStatusCode::OK)); http_response->setBodyAsJson(response); http_response->finalize(); return (http_response); } HttpResponseJsonPtr CmdResponseCreator::filterCommand(const HttpRequestPtr& request, const ConstElementPtr& body, const unordered_set& accept) { HttpResponseJsonPtr response; if (!body || accept.empty()) { return (response); } if (body->getType() != Element::map) { return (response); } ConstElementPtr elem = body->get(CONTROL_COMMAND); if (!elem || (elem->getType() != Element::string)) { return (response); } string command = elem->stringValue(); if (command.empty() || accept.count(command)) { return (response); } // Reject the command. LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HTTP_LISTENER_COMMAND_REJECTED) .arg(command) .arg(request->getRemote()); // From CtrlAgentResponseCreator::createStockHttpResponseInternal. HttpVersion http_version(request->context()->http_version_major_, request->context()->http_version_minor_); if ((http_version < HttpVersion(1, 0)) || (HttpVersion(1, 1) < http_version)) { http_version.major_ = 1; http_version.minor_ = 0; } HttpStatusCode status_code = HttpStatusCode::FORBIDDEN; response.reset(new HttpResponseJson(http_version, status_code)); ElementPtr response_body = Element::createMap(); uint16_t result = HttpResponse::statusCodeToNumber(status_code); response_body->set(CONTROL_RESULT, Element::create(static_cast(result))); const string& text = HttpResponse::statusCodeToString(status_code); response_body->set(CONTROL_TEXT, Element::create(text)); response->setBodyAsJson(response_body); response->finalize(); return (response); } } // end of namespace isc::config } // end of namespace isc