Kea: http_command_response_creator.cc Source File

| Kea 3.2.0-git

http_command_response_creator.cc
Go to the documentation of this file.
1// Copyright (C) 2021-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <cc/command_interpreter.h>
10#include <config/http_command_response_creator.h>
11#include <config/command_mgr.h>
12#include <config/config_log.h>
13#include <hooks/callout_handle.h>
14#include <hooks/hooks_log.h>
15#include <hooks/hooks_manager.h>
16#include <http/post_request_json.h>
17#include <http/response_json.h>
18#include <boost/pointer_cast.hpp>
19#include <iostream>
20
21using namespace isc::config;
22using namespace isc::data;
23using namespace isc::hooks;
24using namespace isc::http;
25using namespace std;
26
27namespace {
28
30struct HttpCommandHooks {
31 int hook_index_http_auth_;
32 int hook_index_http_response_;
33
35 HttpCommandHooks() {
36 hook_index_http_auth_ = HooksManager::registerHook("http_auth");
37 hook_index_http_response_ = HooksManager::registerHook("http_response");
38 }
39};
40
41} // end of anonymous namespace.
42
43// Declare a Hooks object. As this is outside any function or method, it
44// will be instantiated (and the constructor run) when the module is loaded.
45// As a result, the hook indexes will be defined before any method in this
46// module is called.
47HttpCommandHooks Hooks;
48
49namespace isc {
50namespace config {
51
52 HttpRequestPtr
53HttpCommandResponseCreator::createNewHttpRequest() const {
54 return (HttpRequestPtr(new PostHttpRequestJson()));
55}
56
57 HttpResponsePtr
58HttpCommandResponseCreator::
59 createStockHttpResponse(const HttpRequestPtr& request,
60 const HttpStatusCode& status_code) const {
61 HttpResponsePtr response = createStockHttpResponseInternal(request, status_code);
62 response->finalize();
63 return (response);
64}
65
66 HttpResponsePtr
67HttpCommandResponseCreator::
68createStockHttpResponseInternal(const HttpRequestPtr& request,
69 const HttpStatusCode& status_code) const {
70 // The request hasn't been finalized so the request object
71 // doesn't contain any information about the HTTP version number
72 // used. But, the context should have this data (assuming the
73 // HTTP version is parsed OK).
74 HttpVersion http_version(request->context()->http_version_major_,
75 request->context()->http_version_minor_);
76 // We only accept HTTP version 1.0 or 1.1. If other version number is found
77 // we fall back to HTTP/1.0.
78 if ((http_version < HttpVersion(1, 0)) || (HttpVersion(1, 1) < http_version)) {
79 http_version.major_ = 1;
80 http_version.minor_ = 0;
81 }
82 // This will generate the response holding JSON content.
83 HttpResponsePtr response(new HttpResponseJson(http_version, status_code));
84 // Add extra headers.
85 if (config_) {
86 copyHttpHeaders(config_->getHttpHeaders(), *response);
87 }
88 return (response);
89}
90
91 HttpResponsePtr
92HttpCommandResponseCreator::createDynamicHttpResponse(HttpRequestPtr request) {
93 CfgHttpHeaders headers;
94 HttpResponseJsonPtr http_response;
95
96 // Check the basic HTTP authentication.
97 if (config_) {
98 headers = config_->getHttpHeaders();
99 const HttpAuthConfigPtr& auth = config_->getAuthConfig();
100 if (auth) {
101 http_response = auth->checkAuth(*this, request);
102 }
103 }
104
105 // Pass extra headers to the hook.
106 bool auth_failed = false;
107 if (http_response) {
108 auth_failed = true;
109 copyHttpHeaders(headers, *http_response);
110 }
111
112 // Callout point for "http_auth".
113 bool reset_handle = false;
114 if (HooksManager::calloutsPresent(Hooks.hook_index_http_auth_)) {
115 // Get callout handle.
116 CalloutHandlePtr callout_handle = request->getCalloutHandle();
117 ScopedCalloutHandleState callout_handle_state(callout_handle);
118
119 // Pass arguments.
120 callout_handle->setArgument("request", request);
121 callout_handle->setArgument("response", http_response);
122
123 // Call callouts.
124 HooksManager::callCallouts(Hooks.hook_index_http_auth_,
125 *callout_handle);
126 callout_handle->getArgument("request", request);
127 callout_handle->getArgument("response", http_response);
128
129 // Status other than continue means 'please reset the handle'.
130 if (callout_handle->getStatus() != CalloutHandle::NEXT_STEP_CONTINUE) {
131 reset_handle = true;
132 }
133 }
134
135 // The basic HTTP authentication check or a callout failed and
136 // left a response.
137 if (http_response) {
138 // Avoid to copy extra headers twice even this should not be required.
139 if (!auth_failed && !headers.empty()) {
140 copyHttpHeaders(headers, *http_response);
141 if (http_response->isFinalized()) {
142 // Argh! The response was already finalized.
143 http_response->reset();
144 http_response->finalize();
145 }
146 }
147 return (http_response);
148 }
149
150 // Reset the handle when a hook asks for.
151 if (reset_handle) {
152 request->resetCalloutHandle();
153 }
154
155 // The request is always non-null, because this is verified by the
156 // createHttpResponse method. Let's try to convert it to the
157 // PostHttpRequestJson type as this is the type generated by the
158 // createNewHttpRequest. If the conversion result is null it means that
159 // the caller did not use createNewHttpRequest method to create this
160 // instance. This is considered an error in the server logic.
161 PostHttpRequestJsonPtr request_json =
162 boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
163 if (!request_json) {
164 // Notify the client that we have a problem with our server.
165 return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
166 }
167
168 // We have already checked that the request is finalized so the call
169 // to getBodyAsJson must not trigger an exception.
170 ConstElementPtr command = request_json->getBodyAsJson();
171
172 // Process command doesn't generate exceptions but can possibly return
173 // null response, if the handler is not implemented properly. This is
174 // again an internal server issue.
175 // Check the services argument first.
176 ConstElementPtr response = checkService(command);
177 if (!response) {
178 response = config::CommandMgr::instance().processCommand(command);
179 if (!response) {
180 // Notify the client that we have a problem with our server.
181 return (createStockHttpResponse(request,
182 HttpStatusCode::INTERNAL_SERVER_ERROR));
183 }
184 }
185
186 // Normal Responses coming from the Kea server must always be wrapped in
187 // a list as they may contain responses from multiple daemons.
188 // If we're emulating that for backward compatibility, then we need to wrap
189 // the answer in a list if it isn't in one already.
190 if (HttpCommandConfig::EMULATE_AGENT_RESPONSE &&
191 (response->getType() != Element::list)) {
192 ElementPtr response_list = Element::createList();
193 response_list->add(boost::const_pointer_cast<Element>(response));
194 response = response_list;
195 }
196
197 // The response is OK, so let's create new HTTP response with the status OK.
198 http_response = boost::dynamic_pointer_cast<
199 HttpResponseJson>(createStockHttpResponseInternal(request, HttpStatusCode::OK));
200 http_response->setBodyAsJson(response);
201 http_response->finalize();
202
203 // Callout point for "http_response".
204 if (HooksManager::calloutsPresent(Hooks.hook_index_http_response_)) {
205 // Get callout handle.
206 CalloutHandlePtr callout_handle = request->getCalloutHandle();
207 ScopedCalloutHandleState callout_handle_state(callout_handle);
208
209 // Pass arguments.
210 callout_handle->setArgument("request", request);
211 callout_handle->setArgument("response", http_response);
212
213 // Call callouts.
214 HooksManager::callCallouts(Hooks.hook_index_http_response_,
215 *callout_handle);
216 callout_handle->getArgument("response", http_response);
217
218 // Ignore status as the HTTP response is used instead.
219 }
220
221 return (http_response);
222}
223
224 ConstElementPtr
225HttpCommandResponseCreator::checkService(ConstElementPtr command) const {
226 if (HttpCommandConfig::SUPPORTED_SERVICE.empty()) {
227 return (ConstElementPtr());
228 }
229
230 // Sanity checks (errors will be handled by processCommand).
231 if (!command || (command->getType() != Element::map)) {
232 return (ConstElementPtr());
233 }
234 ConstElementPtr services = command->get("service");
235 if (!services) {
236 return (ConstElementPtr());
237 }
238 if (services->getType() != Element::list) {
239 return (createAnswer(CONTROL_RESULT_ERROR, "service value must be a list"));
240 }
241 // Ignore empty service list as the control agent does.
242 if (services->empty()) {
243 return (ConstElementPtr());
244 }
245 if (services->size() != 1) {
246 return (createAnswer(CONTROL_RESULT_ERROR, "service value has more than one item"));
247 }
248 ConstElementPtr service = services->get(0);
249 if (!service || (service->getType() != Element::string)) {
250 return (createAnswer(CONTROL_RESULT_ERROR, "service name must be a string"));
251 }
252 string name = service->stringValue();
253 if (name.empty() || (name == HttpCommandConfig::SUPPORTED_SERVICE)) {
254 return (ConstElementPtr());
255 }
256 // Service name mismatch.
257 string msg = "unsupported service '" + name + "', expected '";
258 msg += HttpCommandConfig::SUPPORTED_SERVICE + "'";
259 return (createAnswer(CONTROL_RESULT_ERROR,msg));
260}
261
262} // end of namespace isc::config
263} // end of namespace isc
callout_handle.h
CalloutHandle::NEXT_STEP_CONTINUE
@ NEXT_STEP_CONTINUE
continue normally
Definition callout_handle.h:87
Element::map
@ map
Definition data.h:160
Element::list
@ list
Definition data.h:159
Element::string
@ string
Definition data.h:157
Element::createList
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:349
isc::config::BaseCommandMgr::processCommand
virtual isc::data::ConstElementPtr processCommand(const isc::data::ConstElementPtr &cmd)
Triggers command processing.
Definition base_command_mgr.cc:119
isc::config::CommandMgr::instance
static CommandMgr & instance()
CommandMgr is a singleton class.
Definition command_mgr.cc:18
isc::config::HttpCommandConfig::SUPPORTED_SERVICE
static std::string SUPPORTED_SERVICE
Supported service.
Definition http_command_config.h:183
isc::config::HttpCommandConfig::EMULATE_AGENT_RESPONSE
static bool EMULATE_AGENT_RESPONSE
Emulation flag.
Definition http_command_config.h:180
isc::config::HttpCommandResponseCreator::createNewHttpRequest
virtual http::HttpRequestPtr createNewHttpRequest() const
Create a new request.
Definition http_command_response_creator.cc:53
isc::config::HttpCommandResponseCreator::createStockHttpResponse
virtual http::HttpResponsePtr createStockHttpResponse(const http::HttpRequestPtr &request, const http::HttpStatusCode &status_code) const
Creates stock HTTP response.
Definition http_command_response_creator.cc:59
isc::hooks::HooksManager::registerHook
static int registerHook(const std::string &name)
Register Hook.
Definition hooks_manager.cc:232
isc::hooks::HooksManager::calloutsPresent
static bool calloutsPresent(int index)
Are callouts present?
Definition hooks_manager.cc:54
isc::hooks::HooksManager::callCallouts
static void callCallouts(int index, CalloutHandle &handle)
Calls the callouts for a given hook.
Definition hooks_manager.cc:76
isc::http::HttpResponseJson
Represents HTTP response with JSON content.
Definition response_json.h:34
isc::http::PostHttpRequestJson
Represents HTTP POST request with JSON body.
Definition post_request_json.h:37
command_interpreter.h
This file contains several functions and constants that are used for handling commands and responses ...
command_mgr.h
config_log.h
Hooks
Dhcp4Hooks Hooks
Definition dhcp4_srv.cc:213
hooks_log.h
hooks_manager.h
http_command_response_creator.h
isc::config
Definition command_interpreter.cc:23
isc::config::CONTROL_RESULT_ERROR
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
Definition command_interpreter.h:45
isc::config::createAnswer
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
Definition command_interpreter.cc:54
isc::data
Definition base_stamped_element.cc:12
isc::data::ConstElementPtr
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
isc::data::ElementPtr
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
isc::hooks
Definition callout_handle.cc:21
isc::hooks::CalloutHandlePtr
boost::shared_ptr< CalloutHandle > CalloutHandlePtr
A shared pointer to a CalloutHandle object.
Definition callout_handle.h:470
isc::http
Definition auth_config.h:19
isc::http::HttpStatusCode
HttpStatusCode
HTTP status codes (cf RFC 2068).
Definition response.h:30
isc::http::copyHttpHeaders
void copyHttpHeaders(const CfgHttpHeaders &headers, const HTTP_MSG &message)
Copy config HTTP headers to message.
Definition cfg_http_header.h:55
isc::http::PostHttpRequestJsonPtr
boost::shared_ptr< PostHttpRequestJson > PostHttpRequestJsonPtr
Pointer to PostHttpRequestJson.
Definition post_request_json.h:29
isc::http::HttpAuthConfigPtr
boost::shared_ptr< HttpAuthConfig > HttpAuthConfigPtr
Type of shared pointers to HTTP authentication configuration.
Definition auth_config.h:97
isc::http::HttpResponseJsonPtr
boost::shared_ptr< HttpResponseJson > HttpResponseJsonPtr
Pointer to the HttpResponseJson object.
Definition response_json.h:27
isc::http::HttpResponsePtr
boost::shared_ptr< HttpResponse > HttpResponsePtr
Pointer to the HttpResponse object.
Definition response.h:81
isc::http::HttpRequestPtr
boost::shared_ptr< HttpRequest > HttpRequestPtr
Pointer to the HttpRequest object.
Definition request.h:30
isc::http::CfgHttpHeaders
std::vector< CfgHttpHeader > CfgHttpHeaders
Collection of config HTTP headers.
Definition cfg_http_header.h:47
isc
Defines the logger used by the top-level component of kea-lfc.
Definition check_exists_add.cc:18
post_request_json.h
response_json.h
isc::http::HttpVersion
HTTP protocol version.
Definition http_types.h:14