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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// Copyright (C) 2017-2024 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 <agent/ca_cfg_mgr.h>
#include <agent/ca_command_mgr.h>
#include <agent/ca_controller.h>
#include <agent/ca_log.h>
#include <agent/ca_process.h>
#include <asiolink/asio_wrapper.h>
#include <asiolink/io_service.h>
#include <asiolink/unix_domain_socket.h>
#include <cc/command_interpreter.h>
#include <cc/data.h>
#include <cc/json_feed.h>
#include <config/client_connection.h>
#include <config/timeouts.h>
#include <boost/pointer_cast.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <iterator><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <sstream><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <string><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <vector><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc::asiolink;
using namespace isc::config;
using namespace isc::data;
using namespace isc::hooks;
using namespace isc::process;

namespace isc {
namespace agent {

CtrlAgentCommandMgr&
CtrlAgentCommandMgr::instance() {
    static CtrlAgentCommandMgr command_mgr;
    return (command_mgr);
}

CtrlAgentCommandMgr::CtrlAgentCommandMgr()
    : HookedCommandMgr() {
}

isc::data::ConstElementPtr
CtrlAgentCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
    ConstElementPtr answer = HookedCommandMgr::processCommand(cmd);

    // Responses from the Kea Control Agent must be always wrapped
    // in a list because in general they contain responses from
    // multiple daemons.
    if (answer->getType() == Element::list) {
        return (answer);
    }
    ElementPtr answer_list = Element::createList();
    answer_list->add(boost::const_pointer_cast<Element>(answer));

    return (answer_list);
}

ConstElementPtr
CtrlAgentCommandMgr::handleCommand(const std::string& cmd_name,
                                   const isc::data::ConstElementPtr& params,
                                   const isc::data::ConstElementPtr& original_cmd) {

    ConstElementPtr raddr_ptr = original_cmd->get("remote-address");
    if (raddr_ptr && (raddr_ptr->getType() == Element::string)) {
        remote_addr_ = raddr_ptr->stringValue();
    } else {
        remote_addr_ = "(unknown)";
    }
    LOG_INFO(agent_logger, CTRL_AGENT_COMMAND_RECEIVED)
        .arg(cmd_name)
        .arg(remote_addr_);

    ConstElementPtr services = Element::createList();

    // Retrieve 'service' parameter to determine if we should forward the
    // command or handle it on our own.
    if (original_cmd && original_cmd->contains("service")) {
        services = original_cmd->get("service");
        // If 'service' value is not a list, this is a fatal error. We don't want
        // to try processing commands that don't adhere to the required format.
        if (services->getType() != Element::list) {
            return (createAnswer(CONTROL_RESULT_ERROR, "service value must be a list"));
        }
    }

    // 'service' parameter hasn't been specified which indicates that the command
    // is intended to be processed by the CA. The following command will try to
    // process the command with hooks libraries (if available) or by one of the
    // CA's native handlers.
    if (services->empty()) {

        // It is frequent user error to not include the 'service' parameter in
        // the commands that should be forwarded to Kea servers. If the command
        // lacks this parameter the CA will try to process it and often fail
        // because it is not supported by the CA. In the future we may want to
        // make this parameter mandatory. For now, we're going to improve the
        // situation by clearly explaining to the controlling client that the
        // command is not supported by the CA, but it is possible that he may
        // achieve what he wants by providing the 'service' parameter.

        // Our interface is very restrictive so we walk around this by const
        // casting the returned pointer. It is certainly easier to do than
        // changing the whole data interface.
        ElementPtr answer = boost::const_pointer_cast<Element>
            (HookedCommandMgr::handleCommand(cmd_name, params, original_cmd));

        try {
            // Check what error code was returned by the handler.
            int rcode = 0;
            ConstElementPtr text = parseAnswer(rcode, answer);

            // There is a dedicated error code for unsupported command case.
            if (rcode == CONTROL_RESULT_COMMAND_UNSUPPORTED) {

                // Append the explanatory text to the text reported by the handler.
                // Smart, eh?
                std::ostringstream s;
                s << text->stringValue();
                s << " You did not include \"service\" parameter in the command,"
                    " which indicates that Kea Control Agent should process this"
                    " command rather than forward it to one or more Kea servers. If you"
                    " aimed to send this command to one of the Kea servers you"
                    " should include the \"service\" parameter in your request, e.g."
                    " \"service\": [ \"dhcp4\" ] to forward the command to the DHCPv4"
                    " server, or \"service\": [ \"dhcp4\", \"dhcp6\", \"d2\" ] to forward it to"
                    " DHCPv4, DHCPv6 and D2 servers etc.";

                answer->set(CONTROL_TEXT, Element::create(s.str()));
            }

        } catch (...) {
            // Exceptions are not really possible assuming that the BaseCommandMgr
            // creates the response correctly.
        }

        return (answer);
    }

    ElementPtr answer_list = Element::createList();

    // Before the command is forwarded we check if there are any hooks libraries
    // which would process the command.
    if (HookedCommandMgr::delegateCommandToHookLibrary(cmd_name, params, original_cmd,
                                                       answer_list)) {
        // The command has been processed by hooks library. Return the result.
        return (answer_list);
    }

    // We don't know whether the hooks libraries modified the value of the
    //  answer list, so let's be safe and re-create the answer_list.
    answer_list = Element::createList();

    // For each value within 'service' we have to try forwarding the command.
    for (unsigned i = 0; i < services->size(); ++i) {
        if (original_cmd) {
            ConstElementPtr answer;
            try {
                LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
                          CTRL_AGENT_COMMAND_FORWARD_BEGIN)
                    .arg(cmd_name).arg(services->get(i)->stringValue());

                answer = forwardCommand(services->get(i)->stringValue(),
                                        cmd_name, original_cmd);

            } catch (const CommandForwardingError& ex) {
                LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
                          CTRL_AGENT_COMMAND_FORWARD_FAILED)
                    .arg(cmd_name).arg(ex.what());
                answer = createAnswer(CONTROL_RESULT_ERROR, ex.what());
            }

            answer_list->add(boost::const_pointer_cast<Element>(answer));
        }
    }

    return (answer_list);
}

ConstElementPtr
CtrlAgentCommandMgr::forwardCommand(const std::string& service,
                                    const std::string& cmd_name,
                                    const isc::data::ConstElementPtr& command) {
    // Context will hold the server configuration.
    CtrlAgentCfgContextPtr ctx;

    // There is a hierarchy of the objects through which we need to pass to get
    // the configuration context. We may simplify this at some point but since
    // we're in the singleton we want to make sure that we're using most current
    // configuration.
    boost::shared_ptr<CtrlAgentController> controller =
        boost::dynamic_pointer_cast<CtrlAgentController>(CtrlAgentController::instance());
    if (controller) {
        CtrlAgentProcessPtr process = controller->getCtrlAgentProcess();
        if (process) {
            CtrlAgentCfgMgrPtr cfgmgr = process->getCtrlAgentCfgMgr();
            if (cfgmgr) {
                ctx = cfgmgr->getCtrlAgentCfgContext();
            }
        }
    }

    // This is highly unlikely but keep the checks just in case someone messes up
    // in the code.
    if (!ctx) {
        isc_throw(CommandForwardingError, "internal server error: unable to retrieve"
                  " Control Agent configuration information");
    }

    // Now that we know what service it should be forwarded to, we should
    // find a matching forwarding socket. If this socket is not configured,
    // we have to communicate it to the client.
    ConstElementPtr socket_info = ctx->getControlSocketInfo(service);
    if (!socket_info) {
        isc_throw(CommandForwardingError, "forwarding socket is not configured"
                  " for the server type " << service);
    }

    // If the configuration does its job properly the socket-name must be
    // specified and must be a string value.
    std::string socket_name = socket_info->get("socket-name")->stringValue();

    // Forward command and receive reply.
    IOServicePtr io_service(new IOService());;
    ClientConnection conn(io_service);
    boost::system::error_code received_ec;
    ConstJSONFeedPtr received_feed;
    conn.start(ClientConnection::SocketPath(socket_name),
               ClientConnection::ControlCommand(command->toWire()),
               [&io_service, &received_ec, &received_feed]
               (const boost::system::error_code& ec, ConstJSONFeedPtr feed) {
                   // Capture error code and parsed data.
                   received_ec = ec;
                   received_feed = feed;
                   // Got the IO service so stop IO service. This causes to
                   // stop IO service when all handlers have been invoked.
                   io_service->stopWork();
               }, ClientConnection::Timeout(TIMEOUT_AGENT_FORWARD_COMMAND));
    io_service->run();

    if (received_ec) {
        isc_throw(CommandForwardingError, "unable to forward command to the "
                  << service << " service: " << received_ec.message()
                  << ". The server is likely to be offline");
    }

    // This shouldn't happen because the fact that there was no time out indicates
    // that the whole response has been read and it should be stored within the
    // feed. But, let's check to prevent assertions.
    if (!received_feed) {
        isc_throw(CommandForwardingError, "internal server error: empty response"
                  " received from the unix domain socket");
    }

    ConstElementPtr answer;
    try {
        answer = received_feed->toElement();

        LOG_INFO(agent_logger, CTRL_AGENT_COMMAND_FORWARDED)
            .arg(cmd_name)
            .arg(service)
            .arg(remote_addr_);

    } catch (const std::exception& ex) {
        isc_throw(CommandForwardingError, "internal server error: unable to parse"
                  " server's answer to the forwarded message: " << ex.what());
    }

    return (answer);
}


} // end of namespace isc::agent
} // end of namespace isc