File indexing completed on 2024-04-28 12:32:33

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 }