Ring Daemon
Loading...
Searching...
No Matches
jamipluginmanager.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 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// NOLINTNEXTLINE
26#include "store_ca_crt.cpp"
27
28#include <fstream>
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#ifdef WIN32
40#define LIB_TYPE ".dll"
41#define LIB_PREFIX ""
42#else
43#ifdef __APPLE__
44#define LIB_TYPE ".dylib"
45#define LIB_PREFIX "lib"
46#else
47#define LIB_TYPE ".so"
48#define LIB_PREFIX "lib"
49#endif
50#endif
51
52namespace jami {
53
55 : callsm_ {pm_}
56 , chatsm_ {pm_}
57 , webviewsm_ {pm_}
58 , preferencesm_ {pm_}
59{
60 registerServices();
61}
62
63std::string
64JamiPluginManager::getPluginAuthor(const std::string& rootPath, const std::string& pluginId)
65{
67 if (!cert) {
68 JAMI_ERROR("Unable to read plugin certificate");
69 return {};
70 }
71 return cert->getIssuerName();
72}
73
74std::map<std::string, std::string>
75JamiPluginManager::getPluginDetails(const std::string& rootPath, bool reset)
76{
77 auto detailsIt = pluginDetailsMap_.find(rootPath);
78 if (detailsIt != pluginDetailsMap_.end()) {
79 if (!reset)
80 return detailsIt->second;
81 pluginDetailsMap_.erase(detailsIt);
82 }
83
85 rootPath);
86 if (!details.empty()) {
87 auto itIcon = details.find("iconPath");
88 itIcon->second.insert(0, rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH);
89
90 auto itImage = details.find("backgroundPath");
91 itImage->second.insert(0, rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH);
92
94 details["author"] = getPluginAuthor(rootPath, details["id"]);
95 detailsIt = pluginDetailsMap_.emplace(rootPath, std::move(details)).first;
96 return detailsIt->second;
97 }
98 return {};
99}
100
101std::vector<std::string>
103{
104 // Gets all plugins in standard path
105 auto pluginsPath = fileutils::get_data_dir() / "plugins";
106 std::vector<std::string> pluginsPaths;
107 std::error_code ec;
108 for (const auto& entry : std::filesystem::directory_iterator(pluginsPath, ec)) {
109 const auto& p = entry.path();
111 pluginsPaths.emplace_back(p.string());
112 }
113
114 // Gets plugins installed in non standard path
115 std::vector<std::string> nonStandardInstalls = jami::Manager::instance().pluginPreferences.getInstalledPlugins();
116 for (auto& path : nonStandardInstalls) {
118 pluginsPaths.emplace_back(path);
119 }
120
121 return pluginsPaths;
122}
123
124bool
126{
128 oldJplPath);
129 std::error_code ec;
130 if (oldDetails.empty()
131 || !std::filesystem::is_regular_file(oldJplPath + DIR_SEPARATOR_CH + oldDetails["id"] + ".crt", ec)
132 || !std::filesystem::is_regular_file(newJplPath, ec))
133 return false;
134 try {
137 if (!oldCert || !newCert) {
138 return false;
139 }
140 return oldCert->getPublicKey() == newCert->getPublicKey();
141 } catch (const std::exception& e) {
142 JAMI_ERR() << e.what();
143 return false;
144 }
145 return true;
146}
147
148bool
150{
151 if (!cert || !*cert)
152 return false;
153 trust_.add(crypto::Certificate(store_ca_crt, sizeof(store_ca_crt)));
154 auto result = trust_.verify(*cert);
155 if (!result) {
156 JAMI_ERROR("Certificate verification failed: {}", result.toString());
157 }
158 return (bool) result;
159}
160
161std::map<std::string, std::string>
166
167bool
169{
170 // check if the file exists
171 std::error_code ec;
172 if (!std::filesystem::is_regular_file(jplPath, ec)) {
173 return false;
174 }
175 try {
178 const std::string& name = manifest["id"];
180 for (const auto& file : filesPath) {
181 // we skip the signatures and signatures.sig file
182 if (file == "signatures" || file == "signatures.sig")
183 continue;
184 // we also skip the plugin certificate
185 if (file == name + ".crt")
186 continue;
187
188 if (signatures.count(file) == 0) {
189 return false;
190 }
191 }
192 } catch (const std::exception& e) {
193 return false;
194 }
195 return true;
196}
197
198bool
199JamiPluginManager::checkPluginSignatureValidity(const std::string& jplPath, dht::crypto::Certificate* cert)
200{
201 if (!std::filesystem::is_regular_file(jplPath))
202 return false;
203 try {
204 const auto& pk = cert->getPublicKey();
207 if (!pk.checkSignature(signaturesData, signatureFile))
208 return false;
210 for (const auto& signature : signatures) {
211 auto file = archiver::readFileFromArchive(jplPath, signature.first);
212 if (!pk.checkSignature(file, signature.second)) {
213 JAMI_ERROR("{} not correctly signed", signature.first);
214 return false;
215 }
216 }
217 } catch (const std::exception& e) {
218 return false;
219 }
220
221 return true;
222}
223
224bool
225JamiPluginManager::checkPluginSignature(const std::string& jplPath, dht::crypto::Certificate* cert)
226{
227 if (!std::filesystem::is_regular_file(jplPath) || !cert || !*cert)
228 return false;
229 try {
231 } catch (const std::exception& e) {
232 return false;
233 }
234}
235
236std::unique_ptr<dht::crypto::Certificate>
238{
239 if (!std::filesystem::is_regular_file(jplPath))
240 return {};
241 try {
244 return cert;
245 }
246 return {};
247 } catch (const std::exception& e) {
248 return {};
249 }
250}
251
252int
254{
255 int r {SUCCESS};
256 std::error_code ec;
257 if (std::filesystem::is_regular_file(jplPath, ec)) {
258 try {
260 const std::string& name = manifestMap["id"];
261 if (name.empty())
262 return INVALID_PLUGIN;
264 if (!cert)
266 if (!checkPluginSignature(jplPath, cert.get()))
268 const std::string& version = manifestMap["version"];
269 auto destinationDir = (fileutils::get_data_dir() / "plugins" / name).string();
270 // Find if there is an existing version of this plugin
274
275 if (!alreadyInstalledManifestMap.empty()) {
276 if (force) {
278 if (r == SUCCESS) {
280 }
281 } else {
282 std::string installedVersion = alreadyInstalledManifestMap.at("version");
287 if (r == SUCCESS) {
289 }
290 } else if (version == installedVersion) {
292 } else {
294 }
295 }
296 } else {
298 }
302 loadPlugins();
303 return r;
304 }
306 } catch (const std::exception& e) {
307 JAMI_ERR() << e.what();
308 }
309 }
310 return r;
311}
312
313int
315{
316 std::error_code ec;
318 auto detailsIt = pluginDetailsMap_.find(rootPath);
319 if (detailsIt != pluginDetailsMap_.end()) {
321 if (loaded) {
322 JAMI_INFO() << "PLUGIN: unloading before uninstall.";
323 bool status = libjami::unloadPlugin(rootPath);
324 if (!status) {
325 JAMI_INFO() << "PLUGIN: unable to unload, not performing uninstall.";
326 return FAILURE;
327 }
328 }
329 for (const auto& accId : jami::Manager::instance().getAccountList())
330 std::filesystem::remove_all(fileutils::get_data_dir() / accId / "plugins" / detailsIt->second.at("id"),
331 ec);
332 pluginDetailsMap_.erase(detailsIt);
333 }
334 return std::filesystem::remove_all(rootPath, ec) ? SUCCESS : FAILURE;
335 } else {
336 JAMI_INFO() << "PLUGIN: not installed.";
337 return FAILURE;
338 }
339}
340
341bool
343{
344#ifdef ENABLE_PLUGIN
345 try {
346 bool status = pm_.load(getPluginDetails(rootPath).at("soPath"));
347 JAMI_INFO() << "PLUGIN: load status - " << status;
348
349 return status;
350
351 } catch (const std::exception& e) {
352 JAMI_ERR() << e.what();
353 return false;
354 }
355#endif
356 return false;
357}
358
359bool
361{
362#ifdef ENABLE_PLUGIN
363 bool status = true;
364 auto loadedPlugins = jami::Manager::instance().pluginPreferences.getLoadedPlugins();
365 for (const auto& pluginPath : loadedPlugins) {
366 status &= loadPlugin(pluginPath);
367 }
368 return status;
369#endif
370 return false;
371}
372
373bool
375{
376#ifdef ENABLE_PLUGIN
377 try {
378 bool status = pm_.unload(getPluginDetails(rootPath).at("soPath"));
379 JAMI_INFO() << "PLUGIN: unload status - " << status;
380
381 return status;
382 } catch (const std::exception& e) {
383 JAMI_ERR() << e.what();
384 return false;
385 }
386#endif
387 return false;
388}
389
390std::vector<std::string>
392{
393 std::vector<std::string> loadedSoPlugins = pm_.getLoadedPlugins();
394 std::vector<std::string> loadedPlugins {};
395 loadedPlugins.reserve(loadedSoPlugins.size());
396 std::transform(loadedSoPlugins.begin(),
397 loadedSoPlugins.end(),
398 std::back_inserter(loadedPlugins),
399 [](const std::string& soPath) { return PluginUtils::getRootPathFromSoPath(soPath).string(); });
400 return loadedPlugins;
401}
402
403std::vector<std::map<std::string, std::string>>
404JamiPluginManager::getPluginPreferences(const std::string& rootPath, const std::string& accountId)
405{
407}
408
409bool
411 const std::string& accountId,
412 const std::string& key,
413 const std::string& value)
414{
415 std::string acc = accountId;
416
417 // If we try to change a preference value linked to an account
418 // but that preference is global, we must ignore accountId and
419 // change the preference for every account
420 if (!accountId.empty()) {
421 // Get global preferences
422 auto preferences = PluginPreferencesUtils::getPreferences(rootPath, "");
423 // Check if the preference we want to change is global
424 auto it = std::find_if(preferences.cbegin(),
425 preferences.cend(),
426 [key](const std::map<std::string, std::string>& preference) {
427 return preference.at("key") == key;
428 });
429 // Ignore accountId if global preference
430 if (it != preferences.cend())
431 acc.clear();
432 }
433
434 std::map<std::string, std::string> pluginUserPreferencesMap
437 acc);
438
439 // If any plugin handler is active we may have to reload it
440 bool force {pm_.checkLoadedPlugin(rootPath.string())};
441
442 // We check if the preference is modified without having to reload plugin
443 force &= preferencesm_.setPreference(key, value, rootPath.string(), acc);
444 force &= callsm_.setPreference(key, value, rootPath.string());
445 force &= chatsm_.setPreference(key, value, rootPath.string());
446
447 if (force)
448 unloadPlugin(rootPath.string());
449
450 // Save preferences.msgpack with modified preferences values
451 auto find = pluginPreferencesMap.find(key);
452 if (find != pluginPreferencesMap.end()) {
453 pluginUserPreferencesMap[key] = value;
455 std::lock_guard guard(dhtnet::fileutils::getFileLock(preferencesValuesFilePath));
456 std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
457 if (!fs.good()) {
458 if (force) {
459 loadPlugin(rootPath.string());
460 }
461 return false;
462 }
463 try {
464 msgpack::pack(fs, pluginUserPreferencesMap);
465 } catch (const std::exception& e) {
466 JAMI_ERR() << e.what();
467 if (force) {
468 loadPlugin(rootPath.string());
469 }
470 return false;
471 }
472 }
473 if (force) {
474 loadPlugin(rootPath.string());
475 }
476 return true;
477}
478
479std::map<std::string, std::string>
480JamiPluginManager::getPluginPreferencesValuesMap(const std::string& rootPath, const std::string& accountId)
481{
483}
484
485bool
486JamiPluginManager::resetPluginPreferencesValuesMap(const std::string& rootPath, const std::string& accountId)
487{
488 bool acc {accountId.empty()};
490 if (loaded && acc)
493 preferencesm_.resetPreferences(rootPath, accountId);
494 if (loaded && acc) {
496 }
497 return status;
498}
499
500void
501JamiPluginManager::registerServices()
502{
503 // Register getPluginPreferences so that plugin's can receive it's preferences
504 pm_.registerService("getPluginPreferences", [](const DLPlugin* plugin, void* data) {
505 auto* ppp = static_cast<std::map<std::string, std::string>*>(data);
507 return SUCCESS;
508 });
509
510 // Register getPluginDataPath so that plugin's can receive the path to it's data folder
511 pm_.registerService("getPluginDataPath", [](const DLPlugin* plugin, void* data) {
512 auto* dataPath = static_cast<std::string*>(data);
513 dataPath->assign(PluginUtils::dataPath(plugin->getPath()).string());
514 return SUCCESS;
515 });
516
517 // getPluginAccPreferences is a service that allows plugins to load saved per account preferences.
518 auto getPluginAccPreferences = [](const DLPlugin* plugin, void* data) {
519 const auto path = PluginUtils::getRootPathFromSoPath(plugin->getPath());
520 auto* preferencesPtr {(static_cast<PreferencesMap*>(data))};
521 if (!preferencesPtr)
522 return FAILURE;
523
524 preferencesPtr->emplace("default", PluginPreferencesUtils::getPreferencesValuesMap(path, "default"));
525
526 for (const auto& accId : jami::Manager::instance().getAccountList())
527 preferencesPtr->emplace(accId, PluginPreferencesUtils::getPreferencesValuesMap(path, accId));
528 return SUCCESS;
529 };
530
531 pm_.registerService("getPluginAccPreferences", getPluginAccPreferences);
532}
533
534#ifdef LIBJAMI_TEST
535void
536JamiPluginManager::addPluginAuthority(const dht::crypto::Certificate& cert)
537{
538 trust_.add(cert);
539}
540#endif
541
542} // 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:2687
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
void saveConfig()
Save config to file.
Definition manager.cpp:1755
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:230
#define JAMI_ERROR(formatstr,...)
Definition logger.h:243
#define JAMI_INFO(...)
Definition logger.h:227
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:417
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:200
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:329
const std::filesystem::path & get_data_dir()
static constexpr int version
void emitSignal(Args... args)
Definition jami_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[]