summaryrefslogtreecommitdiffstats
path: root/src/lib/config/cmd_response_creator.cc
blob: c586d98b8833c95041627a9ee8a228e68df1fc14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// 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 <config.h>

#include <config/cmd_response_creator.h>
#include <config/command_mgr.h>
#include <config/config_log.h>
#include <cc/command_interpreter.h>
#include <http/post_request_json.h>
#include <http/response_json.h>
#include <boost/pointer_cast.hpp>
#include <iostream>

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<string> 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<Element>(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<string>& 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<long long>(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