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