File indexing completed on 2025-04-20 12:51:07
0001 /* 0002 * Copyright (C) 2004-2008 Justin Karneges <justin@affinix.com> 0003 * 0004 * This library is free software; you can redistribute it and/or 0005 * modify it under the terms of the GNU Lesser General Public 0006 * License as published by the Free Software Foundation; either 0007 * version 2.1 of the License, or (at your option) any later version. 0008 * 0009 * This library is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 * Lesser General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU Lesser General Public 0015 * License along with this library; if not, write to the Free Software 0016 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0017 * 02110-1301 USA 0018 * 0019 */ 0020 0021 // Note: The basic thread-safety approach with the plugin manager is that 0022 // it is safe to add/get providers, however it is unsafe to remove them. 0023 // The expectation is that providers will almost always be unloaded on 0024 // application shutdown. For safe provider unload, ensure no threads are 0025 // using the manager, the provider in question, nor any sub-objects from 0026 // the provider. 0027 0028 #include "qca_plugin.h" 0029 0030 #include "qcaprovider.h" 0031 0032 #include <QCoreApplication> 0033 #include <QDir> 0034 #include <QFileInfo> 0035 #include <QLibrary> 0036 #include <QPluginLoader> 0037 0038 #define PLUGIN_SUBDIR QStringLiteral("crypto") 0039 0040 namespace QCA { 0041 0042 // from qca_core.cpp 0043 QVariantMap getProviderConfig_internal(Provider *p); 0044 0045 // from qca_default.cpp 0046 QStringList skip_plugins(Provider *defaultProvider); 0047 QStringList plugin_priorities(Provider *defaultProvider); 0048 0049 // stupidly simple log truncation function. if the log exceeds size chars, 0050 // then throw out the top half, to nearest line. 0051 QString truncate_log(const QString &in, int size) 0052 { 0053 if (size < 2 || in.length() < size) 0054 return in; 0055 0056 // start by pointing at the last chars 0057 int at = in.length() - (size / 2); 0058 0059 // if the previous char is a newline, then this is a perfect cut. 0060 // otherwise, we need to skip to after the next newline. 0061 if (in[at - 1] != QLatin1Char('\n')) { 0062 while (at < in.length() && in[at] != QLatin1Char('\n')) { 0063 ++at; 0064 } 0065 0066 // at this point we either reached a newline, or end of 0067 // the entire buffer 0068 0069 if (in[at] == QLatin1Char('\n')) 0070 ++at; 0071 } 0072 0073 return in.mid(at); 0074 } 0075 0076 static ProviderManager *g_pluginman = nullptr; 0077 0078 static void logDebug(const QString &str) 0079 { 0080 if (g_pluginman) 0081 g_pluginman->appendDiagnosticText(str + QLatin1Char('\n')); 0082 } 0083 0084 static bool validVersion(int ver) 0085 { 0086 // major version must be equal, minor version must be equal or lesser 0087 if ((ver & 0xff0000) == (QCA_VERSION & 0xff0000) && (ver & 0xff00) <= (QCA_VERSION & 0xff00)) 0088 return true; 0089 return false; 0090 } 0091 0092 class PluginInstance 0093 { 0094 private: 0095 QPluginLoader *_loader; 0096 QObject *_instance; 0097 bool _ownInstance; 0098 0099 PluginInstance() 0100 { 0101 } 0102 0103 public: 0104 static PluginInstance *fromFile(const QString &fname, QString *errstr = nullptr) 0105 { 0106 QPluginLoader *loader = new QPluginLoader(fname); 0107 if (!loader->load()) { 0108 if (errstr) 0109 *errstr = QStringLiteral("failed to load: %1").arg(loader->errorString()); 0110 delete loader; 0111 return nullptr; 0112 } 0113 QObject *obj = loader->instance(); 0114 if (!obj) { 0115 if (errstr) 0116 *errstr = QStringLiteral("failed to get instance"); 0117 loader->unload(); 0118 delete loader; 0119 return nullptr; 0120 } 0121 PluginInstance *i = new PluginInstance; 0122 i->_loader = loader; 0123 i->_instance = obj; 0124 i->_ownInstance = true; 0125 return i; 0126 } 0127 0128 static PluginInstance *fromStatic(QObject *obj) 0129 { 0130 PluginInstance *i = new PluginInstance; 0131 i->_loader = nullptr; 0132 i->_instance = obj; 0133 i->_ownInstance = false; 0134 return i; 0135 } 0136 0137 static PluginInstance *fromInstance(QObject *obj) 0138 { 0139 PluginInstance *i = new PluginInstance; 0140 i->_loader = nullptr; 0141 i->_instance = obj; 0142 i->_ownInstance = true; 0143 return i; 0144 } 0145 0146 ~PluginInstance() 0147 { 0148 if (_ownInstance) 0149 delete _instance; 0150 0151 if (_loader) { 0152 _loader->unload(); 0153 delete _loader; 0154 } 0155 } 0156 0157 PluginInstance(const PluginInstance &) = delete; 0158 PluginInstance &operator=(const PluginInstance &) = delete; 0159 0160 void claim() 0161 { 0162 if (_loader) 0163 _loader->moveToThread(nullptr); 0164 if (_ownInstance) 0165 _instance->moveToThread(nullptr); 0166 } 0167 0168 QObject *instance() 0169 { 0170 return _instance; 0171 } 0172 }; 0173 0174 class ProviderItem 0175 { 0176 public: 0177 QString fname; 0178 Provider *p; 0179 int priority; 0180 QMutex m; 0181 0182 static ProviderItem *load(const QString &fname, QString *out_errstr = nullptr) 0183 { 0184 QString errstr; 0185 PluginInstance *i = PluginInstance::fromFile(fname, &errstr); 0186 if (!i) { 0187 if (out_errstr) 0188 *out_errstr = errstr; 0189 return nullptr; 0190 } 0191 QCAPlugin *plugin = qobject_cast<QCAPlugin *>(i->instance()); 0192 if (!plugin) { 0193 if (out_errstr) 0194 *out_errstr = QStringLiteral("does not offer QCAPlugin interface"); 0195 delete i; 0196 return nullptr; 0197 } 0198 0199 Provider *p = plugin->createProvider(); 0200 if (!p) { 0201 if (out_errstr) 0202 *out_errstr = QStringLiteral("unable to create provider"); 0203 delete i; 0204 return nullptr; 0205 } 0206 0207 ProviderItem *pi = new ProviderItem(i, p); 0208 pi->fname = fname; 0209 return pi; 0210 } 0211 0212 static ProviderItem *loadStatic(QObject *instance, QString *errstr = nullptr) 0213 { 0214 PluginInstance *i = PluginInstance::fromStatic(instance); 0215 QCAPlugin *plugin = qobject_cast<QCAPlugin *>(i->instance()); 0216 if (!plugin) { 0217 if (errstr) 0218 *errstr = QStringLiteral("does not offer QCAPlugin interface"); 0219 delete i; 0220 return nullptr; 0221 } 0222 0223 Provider *p = plugin->createProvider(); 0224 if (!p) { 0225 if (errstr) 0226 *errstr = QStringLiteral("unable to create provider"); 0227 delete i; 0228 return nullptr; 0229 } 0230 0231 ProviderItem *pi = new ProviderItem(i, p); 0232 return pi; 0233 } 0234 0235 static ProviderItem *fromClass(Provider *p) 0236 { 0237 ProviderItem *pi = new ProviderItem(nullptr, p); 0238 return pi; 0239 } 0240 0241 ~ProviderItem() 0242 { 0243 delete p; 0244 delete instance; 0245 } 0246 0247 void ensureInit() 0248 { 0249 QMutexLocker locker(&m); 0250 if (init_done) 0251 return; 0252 init_done = true; 0253 0254 p->init(); 0255 0256 // load config 0257 QVariantMap conf = getProviderConfig_internal(p); 0258 if (!conf.isEmpty()) 0259 p->configChanged(conf); 0260 } 0261 0262 bool initted() const 0263 { 0264 return init_done; 0265 } 0266 0267 // null if not a plugin 0268 QObject *objectInstance() const 0269 { 0270 if (instance) 0271 return instance->instance(); 0272 else 0273 return nullptr; 0274 } 0275 0276 private: 0277 PluginInstance *instance; 0278 bool init_done; 0279 0280 ProviderItem(PluginInstance *_instance, Provider *_p) 0281 { 0282 instance = _instance; 0283 p = _p; 0284 init_done = false; 0285 0286 // disassociate from threads 0287 if (instance) 0288 instance->claim(); 0289 } 0290 }; 0291 0292 ProviderManager::ProviderManager() 0293 { 0294 g_pluginman = this; 0295 def = nullptr; 0296 scanned_static = false; 0297 } 0298 0299 ProviderManager::~ProviderManager() 0300 { 0301 if (def) 0302 def->deinit(); 0303 unloadAll(); 0304 delete def; 0305 g_pluginman = nullptr; 0306 } 0307 0308 void ProviderManager::scan() 0309 { 0310 QMutexLocker locker(&providerMutex); 0311 0312 // check static first, but only once 0313 if (!scanned_static) { 0314 logDebug(QStringLiteral("Checking Qt static plugins:")); 0315 const QObjectList list = QPluginLoader::staticInstances(); 0316 if (list.isEmpty()) 0317 logDebug(QStringLiteral(" (none)")); 0318 for (int n = 0; n < list.count(); ++n) { 0319 QObject *instance = list[n]; 0320 const QString className = QString::fromLatin1(instance->metaObject()->className()); 0321 0322 QString errstr; 0323 ProviderItem *i = ProviderItem::loadStatic(instance, &errstr); 0324 if (!i) { 0325 logDebug(QStringLiteral(" %1: %2").arg(className, errstr)); 0326 continue; 0327 } 0328 0329 const QString providerName = i->p->name(); 0330 if (haveAlready(providerName)) { 0331 logDebug( 0332 QStringLiteral(" %1: (as %2) already loaded provider, skipping").arg(className, providerName)); 0333 delete i; 0334 continue; 0335 } 0336 0337 const int ver = i->p->qcaVersion(); 0338 if (!validVersion(ver)) { 0339 errstr = QString::asprintf("plugin version 0x%06x is in the future", ver); 0340 logDebug(QStringLiteral(" %1: (as %2) %3").arg(className, providerName, errstr)); 0341 delete i; 0342 continue; 0343 } 0344 0345 addItem(i, get_default_priority(providerName)); 0346 logDebug(QStringLiteral(" %1: loaded as %2").arg(className, providerName)); 0347 } 0348 scanned_static = true; 0349 } 0350 0351 #ifndef QCA_NO_PLUGINS 0352 if (qgetenv("QCA_NO_PLUGINS") == "1") 0353 return; 0354 0355 const QStringList dirs = pluginPaths(); 0356 if (dirs.isEmpty()) 0357 logDebug(QStringLiteral("No Qt Library Paths")); 0358 for (const QString &dirIt : dirs) { 0359 #ifdef DEVELOPER_MODE 0360 logDebug(QStringLiteral("Checking QCA build tree Path: %1").arg(QDir::toNativeSeparators(dirIt))); 0361 #else 0362 logDebug(QStringLiteral("Checking Qt Library Path: %1").arg(QDir::toNativeSeparators(dirIt))); 0363 #endif 0364 QDir libpath(dirIt); 0365 QDir dir(libpath.filePath(PLUGIN_SUBDIR)); 0366 if (!dir.exists()) { 0367 logDebug(QStringLiteral(" (No 'crypto' subdirectory)")); 0368 continue; 0369 } 0370 0371 const QStringList entryList = dir.entryList(QDir::Files); 0372 if (entryList.isEmpty()) { 0373 logDebug(QStringLiteral(" (No files in 'crypto' subdirectory)")); 0374 continue; 0375 } 0376 0377 foreach (const QString &maybeFile, entryList) { 0378 const QFileInfo fi(dir.filePath(maybeFile)); 0379 0380 const QString filePath = fi.filePath(); // file name with path 0381 const QString fileName = fi.fileName(); // just file name 0382 0383 if (!QLibrary::isLibrary(filePath)) { 0384 logDebug(QStringLiteral(" %1: not a library, skipping").arg(fileName)); 0385 continue; 0386 } 0387 0388 // make sure we haven't loaded this file before 0389 bool haveFile = false; 0390 for (int n = 0; n < providerItemList.count(); ++n) { 0391 ProviderItem *pi = providerItemList[n]; 0392 if (!pi->fname.isEmpty() && pi->fname == filePath) { 0393 haveFile = true; 0394 break; 0395 } 0396 } 0397 if (haveFile) { 0398 logDebug(QStringLiteral(" %1: already loaded file, skipping").arg(fileName)); 0399 continue; 0400 } 0401 0402 QString errstr; 0403 ProviderItem *i = ProviderItem::load(filePath, &errstr); 0404 if (!i) { 0405 logDebug(QStringLiteral(" %1: %2").arg(fileName, errstr)); 0406 continue; 0407 } 0408 0409 const QString className = QString::fromLatin1(i->objectInstance()->metaObject()->className()); 0410 0411 const QString providerName = i->p->name(); 0412 if (haveAlready(providerName)) { 0413 logDebug(QStringLiteral(" %1: (class: %2, as %3) already loaded provider, skipping") 0414 .arg(fileName, className, providerName)); 0415 delete i; 0416 continue; 0417 } 0418 0419 const int ver = i->p->qcaVersion(); 0420 if (!validVersion(ver)) { 0421 errstr = QString::asprintf("plugin version 0x%06x is in the future", ver); 0422 logDebug(QStringLiteral(" %1: (class: %2, as %3) %4").arg(fileName, className, providerName, errstr)); 0423 delete i; 0424 continue; 0425 } 0426 0427 if (skip_plugins(def).contains(providerName)) { 0428 logDebug(QStringLiteral(" %1: (class: %2, as %3) explicitly disabled, skipping") 0429 .arg(fileName, className, providerName)); 0430 delete i; 0431 continue; 0432 } 0433 0434 addItem(i, get_default_priority(providerName)); 0435 logDebug(QStringLiteral(" %1: (class: %2) loaded as %3").arg(fileName, className, providerName)); 0436 } 0437 } 0438 #endif 0439 } 0440 0441 bool ProviderManager::add(Provider *p, int priority) 0442 { 0443 QMutexLocker locker(&providerMutex); 0444 0445 const QString providerName = p->name(); 0446 0447 if (haveAlready(providerName)) { 0448 logDebug(QStringLiteral("Directly adding: %1: already loaded provider, skipping").arg(providerName)); 0449 return false; 0450 } 0451 0452 const int ver = p->qcaVersion(); 0453 if (!validVersion(ver)) { 0454 QString errstr = QString::asprintf("plugin version 0x%06x is in the future", ver); 0455 logDebug(QStringLiteral("Directly adding: %1: %2").arg(providerName, errstr)); 0456 return false; 0457 } 0458 0459 ProviderItem *i = ProviderItem::fromClass(p); 0460 addItem(i, priority); 0461 logDebug(QStringLiteral("Directly adding: %1: loaded").arg(providerName)); 0462 return true; 0463 } 0464 0465 bool ProviderManager::unload(const QString &name) 0466 { 0467 for (int n = 0; n < providerItemList.count(); ++n) { 0468 ProviderItem *i = providerItemList[n]; 0469 if (i->p && i->p->name() == name) { 0470 if (i->initted()) 0471 i->p->deinit(); 0472 0473 delete i; 0474 providerItemList.removeAt(n); 0475 providerList.removeAt(n); 0476 0477 logDebug(QStringLiteral("Unloaded: %1").arg(name)); 0478 return true; 0479 } 0480 } 0481 0482 return false; 0483 } 0484 0485 void ProviderManager::unloadAll() 0486 { 0487 foreach (ProviderItem *i, providerItemList) { 0488 if (i->initted()) 0489 i->p->deinit(); 0490 } 0491 0492 while (!providerItemList.isEmpty()) { 0493 ProviderItem *i = providerItemList.first(); 0494 const QString name = i->p->name(); 0495 delete i; 0496 providerItemList.removeFirst(); 0497 providerList.removeFirst(); 0498 0499 logDebug(QStringLiteral("Unloaded: %1").arg(name)); 0500 } 0501 } 0502 0503 void ProviderManager::setDefault(Provider *p) 0504 { 0505 QMutexLocker locker(&providerMutex); 0506 0507 if (def) 0508 delete def; 0509 def = p; 0510 if (def) { 0511 def->init(); 0512 QVariantMap conf = getProviderConfig_internal(def); 0513 if (!conf.isEmpty()) 0514 def->configChanged(conf); 0515 } 0516 } 0517 0518 Provider *ProviderManager::find(Provider *_p) const 0519 { 0520 ProviderItem *i = nullptr; 0521 Provider *p = nullptr; 0522 0523 providerMutex.lock(); 0524 if (_p == def) { 0525 p = def; 0526 } else { 0527 for (int n = 0; n < providerItemList.count(); ++n) { 0528 ProviderItem *pi = providerItemList[n]; 0529 if (pi->p && pi->p == _p) { 0530 i = pi; 0531 p = pi->p; 0532 break; 0533 } 0534 } 0535 } 0536 providerMutex.unlock(); 0537 0538 if (i) 0539 i->ensureInit(); 0540 return p; 0541 } 0542 0543 Provider *ProviderManager::find(const QString &name) const 0544 { 0545 ProviderItem *i = nullptr; 0546 Provider *p = nullptr; 0547 0548 providerMutex.lock(); 0549 if (def && name == def->name()) { 0550 p = def; 0551 } else { 0552 for (int n = 0; n < providerItemList.count(); ++n) { 0553 ProviderItem *pi = providerItemList[n]; 0554 if (pi->p && pi->p->name() == name) { 0555 i = pi; 0556 p = pi->p; 0557 break; 0558 } 0559 } 0560 } 0561 providerMutex.unlock(); 0562 0563 if (i) 0564 i->ensureInit(); 0565 return p; 0566 } 0567 0568 Provider *ProviderManager::findFor(const QString &name, const QString &type) const 0569 { 0570 if (name.isEmpty()) { 0571 providerMutex.lock(); 0572 const QList<ProviderItem *> list = providerItemList; 0573 providerMutex.unlock(); 0574 0575 // find the first one that can do it 0576 for (int n = 0; n < list.count(); ++n) { 0577 ProviderItem *pi = list[n]; 0578 pi->ensureInit(); 0579 if (pi->p && pi->p->features().contains(type)) 0580 return pi->p; 0581 } 0582 0583 // try the default provider as a last resort 0584 providerMutex.lock(); 0585 Provider *p = def; 0586 providerMutex.unlock(); 0587 if (p && p->features().contains(type)) 0588 return p; 0589 0590 return nullptr; 0591 } else { 0592 Provider *p = find(name); 0593 if (p && p->features().contains(type)) 0594 return p; 0595 return nullptr; 0596 } 0597 } 0598 0599 void ProviderManager::changePriority(const QString &name, int priority) 0600 { 0601 QMutexLocker locker(&providerMutex); 0602 0603 ProviderItem *i = nullptr; 0604 int n = 0; 0605 for (; n < providerItemList.count(); ++n) { 0606 ProviderItem *pi = providerItemList[n]; 0607 if (pi->p && pi->p->name() == name) { 0608 i = pi; 0609 break; 0610 } 0611 } 0612 if (!i) 0613 return; 0614 0615 providerItemList.removeAt(n); 0616 providerList.removeAt(n); 0617 0618 addItem(i, priority); 0619 } 0620 0621 int ProviderManager::getPriority(const QString &name) 0622 { 0623 QMutexLocker locker(&providerMutex); 0624 0625 ProviderItem *i = nullptr; 0626 for (int n = 0; n < providerItemList.count(); ++n) { 0627 ProviderItem *pi = providerItemList[n]; 0628 if (pi->p && pi->p->name() == name) { 0629 i = pi; 0630 break; 0631 } 0632 } 0633 if (!i) 0634 return -1; 0635 0636 return i->priority; 0637 } 0638 0639 QStringList ProviderManager::allFeatures() const 0640 { 0641 QStringList featureList; 0642 0643 providerMutex.lock(); 0644 Provider *p = def; 0645 providerMutex.unlock(); 0646 if (p) 0647 featureList = p->features(); 0648 0649 providerMutex.lock(); 0650 const QList<ProviderItem *> list = providerItemList; 0651 providerMutex.unlock(); 0652 for (int n = 0; n < list.count(); ++n) { 0653 ProviderItem *i = list[n]; 0654 if (i->p) 0655 mergeFeatures(&featureList, i->p->features()); 0656 } 0657 0658 return featureList; 0659 } 0660 0661 ProviderList ProviderManager::providers() const 0662 { 0663 QMutexLocker locker(&providerMutex); 0664 0665 return providerList; 0666 } 0667 0668 QString ProviderManager::diagnosticText() const 0669 { 0670 QMutexLocker locker(&logMutex); 0671 0672 return dtext; 0673 } 0674 0675 void ProviderManager::appendDiagnosticText(const QString &str) 0676 { 0677 QMutexLocker locker(&logMutex); 0678 0679 dtext += str; 0680 dtext = truncate_log(dtext, 20000); 0681 } 0682 0683 void ProviderManager::clearDiagnosticText() 0684 { 0685 QMutexLocker locker(&logMutex); 0686 0687 dtext = QString(); 0688 } 0689 0690 void ProviderManager::addItem(ProviderItem *item, int priority) 0691 { 0692 if (priority < 0) { 0693 // for -1, make the priority the same as the last item 0694 if (!providerItemList.isEmpty()) { 0695 const ProviderItem *last = providerItemList.last(); 0696 item->priority = last->priority; 0697 } else 0698 item->priority = 0; 0699 0700 providerItemList.append(item); 0701 providerList.append(item->p); 0702 } else { 0703 // place the item before any other items with same or greater priority 0704 int n = 0; 0705 for (; n < providerItemList.count(); ++n) { 0706 const ProviderItem *i = providerItemList[n]; 0707 if (i->priority >= priority) 0708 break; 0709 } 0710 0711 item->priority = priority; 0712 providerItemList.insert(n, item); 0713 providerList.insert(n, item->p); 0714 } 0715 } 0716 0717 bool ProviderManager::haveAlready(const QString &name) const 0718 { 0719 if (def && name == def->name()) 0720 return true; 0721 0722 for (int n = 0; n < providerItemList.count(); ++n) { 0723 ProviderItem *pi = providerItemList[n]; 0724 if (pi->p && pi->p->name() == name) 0725 return true; 0726 } 0727 0728 return false; 0729 } 0730 0731 void ProviderManager::mergeFeatures(QStringList *a, const QStringList &b) 0732 { 0733 for (const QString &s : b) { 0734 if (!a->contains(s)) 0735 a->append(s); 0736 } 0737 } 0738 0739 int ProviderManager::get_default_priority(const QString &name) const 0740 { 0741 const QStringList list = plugin_priorities(def); 0742 foreach (const QString &s, list) { 0743 // qca_default already sanity checks the strings 0744 const int n = s.indexOf(QLatin1Char(':')); 0745 const QString sname = s.mid(0, n); 0746 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 2) 0747 const int spriority = QStringView(s).mid(n + 1).toInt(); 0748 #else 0749 const int spriority = s.midRef(n + 1).toInt(); 0750 #endif 0751 if (sname == name) 0752 return spriority; 0753 } 0754 return -1; 0755 } 0756 0757 }