Ring Daemon 16.0.0
Loading...
Searching...
No Matches
jamipluginmanager.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include "jamipluginmanager.h"
19#include "pluginsutils.h"
20#include "fileutils.h"
21#include "archiver.h"
22#include "logger.h"
23#include "manager.h"
25#include "store_ca_crt.cpp"
26
27#include <fstream>
28#include <stdexcept>
29#include <msgpack.hpp>
30
31#define FAILURE -1
32#define SUCCESS 0
33#define PLUGIN_ALREADY_INSTALLED 100 /* Plugin already installed with the same version */
34#define PLUGIN_OLD_VERSION 200 /* Plugin already installed with a newer version */
35#define SIGNATURE_VERIFICATION_FAILED 300
36#define CERTIFICATE_VERIFICATION_FAILED 400
37#define INVALID_PLUGIN 500
38
39
40#ifdef WIN32
41#define LIB_TYPE ".dll"
42#define LIB_PREFIX ""
43#else
44#ifdef __APPLE__
45#define LIB_TYPE ".dylib"
46#define LIB_PREFIX "lib"
47#else
48#define LIB_TYPE ".so"
49#define LIB_PREFIX "lib"
50#endif
51#endif
52
53namespace jami {
54
56 : callsm_ {pm_}
57 , chatsm_ {pm_}
58 , webviewsm_ {pm_}
59 , preferencesm_ {pm_}
60{
61 registerServices();
62}
63
64std::string
65JamiPluginManager::getPluginAuthor(const std::string& rootPath, const std::string& pluginId)
66{
68 if (!cert) {
69 JAMI_ERROR("Unable to read plugin certificate");
70 return {};
71 }
72 return cert->getIssuerName();
73}
74
75std::map<std::string, std::string>
76JamiPluginManager::getPluginDetails(const std::string& rootPath, bool reset)
77{
78 auto detailsIt = pluginDetailsMap_.find(rootPath);
79 if (detailsIt != pluginDetailsMap_.end()) {
80 if (!reset)
81 return detailsIt->second;
82 pluginDetailsMap_.erase(detailsIt);
83 }
84
85 std::map<std::string, std::string> details = PluginUtils::parseManifestFile(
87 if (!details.empty()) {
88 auto itIcon = details.find("iconPath");
89 itIcon->second.insert(0, rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH);
90
91 auto itImage = details.find("backgroundPath");
92 itImage->second.insert(0, rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH);
93
95 details["author"] = getPluginAuthor(rootPath, details["id"]);
96 detailsIt = pluginDetailsMap_.emplace(rootPath, std::move(details)).first;
97 return detailsIt->second;
98 }
99 return {};
100}
101
102std::vector<std::string>
104{
105 // Gets all plugins in standard path
106 auto pluginsPath = fileutils::get_data_dir() / "plugins";
107 std::vector<std::string> pluginsPaths;
108 std::error_code ec;
109 for (const auto& entry : std::filesystem::directory_iterator(pluginsPath, ec)) {
110 const auto& p = entry.path();
112 pluginsPaths.emplace_back(p.string());
113 }
114
115 // Gets plugins installed in non standard path
116 std::vector<std::string> nonStandardInstalls = jami::Manager::instance()
117 .pluginPreferences.getInstalledPlugins();
118 for (auto& path : nonStandardInstalls) {
120 pluginsPaths.emplace_back(path);
121 }
122
123 return pluginsPaths;
124}
125
126bool
128{
130 std::error_code ec;
131 if (
132 oldDetails.empty() ||
133 !std::filesystem::is_regular_file(oldJplPath + DIR_SEPARATOR_CH + oldDetails["id"] + ".crt", ec) ||
134 !std::filesystem::is_regular_file(newJplPath, ec)
135 )
136 return false;
137 try {
140 if (!oldCert || !newCert) {
141 return false;
142 }
143 return oldCert->getPublicKey() == newCert->getPublicKey();
144 } catch (const std::exception& e) {
145 JAMI_ERR() << e.what();
146 return false;
147 }
148 return true;
149}
150
151bool
153{
154 if (!cert || !*cert)
155 return false;
156 trust_.add(crypto::Certificate(store_ca_crt, sizeof(store_ca_crt)));
157 auto result = trust_.verify(*cert);
158 if (!result) {
159 JAMI_ERROR("Certificate verification failed: {}", result.toString());
160 }
161 return (bool)result;
162}
163
164std::map<std::string, std::string>
169
170bool
172{
173 // check if the file exists
174 std::error_code ec;
175 if (!std::filesystem::is_regular_file(jplPath, ec)){
176 return false;
177 }
178 try {
181 const std::string& name = manifest["id"];
183 for (const auto& file : filesPath) {
184 // we skip the signatures and signatures.sig file
185 if (file == "signatures" || file == "signatures.sig")
186 continue;
187 // we also skip the plugin certificate
188 if (file == name + ".crt")
189 continue;
190
191 if (signatures.count(file) == 0) {
192 return false;
193 }
194 }
195 } catch (const std::exception& e) {
196 return false;
197 }
198 return true;
199}
200
201bool
202JamiPluginManager::checkPluginSignatureValidity(const std::string& jplPath, dht::crypto::Certificate* cert)
203{
204 if (!std::filesystem::is_regular_file(jplPath))
205 return false;
206 try{
207 const auto& pk = cert->getPublicKey();
210 if (!pk.checkSignature(signaturesData, signatureFile))
211 return false;
213 for (const auto& signature : signatures) {
214 auto file = archiver::readFileFromArchive(jplPath, signature.first);
215 if (!pk.checkSignature(file, signature.second)){
216 JAMI_ERROR("{} not correctly signed", signature.first);
217 return false;
218 }
219 }
220 } catch (const std::exception& e) {
221 return false;
222 }
223
224 return true;
225}
226
227bool
228JamiPluginManager::checkPluginSignature(const std::string& jplPath, dht::crypto::Certificate* cert)
229{
230 if (!std::filesystem::is_regular_file(jplPath) || !cert || !*cert)
231 return false;
232 try {
235 } catch (const std::exception& e) {
236 return false;
237 }
238}
239
240std::unique_ptr<dht::crypto::Certificate>
242{
243 if (!std::filesystem::is_regular_file(jplPath))
244 return {};
245 try {
248 return cert;
249 }
250 return {};
251 } catch (const std::exception& e) {
252 return {};
253 }
254}
255
256int
258{
259 int r {SUCCESS};
260 std::error_code ec;
261 if (std::filesystem::is_regular_file(jplPath, ec)) {
262 try {
264 const std::string& name = manifestMap["id"];
265 if (name.empty())
266 return INVALID_PLUGIN;
268 if (!cert)
272 const std::string& version = manifestMap["version"];
273 auto destinationDir = (fileutils::get_data_dir() / "plugins" / name).string();
274 // Find if there is an existing version of this plugin
277
278 if (!alreadyInstalledManifestMap.empty()) {
279 if (force) {
281 if (r == SUCCESS) {
285 }
286 } else {
287 std::string installedVersion = alreadyInstalledManifestMap.at("version");
292 if (r == SUCCESS) {
296 }
297 } else if (version == installedVersion) {
299 } else {
301 }
302 }
303 } else {
307 }
311 loadPlugins();
312 return r;
313 }
315 } catch (const std::exception& e) {
316 JAMI_ERR() << e.what();
317 }
318 }
319 return r;
320}
321
322int
324{
325 std::error_code ec;
327 auto detailsIt = pluginDetailsMap_.find(rootPath);
328 if (detailsIt != pluginDetailsMap_.end()) {
330 if (loaded) {
331 JAMI_INFO() << "PLUGIN: unloading before uninstall.";
332 bool status = libjami::unloadPlugin(rootPath);
333 if (!status) {
334 JAMI_INFO() << "PLUGIN: unable to unload, not performing uninstall.";
335 return FAILURE;
336 }
337 }
338 for (const auto& accId : jami::Manager::instance().getAccountList())
339 std::filesystem::remove_all(fileutils::get_data_dir() / accId
340 / "plugins" / detailsIt->second.at("id"), ec);
341 pluginDetailsMap_.erase(detailsIt);
342 }
343 return std::filesystem::remove_all(rootPath, ec) ? SUCCESS : FAILURE;
344 } else {
345 JAMI_INFO() << "PLUGIN: not installed.";
346 return FAILURE;
347 }
348}
349
350bool
352{
353#ifdef ENABLE_PLUGIN
354 try {
355 bool status = pm_.load(getPluginDetails(rootPath).at("soPath"));
356 JAMI_INFO() << "PLUGIN: load status - " << status;
357
358 return status;
359
360 } catch (const std::exception& e) {
361 JAMI_ERR() << e.what();
362 return false;
363 }
364#endif
365 return false;
366}
367
368bool
370{
371#ifdef ENABLE_PLUGIN
372 bool status = true;
373 auto loadedPlugins = jami::Manager::instance().pluginPreferences.getLoadedPlugins();
374 for (const auto& pluginPath : loadedPlugins) {
375 status &= loadPlugin(pluginPath);
376 }
377 return status;
378#endif
379 return false;
380}
381
382bool
384{
385#ifdef ENABLE_PLUGIN
386 try {
387 bool status = pm_.unload(getPluginDetails(rootPath).at("soPath"));
388 JAMI_INFO() << "PLUGIN: unload status - " << status;
389
390 return status;
391 } catch (const std::exception& e) {
392 JAMI_ERR() << e.what();
393 return false;
394 }
395#endif
396 return false;
397}
398
399std::vector<std::string>
401{
402 std::vector<std::string> loadedSoPlugins = pm_.getLoadedPlugins();
403 std::vector<std::string> loadedPlugins {};
404 loadedPlugins.reserve(loadedSoPlugins.size());
405 std::transform(loadedSoPlugins.begin(),
406 loadedSoPlugins.end(),
407 std::back_inserter(loadedPlugins),
408 [](const std::string& soPath) {
409 return PluginUtils::getRootPathFromSoPath(soPath).string();
410 });
411 return loadedPlugins;
412}
413
414std::vector<std::map<std::string, std::string>>
415JamiPluginManager::getPluginPreferences(const std::string& rootPath, const std::string& accountId)
416{
418}
419
420bool
422 const std::string& accountId,
423 const std::string& key,
424 const std::string& value)
425{
426 std::string acc = accountId;
427
428 // If we try to change a preference value linked to an account
429 // but that preference is global, we must ignore accountId and
430 // change the preference for every account
431 if (!accountId.empty()) {
432 // Get global preferences
433 auto preferences = PluginPreferencesUtils::getPreferences(rootPath, "");
434 // Check if the preference we want to change is global
435 auto it = std::find_if(preferences.cbegin(),
436 preferences.cend(),
437 [key](const std::map<std::string, std::string>& preference) {
438 return preference.at("key") == key;
439 });
440 // Ignore accountId if global preference
441 if (it != preferences.cend())
442 acc.clear();
443 }
444
445 std::map<std::string, std::string> pluginUserPreferencesMap
447 std::map<std::string, std::string> pluginPreferencesMap
449
450 // If any plugin handler is active we may have to reload it
451 bool force {pm_.checkLoadedPlugin(rootPath.string())};
452
453 // We check if the preference is modified without having to reload plugin
454 force &= preferencesm_.setPreference(key, value, rootPath.string(), acc);
455 force &= callsm_.setPreference(key, value, rootPath.string());
456 force &= chatsm_.setPreference(key, value, rootPath.string());
457
458 if (force)
459 unloadPlugin(rootPath.string());
460
461 // Save preferences.msgpack with modified preferences values
462 auto find = pluginPreferencesMap.find(key);
463 if (find != pluginPreferencesMap.end()) {
467 std::lock_guard guard(dhtnet::fileutils::getFileLock(preferencesValuesFilePath));
468 std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
469 if (!fs.good()) {
470 if (force) {
471 loadPlugin(rootPath.string());
472 }
473 return false;
474 }
475 try {
476 msgpack::pack(fs, pluginUserPreferencesMap);
477 } catch (const std::exception& e) {
478 JAMI_ERR() << e.what();
479 if (force) {
480 loadPlugin(rootPath.string());
481 }
482 return false;
483 }
484 }
485 if (force) {
486 loadPlugin(rootPath.string());
487 }
488 return true;
489}
490
491std::map<std::string, std::string>
493 const std::string& accountId)
494{
496}
497
498bool
500 const std::string& accountId)
501{
502 bool acc {accountId.empty()};
504 if (loaded && acc)
507 preferencesm_.resetPreferences(rootPath, accountId);
508 if (loaded && acc) {
510 }
511 return status;
512}
513
514void
515JamiPluginManager::registerServices()
516{
517 // Register getPluginPreferences so that plugin's can receive it's preferences
518 pm_.registerService("getPluginPreferences", [](const DLPlugin* plugin, void* data) {
519 auto ppp = static_cast<std::map<std::string, std::string>*>(data);
522 return SUCCESS;
523 });
524
525 // Register getPluginDataPath so that plugin's can receive the path to it's data folder
526 pm_.registerService("getPluginDataPath", [](const DLPlugin* plugin, void* data) {
527 auto dataPath = static_cast<std::string*>(data);
528 dataPath->assign(PluginUtils::dataPath(plugin->getPath()).string());
529 return SUCCESS;
530 });
531
532 // getPluginAccPreferences is a service that allows plugins to load saved per account preferences.
533 auto getPluginAccPreferences = [](const DLPlugin* plugin, void* data) {
534 const auto path = PluginUtils::getRootPathFromSoPath(plugin->getPath());
535 auto preferencesPtr {(static_cast<PreferencesMap*>(data))};
536 if (!preferencesPtr)
537 return FAILURE;
538
539 preferencesPtr->emplace("default",
541
542 for (const auto& accId : jami::Manager::instance().getAccountList())
544 PluginPreferencesUtils::getPreferencesValuesMap(path, accId));
545 return SUCCESS;
546 };
547
548 pm_.registerService("getPluginAccPreferences", getPluginAccPreferences);
549}
550
551#ifdef LIBJAMI_TEST
552void
553JamiPluginManager::addPluginAuthority(const dht::crypto::Certificate& cert)
554{
555 trust_.add(cert);
556}
557#endif
558
559} // namespace jami
bool setPreference(const std::string &key, const std::string &value, const std::string &rootPath)
Sets a preference that may be changed while MediaHandler is active.
bool setPreference(const std::string &key, const std::string &value, const std::string &rootPath)
Sets a preference that may be changed while ChatHandler is active.
This class is used after a plugin library is successfully loaded.
bool loadPlugins()
Returns True if success.
std::vector< std::map< std::string, std::string > > getPluginPreferences(const std::string &rootPath, const std::string &accountId)
Returns contents of plugin's preferences.json file.
std::string getPluginAuthor(const std::string &rootPath, const std::string &pluginId)
get the plugin's author
bool checkPluginSignature(const std::string &jplPath, dht::crypto::Certificate *cert)
Checks if the plugin signature mechanism is valid by signature of files and each files is signed.
bool resetPluginPreferencesValuesMap(const std::string &rootPath, const std::string &accountId)
Reset plugin's preferences values to their defaultValues.
std::vector< std::string > getLoadedPlugins() const
Returns vector with rootpaths of the loaded plugins.
bool loadPlugin(const std::string &rootPath)
Returns True if success.
int installPlugin(const std::string &jplPath, bool force)
Checks if the plugin has a valid manifest, installs the plugin if not previously installed or if inst...
std::unique_ptr< dht::crypto::Certificate > checkPluginCertificate(const std::string &jplPath, bool force)
Checks if the certificate mechanism is valid by checking certificate of the plugin.
bool checkPluginSignatureValidity(const std::string &jplPath, dht::crypto::Certificate *cert)
Check the validity of a plugin signature.
std::map< std::string, std::string > getPluginDetails(const std::string &rootPath, bool reset=false)
Parses a manifest file and return its content along with other internally added values.
bool checkPluginSignatureFile(const std::string &jplPath)
check if all file are present in the signature file
bool unloadPlugin(const std::string &rootPath)
Returns True if success.
std::map< std::string, std::string > getPluginPreferencesValuesMap(const std::string &rootPath, const std::string &accountId)
Returns a Map with preferences keys and values.
std::vector< std::string > getInstalledPlugins()
Returns a vector with installed plugins.
bool checkPluginCertificatePublicKey(const std::string &oldJplPath, const std::string &newJplPath)
check if the if the public key of the certificate is the same as the public key in the new plugin
bool setPluginPreference(const std::filesystem::path &rootPath, const std::string &accountId, const std::string &key, const std::string &value)
Modifies a preference value by saving it to a preferences.msgpack.
bool checkPluginCertificateValidity(dht::crypto::Certificate *cert)
Check the validity of a plugin certificate.
int uninstallPlugin(const std::string &rootPath)
Checks if the plugin has a valid manifest and if the plugin is loaded, tries to unload it and then re...
std::map< std::string, std::string > getPlatformInfo()
Returns a Map of platform system.
std::vector< std::string > getAccountList() const
Get account list.
Definition manager.cpp:2667
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
void saveConfig()
Save config to file.
Definition manager.cpp:1751
std::vector< std::string > getLoadedPlugins() const
Returns vector with loaded plugins' libraries paths.
bool unload(const std::string &path)
Unloads the plugin.
bool checkLoadedPlugin(const std::string &rootPath) const
Returns True if plugin is loaded.
bool registerService(const std::string &name, ServiceFunction &&func)
Register a new service in the Plugin System.
bool load(const std::string &path)
Load a dynamic plugin by filename.
static std::map< std::string, std::string > getPreferencesValuesMap(const std::filesystem::path &rootPath, const std::string &accountId="")
Reads preferences values.
static std::vector< std::map< std::string, std::string > > getPreferences(const std::filesystem::path &rootPath, const std::string &accountId="")
Reads a preference.json file from the plugin installed in rootPath.
static std::filesystem::path valuesFilePath(const std::filesystem::path &rootPath, const std::string &accountId="")
Given a plugin installation path, returns the path to the preference.msgpack file.
static bool resetPreferencesValuesMap(const std::string &rootPath, const std::string &accountId)
Resets all preferences values to their defaultValues by erasing all data saved in preferences....
static std::map< std::string, std::string > getUserPreferencesValuesMap(const std::filesystem::path &rootPath, const std::string &accountId="")
Reads preferences values which were modified from defaultValue.
void resetPreferences(const std::string &rootPath, const std::string &accountId)
Resets acc preferences to default values.
bool setPreference(const std::string &key, const std::string &value, const std::string &rootPath, const std::string &accountId)
Sets a preference.
#define DIR_SEPARATOR_CH
Definition fileutils.h:34
#define PLUGIN_ALREADY_INSTALLED
#define PLUGIN_OLD_VERSION
#define FAILURE
#define SIGNATURE_VERIFICATION_FAILED
#define SUCCESS
#define LIB_PREFIX
#define CERTIFICATE_VERIFICATION_FAILED
#define INVALID_PLUGIN
#define LIB_TYPE
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_ERROR(formatstr,...)
Definition logger.h:228
#define JAMI_INFO(...)
Definition logger.h:215
std::unique_ptr< dht::crypto::Certificate > readPluginCertificate(const std::string &rootPath, const std::string &pluginId)
Read the plugin's certificate.
std::map< std::string, std::string > readPluginManifestFromArchive(const std::string &jplPath)
Reads the manifest file content without uncompressing the whole archive and return a map with manifes...
std::filesystem::path dataPath(const std::filesystem::path &pluginSoPath)
Returns data path given a plugin's library path.
std::map< std::string, std::string > getPlatformInfo()
Returns a map with platform information.
std::filesystem::path getRootPathFromSoPath(const std::filesystem::path &soPath)
Returns installation path given a plugin's library path.
std::vector< uint8_t > readSignatureFileFromArchive(const std::string &jplPath)
Read the signature of the file signature without uncompressing the whole archive.
bool checkPluginValidity(const std::filesystem::path &rootPath)
Validates a plugin based on its manifest.json file.
std::filesystem::path manifestPath(const std::filesystem::path &rootPath)
Returns complete manifest.json file path given a installation path.
std::unique_ptr< dht::crypto::Certificate > readPluginCertificateFromArchive(const std::string &jplPath)
Read plugin certificate without uncompressing the whole archive.and return an object Certificate.
std::pair< bool, std::string_view > uncompressJplFunction(std::string_view relativeFileName)
Function used by archiver to extract files from plugin jpl to the plugin installation path.
std::map< std::string, std::string > parseManifestFile(const std::filesystem::path &manifestFilePath, const std::string &rootPath)
Parses the manifest file of an installed plugin if it's valid.
std::map< std::string, std::vector< uint8_t > > readPluginSignatureFromArchive(const std::string &jplPath)
Reads signature file content without uncompressing the whole archive and.
std::vector< std::string > listFilesFromArchive(const std::string &path)
listFilesFromArchive list all files from an archive
Definition archiver.cpp:438
void uncompressArchive(const std::string &archivePath, const std::string &dir, const FileMatchPair &f)
uncompressArchive Uncompresses an archive and puts the different files in dir folder according to a F...
Definition archiver.cpp:212
std::vector< uint8_t > readFileFromArchive(const std::string &archivePath, const std::string &fileRelativePathName)
readFileFromArchive read a file from an archive without uncompressing the whole archive
Definition archiver.cpp:348
const std::filesystem::path & get_data_dir()
static constexpr int version
void emitSignal(Args... args)
Definition ring_signal.h:64
std::map< std::string, std::map< std::string, std::string > > PreferencesMap
std::vector< std::string > getAccountList()
bool unloadPlugin(const std::string &path)
void setPluginsEnabled(bool state)
bool loadPlugin(const std::string &path)
unsigned char store_ca_crt[]