LCOV - code coverage report
Current view: top level - home/jelte/repos/coverage_bind10/src/bin/auth - command.cc (source / functions) Hit Total Coverage
Test: report.info Lines: 93 93 100.0 %
Date: 2012-05-15 Functions: 13 18 72.2 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 188 316 59.5 %

           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 : }

Generated by: LCOV version 1.9