1 // Copyright (C) 2013-2020 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 <hooks/hooks.h>
11 #include <hooks/hooks_log.h>
12 #include <hooks/callout_manager.h>
13 #include <hooks/library_handle.h>
14 #include <hooks/library_manager.h>
15 #include <hooks/pointer_converter.h>
16 #include <hooks/server_hooks.h>
17 #include <log/logger_manager.h>
18 #include <log/logger_support.h>
19 #include <log/message_initializer.h>
20 #include <util/multi_threading_mgr.h>
21 
22 #include <string>
23 #include <vector>
24 
25 #include <dlfcn.h>
26 
27 using namespace std;
28 
29 namespace isc {
30 namespace hooks {
31 
32 // Constructor (used by external agency)
LibraryManager(const std::string & name,int index,const boost::shared_ptr<CalloutManager> & manager)33 LibraryManager::LibraryManager(const std::string& name, int index,
34                                const boost::shared_ptr<CalloutManager>& manager)
35         : dl_handle_(NULL), index_(index), manager_(manager),
36           library_name_(name),
37           server_hooks_(ServerHooks::getServerHooksPtr())
38 {
39     if (!manager) {
40         isc_throw(NoCalloutManager, "must specify a CalloutManager when "
41                   "instantiating a LibraryManager object");
42     }
43 }
44 
45 // Constructor (used by "validate" for library validation).  Note that this
46 // sets "manager_" to not point to anything, which means that methods such as
47 // registerStandardCallout() will fail, probably with a segmentation fault.
48 // There are no checks for this condition in those methods: this constructor
49 // is declared "private", so can only be executed by a method in this class.
50 // The only method to do so is "validateLibrary", which takes care not to call
51 // methods requiring a non-NULL manager.
LibraryManager(const std::string & name)52 LibraryManager::LibraryManager(const std::string& name)
53         : dl_handle_(NULL), index_(-1), manager_(), library_name_(name)
54 {}
55 
56 // Destructor.
~LibraryManager()57 LibraryManager::~LibraryManager() {
58     if (index_ >= 0) {
59         // LibraryManager instantiated to load a library, so ensure that
60         // it is unloaded before exiting.
61         static_cast<void>(prepareUnloadLibrary());
62     }
63 
64     // LibraryManager instantiated to validate a library, so just ensure
65     // that it is closed before exiting.
66     static_cast<void>(closeLibrary());
67 }
68 
69 // Open the library
70 
71 bool
openLibrary()72 LibraryManager::openLibrary() {
73 
74     // Open the library.  We'll resolve names now, so that if there are any
75     // issues we don't bugcheck in the middle of apparently unrelated code.
76     dl_handle_ = dlopen(library_name_.c_str(), RTLD_NOW | RTLD_LOCAL);
77     if (dl_handle_ == NULL) {
78         LOG_ERROR(hooks_logger, HOOKS_OPEN_ERROR).arg(library_name_)
79                   .arg(dlerror());
80     }
81 
82     return (dl_handle_ != NULL);
83 }
84 
85 // Close the library if not already open
86 
87 bool
closeLibrary()88 LibraryManager::closeLibrary() {
89 
90     // Close the library if it is open. (If not, this is a no-op.)
91     int status = 0;
92     if (dl_handle_ != NULL) {
93         status = dlclose(dl_handle_);
94         dl_handle_ = NULL;
95         if (status != 0) {
96             LOG_ERROR(hooks_logger, HOOKS_CLOSE_ERROR).arg(library_name_)
97                       .arg(dlerror());
98         } else {
99             LOG_INFO(hooks_logger, HOOKS_LIBRARY_CLOSED).arg(library_name_);
100         }
101     }
102 
103     return (status == 0);
104 }
105 
106 // Check the version of the library
107 
108 bool
checkVersion() const109 LibraryManager::checkVersion() const {
110 
111     // Get the pointer to the "version" function.
112     PointerConverter pc(dlsym(dl_handle_, VERSION_FUNCTION_NAME));
113     if (pc.versionPtr() != NULL) {
114         int version = KEA_HOOKS_VERSION - 1; // This is an invalid value
115         try {
116             version = (*pc.versionPtr())();
117         } catch (...) {
118             LOG_ERROR(hooks_logger, HOOKS_VERSION_EXCEPTION).arg(library_name_);
119             return (false);
120         }
121 
122         if (version == KEA_HOOKS_VERSION) {
123             // All OK, version checks out
124             LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_LIBRARY_VERSION)
125                       .arg(library_name_).arg(version);
126             return (true);
127 
128         } else {
129             LOG_ERROR(hooks_logger, HOOKS_INCORRECT_VERSION).arg(library_name_)
130                       .arg(version).arg(KEA_HOOKS_VERSION);
131         }
132     } else {
133         LOG_ERROR(hooks_logger, HOOKS_NO_VERSION).arg(library_name_);
134     }
135 
136     return (false);
137 }
138 
139 // Check the multi-threading compatibility of the library
140 
141 bool
checkMultiThreadingCompatible() const142 LibraryManager::checkMultiThreadingCompatible() const {
143 
144     // Compatible with single-threaded.
145     if (!util::MultiThreadingMgr::instance().getMode()) {
146         return (true);
147     }
148 
149     // Get the pointer to the "multi_threading_compatible" function.
150     PointerConverter pc(dlsym(dl_handle_, MULTI_THREADING_COMPATIBLE_FUNCTION_NAME));
151     int compatible = 0;
152     if (pc.multiThreadingCompatiblePtr()) {
153         try {
154             compatible = (*pc.multiThreadingCompatiblePtr())();
155         } catch (...) {
156             LOG_ERROR(hooks_logger, HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION)
157                 .arg(library_name_);
158             return (false);
159         }
160 
161         LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS,
162                   HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE)
163             .arg(library_name_)
164             .arg(compatible);
165     }
166     if (compatible == 0) {
167         LOG_ERROR(hooks_logger, HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE)
168             .arg(library_name_);
169     }
170     return (compatible != 0);
171 }
172 
173 // Register the standard callouts
174 
175 void
registerStandardCallouts()176 LibraryManager::registerStandardCallouts() {
177     // Set the library index for doing the registration.  This is picked up
178     // when the library handle is created.
179     manager_->setLibraryIndex(index_);
180 
181     // Iterate through the list of known hooks
182     vector<string> hook_names = ServerHooks::getServerHooks().getHookNames();
183     for (size_t i = 0; i < hook_names.size(); ++i) {
184 
185         // Look up the symbol
186         void* dlsym_ptr = dlsym(dl_handle_, hook_names[i].c_str());
187         PointerConverter pc(dlsym_ptr);
188         if (pc.calloutPtr() != NULL) {
189             // Found a symbol, so register it.
190             manager_->getLibraryHandle().registerCallout(hook_names[i],
191                                                          pc.calloutPtr());
192             LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS,
193                       HOOKS_STD_CALLOUT_REGISTERED).arg(library_name_)
194                       .arg(hook_names[i]).arg(dlsym_ptr);
195 
196         }
197     }
198 }
199 
200 // Run the "load" function if present.
201 
202 bool
runLoad()203 LibraryManager::runLoad() {
204 
205     // Get the pointer to the "load" function.
206     PointerConverter pc(dlsym(dl_handle_, LOAD_FUNCTION_NAME));
207     if (pc.loadPtr() != NULL) {
208 
209         // Call the load() function with the library handle.  We need to set
210         // the CalloutManager's index appropriately.  We'll invalidate it
211         // afterwards.
212 
213         int status = -1;
214         try {
215             manager_->setLibraryIndex(index_);
216             status = (*pc.loadPtr())(manager_->getLibraryHandle());
217         } catch (const isc::Exception& ex) {
218             LOG_ERROR(hooks_logger, HOOKS_LOAD_FRAMEWORK_EXCEPTION)
219                 .arg(library_name_).arg(ex.what());
220             return (false);
221         } catch (...) {
222             LOG_ERROR(hooks_logger, HOOKS_LOAD_EXCEPTION).arg(library_name_);
223             return (false);
224         }
225 
226         if (status != 0) {
227             LOG_ERROR(hooks_logger, HOOKS_LOAD_ERROR).arg(library_name_)
228                       .arg(status);
229             return (false);
230         } else {
231         LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LOAD_SUCCESS)
232             .arg(library_name_);
233         }
234 
235     } else {
236         LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_LOAD)
237             .arg(library_name_);
238     }
239 
240     return (true);
241 }
242 
243 
244 // Run the "unload" function if present.
245 
246 bool
prepareUnloadLibrary()247 LibraryManager::prepareUnloadLibrary() {
248 
249     // Nothing to do.
250     if (dl_handle_ == NULL) {
251         return (true);
252     }
253 
254     // Call once.
255     if (index_ < 0) {
256         return (true);
257     }
258 
259     // Get the pointer to the "load" function.
260     bool result = false;
261     PointerConverter pc(dlsym(dl_handle_, UNLOAD_FUNCTION_NAME));
262     if (pc.unloadPtr() != NULL) {
263 
264         // Call the load() function with the library handle.  We need to set
265         // the CalloutManager's index appropriately.  We'll invalidate it
266         // afterwards.
267         int status = -1;
268         try {
269             status = (*pc.unloadPtr())();
270             result = true;
271         } catch (const isc::Exception& ex) {
272             LOG_ERROR(hooks_logger, HOOKS_UNLOAD_FRAMEWORK_EXCEPTION)
273                 .arg(library_name_).arg(ex.what());
274         } catch (...) {
275             // Exception generated.  Note a warning as the unload will occur
276             // anyway.
277             LOG_WARN(hooks_logger, HOOKS_UNLOAD_EXCEPTION).arg(library_name_);
278         }
279 
280         if (result) {
281             if (status != 0) {
282                 LOG_ERROR(hooks_logger, HOOKS_UNLOAD_ERROR).arg(library_name_)
283                     .arg(status);
284                 result = false;
285             } else {
286                 LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_UNLOAD_SUCCESS)
287                     .arg(library_name_);
288             }
289         }
290     } else {
291         LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_UNLOAD)
292             .arg(library_name_);
293         result = true;
294     }
295 
296     // Regardless of status, remove all callouts associated with this
297     // library on all hooks.
298     vector<string> hooks = ServerHooks::getServerHooks().getHookNames();
299     manager_->setLibraryIndex(index_);
300     for (size_t i = 0; i < hooks.size(); ++i) {
301         bool removed = manager_->deregisterAllCallouts(hooks[i], index_);
302         if (removed) {
303             LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_REMOVED)
304                 .arg(hooks[i]).arg(library_name_);
305         }
306     }
307 
308     // Mark as unload() ran.
309     index_ = -1;
310 
311     return (result);
312 }
313 
314 // The main library loading function.
315 
316 bool
loadLibrary()317 LibraryManager::loadLibrary() {
318     LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_LOADING)
319         .arg(library_name_);
320 
321     // In the following, if a method such as openLibrary() fails, it will
322     // have issued an error message so there is no need to issue another one
323     // here.
324 
325     // Open the library (which is a check that it exists and is accessible).
326     if (openLibrary()) {
327 
328         // The hook libraries provide their own log messages and logger
329         // instances. This step is required to register log messages for
330         // the library being loaded in the global dictionary. Ideally, this
331         // should be called after all libraries have been loaded but we're
332         // going to call the version() and load() functions here and these
333         // functions may already contain logging statements.
334         isc::log::MessageInitializer::loadDictionary();
335 
336         // The log messages registered by the new hook library may duplicate
337         // some of the existing messages. Log warning for each duplicated
338         // message now.
339         isc::log::LoggerManager::logDuplicatedMessages();
340 
341         // Library opened OK, see if a version function is present and if so,
342         // check what value it returns. Check multi-threading compatibility.
343         if (checkVersion() && checkMultiThreadingCompatible()) {
344             // Version OK, so now register the standard callouts and call the
345             // library's load() function if present.
346             registerStandardCallouts();
347             if (runLoad()) {
348 
349                 // Success - the library has been successfully loaded.
350                 LOG_INFO(hooks_logger, HOOKS_LIBRARY_LOADED).arg(library_name_);
351                 return (true);
352 
353             } else {
354 
355                 // The load function failed, so back out.  We can't just close
356                 // the library as (a) we need to call the library's "unload"
357                 // function (if present) in case "load" allocated resources that
358                 // need to be freed and (b) we need to remove any callouts that
359                 // have been installed.
360                 static_cast<void>(prepareUnloadLibrary());
361             }
362         }
363 
364         // Either the version check or call to load() failed, so close the
365         // library and free up resources.  Ignore the status return here - we
366         // already know there's an error and will have output a message.
367         static_cast<void>(closeLibrary());
368     }
369 
370     return (false);
371 }
372 
373 // The library unloading function.  Call the unload() function (if present),
374 // remove callouts from the callout manager, then close the library.  This is
375 // only run if the library is still loaded and is a no-op if the library is
376 // not open.
377 
378 bool
unloadLibrary()379 LibraryManager::unloadLibrary() {
380     bool result = true;
381     if (dl_handle_ != NULL) {
382         LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_UNLOADING)
383             .arg(library_name_);
384 
385         // Call the unload() function if present.  Note that this is done first
386         // - operations take place in the reverse order to which they were done
387         // when the library was loaded.
388         if (index_ >= 0) {
389             result = prepareUnloadLibrary();
390         }
391 
392         // ... and close the library.
393         result = closeLibrary() && result;
394         if (result) {
395 
396             // Issue the informational message only if the library was unloaded
397             // with no problems.  If there was an issue, an error message would
398             // have been issued.
399             LOG_INFO(hooks_logger, HOOKS_LIBRARY_UNLOADED).arg(library_name_);
400         }
401     }
402     return (result);
403 }
404 
405 // Validate the library.  We must be able to open it, and the version function
406 // must both exist and return the right number.  Note that this is a static
407 // method.
408 
409 bool
validateLibrary(const std::string & name)410 LibraryManager::validateLibrary(const std::string& name) {
411     // Instantiate a library manager for the validation.  We use the private
412     // constructor as we don't supply a CalloutManager.
413     LibraryManager manager(name);
414 
415     // Try to open it and, if we succeed, check the version.
416     bool validated = manager.openLibrary() && manager.checkVersion() &&
417         manager.checkMultiThreadingCompatible();
418 
419     // Regardless of whether the version checked out, close the library. (This
420     // is a no-op if the library failed to open.)
421     static_cast<void>(manager.closeLibrary());
422 
423     return (validated);
424 }
425 
426 // @note Moved from its own hooks.cc file to avoid undefined reference
427 // with static link.
hooksStaticLinkInit()428 void hooksStaticLinkInit() {
429     if (!isc::log::isLoggingInitialized()) {
430         isc::log::initLogger(std::string("userlib"));
431     }
432 }
433 
434 } // namespace hooks
435 } // namespace isc
436