Branch data Line data Source code
1 : : // Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
2 : : //
3 : : // Permission to use, copy, modify, and/or distribute this software for any
4 : : // purpose with or without fee is hereby granted, provided that the above
5 : : // copyright notice and this permission notice appear in all copies.
6 : : //
7 : : // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
8 : : // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9 : : // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
10 : : // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11 : : // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
12 : : // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13 : : // PERFORMANCE OF THIS SOFTWARE.
14 : :
15 : : #include <auth/command.h>
16 : : #include <auth/auth_log.h>
17 : : #include <auth/auth_srv.h>
18 : :
19 : : #include <cc/data.h>
20 : : #include <datasrc/memory_datasrc.h>
21 : : #include <datasrc/factory.h>
22 : : #include <config/ccsession.h>
23 : : #include <exceptions/exceptions.h>
24 : : #include <dns/rrclass.h>
25 : :
26 : : #include <string>
27 : :
28 : : #include <boost/scoped_ptr.hpp>
29 : : #include <boost/shared_ptr.hpp>
30 : :
31 : : #include <sys/types.h>
32 : : #include <unistd.h>
33 : :
34 : : using boost::scoped_ptr;
35 : : using namespace isc::auth;
36 : : using namespace isc::config;
37 : : using namespace isc::data;
38 : : using namespace isc::datasrc;
39 : : using namespace isc::dns;
40 : : using namespace std;
41 : :
42 : : namespace {
43 : : /// An exception that is thrown if an error occurs while handling a command
44 : : /// on an \c AuthSrv object.
45 : : ///
46 : : /// Currently it's only used internally, since \c execAuthServerCommand()
47 : : /// (which is the only interface to this module) catches all \c isc::
48 : : /// exceptions and converts them.
49 : 9 : class AuthCommandError : public isc::Exception {
50 : : public:
51 : : AuthCommandError(const char* file, size_t line, const char* what) :
52 [ + - ][ + - ]: 9 : isc::Exception(file, line, what) {}
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
53 : : };
54 : :
55 : : /// An abstract base class that represents a single command identifier
56 : : /// for an \c AuthSrv object.
57 : : ///
58 : : /// Each of derived classes of \c AuthCommand, which are hidden inside the
59 : : /// implementation, corresponds to a command executed on \c AuthSrv, such as
60 : : /// "shutdown". The derived class is responsible to execute the corresponding
61 : : /// command with the given command arguments (if any) in its \c exec()
62 : : /// method.
63 : : ///
64 : : /// In the initial implementation the existence of the command classes is
65 : : /// hidden inside the implementation since the only public interface is
66 : : /// \c execAuthServerCommand(), which does not expose this class.
67 : : /// In future, we may want to make this framework more dynamic, i.e.,
68 : : /// registering specific derived classes run time outside of this
69 : : /// implementation. If and when that happens the definition of the abstract
70 : : /// class will be published.
71 : : class AuthCommand {
72 : : ///
73 : : /// \name Constructors and Destructor
74 : : ///
75 : : /// Note: The copy constructor and the assignment operator are
76 : : /// intentionally defined as private to make it explicit that this is a
77 : : /// pure base class.
78 : : //@{
79 : : private:
80 : : AuthCommand(const AuthCommand& source);
81 : : AuthCommand& operator=(const AuthCommand& source);
82 : : protected:
83 : : /// \brief The default constructor.
84 : : ///
85 : : /// This is intentionally defined as \c protected as this base class should
86 : : /// never be instantiated (except as part of a derived class).
87 : 26 : AuthCommand() {}
88 : : public:
89 : : /// The destructor.
90 : 26 : virtual ~AuthCommand() {}
91 : : //@}
92 : :
93 : : /// Execute a single control command.
94 : : ///
95 : : /// Specific derived methods can throw exceptions. When called via
96 : : /// \c execAuthServerCommand(), all BIND 10 exceptions are caught
97 : : /// and converted into an error code.
98 : : /// The derived method may also throw an exception of class
99 : : /// \c AuthCommandError when it encounters an internal error, such as
100 : : /// semantics error on the command arguments.
101 : : ///
102 : : /// \param server The \c AuthSrv object on which the command is executed.
103 : : /// \param args Command specific argument.
104 : : virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) = 0;
105 : : };
106 : :
107 : : // Handle the "shutdown" command. An optional parameter "pid" is used to
108 : : // see if it is really for our instance.
109 : 6 : class ShutdownCommand : public AuthCommand {
110 : : public:
111 : 6 : virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
112 : : // Is the pid argument provided?
113 [ + + ][ + - ]: 6 : if (args && args->contains("pid")) {
[ + - ][ - + ]
[ + + ][ + + ]
[ # # ]
114 : : // If it is, we check it is the same as our PID
115 : :
116 : : // This might throw in case the type is not an int, but that's
117 : : // OK, as it'll get converted to an error on higher level.
118 [ + - ][ + + ]: 6 : const int pid(args->get("pid")->intValue());
119 : 2 : const pid_t my_pid(getpid());
120 [ + + ]: 2 : if (my_pid != pid) {
121 : : // It is not for us
122 : : //
123 : : // Note that this is completely expected situation, if
124 : : // there are multiple instances of the server running and
125 : : // another instance is being shut down, we get the message
126 : : // too, due to the multicast nature of our message bus.
127 : 5 : return;
128 : : }
129 : : }
130 [ + - ]: 4 : LOG_DEBUG(auth_logger, DBG_AUTH_SHUT, AUTH_SHUTDOWN);
131 : 4 : server.stop();
132 : : }
133 : : };
134 : :
135 : : // Handle the "sendstats" command. No argument is assumed.
136 : 1 : class SendStatsCommand : public AuthCommand {
137 : : public:
138 : 1 : virtual void exec(AuthSrv& server, isc::data::ConstElementPtr) {
139 [ + - ]: 1 : LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_RECEIVED_SENDSTATS);
140 : 1 : server.submitStatistics();
141 : 1 : }
142 : : };
143 : :
144 : : // Handle the "loadzone" command.
145 : 38 : class LoadZoneCommand : public AuthCommand {
146 : : public:
147 : 19 : virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
148 : : // parse and validate the args.
149 [ + + + + ]: 27 : if (!validate(server, args)) {
150 : 3 : return;
151 : : }
152 : :
153 : 7 : const ConstElementPtr zone_config = getZoneConfig(server);
154 : :
155 : : // Load a new zone and replace the current zone with the new one.
156 : : // TODO: eventually this should be incremental or done in some way
157 : : // that doesn't block other server operations.
158 : : // TODO: we may (should?) want to check the "last load time" and
159 : : // the timestamp of the file and skip loading if the file isn't newer.
160 [ + - ][ + - ]: 4 : const ConstElementPtr type(zone_config->get("filetype"));
161 : : boost::shared_ptr<InMemoryZoneFinder> zone_finder(
162 : 4 : new InMemoryZoneFinder(old_zone_finder_->getClass(),
163 [ + - ][ + - ]: 8 : old_zone_finder_->getOrigin()));
[ + - ][ + - ]
164 [ + + ][ + - ]: 4 : if (type && type->stringValue() == "sqlite3") {
[ + - ][ - + ]
[ + + ][ + + ]
[ # # ]
165 : : scoped_ptr<DataSourceClientContainer>
166 : : container(new DataSourceClientContainer("sqlite3",
167 : : Element::fromJSON(
168 : : "{\"database_file\": \"" +
169 [ + - ][ + - ]: 6 : zone_config->get("file")->stringValue() + "\"}")));
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
170 : : zone_finder->load(*container->getInstance().getIterator(
171 [ + - ][ + - ]: 3 : old_zone_finder_->getOrigin()));
[ + - ]
172 : : } else {
173 [ + - ][ + + ]: 3 : zone_finder->load(old_zone_finder_->getFileName());
174 : : }
175 [ + - ]: 2 : old_zone_finder_->swap(*zone_finder);
176 [ + - ][ + - ]: 4 : LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
[ + - ]
177 [ + - ][ + - ]: 4 : .arg(zone_finder->getOrigin()).arg(zone_finder->getClass());
[ + - ][ + - ]
[ + - ]
178 : : }
179 : :
180 : : private:
181 : : // zone finder to be updated with the new file.
182 : : boost::shared_ptr<InMemoryZoneFinder> old_zone_finder_;
183 : :
184 : : // A helper private method to parse and validate command parameters.
185 : : // On success, it sets 'old_zone_finder_' to the zone to be updated.
186 : : // It returns true if everything is okay; and false if the command is
187 : : // valid but there's no need for further process.
188 : 19 : bool validate(AuthSrv& server, isc::data::ConstElementPtr args) {
189 [ + + ]: 19 : if (args == NULL) {
190 [ + - ]: 2 : isc_throw(AuthCommandError, "Null argument");
191 : : }
192 : :
193 : : // In this initial implementation, we assume memory data source
194 : : // for class IN by default.
195 [ + - ]: 18 : ConstElementPtr datasrc_elem = args->get("datasrc");
196 [ + + ]: 18 : if (datasrc_elem) {
197 [ + + ][ + - ]: 3 : if (datasrc_elem->stringValue() == "sqlite3") {
[ + + ]
198 [ + - ][ + - ]: 1 : LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_SQLITE3);
[ + - ][ + - ]
199 : : return (false);
200 [ + - ][ + - ]: 2 : } else if (datasrc_elem->stringValue() != "memory") {
201 : : // (note: at this point it's guaranteed that datasrc_elem
202 : : // is of string type)
203 [ + - ][ + - ]: 2 : isc_throw(AuthCommandError,
[ + - ][ + - ]
[ + - ]
204 : : "Data source type " << datasrc_elem->stringValue()
205 : : << " is not supported");
206 : : }
207 : : }
208 : :
209 [ + - ][ + - ]: 15 : ConstElementPtr class_elem = args->get("class");
210 : : const RRClass zone_class =
211 [ + + ][ + + ]: 27 : class_elem ? RRClass(class_elem->stringValue()) : RRClass::IN();
[ + + ][ + + ]
[ + + ]
212 : :
213 : : AuthSrv::InMemoryClientPtr datasrc(server.
214 [ + + ]: 13 : getInMemoryClient(zone_class));
215 [ + + ]: 12 : if (datasrc == NULL) {
216 [ + - ][ + - ]: 2 : isc_throw(AuthCommandError, "Memory data source is disabled");
217 : : }
218 : :
219 [ + - ][ + - ]: 11 : ConstElementPtr origin_elem = args->get("origin");
220 [ + + ]: 11 : if (!origin_elem) {
221 [ + - ][ + - ]: 2 : isc_throw(AuthCommandError, "Zone origin is missing");
222 : : }
223 [ + + ][ + + ]: 18 : const Name origin = Name(origin_elem->stringValue());
224 : :
225 : : // Get the current zone
226 [ + - ]: 8 : const InMemoryClient::FindResult result = datasrc->findZone(origin);
227 [ + + ]: 8 : if (result.code != result::SUCCESS) {
228 [ + - ][ + - ]: 2 : isc_throw(AuthCommandError, "Zone " << origin <<
[ + - ][ + - ]
229 : : " is not found in data source");
230 : : }
231 : :
232 : : old_zone_finder_ = boost::dynamic_pointer_cast<InMemoryZoneFinder>(
233 [ + - ]: 7 : result.zone_finder);
234 : :
235 : 7 : return (true);
236 : : }
237 : :
238 : 7 : ConstElementPtr getZoneConfig(const AuthSrv &server) {
239 [ + + ]: 7 : if (!server.getConfigSession()) {
240 : : // FIXME: This is a hack to make older tests pass. We should
241 : : // update these tests as well sometime and remove this hack.
242 : : // (note that under normal situation, the
243 : : // server.getConfigSession() does not return NULL)
244 : :
245 : : // We provide an empty map, which means no configuration --
246 : : // defaults.
247 : 3 : return (ConstElementPtr(new MapElement()));
248 : : }
249 : :
250 : : // Find the config corresponding to the zone.
251 : : // We expect the configuration to be valid, as we have it and we
252 : : // accepted it before, therefore it must be validated.
253 [ + - ]: 4 : const ConstElementPtr config(server.getConfigSession()->
254 [ + - ]: 8 : getValue("datasources"));
255 : : ConstElementPtr zone_list;
256 : : // Unfortunately, we need to walk the list to find the correct data
257 : : // source.
258 : : // TODO: Make it named sets. These lists are uncomfortable.
259 [ + - ][ + + ]: 4 : for (size_t i(0); i < config->size(); ++i) {
260 : : // We use the getValue to get defaults as well
261 [ + - ]: 3 : const ConstElementPtr dsrc_config(config->get(i));
262 [ + - ][ + - ]: 3 : const ConstElementPtr class_config(dsrc_config->get("class"));
263 : : const string class_type(class_config ?
264 [ - + ][ # # ]: 6 : class_config->stringValue() : "IN");
[ + - ]
265 : : // It is in-memory and our class matches.
266 : : // FIXME: Is it allowed to have two datasources for the same
267 : : // type and class at once? It probably would not work now
268 : : // anyway and we may want to change the configuration of
269 : : // datasources somehow.
270 [ + - ][ + - ]: 9 : if (dsrc_config->get("type")->stringValue() == "memory" &&
[ + - ][ + - ]
[ + - ][ - + ]
[ + - ][ # # ]
[ # # ][ # # ]
271 [ + - ][ + - ]: 3 : RRClass(class_type) == old_zone_finder_->getClass()) {
272 [ + - ][ + - ]: 6 : zone_list = dsrc_config->get("zones");
[ + - ]
273 : : break;
274 : : }
275 : : }
276 : :
277 [ + + ]: 4 : if (!zone_list) {
278 [ + - ][ + - ]: 2 : isc_throw(AuthCommandError,
279 : : "Corresponding data source configuration was not found");
280 : : }
281 : :
282 : : // Now we need to walk the zones and find the correct one.
283 [ + - ][ + + ]: 5 : for (size_t i(0); i < zone_list->size(); ++i) {
284 [ + - ]: 3 : const ConstElementPtr zone_config(zone_list->get(i));
285 [ + - ][ + - ]: 12 : if (Name(zone_config->get("origin")->stringValue()) ==
[ + + ]
286 [ + - + - ]: 9 : old_zone_finder_->getOrigin()) {
[ + - ]
287 : : // The origins are the same, so we consider this config to be
288 : : // for the zone.
289 : : return (zone_config);
290 : : }
291 : : }
292 : :
293 [ + - ][ + - ]: 4 : isc_throw(AuthCommandError,
294 : : "Corresponding zone configuration was not found");
295 : : }
296 : : };
297 : :
298 : : // The factory of command objects.
299 : : AuthCommand*
300 : 27 : createAuthCommand(const string& command_id) {
301 : : // For the initial implementation we use a naive if-else blocks
302 : : // (see also createAuthConfigParser())
303 [ + + ]: 27 : if (command_id == "shutdown") {
304 : 6 : return (new ShutdownCommand());
305 [ + + ]: 21 : } else if (command_id == "sendstats") {
306 : 1 : return (new SendStatsCommand());
307 [ + + ]: 20 : } else if (command_id == "loadzone") {
308 : 19 : return (new LoadZoneCommand());
309 : : } else if (false && command_id == "_throw_exception") {
310 : : // This is for testing purpose only and should not appear in the
311 : : // actual configuration syntax.
312 : : // XXX: ModuleCCSession doesn't seem to validate commands (unlike
313 : : // config), so we should disable this case for now.
314 : : throw runtime_error("throwing for test");
315 : : }
316 : :
317 [ + - ][ + - ]: 28 : isc_throw(AuthCommandError, "Unknown command identifier: " << command_id);
318 : : }
319 : : } // end of unnamed namespace
320 : :
321 : : ConstElementPtr
322 : 27 : execAuthServerCommand(AuthSrv& server, const string& command_id,
323 : : ConstElementPtr args)
324 : : {
325 [ + - ][ + - ]: 27 : LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_RECEIVED_COMMAND).arg(command_id);
326 : : try {
327 [ + + ]: 27 : scoped_ptr<AuthCommand>(createAuthCommand(command_id))->exec(server,
328 [ + + ]: 26 : args);
329 [ - + ]: 36 : } catch (const isc::Exception& ex) {
330 [ - + ]: 54 : LOG_ERROR(auth_logger, AUTH_COMMAND_FAILED).arg(command_id)
[ + - - + ]
[ - + ][ - + ]
331 [ - + ]: 36 : .arg(ex.what());
332 [ - + ][ - + ]: 18 : return (createAnswer(1, ex.what()));
333 : : }
334 : :
335 : 27 : return (createAnswer());
336 : 1 : }
|