1 // Copyright (C) 2009-2018 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 <exceptions/exceptions.h>
10 #include <cc/command_interpreter.h>
11 #include <cc/data.h>
12 #include <string>
13 #include <set>
14
15 using namespace std;
16
17 using isc::data::Element;
18 using isc::data::ConstElementPtr;
19 using isc::data::ElementPtr;
20 using isc::data::JSONError;
21
22 namespace isc {
23 namespace config {
24
25 const char *CONTROL_COMMAND = "command";
26 const char *CONTROL_RESULT = "result";
27 const char *CONTROL_TEXT = "text";
28 const char *CONTROL_ARGUMENTS = "arguments";
29 const char *CONTROL_SERVICE = "service";
30
31 // Full version, with status, text and arguments
32 ConstElementPtr
createAnswer(const int status_code,const std::string & text,const ConstElementPtr & arg)33 createAnswer(const int status_code, const std::string& text,
34 const ConstElementPtr& arg) {
35 if (status_code != 0 && text.empty()) {
36 isc_throw(CtrlChannelError, "Text has to be provided for status_code != 0");
37 }
38
39 ElementPtr answer = Element::createMap();
40 ElementPtr result = Element::create(status_code);
41 answer->set(CONTROL_RESULT, result);
42
43 if (!text.empty()) {
44 answer->set(CONTROL_TEXT, Element::create(text));
45 }
46 if (arg) {
47 answer->set(CONTROL_ARGUMENTS, arg);
48 }
49 return (answer);
50 }
51
52 ConstElementPtr
createAnswer()53 createAnswer() {
54 return (createAnswer(0, string(""), ConstElementPtr()));
55 }
56
57 ConstElementPtr
createAnswer(const int status_code,const std::string & text)58 createAnswer(const int status_code, const std::string& text) {
59 return (createAnswer(status_code, text, ElementPtr()));
60 }
61
62 ConstElementPtr
createAnswer(const int status_code,const ConstElementPtr & arg)63 createAnswer(const int status_code, const ConstElementPtr& arg) {
64 return (createAnswer(status_code, "", arg));
65 }
66
67 ConstElementPtr
parseAnswer(int & rcode,const ConstElementPtr & msg)68 parseAnswer(int &rcode, const ConstElementPtr& msg) {
69 if (!msg) {
70 isc_throw(CtrlChannelError, "No answer specified");
71 }
72 if (msg->getType() != Element::map) {
73 isc_throw(CtrlChannelError,
74 "Invalid answer Element specified, expected map");
75 }
76 if (!msg->contains(CONTROL_RESULT)) {
77 isc_throw(CtrlChannelError,
78 "Invalid answer specified, does not contain mandatory 'result'");
79 }
80
81 ConstElementPtr result = msg->get(CONTROL_RESULT);
82 if (result->getType() != Element::integer) {
83 isc_throw(CtrlChannelError,
84 "Result element in answer message is not a string");
85 }
86
87 rcode = result->intValue();
88
89 // If there are arguments, return them.
90 ConstElementPtr args = msg->get(CONTROL_ARGUMENTS);
91 if (args) {
92 return (args);
93 }
94
95 // There are no arguments, let's try to return just the text status
96 return (msg->get(CONTROL_TEXT));
97 }
98
99 std::string
answerToText(const ConstElementPtr & msg)100 answerToText(const ConstElementPtr& msg) {
101 if (!msg) {
102 isc_throw(CtrlChannelError, "No answer specified");
103 }
104 if (msg->getType() != Element::map) {
105 isc_throw(CtrlChannelError,
106 "Invalid answer Element specified, expected map");
107 }
108 if (!msg->contains(CONTROL_RESULT)) {
109 isc_throw(CtrlChannelError,
110 "Invalid answer specified, does not contain mandatory 'result'");
111 }
112
113 ConstElementPtr result = msg->get(CONTROL_RESULT);
114 if (result->getType() != Element::integer) {
115 isc_throw(CtrlChannelError,
116 "Result element in answer message is not a string");
117 }
118
119 stringstream txt;
120 int rcode = result->intValue();
121 if (rcode == 0) {
122 txt << "success(0)";
123 } else {
124 txt << "failure(" << rcode << ")";
125 }
126
127 // Was any text provided? If yes, include it.
128 ConstElementPtr txt_elem = msg->get(CONTROL_TEXT);
129 if (txt_elem) {
130 txt << ", text=" << txt_elem->stringValue();
131 }
132
133 return (txt.str());
134 }
135
136 ConstElementPtr
createCommand(const std::string & command)137 createCommand(const std::string& command) {
138 return (createCommand(command, ElementPtr(), ""));
139 }
140
141 ConstElementPtr
createCommand(const std::string & command,ConstElementPtr arg)142 createCommand(const std::string& command, ConstElementPtr arg) {
143 return (createCommand(command, arg, ""));
144 }
145
146 ConstElementPtr
createCommand(const std::string & command,const std::string & service)147 createCommand(const std::string& command, const std::string& service) {
148 return (createCommand(command, ElementPtr(), service));
149 }
150
151 ConstElementPtr
createCommand(const std::string & command,ConstElementPtr arg,const std::string & service)152 createCommand(const std::string& command,
153 ConstElementPtr arg,
154 const std::string& service) {
155 ElementPtr query = Element::createMap();
156 ElementPtr cmd = Element::create(command);
157 query->set(CONTROL_COMMAND, cmd);
158 if (arg) {
159 query->set(CONTROL_ARGUMENTS, arg);
160 }
161 if (!service.empty()) {
162 ElementPtr services = Element::createList();
163 services->add(Element::create(service));
164 query->set(CONTROL_SERVICE, services);
165 }
166 return (query);
167 }
168
169 std::string
parseCommand(ConstElementPtr & arg,ConstElementPtr command)170 parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
171 if (!command) {
172 isc_throw(CtrlChannelError, "No command specified");
173 }
174 if (command->getType() != Element::map) {
175 isc_throw(CtrlChannelError, "Invalid command Element specified, expected map");
176 }
177 if (!command->contains(CONTROL_COMMAND)) {
178 isc_throw(CtrlChannelError,
179 "Invalid answer specified, does not contain mandatory 'command'");
180 }
181
182 // Make sure that all specified parameters are supported.
183 auto command_params = command->mapValue();
184 for (auto param : command_params) {
185 if ((param.first != CONTROL_COMMAND) &&
186 (param.first != CONTROL_ARGUMENTS) &&
187 (param.first != CONTROL_SERVICE)) {
188 isc_throw(CtrlChannelError, "Received command contains unsupported "
189 "parameter '" << param.first << "'");
190 }
191 }
192
193 ConstElementPtr cmd = command->get(CONTROL_COMMAND);
194 if (cmd->getType() != Element::string) {
195 isc_throw(CtrlChannelError,
196 "'command' element in command message is not a string");
197 }
198
199 arg = command->get(CONTROL_ARGUMENTS);
200
201 return (cmd->stringValue());
202 }
203
204 std::string
parseCommandWithArgs(ConstElementPtr & arg,ConstElementPtr command)205 parseCommandWithArgs(ConstElementPtr& arg, ConstElementPtr command) {
206 std::string command_name = parseCommand(arg, command);
207
208 // This function requires arguments within the command.
209 if (!arg) {
210 isc_throw(CtrlChannelError,
211 "no arguments specified for the '" << command_name
212 << "' command");
213 }
214
215 // Arguments must be a map.
216 if (arg->getType() != Element::map) {
217 isc_throw(CtrlChannelError, "arguments specified for the '" << command_name
218 << "' command are not a map");
219 }
220
221 // At least one argument is required.
222 if (arg->size() == 0) {
223 isc_throw(CtrlChannelError, "arguments must not be empty for "
224 "the '" << command_name << "' command");
225 }
226
227 return (command_name);
228 }
229
230 ConstElementPtr
combineCommandsLists(const ConstElementPtr & response1,const ConstElementPtr & response2)231 combineCommandsLists(const ConstElementPtr& response1,
232 const ConstElementPtr& response2) {
233 // Usually when this method is called there should be two non-null
234 // responses. If there is just a single response, return this
235 // response.
236 if (!response1 && response2) {
237 return (response2);
238
239 } else if (response1 && !response2) {
240 return (response1);
241
242 } else if (!response1 && !response2) {
243 return (ConstElementPtr());
244
245 } else {
246 // Both responses are non-null so we need to combine the lists
247 // of supported commands if the status codes are 0.
248 int status_code;
249 ConstElementPtr args1 = parseAnswer(status_code, response1);
250 if (status_code != 0) {
251 return (response1);
252 }
253
254 ConstElementPtr args2 = parseAnswer(status_code, response2);
255 if (status_code != 0) {
256 return (response2);
257 }
258
259 const std::vector<ElementPtr> vec1 = args1->listValue();
260 const std::vector<ElementPtr> vec2 = args2->listValue();
261
262 // Storing command names in a set guarantees that the non-unique
263 // command names are aggregated.
264 std::set<std::string> combined_set;
265 for (auto v = vec1.cbegin(); v != vec1.cend(); ++v) {
266 combined_set.insert((*v)->stringValue());
267 }
268 for (auto v = vec2.cbegin(); v != vec2.cend(); ++v) {
269 combined_set.insert((*v)->stringValue());
270 }
271
272 // Create a combined list of commands.
273 ElementPtr combined_list = Element::createList();
274 for (auto s = combined_set.cbegin(); s != combined_set.cend(); ++s) {
275 combined_list->add(Element::create(*s));
276 }
277 return (createAnswer(CONTROL_RESULT_SUCCESS, combined_list));
278 }
279 }
280
281 }
282 }
283